public List <GeneralPolygon2d> GetSolids() { Frame3f f = frameL; if (OutputSpace != CoordSpace.ObjectCoords) { f = SceneTransforms.TransformTo(f, SO, CoordSpace.ObjectCoords, OutputSpace); } PlanarComplex complex = new PlanarComplex(); foreach (DCurve3 c in localCurves.Loops) { Polygon2d poly = new Polygon2d(); for (int i = 0; i < c.VertexCount; ++i) { Vector2f uv = f.ToPlaneUV((Vector3f)c[i], 2); poly.AppendVertex(uv); } complex.Add(poly); } PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.SortPolygons; var info = complex.FindSolidRegions(options); return(info.Polygons); }
/// <summary> /// Construct the set of polygons-with-holes that represents the input string /// </summary> static List <GeneralPolygon2d> GetPolygons(string s, string fontName = "Consolas", FontStyle style = FontStyle.Regular, int emSize = 128) { GraphicsPath path = new GraphicsPath(); string text = s; FontFamily font = new FontFamily(fontName); Point origin = new Point(0, 0); StringFormat format = new StringFormat(); path.AddString(text, font, (int)style, emSize, origin, format); path.Flatten(); PlanarComplex complex = new PlanarComplex(); Polygon2d cur_poly = new Polygon2d(); for (int i = 0; i < path.PathPoints.Length; ++i) { PointF pt = path.PathPoints[i]; Vector2d v = new Vector2d(pt.X, pt.Y); int type = path.PathTypes[i] & 0x7; int flags = path.PathTypes[i] & 0xF8; if (type == 0) { cur_poly = new Polygon2d(); cur_poly.AppendVertex(v); } else if (type == 1) { cur_poly.AppendVertex(v); if ((flags & 0x80) != 0) { // [RMS] some characters have a duplicate start/end point after Flatten(). Some do not. Clusterfuck! int iLast = cur_poly.VertexCount - 1; if (cur_poly[0].Distance(cur_poly[iLast]) < 0.001) { cur_poly.RemoveVertex(iLast); // we just duplicated first point } complex.Add(cur_poly); } } } PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(0.0f, false); List <GeneralPolygon2d> result = solids.Polygons; foreach (var gp in result) { gp.Transform((v) => { return(new Vector2d(v.x, emSize - v.y)); }); } return(result); }
/// <summary> /// Format is: /// [num_slices] /// [slice0_z] /// [num_polys_in_slice_0] /// [x0 y0 x1 y1 x2 y3 ...] /// [x0 y0 x1 y1 ... ] /// [slice1_z] /// [num_polys_in_slice_1] /// ... /// </summary> public void ReadSimpleSliceFormat(TextReader reader) { PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.SortPolygons; options.TrustOrientations = false; char[] splitchars = new char[] { ' ' }; int nSlices = int.Parse(reader.ReadLine()); PlanarComplex[] layer_complexes = new PlanarComplex[nSlices]; for (int si = 0; si < nSlices; ++si) { PlanarSlice slice = new PlanarSlice(); slice.Z = double.Parse(reader.ReadLine()); PlanarComplex complex = new PlanarComplex(); int nPolys = int.Parse(reader.ReadLine()); for (int pi = 0; pi < nPolys; pi++) { string[] stringValues = reader.ReadLine().Split(splitchars, StringSplitOptions.RemoveEmptyEntries); double[] values = Array.ConvertAll(stringValues, Double.Parse); Polygon2d poly = new Polygon2d(values); if (poly.VertexCount < 3) { continue; } complex.Add(poly); } layer_complexes[si] = complex; //// this could be done in separate thread... //var solidInfo = complex.FindSolidRegions(options); //slice.InputSolids = solidInfo.Polygons; //slice.Resolve(); Slices.Add(slice); } gParallel.ForEach(Interval1i.Range(nSlices), (si) => { var solidInfo = layer_complexes[si].FindSolidRegions(options); Slices[si].InputSolids = solidInfo.Polygons; Slices[si].Resolve(); }); }
static GeneralPolygon2d GetPolygonFromMesh(string sPath) { DMesh3 mesh = StandardMeshReader.ReadMesh(sPath); MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); PlanarComplex complex = new PlanarComplex(); foreach (var loop in loops) { Polygon2d poly = new Polygon2d(); DCurve3 curve = MeshUtil.ExtractLoopV(mesh, loop.Vertices); foreach (Vector3d v in curve.Vertices) { poly.AppendVertex(v.xy); } complex.Add(poly); } PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(0.0, false); return(solids.Polygons[0]); }
/// <summary> /// Slice the meshes and return the slice stack. /// </summary> public PlanarSliceStack Compute() { if (Meshes.Count == 0) { return(new PlanarSliceStack()); } Interval1d zrange = Interval1d.Empty; foreach (var meshinfo in Meshes) { zrange.Contain(meshinfo.bounds.Min.z); zrange.Contain(meshinfo.bounds.Max.z); } if (SetMinZValue != double.MinValue) { zrange.a = SetMinZValue; } // construct layers List <PlanarSlice> slice_list = new List <PlanarSlice>(); double cur_layer_z = zrange.a; int layer_i = 0; while (cur_layer_z < zrange.b) { double layer_height = get_layer_height(layer_i); double z = cur_layer_z; Interval1d zspan = new Interval1d(z, z + layer_height); if (SliceLocation == SliceLocations.EpsilonBase) { z += 0.01 * layer_height; } else if (SliceLocation == SliceLocations.MidLine) { z += 0.5 * layer_height; } PlanarSlice slice = SliceFactoryF(zspan, z, layer_i); slice.EmbeddedPathWidth = OpenPathDefaultWidthMM; slice_list.Add(slice); layer_i++; cur_layer_z += layer_height; } int NH = slice_list.Count; if (NH > MaxLayerCount) { throw new Exception("MeshPlanarSlicer.Compute: exceeded layer limit. Increase .MaxLayerCount."); } PlanarSlice[] slices = slice_list.ToArray(); // determine if we have crop objects bool have_crop_objects = false; foreach (var mesh in Meshes) { if (mesh.options.IsCropRegion) { have_crop_objects = true; } } // assume Resolve() takes 2x as long as meshes... TotalCompute = (Meshes.Count * NH) + (2 * NH); Progress = 0; // compute slices separately for each mesh for (int mi = 0; mi < Meshes.Count; ++mi) { if (Cancelled()) { break; } DMesh3 mesh = Meshes[mi].mesh; PrintMeshOptions mesh_options = Meshes[mi].options; // [TODO] should we hang on to this spatial? or should it be part of assembly? DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); AxisAlignedBox3d bounds = Meshes[mi].bounds; bool is_cavity = mesh_options.IsCavity; bool is_crop = mesh_options.IsCropRegion; bool is_support = mesh_options.IsSupport; bool is_closed = (mesh_options.IsOpen) ? false : mesh.IsClosed(); var useOpenMode = (mesh_options.OpenPathMode == OpenPathsModes.Default) ? DefaultOpenPathMode : mesh_options.OpenPathMode; // each layer is independent so we can do in parallel gParallel.ForEach(Interval1i.Range(NH), (i) => { if (Cancelled()) { return; } double z = slices[i].Z; if (z < bounds.Min.z || z > bounds.Max.z) { return; } // compute cut Polygon2d[] polys; PolyLine2d[] paths; compute_plane_curves(mesh, spatial, z, is_closed, out polys, out paths); // if we didn't hit anything, try again with jittered plane // [TODO] this could be better... if ((is_closed && polys.Length == 0) || (is_closed == false && polys.Length == 0 && paths.Length == 0)) { double jitterz = slices[i].LayerZSpan.Interpolate(0.75); compute_plane_curves(mesh, spatial, jitterz, is_closed, out polys, out paths); } if (is_closed) { // construct planar complex and "solids" // (ie outer polys and nested holes) PlanarComplex complex = new PlanarComplex(); foreach (Polygon2d poly in polys) { complex.Add(poly); } PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0.001; options.TrustOrientations = true; options.AllowOverlappingHoles = true; PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(options); List <GeneralPolygon2d> solid_polygons = ApplyValidRegions(solids.Polygons); if (is_support) { add_support_polygons(slices[i], solid_polygons, mesh_options); } else if (is_cavity) { add_cavity_polygons(slices[i], solid_polygons, mesh_options); } else if (is_crop) { add_crop_region_polygons(slices[i], solid_polygons, mesh_options); } else { add_solid_polygons(slices[i], solid_polygons, mesh_options); } } else if (useOpenMode != OpenPathsModes.Ignored) { // [TODO] // - does not really handle clipped polygons properly, there will be an extra break somewhere... List <PolyLine2d> all_paths = new List <PolyLine2d>(paths); foreach (Polygon2d poly in polys) { all_paths.Add(new PolyLine2d(poly, true)); } List <PolyLine2d> open_polylines = ApplyValidRegions(all_paths); foreach (PolyLine2d pline in open_polylines) { if (useOpenMode == OpenPathsModes.Embedded) { slices[i].AddEmbeddedPath(pline); } else { slices[i].AddClippedPath(pline); } } } Interlocked.Increment(ref Progress); }); // end of parallel.foreach } // end mesh iter // resolve planar intersections, etc gParallel.ForEach(Interval1i.Range(NH), (i) => { if (Cancelled()) { return; } if (have_crop_objects && slices[i].InputCropRegions.Count == 0) { // don't resolve, we have fully cropped this layer } else { slices[i].Resolve(); } Interlocked.Add(ref Progress, 2); }); // discard spurious empty slices int last = slices.Length - 1; while (slices[last].IsEmpty && last > 0) { last--; } int first = 0; if (DiscardEmptyBaseSlices || have_crop_objects) { while (slices[first].IsEmpty && first < slices.Length) { first++; } } PlanarSliceStack stack = SliceStackFactoryF(); for (int k = first; k <= last; ++k) { stack.Add(slices[k]); } if (SupportMinZTips) { stack.AddMinZTipSupportPoints(MinZTipMaxDiam, MinZTipExtraLayers); } return(stack); }
/// <summary> /// Slice the meshes and return the slice stack. /// </summary> public PlanarSliceStack Compute() { if (Meshes.Count == 0) { return(new PlanarSliceStack()); } Interval1d zrange = Interval1d.Empty; foreach (var meshinfo in Meshes) { zrange.Contain(meshinfo.bounds.Min.z); zrange.Contain(meshinfo.bounds.Max.z); } if (SetMinZValue != double.MinValue) { zrange.a = SetMinZValue; } int nLayers = (int)(zrange.Length / LayerHeightMM); if (nLayers > MaxLayerCount) { throw new Exception("MeshPlanarSlicer.Compute: exceeded layer limit. Increase .MaxLayerCount."); } // make list of slice heights (could be irregular) List <double> heights = new List <double>(); for (int i = 0; i < nLayers + 1; ++i) { double t = zrange.a + (double)i * LayerHeightMM; if (SliceLocation == SliceLocations.EpsilonBase) { t += 0.01 * LayerHeightMM; } else if (SliceLocation == SliceLocations.MidLine) { t += 0.5 * LayerHeightMM; } heights.Add(t); } int NH = heights.Count; // process each *slice* in parallel PlanarSlice[] slices = new PlanarSlice[NH]; for (int i = 0; i < NH; ++i) { slices[i] = SliceFactoryF(heights[i], i); slices[i].EmbeddedPathWidth = OpenPathDefaultWidthMM; } // assume Resolve() takes 2x as long as meshes... TotalCompute = (Meshes.Count * NH) + (2 * NH); Progress = 0; // compute slices separately for each mesh for (int mi = 0; mi < Meshes.Count; ++mi) { if (Cancelled()) { break; } DMesh3 mesh = Meshes[mi].mesh; PrintMeshOptions mesh_options = Meshes[mi].options; // [TODO] should we hang on to this spatial? or should it be part of assembly? DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); AxisAlignedBox3d bounds = Meshes[mi].bounds; bool is_cavity = mesh_options.IsCavity; bool is_crop = mesh_options.IsCropRegion; bool is_support = mesh_options.IsSupport; bool is_closed = (mesh_options.IsOpen) ? false : mesh.IsClosed(); var useOpenMode = (mesh_options.OpenPathMode == PrintMeshOptions.OpenPathsModes.Default) ? DefaultOpenPathMode : mesh_options.OpenPathMode; // each layer is independent so we can do in parallel gParallel.ForEach(Interval1i.Range(NH), (i) => { if (Cancelled()) { return; } double z = heights[i]; if (z < bounds.Min.z || z > bounds.Max.z) { return; } // compute cut Polygon2d[] polys; PolyLine2d[] paths; compute_plane_curves(mesh, spatial, z, is_closed, out polys, out paths); // if we didn't hit anything, try again with jittered plane // [TODO] this could be better... if ((is_closed && polys.Length == 0) || (is_closed == false && polys.Length == 0 && paths.Length == 0)) { compute_plane_curves(mesh, spatial, z + LayerHeightMM * 0.25, is_closed, out polys, out paths); } if (is_closed) { // construct planar complex and "solids" // (ie outer polys and nested holes) PlanarComplex complex = new PlanarComplex(); foreach (Polygon2d poly in polys) { complex.Add(poly); } PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0.001; options.TrustOrientations = true; options.AllowOverlappingHoles = true; PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(options); if (is_support) { add_support_polygons(slices[i], solids.Polygons, mesh_options); } else if (is_cavity) { add_cavity_polygons(slices[i], solids.Polygons, mesh_options); } else if (is_crop) { add_crop_region_polygons(slices[i], solids.Polygons, mesh_options); } else { add_solid_polygons(slices[i], solids.Polygons, mesh_options); } } else if (useOpenMode != PrintMeshOptions.OpenPathsModes.Ignored) { foreach (PolyLine2d pline in paths) { if (useOpenMode == PrintMeshOptions.OpenPathsModes.Embedded) { slices[i].AddEmbeddedPath(pline); } else { slices[i].AddClippedPath(pline); } } // [TODO] // - does not really handle clipped polygons properly, there will be an extra break somewhere... foreach (Polygon2d poly in polys) { PolyLine2d pline = new PolyLine2d(poly, true); if (useOpenMode == PrintMeshOptions.OpenPathsModes.Embedded) { slices[i].AddEmbeddedPath(pline); } else { slices[i].AddClippedPath(pline); } } } Interlocked.Increment(ref Progress); }); // end of parallel.foreach } // end mesh iter // resolve planar intersections, etc gParallel.ForEach(Interval1i.Range(NH), (i) => { if (Cancelled()) { return; } slices[i].Resolve(); Interlocked.Add(ref Progress, 2); }); // discard spurious empty slices int last = slices.Length - 1; while (slices[last].IsEmpty && last > 0) { last--; } int first = 0; if (DiscardEmptyBaseSlices) { while (slices[first].IsEmpty && first < slices.Length) { first++; } } PlanarSliceStack stack = SliceStackFactoryF(); for (int k = first; k <= last; ++k) { stack.Add(slices[k]); } if (SupportMinZTips) { stack.AddMinZTipSupportPoints(MinZTipMaxDiam, MinZTipExtraLayers); } return(stack); }
protected PlanarSlice ComputeSolidSliceAtZ(double z, bool bCavitySolids) { PlanarSlice temp = new PlanarSlice(); for (int mi = 0; mi < Meshes.Count; ++mi) { if (Cancelled()) { break; } DMesh3 mesh = Meshes[mi].mesh; PrintMeshOptions mesh_options = Meshes[mi].options; // [TODO] should we hang on to this spatial? or should it be part of assembly? DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); AxisAlignedBox3d bounds = Meshes[mi].bounds; bool is_cavity = mesh_options.IsCavity; bool is_crop = mesh_options.IsCropRegion; bool is_support = mesh_options.IsSupport; bool is_closed = (mesh_options.IsOpen) ? false : mesh.IsClosed(); var useOpenMode = (mesh_options.OpenPathMode == PrintMeshOptions.OpenPathsModes.Default) ? DefaultOpenPathMode : mesh_options.OpenPathMode; if (is_crop || is_support) { throw new Exception("Not supported!"); } if (bCavitySolids && is_cavity == false) { continue; } // compute cut Polygon2d[] polys; PolyLine2d[] paths; ComputeSlicePlaneCurves(mesh, spatial, z, is_closed, out polys, out paths); if (is_closed) { // construct planar complex and "solids" // (ie outer polys and nested holes) PlanarComplex complex = new PlanarComplex(); foreach (Polygon2d poly in polys) { complex.Add(poly); } PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0.001; options.TrustOrientations = true; options.AllowOverlappingHoles = true; PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(options); List <GeneralPolygon2d> solid_polygons = ApplyValidRegions(solids.Polygons); if (is_cavity && bCavitySolids == false) { add_cavity_polygons(temp, solid_polygons, mesh_options); } else { add_solid_polygons(temp, solid_polygons, mesh_options); } } } return(temp); }
/// <summary> /// Slice the meshes and return the slice stack. /// </summary> public Result Compute() { Result result = new Result(); if (Meshes.Count == 0) { return(result); } // find Z interval we want to slice in Interval1d zrange = Interval1d.Empty; foreach (var meshinfo in Meshes) { zrange.Contain(meshinfo.bounds.Min.z); zrange.Contain(meshinfo.bounds.Max.z); } if (SetMinZValue != double.MinValue) { zrange.a = SetMinZValue; } result.TopZ = Math.Round(zrange.b, PrecisionDigits); result.BaseZ = Math.Round(zrange.a, PrecisionDigits); // [TODO] might be able to make better decisions if we took flat regions // into account when constructing initial Z-heights? if we have large flat // region just below Zstep, might make sense to do two smaller Z-steps so we // can exactly hit it?? // construct list of clearing Z-heights List <double> clearingZLayers = new List <double>(); double cur_layer_z = zrange.b; int layer_i = 0; while (cur_layer_z > zrange.a) { double layer_height = get_layer_height(layer_i); cur_layer_z -= layer_height; double z = Math.Round(cur_layer_z, PrecisionDigits); clearingZLayers.Add(z); layer_i++; } if (clearingZLayers.Last() < result.BaseZ) { clearingZLayers[clearingZLayers.Count - 1] = result.BaseZ; } if (clearingZLayers.Last() == clearingZLayers[clearingZLayers.Count - 2]) { clearingZLayers.RemoveAt(clearingZLayers.Count - 1); } // construct layer slices from Z-heights List <PlanarSlice> clearing_slice_list = new List <PlanarSlice>(); layer_i = 0; for (int i = 0; i < clearingZLayers.Count; ++i) { double layer_height = (i == clearingZLayers.Count - 1) ? (result.TopZ - clearingZLayers[i]) : (clearingZLayers[i + 1] - clearingZLayers[i]); double z = clearingZLayers[i]; Interval1d zspan = new Interval1d(z, z + layer_height); if (SliceLocation == SliceLocations.EpsilonBase) { z += 0.001; } PlanarSlice slice = SliceFactoryF(zspan, z, layer_i); clearing_slice_list.Add(slice); layer_i++; } int NH = clearing_slice_list.Count; if (NH > MaxLayerCount) { throw new Exception("MeshPlanarSlicer.Compute: exceeded layer limit. Increase .MaxLayerCount."); } PlanarSlice[] clearing_slices = clearing_slice_list.ToArray(); // assume Resolve() takes 2x as long as meshes... TotalCompute = (Meshes.Count * NH) + (2 * NH); Progress = 0; // compute slices separately for each mesh for (int mi = 0; mi < Meshes.Count; ++mi) { if (Cancelled()) { break; } DMesh3 mesh = Meshes[mi].mesh; PrintMeshOptions mesh_options = Meshes[mi].options; // [TODO] should we hang on to this spatial? or should it be part of assembly? DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); AxisAlignedBox3d bounds = Meshes[mi].bounds; bool is_cavity = mesh_options.IsCavity; bool is_crop = mesh_options.IsCropRegion; bool is_support = mesh_options.IsSupport; bool is_closed = (mesh_options.IsOpen) ? false : mesh.IsClosed(); var useOpenMode = (mesh_options.OpenPathMode == PrintMeshOptions.OpenPathsModes.Default) ? DefaultOpenPathMode : mesh_options.OpenPathMode; if (is_crop || is_support) { throw new Exception("Not supported!"); } // each layer is independent so we can do in parallel gParallel.ForEach(Interval1i.Range(NH), (i) => { if (Cancelled()) { return; } double z = clearing_slices[i].Z; if (z < bounds.Min.z || z > bounds.Max.z) { return; } // compute cut Polygon2d[] polys; PolyLine2d[] paths; ComputeSlicePlaneCurves(mesh, spatial, z, is_closed, out polys, out paths); if (is_closed) { // construct planar complex and "solids" // (ie outer polys and nested holes) PlanarComplex complex = new PlanarComplex(); foreach (Polygon2d poly in polys) { complex.Add(poly); } PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0.001; options.TrustOrientations = true; options.AllowOverlappingHoles = true; PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(options); List <GeneralPolygon2d> solid_polygons = ApplyValidRegions(solids.Polygons); if (is_cavity) { add_cavity_polygons(clearing_slices[i], solid_polygons, mesh_options); } else { if (ExpandStockAmount > 0) { solid_polygons = ClipperUtil.MiterOffset(solid_polygons, ExpandStockAmount); } add_solid_polygons(clearing_slices[i], solid_polygons, mesh_options); } } Interlocked.Increment(ref Progress); }); // end of parallel.foreach } // end mesh iter // resolve planar intersections, etc gParallel.ForEach(Interval1i.Range(NH), (i) => { if (Cancelled()) { return; } clearing_slices[i].Resolve(); Interlocked.Add(ref Progress, 2); }); // add to clearing stack result.Clearing = SliceStackFactoryF(); for (int k = 0; k < clearing_slices.Length; ++k) { result.Clearing.Add(clearing_slices[k]); } /* * Horizontal planar regions finishing pass. * First we find all planar horizontal Z-regions big enough to mill. * Then we add slices at the Z's we haven't touched yet. * * Cannot just 'fill' planar regions because we will miss edges that might * be millable. So we grow region and then intersect with full-slice millable area. */ // find set of horizontal flat regions Dictionary <double, List <PlanarRegion> > flat_regions = FindPlanarZRegions(ToolDiameter); if (flat_regions.Count == 0) { goto done_slicing; } // if we have already milled this exact Z-height in clearing pass, then we can skip it List <double> doneZ = new List <double>(); foreach (double z in flat_regions.Keys) { if (clearingZLayers.Contains(z)) { doneZ.Add(z); } } foreach (var z in doneZ) { flat_regions.Remove(z); } // create slice for each layer PlanarSlice[] horz_slices = new PlanarSlice[flat_regions.Count]; List <double> flatZ = new List <double>(flat_regions.Keys); flatZ.Sort(); for (int k = 0; k < horz_slices.Length; ++k) { double z = flatZ[k]; Interval1d zspan = new Interval1d(z, z + LayerHeightMM); horz_slices[k] = SliceFactoryF(zspan, z, k); // compute full millable region slightly above this slice. PlanarSlice clip_slice = ComputeSolidSliceAtZ(z + 0.0001, false); clip_slice.Resolve(); // extract planar polys List <Polygon2d> polys = GetPlanarPolys(flat_regions[z]); PlanarComplex complex = new PlanarComplex(); foreach (Polygon2d poly in polys) { complex.Add(poly); } // convert to planar solids PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.SortPolygons; options.SimplifyDeviationTolerance = 0.001; options.TrustOrientations = true; options.AllowOverlappingHoles = true; PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(options); List <GeneralPolygon2d> solid_polygons = ApplyValidRegions(solids.Polygons); // If planar solid has holes, then when we do inset later, we might lose // too-thin parts. Shrink the holes to avoid this case. //FilterHoles(solid_polygons, 0.55 * ToolDiameter); // ok now we need to expand region and intersect with full region. solid_polygons = ClipperUtil.MiterOffset(solid_polygons, ToolDiameter * 0.5, 0.0001); solid_polygons = ClipperUtil.Intersection(solid_polygons, clip_slice.Solids, 0.0001); // Same idea as above, but if we do after, we keep more of the hole and // hence do less extra clearing. // Also this could then be done at the slicer level instead of here... // (possibly this entire thing should be done at slicer level, except we need clip_slice!) FilterHoles(solid_polygons, 1.1 * ToolDiameter); add_solid_polygons(horz_slices[k], solid_polygons, PrintMeshOptions.Default()); } // resolve planar intersections, etc int NF = horz_slices.Length; gParallel.ForEach(Interval1i.Range(NF), (i) => { if (Cancelled()) { return; } horz_slices[i].Resolve(); Interlocked.Add(ref Progress, 2); }); // add to clearing stack result.HorizontalFinish = SliceStackFactoryF(); for (int k = 0; k < horz_slices.Length; ++k) { result.HorizontalFinish.Add(horz_slices[k]); } done_slicing: return(result); }
public static void test_cells() { Polygon2d outer = Polygon2d.MakeCircle(1000, 17); Polygon2d hole = Polygon2d.MakeCircle(100, 32); hole.Reverse(); GeneralPolygon2d gpoly = new GeneralPolygon2d(outer); gpoly.AddHole(hole); DGraph2 graph = new DGraph2(); graph.AppendPolygon(gpoly); GraphSplitter2d splitter = new GraphSplitter2d(graph); splitter.InsideTestF = gpoly.Contains; for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.AxisY); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.AxisX); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.One.Normalized); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], new Vector2d(1, -1).Normalized); splitter.InsertLine(line); } GraphCells2d cells = new GraphCells2d(graph); cells.FindCells(); List <Polygon2d> polys = cells.ContainedCells(gpoly); for (int k = 0; k < polys.Count; ++k) { double offset = polys[k].IsClockwise ? 4 : 20; polys[k].PolyOffset(offset); } PlanarComplex cp = new PlanarComplex(); for (int k = 0; k < polys.Count; ++k) { cp.Add(polys[k]); } // convert back to solids var options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0; var solids = cp.FindSolidRegions(options); SVGWriter svg = new SVGWriter(); svg.AddGraph(graph, SVGWriter.Style.Outline("red", 5)); for (int k = 0; k < polys.Count; ++k) { svg.AddPolygon(polys[k], SVGWriter.Style.Outline("black", 1)); } svg.Write(TestUtil.GetTestOutputPath("cells_graph.svg")); }
/// <summary> /// Convert the input polygons to a set of paths. /// If FilterSelfOverlaps=true, then the paths will be clipped against /// themselves, in an attempt to avoid over-printing. /// </summary> public virtual FillCurveSet2d ShellPolysToPaths(List <GeneralPolygon2d> shell_polys, int nShell) { FillCurveSet2d paths = new FillCurveSet2d(); IFillType currentFillType = nShell == 0 ? firstShellFillType ?? fillType : fillType; if (FilterSelfOverlaps == false) { foreach (var shell in shell_polys) { paths.Append(new FillLoop <FillSegment>(shell.Outer.Vertices) { FillType = currentFillType, PerimeterOrder = nShell }); foreach (var hole in shell.Holes) { paths.Append(new FillLoop <FillSegment>(hole.Vertices) { FillType = currentFillType, PerimeterOrder = nShell, IsHoleShell = true });; } } return(paths); } int outer_shell_edgegroup = 100; foreach (GeneralPolygon2d shell in shell_polys) { PathOverlapRepair repair = new PathOverlapRepair(); repair.OverlapRadius = SelfOverlapTolerance; repair.Add(shell, outer_shell_edgegroup); // Ideally want to presreve outermost shell of external perimeters. // However in many cases internal holes are 'too close' to outer border. // So we will still apply to those, but use edge filter to preserve outermost loop. // [TODO] could we be smarter about this somehow? if (PreserveOuterShells && nShell == 0) { repair.PreserveEdgeFilterF = (eid) => { return(repair.Graph.GetEdgeGroup(eid) == outer_shell_edgegroup); } } ; repair.Compute(); DGraph2Util.Curves c = DGraph2Util.ExtractCurves(repair.GetResultGraph()); #region Borrow nesting calculations from PlanarSlice to enforce winding direction PlanarComplex complex = new PlanarComplex(); foreach (Polygon2d poly in c.Loops) { complex.Add(poly); } PlanarComplex.FindSolidsOptions options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0.001; options.TrustOrientations = false; options.AllowOverlappingHoles = false; PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(options); foreach (var polygon in solids.Polygons) { polygon.EnforceCounterClockwise(); paths.Append(new FillLoop <FillSegment>(polygon.Outer.Vertices) { FillType = currentFillType, PerimeterOrder = nShell }); foreach (var hole in polygon.Holes) { paths.Append(new FillLoop <FillSegment>(hole.Vertices) { FillType = currentFillType, PerimeterOrder = nShell, IsHoleShell = true, }); } } #endregion Borrow nesting calculations from PlanarSlice to enforce winding direction foreach (var polyline in c.Paths) { if (polyline.ArcLength < DiscardTinyPerimeterLengthMM) { continue; } if (polyline.Bounds.MaxDim < DiscardTinyPerimeterLengthMM) { continue; } paths.Append(new FillCurve <FillSegment>(polyline) { FillType = currentFillType, PerimeterOrder = nShell }); } } return(paths); }