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); }
/// <summary> /// fill poly w/ adjacent straight line segments, connected by connectors /// </summary> protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly) { FillCurveSet2d paths = new FillCurveSet2d(); // smooth the input poly a little bit, this simplifies the filling // (simplify after?) if (SimplifyBeforeFilling) { poly = SimplifyInputPolygon(poly); } // compute 2D non-manifold graph consisting of original polygon and // inserted line segments DGraph2 spanGraph = ComputeSpanGraph(poly); if (spanGraph == null || spanGraph.VertexCount == poly.VertexCount) { return(paths); } DGraph2 pathGraph = BuildPathGraph(spanGraph); // filter out self-overlaps from graph if (FilterSelfOverlaps) { pathGraph = FilterPathGraphSelfOverlaps(pathGraph); } return(WalkPathGraph(pathGraph)); }
/// <summary> /// Fallback to deal with very tiny polygons that disappear when insetting. /// This happens at Z-minima-tips, which can be a problem because it may leave /// gaps between layers. For tips we draw a tiny circle. /// For elongated shapes we...?? currently do something dumb. /// Probably should use robust thinning! /// </summary> public virtual void HandleTinyPolygon() { //(InsetFromInputPolygon) ? //ClipperUtil.ComputeOffsetPolygon(Polygon, -ToolWidth / 2, true) : AxisAlignedBox2d bounds = Polygon.Bounds; if (bounds.MaxDim < ToolWidth) { GeneralPolygon2d min_poly = new GeneralPolygon2d(Polygon2d.MakeCircle(ToolWidth / 4, 6)); min_poly.Outer.Translate(bounds.Center); FillCurveSet2d paths = ShellPolysToPaths(new List <GeneralPolygon2d>() { min_poly }, 0); Shells.Add(paths); } else { FillCurveSet2d paths = ShellPolysToPaths(new List <GeneralPolygon2d>() { Polygon }, 0); Shells.Add(paths); } InnerPolygons = new List <GeneralPolygon2d>(); }
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); }
protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly, SegmentSet2d polyCache) { List <List <Segment2d> > StepSpans = ComputeSegments(poly, polyCache); int N = StepSpans.Count; //double hard_max_dist = 5 * PathSpacing; // [TODO] need a pathfinder here, that can chain segments efficiently // (for now just do dumb things?) FillCurveSet2d paths = new FillCurveSet2d(); //FillPolyline2d cur = new FillPolyline2d(); foreach (var seglist in StepSpans) { foreach (Segment2d seg in seglist) { FillPolyline2d fill_seg = new FillPolyline2d() { TypeFlags = FillTypeFlags.SolidInfill }; fill_seg.AppendVertex(seg.P0); fill_seg.AppendVertex(seg.P1); paths.Append(fill_seg); } } return(paths); }
protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly, SegmentSet2d polyCache) { var stepSpans = ComputeSegments(poly, polyCache); // [TODO] need a pathfinder here, that can chain segments efficiently // (for now just do dumb things?) FillCurveSet2d paths = new FillCurveSet2d(); foreach (var seglist in stepSpans) { foreach (Segment2d seg in seglist) { // Discard paths that are too short if (seg.Length < MinPathLengthMM) { continue; } var fill_seg = new FillCurve <FillSegment>() { FillType = FillType, }; fill_seg.BeginCurve(seg.P0); fill_seg.AddToCurve(seg.P1, new FillSegment()); paths.Append(fill_seg); } } return(paths); }
public void SortAndAppendTo(Vector2d startPoint, IFillPathScheduler2d scheduler) { var saveHint = scheduler.SpeedHint; OutPoint = startPoint; List <Index3i> sorted = find_short_path_v1(startPoint); foreach (Index3i idx in sorted) { FillCurveSet2d paths = new FillCurveSet2d(); SchedulerSpeedHint pathHint = SchedulerSpeedHint.Default; if (idx.a == 0) // loop { PathLoop loop = Loops[idx.b]; pathHint = loop.speedHint; if (idx.c != 0) { int iStart = idx.c; FillPolygon2d o = new FillPolygon2d(); int N = loop.curve.VertexCount; for (int i = 0; i < N; ++i) { o.AppendVertex(loop.curve[(i + iStart) % N]); } o.TypeFlags = loop.curve.TypeFlags; paths.Append(o); OutPoint = o.Vertices[0]; } else { paths.Append(loop.curve); OutPoint = loop.curve.Vertices[0]; } } else // span { PathSpan span = Spans[idx.b]; if (idx.c == 1) { span.curve.Reverse(); } paths.Append(span.curve); OutPoint = span.curve.End; pathHint = span.speedHint; } scheduler.SpeedHint = pathHint; scheduler.AppendCurveSets(new List <FillCurveSet2d>() { paths }); } scheduler.SpeedHint = saveHint; }
public virtual void SortAndAppendTo(Vector2d startPoint, IFillPathScheduler2d scheduler) { var saveHint = scheduler.SpeedHint; CurrentPosition = startPoint; List <Index3i> sorted = find_short_path_v1(startPoint); foreach (Index3i idx in sorted) { FillCurveSet2d paths = new FillCurveSet2d(); SpeedHint pathHint = SpeedHint.Default; if (idx.a == 0) { // loop PathLoop loop = Loops[idx.b]; pathHint = loop.speedHint; if (idx.c != 0) { var rolled = loop.loop.RollToVertex(idx.c); paths.Append(rolled); CurrentPosition = rolled.Entry; } else { paths.Append(loop.loop); CurrentPosition = loop.loop.Entry; } } else { // span PathSpan span = Spans[idx.b]; if (idx.c == 1) { span.curve = span.curve.Reversed(); } paths.Append(span.curve); CurrentPosition = span.curve.Exit; pathHint = span.speedHint; } scheduler.SpeedHint = pathHint; scheduler.AppendCurveSets(new List <FillCurveSet2d>() { paths }); } scheduler.SpeedHint = saveHint; }
private FillCurveSet2d WalkPathGraph(DGraph2 pathGraph) { var boundaries = IdentifyBoundaryHashSet(pathGraph); var paths = new FillCurveSet2d(); // walk paths from boundary vertices while (boundaries.Count > 0) { int start_vid = boundaries.First(); boundaries.Remove(start_vid); int vid = start_vid; int eid = pathGraph.GetVtxEdges(vid)[0]; var path = new FillCurve <FillSegment>() { FillType = this.FillType }; path.BeginCurve(pathGraph.GetVertex(vid)); while (true) { Index2i next = DGraph2Util.NextEdgeAndVtx(eid, vid, pathGraph); eid = next.a; vid = next.b; int gid = pathGraph.GetEdgeGroup(eid); if (gid < 0) { path.AddToCurve(pathGraph.GetVertex(vid), new FillSegment(true)); } else { path.AddToCurve(pathGraph.GetVertex(vid)); } if (boundaries.Contains(vid)) { boundaries.Remove(vid); break; } } // discard paths that are too short if (path.TotalLength() < MinPathLengthMM) { continue; } // run polyline simplification to get rid of unneccesary detail in connectors // [TODO] we could do this at graph level...) // [TODO] maybe should be checking for collisions? we could end up creating // non-trivial overlaps here... if (SimplifyAmount != SimplificationLevel.None && path.Elements.Count > 1) { path = SimplifyPath(path); } paths.Append(path); } return(paths); }
/// <summary> /// fill poly w/ adjacent straight line segments, connected by connectors /// </summary> protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly) { FillCurveSet2d paths = new FillCurveSet2d(); // smooth the input poly a little bit, this simplifies the filling // (simplify after?) //GeneralPolygon2d smoothed = poly.Duplicate(); //CurveUtils2.LaplacianSmoothConstrained(smoothed, 0.5, 5, ToolWidth / 2, true, false); //poly = smoothed; // compute 2D non-manifold graph consisting of original polygon and // inserted line segments DGraph2 spanGraph = ComputeSpanGraph(poly); if (spanGraph == null || spanGraph.VertexCount == poly.VertexCount) { return(paths); } DGraph2 pathGraph = BuildPathGraph(spanGraph); // filter out self-overlaps from graph if (FilterSelfOverlaps) { PathOverlapRepair repair = new PathOverlapRepair(pathGraph); repair.OverlapRadius = ToolWidth * SelfOverlapToolWidthX; repair.PreserveEdgeFilterF = (eid) => { return(repair.Graph.GetEdgeGroup(eid) > 0); }; repair.Compute(); pathGraph = repair.GetResultGraph(); } HashSet <int> boundaries = new HashSet <int>(); foreach (int vid in pathGraph.VertexIndices()) { if (pathGraph.IsBoundaryVertex(vid)) { boundaries.Add(vid); } if (pathGraph.IsJunctionVertex(vid)) { throw new Exception("DenseLinesFillPolygon: PathGraph has a junction???"); } } // walk paths from boundary vertices while (boundaries.Count > 0) { int start_vid = boundaries.First(); boundaries.Remove(start_vid); int vid = start_vid; int eid = pathGraph.GetVtxEdges(vid)[0]; FillPolyline2d path = new FillPolyline2d() { TypeFlags = this.TypeFlags }; path.AppendVertex(pathGraph.GetVertex(vid)); while (true) { Index2i next = DGraph2Util.NextEdgeAndVtx(eid, vid, pathGraph); eid = next.a; vid = next.b; int gid = pathGraph.GetEdgeGroup(eid); if (gid < 0) { path.AppendVertex(pathGraph.GetVertex(vid), TPVertexFlags.IsConnector); } else { path.AppendVertex(pathGraph.GetVertex(vid)); } if (boundaries.Contains(vid)) { boundaries.Remove(vid); break; } } // discard paths that are too short if (path.ArcLength < MinPathLengthMM) { continue; } // run polyline simplification to get rid of unneccesary detail in connectors // [TODO] we could do this at graph level...) // [TODO] maybe should be checkign for collisions? we could end up creating // non-trivial overlaps here... if (SimplifyAmount != SimplificationLevel.None && path.VertexCount > 2) { PolySimplification2 simp = new PolySimplification2(path); switch (SimplifyAmount) { default: case SimplificationLevel.Minor: simp.SimplifyDeviationThreshold = ToolWidth / 4; break; case SimplificationLevel.Aggressive: simp.SimplifyDeviationThreshold = ToolWidth; break; case SimplificationLevel.Moderate: simp.SimplifyDeviationThreshold = ToolWidth / 2; break; } simp.Simplify(); path = new FillPolyline2d(simp.Result.ToArray()) { TypeFlags = this.TypeFlags }; } paths.Append(path); } // Check to make sure that we are not putting way too much material in the // available volume. Computes extrusion volume from path length and if the // ratio is too high, scales down the path thickness // TODO: do we need to compute volume? If we just divide everything by // height we get the same scaling, no? Then we don't need layer height. if (MaxOverfillRatio > 0) { throw new NotImplementedException("this is not finished yet"); #if false double LayerHeight = 0.2; // AAAHHH hardcoded nonono double len = paths.TotalLength(); double extrude_vol = ExtrusionMath.PathLengthToVolume(LayerHeight, ToolWidth, len); double polygon_vol = LayerHeight * Math.Abs(poly.Area); double ratio = extrude_vol / polygon_vol; if (ratio > MaxOverfillRatio && PathSpacing == ToolWidth) { double use_width = ExtrusionMath.WidthFromTargetVolume(LayerHeight, len, polygon_vol); //System.Console.WriteLine("Extrusion volume: {0} PolyVolume: {1} % {2} ScaledWidth: {3}", //extrude_vol, polygon_vol, extrude_vol / polygon_vol, use_width); foreach (var path in paths.Curves) { path.CustomThickness = use_width; } } #endif } return(paths); }
/// <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(); FillTypeFlags flags = FillTypeFlags.PerimeterShell; if (nShell == 0 && ShellType == ShellTypes.ExternalPerimeters) { flags = FillTypeFlags.OutermostShell; } else if (ShellType == ShellTypes.InternalShell) { flags = FillTypeFlags.InteriorShell; } else if (ShellType == ShellTypes.BridgeShell) { flags = FillTypeFlags.BridgeSupport; } if (FilterSelfOverlaps == false) { foreach (GeneralPolygon2d shell in shell_polys) { paths.Append(shell, flags); } 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 && ShellType == ShellTypes.ExternalPerimeters) { repair.PreserveEdgeFilterF = (eid) => { return(repair.Graph.GetEdgeGroup(eid) == outer_shell_edgegroup); } } ; repair.Compute(); DGraph2Util.Curves c = DGraph2Util.ExtractCurves(repair.GetResultGraph()); foreach (var polygon in c.Loops) { paths.Append(polygon, flags); } foreach (var polyline in c.Paths) { if (polyline.ArcLength < DiscardTinyPerimterLengthMM) { continue; } if (polyline.Bounds.MaxDim < DiscardTinyPerimterLengthMM) { continue; } paths.Append(new FillPolyline2d(polyline) { TypeFlags = flags }); } } return(paths); }
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); }
/// <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); }