/// <summary> /// Compute inset from input polygon. If requested, will check for /// topological changes and try smaller insets, and if that fails, /// will just return input polygon. /// </summary> protected virtual List <GeneralPolygon2d> ComputeInitialInsetPolygon( bool bForcePreserveTopology) { double fInset = ToolWidth * InsetFromInputPolygonX; List <GeneralPolygon2d> insetPolys = ClipperUtil.MiterOffset(Polygon, -fInset); if (bForcePreserveTopology == false) { return(insetPolys); } if (check_large_topology_change(Polygon, insetPolys, fInset)) { fInset /= 2; insetPolys = ClipperUtil.MiterOffset(Polygon, -fInset); if (check_large_topology_change(Polygon, insetPolys, fInset)) { insetPolys = new List <GeneralPolygon2d>() { Polygon } } ; } return(insetPolys); }
public List <FillPolyline2d> thin_offset(GeneralPolygon2d p) { List <FillPolyline2d> result = new List <FillPolyline2d>(); // to support non-hole thin offsets we need to return polylines if (p.Holes.Count == 0) { return(result); } // compute desired offset from outer polygon GeneralPolygon2d outer = new GeneralPolygon2d(p.Outer); List <GeneralPolygon2d> offsets = ClipperUtil.ComputeOffsetPolygon(outer, -ToolWidth, true); if (offsets == null || offsets.Count == 0) { return(result); } double clip_dist = ToolWidth * ToolWidthClipMultiplier; foreach (GeneralPolygon2d offset_poly in offsets) { List <FillPolyline2d> clipped = clip_to_band(offset_poly.Outer, p, clip_dist); result.AddRange(clipped); } return(result); }
public static List <GeneralPolygon2d> ComputeOffsetPolygon(GeneralPolygon2d poly, double fOffset, bool bMiter = false) { double nIntScale = GetIntScale(poly); CPolygonList clipper_polys = new CPolygonList(); clipper_polys.Add(ClipperUtil.ConvertToClipper(poly.Outer, nIntScale)); foreach (Polygon2d hole in poly.Holes) { clipper_polys.Add(ClipperUtil.ConvertToClipper(hole, nIntScale)); } CPolygonList dilate_solution = new CPolygonList(); try { ClipperOffset co = new ClipperOffset(); if (bMiter) { co.AddPaths(clipper_polys, JoinType.jtMiter, EndType.etClosedPolygon); } else { co.AddPaths(clipper_polys, JoinType.jtRound, EndType.etClosedPolygon); } co.Execute(ref dilate_solution, fOffset * nIntScale); } catch /*( Exception e )*/ { //System.Diagnostics.Debug.WriteLine("ClipperUtil.ComputeOffsetPolygon: Clipper threw exception: " + e.Message); return(new List <GeneralPolygon2d>()); } List <GeneralPolygon2d> polys = ClipperUtil.ConvertFromClipper(dilate_solution, nIntScale); return(polys); }
public bool Compute() { if (InsetFromInputPolygon) { //BoundaryPolygonCache = new SegmentSet2d(Polygon); List <GeneralPolygon2d> current = ClipperUtil.ComputeOffsetPolygon(Polygon, -ToolWidth / 2, true); foreach (GeneralPolygon2d poly in current) { FillCurveSet2d fillPaths = ComputeFillPaths(poly); if (fillPaths != null) { FillCurves.Add(fillPaths); } } } else { List <GeneralPolygon2d> boundary = ClipperUtil.ComputeOffsetPolygon(Polygon, ToolWidth / 2, true); //BoundaryPolygonCache = new SegmentSet2d(boundary); FillCurveSet2d fillPaths = ComputeFillPaths(Polygon); if (fillPaths != null) { FillCurves.Add(fillPaths); } } return(true); }
/// <summary> /// Shrink holes inside polys such that if we do offset/2, the hole and /// outer offsets will (probably) not collide. Two options: /// 1) contract and then dilate each hole. This doesn't handle long skinny holes? /// 2) shrink outer by offset and then intersect with holes /// Currently using (2). This is better, right? /// </summary> protected void FilterHoles(List <GeneralPolygon2d> polys, double offset) { foreach (var poly in polys) { if (poly.Holes.Count == 0) { continue; } List <GeneralPolygon2d> outer_inset = ClipperUtil.MiterOffset( new GeneralPolygon2d(poly.Outer), -offset); List <GeneralPolygon2d> hole_polys = new List <GeneralPolygon2d>(); foreach (var hole in poly.Holes) { hole.Reverse(); hole_polys.Add(new GeneralPolygon2d(hole)); } //List<GeneralPolygon2d> contracted = ClipperUtil.MiterOffset(hole_polys, -offset, 0.01); //List<GeneralPolygon2d> dilated = ClipperUtil.MiterOffset(hole_polys, offset, 0.01); List <GeneralPolygon2d> dilated = ClipperUtil.Intersection(hole_polys, outer_inset, 0.01); poly.ClearHoles(); List <Polygon2d> new_holes = new List <Polygon2d>(); foreach (var dpoly in dilated) { dpoly.Outer.Reverse(); poly.AddHole(dpoly.Outer, false, false); } } }
public bool Compute() { if (InsetFromInputPolygon) { double inset = ToolWidth * InsetFromInputPolygonX; var current = ClipperUtil.ComputeOffsetPolygon(Polygon, -inset, true); foreach (var poly in current) { FillCurveSet2d fillPaths = ComputeFillPaths(poly); if (fillPaths != null) { FillCurves.Add(fillPaths); } } } else { FillCurveSet2d fillPaths = ComputeFillPaths(Polygon); if (fillPaths != null) { FillCurves.Add(fillPaths); } } return(true); }
protected virtual List <GeneralPolygon2d> make_solid(GeneralPolygon2d poly, bool bIsSupportSolid) { // always do a self-union of outer polygon. This allows Clipper to clean up many // problems in input polygons, eg self-intersections and other junk GeneralPolygon2d gouter = new GeneralPolygon2d(poly.Outer); List <GeneralPolygon2d> resolvedSolid = ClipperUtil.Union(gouter, gouter, MIN_AREA); // solid may contain overlapping holes. We need to resolve these before continuing, // otherwise those overlapping regions will be filled by Clipper even/odd rules foreach (Polygon2d hole in poly.Holes) { GeneralPolygon2d holePoly = new GeneralPolygon2d(hole); resolvedSolid = ClipperUtil.PolygonBoolean(resolvedSolid, holePoly, ClipperUtil.BooleanOp.Difference, MIN_AREA); } if (bIsSupportSolid == false && Thickened != null) { // Subtract away any clearance solids foreach (var pair in Thickened) { if (pair.Key != poly) { resolvedSolid = ClipperUtil.Difference(resolvedSolid, pair.Value); } } } return(filter_solids(resolvedSolid)); }
/* * functions for subclasses to override to customize behavior */ protected virtual GeneralPolygon2d[] process_input_polys_before_sort(GeneralPolygon2d[] polys) { if (Offsets.Count == 0) { return(polys); } List <GeneralPolygon2d> newPolys = new List <GeneralPolygon2d>(); bool modified = false; foreach (var poly in polys) { double offset; if (Offsets.TryGetValue(poly, out offset) && Math.Abs(offset) > MathUtil.ZeroTolerancef) { List <GeneralPolygon2d> offsetPolys = ClipperUtil.MiterOffset(poly, offset); foreach (var newpoly in offsetPolys) { transfer_tags(poly, newpoly); newPolys.Add(newpoly); } modified = true; } else { newPolys.Add(poly); } } if (modified == false) { return(polys); } return(newPolys.ToArray()); }
protected virtual List <GeneralPolygon2d> ApplyValidRegions(List <GeneralPolygon2d> polygonsIn) { if (ValidRegions == null || ValidRegions.Count == 0) { return(polygonsIn); } return(ClipperUtil.Intersection(polygonsIn, ValidRegions)); }
protected virtual List <PolyLine2d> ApplyValidRegions(List <PolyLine2d> plinesIn) { if (ValidRegions == null || ValidRegions.Count == 0) { return(plinesIn); } List <PolyLine2d> clipped = new List <PolyLine2d>(); foreach (var pline in plinesIn) { clipped.AddRange(ClipperUtil.ClipAgainstPolygon(ValidRegions, pline, true)); } return(clipped); }
protected virtual GeneralPolygon2d[] process_input_polys_after_sort(GeneralPolygon2d[] solids) { // construct thickened solids Thickened = new Dictionary <GeneralPolygon2d, List <GeneralPolygon2d> >(); for (int k = 0; k < solids.Length; ++k) { double clearance; if (Clearances.TryGetValue(solids[k], out clearance) && clearance > 0) { Thickened.Add(solids[k], ClipperUtil.MiterOffset(solids[k], clearance)); } } return(solids); }
private LayerCache build_cache(PrintLayerData layerData) { LayerCache cache = new LayerCache(); cache.SupportAreas = ClipperUtil.MiterOffset(layerData.SupportAreas, layerData.Settings.Machine.NozzleDiamMM); cache.SupportAreaBounds = new AxisAlignedBox2d[cache.SupportAreas.Count]; cache.AllSupportBounds = AxisAlignedBox2d.Empty; for (int i = 0; i < cache.SupportAreas.Count; ++i) { cache.SupportAreaBounds[i] = cache.SupportAreas[i].Bounds; cache.AllSupportBounds.Contain(cache.SupportAreaBounds[i]); } return(cache); }
virtual protected void cache_brim_polys() { combined_solid = new List <GeneralPolygon2d>(); combined_support = new List <GeneralPolygon2d>(); subtract = new List <GeneralPolygon2d>(); Frame3f cutPlane = new Frame3f((float)layer_height * 0.5f * Vector3f.AxisY, Vector3f.AxisY); foreach (var so in CC.Objects.PrintMeshes) { if (so.Settings.ObjectType == PrintMeshSettings.ObjectTypes.Ignored) { continue; } SOSectionPlane section = new SOSectionPlane(so); section.UpdateSection(cutPlane, CoordSpace.SceneCoords); List <GeneralPolygon2d> solids = section.GetSolids(); // [TODO] should subtract holes explicitly here, like we do in slicer? if (so.Settings.ObjectType == PrintMeshSettings.ObjectTypes.Cavity) { subtract = ClipperUtil.Union(solids, subtract); } else if (so.Settings.ObjectType == PrintMeshSettings.ObjectTypes.Support) { combined_support = ClipperUtil.Union(solids, combined_support); } else if (so.Settings.ObjectType == PrintMeshSettings.ObjectTypes.Solid) { combined_solid = ClipperUtil.Union(solids, combined_solid); } } if (subtract.Count > 0) { combined_solid = ClipperUtil.Difference(combined_solid, subtract); } combined_all = ClipperUtil.Union(combined_solid, combined_support); combined_all = CurveUtils2.FilterDegenerate(combined_all, 0.001); foreach (var poly in combined_all) { poly.Simplify(path_width * 0.02); } }
protected override List <GeneralPolygon2d> make_solid(GeneralPolygon2d poly, bool bIsSupportSolid) { List <GeneralPolygon2d> solid = base.make_solid(poly, bIsSupportSolid); if (bIsSupportSolid == false && Thickened != null) { // subtract clearance solids foreach (var pair in Thickened) { if (pair.Key != poly) { solid = ClipperUtil.Difference(solid, pair.Value); } } } return(solid); }
protected virtual List <GeneralPolygon2d> remove_cavity(List <GeneralPolygon2d> solids, GeneralPolygon2d cavity) { double offset = 0; if (Cavity_Clearances.ContainsKey(cavity)) { offset = Cavity_Clearances[cavity]; } if (Cavity_Offsets.ContainsKey(cavity)) { offset += Cavity_Offsets[cavity]; } if (Math.Abs(offset) > 0.0001) { var offset_cavities = ClipperUtil.MiterOffset(cavity, offset, MIN_AREA); return(ClipperUtil.Difference(solids, offset_cavities, MIN_AREA)); } else { return(ClipperUtil.Difference(solids, cavity, MIN_AREA)); } }
public bool Compute() { if (InsetFromInputPolygon) { BoundaryPolygonCache = new SegmentSet2d(Polygon); List <GeneralPolygon2d> current = ClipperUtil.ComputeOffsetPolygon(Polygon, -ToolWidth / 2, true); foreach (GeneralPolygon2d poly in current) { SegmentSet2d polyCache = new SegmentSet2d(poly); Paths.Add(ComputeFillPaths(poly, polyCache)); } } else { List <GeneralPolygon2d> boundary = ClipperUtil.ComputeOffsetPolygon(Polygon, ToolWidth / 2, true); BoundaryPolygonCache = new SegmentSet2d(boundary); SegmentSet2d polyCache = new SegmentSet2d(Polygon); Paths.Add(ComputeFillPaths(Polygon, polyCache)); } return(true); }
public static List <GeneralPolygon2d> ComputeOffsetPolygon(Polygon2d poly, double fOffset, bool bSharp = false) { double nIntScale = GetIntScale(poly.Vertices); List <IntPoint> clipper_poly = ClipperUtil.ConvertToClipper(poly, nIntScale); CPolygonList clipper_polys = new CPolygonList() { clipper_poly }; CPolygonList dilate_solution = new CPolygonList(); try { ClipperOffset co = new ClipperOffset(); if (bSharp) { co.AddPaths(clipper_polys, JoinType.jtMiter, EndType.etClosedPolygon); } else { co.AddPaths(clipper_polys, JoinType.jtRound, EndType.etClosedPolygon); } co.Execute(ref dilate_solution, fOffset * nIntScale); } catch /*( Exception e )*/ { //System.Diagnostics.Debug.WriteLine("ClipperUtil.ComputeOffsetPolygon: Clipper threw exception: " + e.Message); return(new List <GeneralPolygon2d>()); } if (dilate_solution.Count == 0) { return(new List <GeneralPolygon2d>()); } List <GeneralPolygon2d> polys = ClipperUtil.ConvertFromClipper(dilate_solution, nIntScale); return(polys); }
protected virtual List <GeneralPolygon2d> remove_cavity(List <GeneralPolygon2d> solids, GeneralPolygon2d cavity) { return(ClipperUtil.Difference(solids, cavity, MIN_AREA)); }
public bool Compute() { AxisAlignedBox2d bounds = Polygon.Bounds; ScaleGridIndexer2 index = new ScaleGridIndexer2() { CellSize = TileSize }; Vector2i min = index.ToGrid(bounds.Min) - Vector2i.One; Vector2i max = index.ToGrid(bounds.Max); List <Tile> Tiles = new List <Tile>(); for (int y = min.y; y <= max.y; ++y) { for (int x = min.x; x <= max.x; ++x) { Tile t = new Tile(); t.index = new Vector2i(x, y); Vector2d tile_min = index.FromGrid(t.index); Vector2d tile_max = index.FromGrid(t.index + Vector2i.One); AxisAlignedBox2d tile_box = new AxisAlignedBox2d(tile_min, tile_max); tile_box.Expand(TileOverlap); t.poly = new Polygon2d(new Vector2d[] { tile_box.GetCorner(0), tile_box.GetCorner(1), tile_box.GetCorner(2), tile_box.GetCorner(3) }); Tiles.Add(t); } } gParallel.ForEach(Tiles, (tile) => { tile.regions = ClipperUtil.PolygonBoolean(Polygon, new GeneralPolygon2d(tile.poly), ClipperUtil.BooleanOp.Intersection); }); List <ICurvesFillPolygon> all_fills = new List <ICurvesFillPolygon>(); foreach (Tile t in Tiles) { if (t.regions.Count == 0) { continue; } t.fills = new ICurvesFillPolygon[t.regions.Count]; for (int k = 0; k < t.regions.Count; ++k) { t.fills[k] = TileFillGeneratorF(t.regions[k], t.index); if (t.fills[k] != null) { all_fills.Add(t.fills[k]); } } } gParallel.ForEach(all_fills, (fill) => { fill.Compute(); }); FillCurves = new List <FillCurveSet2d>(); foreach (ICurvesFillPolygon fill in all_fills) { List <FillCurveSet2d> result = fill.GetFillCurves(); if (result != null && result.Count > 0) { FillCurves.AddRange(result); } } return(true); }
/// <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); }
protected virtual void fill_bridge_region_decompose(GeneralPolygon2d poly, IFillPathScheduler2d scheduler, PrintLayerData layer_data) { poly.Simplify(0.1, 0.01, true); TriangulatedPolygonGenerator generator = new TriangulatedPolygonGenerator() { Polygon = poly, Subdivisions = 16 }; DMesh3 mesh = generator.Generate().MakeDMesh(); //Util.WriteDebugMesh(mesh, "/Users/rms/scratch/bridgemesh.obj"); //List<Polygon2d> polys = decompose_mesh_recursive(mesh); List <Polygon2d> polys = decompose_cluster_up(mesh); Util.WriteDebugMesh(mesh, "/Users/rms/scratch/bridgemesh_reduce.obj"); double spacing = Settings.BridgeFillPathSpacingMM(); foreach (Polygon2d polypart in polys) { Box2d box = polypart.MinimalBoundingBox(0.00001); Vector2d axis = (box.Extent.x > box.Extent.y) ? box.AxisY : box.AxisX; double angle = Math.Atan2(axis.y, axis.x) * MathUtil.Rad2Deg; GeneralPolygon2d gp = new GeneralPolygon2d(polypart); ShellsFillPolygon shells_fill = new ShellsFillPolygon(gp); shells_fill.PathSpacing = Settings.SolidFillPathSpacingMM(); shells_fill.ToolWidth = Settings.Machine.NozzleDiamMM; shells_fill.Layers = 1; shells_fill.InsetFromInputPolygonX = 0.25; shells_fill.ShellType = ShellsFillPolygon.ShellTypes.BridgeShell; shells_fill.FilterSelfOverlaps = false; shells_fill.Compute(); scheduler.AppendCurveSets(shells_fill.GetFillCurves()); var fillPolys = shells_fill.InnerPolygons; double offset = Settings.Machine.NozzleDiamMM * Settings.SolidFillBorderOverlapX; fillPolys = ClipperUtil.MiterOffset(fillPolys, offset); foreach (var fp in fillPolys) { BridgeLinesFillPolygon fill_gen = new BridgeLinesFillPolygon(fp) { InsetFromInputPolygon = false, PathSpacing = spacing, ToolWidth = Settings.Machine.NozzleDiamMM, AngleDeg = angle, }; fill_gen.Compute(); scheduler.AppendCurveSets(fill_gen.GetFillCurves()); } } // fit bbox to try to find fill angle that has shortest spans //Box2d box = poly.Outer.MinimalBoundingBox(0.00001); //Vector2d axis = (box.Extent.x > box.Extent.y) ? box.AxisY : box.AxisX; //double angle = Math.Atan2(axis.y, axis.x) * MathUtil.Rad2Deg; // [RMS] should we do something like this? //if (Settings.SolidFillBorderOverlapX > 0) { // double offset = Settings.Machine.NozzleDiamMM * Settings.SolidFillBorderOverlapX; // fillPolys = ClipperUtil.MiterOffset(fillPolys, offset); //} }
public bool Compute() { bool enable_thin_check = false; double thin_check_offset = ToolWidth * 0.45; double thin_check_thresh_sqr = ToolWidth * 0.3; thin_check_thresh_sqr *= thin_check_thresh_sqr; // first shell is either polygon, or inset from that polygon List <GeneralPolygon2d> current = null; if (InsetFromInputPolygonX != 0) { current = ComputeInitialInsetPolygon(PreserveInputInsetTopology); } else { current = new List <GeneralPolygon2d>() { Polygon }; } List <GeneralPolygon2d> current_prev = null; if (current.Count == 0) { HandleTinyPolygon(); return(true); } // convert previous layer to shell, and then compute next layer List <GeneralPolygon2d> failedShells = new List <GeneralPolygon2d>(); List <GeneralPolygon2d> nextShellTooThin = new List <GeneralPolygon2d>(); for (int i = 0; i < Layers; ++i) { FillCurveSet2d paths = ShellPolysToPaths(current, i); Shells.Add(paths); List <GeneralPolygon2d> all_next = new List <GeneralPolygon2d>(); foreach (GeneralPolygon2d gpoly in current) { List <GeneralPolygon2d> offsets = ClipperUtil.ComputeOffsetPolygon(gpoly, -PathSpacing, true); List <GeneralPolygon2d> filtered = new List <GeneralPolygon2d>(); foreach (var v in offsets) { bool bTooSmall = (v.Perimeter < DiscardTinyPerimterLengthMM || v.Area < DiscardTinyPolygonAreaMM2); if (bTooSmall) { continue; } if (enable_thin_check && is_too_thin(v, thin_check_offset, thin_check_thresh_sqr)) { nextShellTooThin.Add(v); } else { filtered.Add(v); } } if (filtered.Count == 0) { failedShells.Add(gpoly); } else { all_next.AddRange(filtered); } } current_prev = current; current = all_next; } // failedShells have no space for internal contours. But // we might be able to fit a single line... //foreach (GeneralPolygon2d gpoly in failedShells) { // if (gpoly.Perimeter < DiscardTinyPerimterLengthMM || // gpoly.Area < DiscardTinyPolygonAreaMM2) // continue; // List<FillPolyline2d> thin_shells = thin_offset(gpoly); // Shells[Shells.Count - 1].Append(thin_shells); //} // remaining inner polygons if (InsetInnerPolygons) { InnerPolygons = current; InnerPolygons.AddRange(nextShellTooThin); } else { InnerPolygons = current_prev; InnerPolygons.AddRange(nextShellTooThin); } return(true); }
protected virtual List <GeneralPolygon2d> combine_solids(List <GeneralPolygon2d> all_solids, List <GeneralPolygon2d> new_solids) { return(ClipperUtil.PolygonBoolean(all_solids, new_solids, ClipperUtil.BooleanOp.Union, MIN_AREA)); }
/// <summary> /// Convert assembly of polygons, polylines, etc, into a set of printable solids and paths /// </summary> public virtual void Resolve() { // combine solids, process largest-to-smallest if (InputSolids.Count > 0) { GeneralPolygon2d[] solids = InputSolids.ToArray(); solids = process_input_polys_before_sort(solids); // sort by decreasing weight double[] weights = new double[solids.Length]; for (int i = 0; i < solids.Length; ++i) { weights[i] = sorting_weight(solids[i]); } Array.Sort(weights, solids); Array.Reverse(solids); solids = process_input_polys_after_sort(solids); Solids = new List <GeneralPolygon2d>(); for (int k = 0; k < solids.Length; ++k) { // convert this polygon into the solid we want to use List <GeneralPolygon2d> resolvedSolid = make_solid(solids[k], false); // now union in with accumulated solids if (Solids.Count == 0) { Solids.AddRange(resolvedSolid); } else { Solids = combine_solids(Solids, resolvedSolid); } } } // subtract input cavities foreach (var cavity in InputCavities) { Solids = remove_cavity(Solids, cavity); } // subtract thickened embedded paths from solids if (EmbeddedPaths.Count > 0 && EmbeddedPathWidth == 0) { throw new Exception("PlanarSlice.Resolve: must set embedded path width!"); } foreach (var path in EmbeddedPaths) { Polygon2d thick_path = make_thickened_path(path, EmbeddedPathWidth); Solids = ClipperUtil.Difference(Solids, new GeneralPolygon2d(thick_path), MIN_AREA); Paths.Add(path); } // cleanup Solids = filter_solids(Solids); // subtract solids from clipped paths foreach (var path in ClippedPaths) { List <PolyLine2d> clipped = ClipperUtil.ClipAgainstPolygon(Solids, path); foreach (var cp in clipped) { Paths.Add(cp); } } // combine support solids, while also subtracting print solids and thickened paths if (InputSupportSolids.Count > 0) { // make assembly of path solids // [TODO] do we need to boolean these? List <GeneralPolygon2d> path_solids = null; if (Paths.Count > 0) { path_solids = new List <GeneralPolygon2d>(); foreach (var path in Paths) { path_solids.Add(new GeneralPolygon2d(make_thickened_path(path, EmbeddedPathWidth))); } } foreach (var solid in InputSupportSolids) { // convert this polygon into the solid we want to use List <GeneralPolygon2d> resolved = make_solid(solid, true); // now subtract print solids resolved = ClipperUtil.PolygonBoolean(resolved, Solids, ClipperUtil.BooleanOp.Difference, MIN_AREA); // now subtract paths if (path_solids != null) { resolved = ClipperUtil.PolygonBoolean(resolved, path_solids, ClipperUtil.BooleanOp.Difference, MIN_AREA); } // now union in with accumulated support solids if (SupportSolids.Count == 0) { SupportSolids.AddRange(resolved); } else { SupportSolids = ClipperUtil.PolygonBoolean(SupportSolids, resolved, ClipperUtil.BooleanOp.Union, MIN_AREA); } } SupportSolids = filter_solids(SupportSolids); } // apply crop regions if (InputCropRegions.Count > 0) { // combine crop regions var CropRegions = make_solid(InputCropRegions[0], false); for (int k = 1; k < InputCropRegions.Count; ++k) { CropRegions = combine_solids(CropRegions, make_solid(InputCropRegions[k], false)); } Solids = ClipperUtil.Intersection(CropRegions, Solids, MIN_AREA); Solids = filter_solids(Solids); List <PolyLine2d> cropped_paths = new List <PolyLine2d>(); foreach (var path in Paths) { cropped_paths.AddRange(ClipperUtil.ClipAgainstPolygon(CropRegions, path, true)); } // TODO: filter paths SupportSolids = ClipperUtil.Intersection(CropRegions, SupportSolids, MIN_AREA); SupportSolids = filter_solids(SupportSolids); } }
virtual public void PreRender() { if (in_shutdown()) { return; } if (parameters_dirty) { // offset List <GeneralPolygon2d> offset = ClipperUtil.RoundOffset(combined_all, offset_distance); // aggressively simplify after round offset... foreach (var poly in offset) { poly.Simplify(path_width); } // subtract initial and add tiny gap so these don't get merged by slicer if (SubtractSolids) { offset = ClipperUtil.Difference(offset, combined_solid); offset = ClipperUtil.MiterOffset(offset, -path_width * 0.1); } offset = CurveUtils2.FilterDegenerate(offset, 0.001); foreach (var poly in offset) { poly.Simplify(path_width * 0.02); } DMesh3 mesh = new DMesh3(); MeshEditor editor = new MeshEditor(mesh); foreach (var poly in offset) { TriangulatedPolygonGenerator polygen = new TriangulatedPolygonGenerator() { Polygon = poly }; editor.AppendMesh(polygen.Generate().MakeDMesh()); } MeshTransforms.ConvertZUpToYUp(mesh); if (mesh.TriangleCount > 0) { MeshExtrudeMesh extrude = new MeshExtrudeMesh(mesh); extrude.ExtrudedPositionF = (v, n, vid) => { return(v + Layers * layer_height * Vector3d.AxisY); }; extrude.Extrude(); MeshTransforms.Translate(mesh, -mesh.CachedBounds.Min.y * Vector3d.AxisY); } PreviewSO.ReplaceMesh(mesh, true); //Vector3d translate = scene_bounds.Point(1, -1, 1); //translate.x += spiral.Bounds.Width + PathWidth; //Frame3f sceneF = Frame3f.Identity.Translated((Vector3f)translate); //PreviewSO.SetLocalFrame(sceneF, CoordSpace.SceneCoords); parameters_dirty = false; } }