/// <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 static void TestOffsetAnimation() { Window window = new Window("TestFill"); window.SetDefaultSize(600, 600); window.SetPosition(WindowPosition.Center); DMesh3 mesh = StandardMeshReader.ReadMesh("c:\\scratch\\remesh.obj"); MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); DCurve3 curve = loops[0].ToCurve(); Polygon2d poly = new Polygon2d(); foreach (Vector3d v in curve.Vertices) { poly.AppendVertex(v.xy); } Outer = new GeneralPolygon2d(poly); DebugViewCanvas view = new DebugViewCanvas(); view.AddPolygon(Outer, Colorf.Black); DGraph2 graph = TopoOffset2d.QuickCompute(Outer, AnimOffset, AnimSpacing); view.AddGraph(graph, Colorf.Red); window.Add(view); window.ShowAll(); Active = view; }
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); }
public static List <GeneralPolygon2d> RoundOffset(GeneralPolygon2d poly, double fOffset, double minArea = -1) { return(ComputeOffsetPolygon(new List <GeneralPolygon2d>() { poly }, fOffset, false, minArea)); }
/// <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)); }
// Compute step-forward at cur_pos. We find the closest point on the poly, // and step away from that, unless we go to far, then we step back. protected Vector2d compute_offset_step(Vector2d cur_pos, GeneralPolygon2d poly, double fTargetOffset, double stepSize, out double err) { int iHole, iSeg; double segT; double distSqr = poly_tree.DistanceSquared(cur_pos, out iHole, out iSeg, out segT); double dist = Math.Sqrt(distSqr); Vector2d normal = poly.GetNormal(iSeg, segT, iHole); // flip for negative offsets if (fTargetOffset < 0) { fTargetOffset = -fTargetOffset; normal = -normal; } double step = stepSize; if (dist > fTargetOffset) { step = Math.Max(fTargetOffset - dist, -step); } else { step = Math.Min(fTargetOffset - dist, step); } err = Math.Abs(fTargetOffset - dist); Vector2d new_pos = cur_pos - step * normal; return(new_pos); }
public void AddPolygonsTest() { PlanarSlice slice = new PlanarSlice(); Polygon2d p2d = new Polygon2d(new List <Vector2d>() { new Vector2d(1, 0), new Vector2d(0, 1), new Vector2d(1, 1) }); GeneralPolygon2d poly = new GeneralPolygon2d(p2d); Polygon2d p2d2 = new Polygon2d(new List <Vector2d>() { new Vector2d(-1, 0), new Vector2d(0, -1), new Vector2d(-1, -1) }); GeneralPolygon2d poly2 = new GeneralPolygon2d(p2d2); slice.AddPolygons(new List <GeneralPolygon2d>() { poly, poly2 }); Assert.AreEqual(slice.InputSolids[0], poly); Assert.AreEqual(slice.InputSolids[1], poly2); }
// [RMS] only using this for hit-testing to make sure no connectors cross polygon border... // [TODO] replace with GeneralPolygon2dBoxTree (currently does not have intersection test!) //SegmentSet2d BoundaryPolygonCache; public ParallelLinesFillPolygon(GeneralPolygon2d poly, IFillType fillType, bool exportToSVG = false) { Polygon = poly; FillCurves = new List <FillCurveSet2d>(); FillType = fillType; this.exportToSVG = exportToSVG; }
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 static SKPath ToSKPath(GeneralPolygon2d g, Func <Vector2d, SKPoint> mapF) { SKPath p = new SKPath(); int N = g.Outer.VertexCount; p.MoveTo(mapF(g.Outer[0])); for (int i = 1; i < N; i++) { p.LineTo(mapF(g.Outer[i])); } p.Close(); foreach (Polygon2d h in g.Holes) { int hN = h.VertexCount; p.MoveTo(mapF(h[0])); for (int i = 1; i < hN; ++i) { p.LineTo(mapF(h[i])); } p.Close(); } return(p); }
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 static List <GeneralPolygon2d> PolygonBoolean(GeneralPolygon2d poly1, List <GeneralPolygon2d> poly2, BooleanOp opType, double minArea = -1) { return(PolygonBoolean(new List <GeneralPolygon2d>() { poly1 }, poly2, opType, minArea)); }
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); }
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)); }
// approximately check thickness of poly. For each segment, offset by check_offset*seg_normal, // then find distance to nearset point on poly. If distance_sqr is < mindist_sqr, // then we are below thin-tolerance. // // Currently returns true/false test, which is stupid... // // Will definitely fail on: squares (w/ short seg near edge), thin narrow bits... ?? bool is_too_thin(GeneralPolygon2d poly, double check_offset, double mindist_sqr) { Debug.Assert(mindist_sqr < 0.95 * check_offset * check_offset); bool failed = false; Action <Segment2d> seg_checkF = (seg) => { if (failed) { return; } if (seg.Length < 0.01) // not robust if segment is too short { return; } Vector2d n = -seg.Direction.Perp; Vector2d pt = seg.Center + check_offset * n; int iHole, iSeg; double segT; double dist_sqr = poly.DistanceSquared(pt, out iHole, out iSeg, out segT); if (dist_sqr < mindist_sqr) { failed = true; } }; gParallel.ForEach(poly.AllSegmentsItr(), seg_checkF); return(failed); }
public void TrimSegment2d_SegmentCollinear() { // Checks that when the segment is coincident with segments of the polygon, // segments that lie on polygon edges are not included, as per the intended // behavior. // Arrange var square = new GeneralPolygon2d(new Polygon2d(new double[] { 0, 0, 3, 0, 3, 1, 2, 1, 2, 1.3, 2, 1.6, 2, 2, 3, 2, 3, 3, 0, 3, })); var seg = new Segment2d(new Vector2d(2, 0.5), new Vector2d(2, 2.5)); // Act var result = square.TrimSegment2d(seg); // Assert Assert.AreEqual(2, result.Count); AssertExtensions.AreEqual(new Vector2d(2, 0.5), result[0].P0); AssertExtensions.AreEqual(new Vector2d(2, 1.0), result[0].P1); AssertExtensions.AreEqual(new Vector2d(2, 2.0), result[1].P0); AssertExtensions.AreEqual(new Vector2d(2, 2.5), result[1].P1); }
public static bool IsOutside(this GeneralPolygon2d poly, Segment2d seg) { bool isOutside = true; if (poly.Outer.IsMember(seg, out isOutside)) { if (isOutside) { return(true); } else { return(false); } } foreach (Polygon2d hole in poly.Holes) { if (hole.IsMember(seg, out isOutside)) { if (isOutside) { return(true); } else { return(false); } } } return(false); }
/// <summary> /// Check for significant topology/area changes in polygon inset. /// Currently returns true if: /// 1) inset has diff # of polygons or holes in single poly /// 2) area change is significantly larger than what we expect /// (inset-band area estimated as perimeter_len * k * inset_dist) /// </summary> protected virtual bool check_large_topology_change( GeneralPolygon2d input, List <GeneralPolygon2d> inset, double fInsetDist) { if (inset.Count != 1 || inset[0].Holes.Count != input.Holes.Count) { return(true); } double orig_area = Math.Abs(input.Area); double perim = input.Perimeter; double perim_band_area = perim * fInsetDist; double est_inset_area = orig_area - 1.5 * perim_band_area; double inset_area = 0; foreach (var poly in inset) { inset_area += Math.Abs(poly.Area); } if (inset_area < est_inset_area) { return(true); } return(false); }
public SegmentSet2d(GeneralPolygon2d poly) { Segments = new List <Segment2d>(poly.Outer.SegmentItr()); foreach (var hole in poly.Holes) { Segments.AddRange(hole.SegmentItr()); } }
public void AddPolygon(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) { poly.Reverse(); } InputSolids.Add(poly); }
public void AddCropRegion(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) { poly.Reverse(); } InputCropRegions.Add(poly); }
public void AddCavityPolygon(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) { poly.Reverse(); } InputCavities.Add(poly); }
/// <summary> /// use during resolve() processing to transfer tags/metadata to child polygons /// created by processing ops /// </summary> protected virtual void transfer_tags(GeneralPolygon2d oldPoly, GeneralPolygon2d newPoly) { if (Tags.Has(oldPoly)) { int t = Tags.Get(oldPoly); Tags.Add(newPoly, t); } }
public void Add(GeneralPolygon2d path, int outer_gid = -1, int hole_gid = -1) { Graph.AppendPolygon(path.Outer, outer_gid); foreach (Polygon2d hole in path.Holes) { Graph.AppendPolygon(hole, hole_gid); } }
private GeneralPolygon2d SimplifyInputPolygon(GeneralPolygon2d poly) { GeneralPolygon2d smoothed = poly.Duplicate(); CurveUtils2.LaplacianSmoothConstrained(smoothed, 0.5, 5, ToolWidth / 2, true, false); poly = smoothed; return(poly); }
public static void Store(GeneralPolygon2d polygon, BinaryWriter writer) { Store(polygon.Outer, writer); writer.Write(polygon.Holes.Count); for (int i = 0; i < polygon.Holes.Count; ++i) { Store(polygon.Holes[i], writer); } }
public TopoOffset2d(GeneralPolygon2d poly, double fOffset, double fPointSpacing, bool bAutoCompute = true) { Polygon = poly; Offset = fOffset; PointSpacing = fPointSpacing; if (bAutoCompute) { Compute(); } }
/// <summary> /// Fill polygon with solid fill strategy. /// If bIsInfillAdjacent, then we optionally add one or more shells around the solid /// fill, to give the solid fill something to stick to (imagine dense linear fill adjacent /// to sparse infill area - when the extruder zigs, most of the time there is nothing /// for the filament to attach to, so it pulls back. ugly!) /// </summary> protected virtual void fill_solid_region(int layer_i, GeneralPolygon2d solid_poly, IFillPathScheduler2d scheduler, bool bIsInfillAdjacent = false) { List <GeneralPolygon2d> fillPolys = new List <GeneralPolygon2d>() { solid_poly }; // if we are on an infill layer, and this shell has some infill region, // then we are going to draw contours around solid fill so it has // something to stick to // [TODO] should only be doing this if solid-fill is adjecent to infill region. // But how to determine this? not easly because we don't know which polys // came from where. Would need to do loop above per-polygon if (bIsInfillAdjacent && Settings.Part.InteriorSolidRegionShells > 0) { ShellsFillPolygon interior_shells = new ShellsFillPolygon(solid_poly, Settings.FillTypeFactory.Solid()); interior_shells.PathSpacing = Settings.SolidFillPathSpacingMM(); interior_shells.ToolWidth = Settings.Machine.NozzleDiamMM; interior_shells.Layers = Settings.Part.InteriorSolidRegionShells; interior_shells.PreserveOuterShells = true; interior_shells.InsetFromInputPolygonX = 0; interior_shells.Compute(); scheduler.AppendCurveSets(interior_shells.Shells); fillPolys = interior_shells.InnerPolygons; } // now actually fill solid regions foreach (GeneralPolygon2d fillPoly in fillPolys) { var solidFillSpacing = Settings.SolidFillPathSpacingMM(); TiledFillPolygon tiled_fill = new TiledFillPolygon(fillPoly) { TileSize = 13.1 * solidFillSpacing, TileOverlap = 0.3 * solidFillSpacing }; tiled_fill.TileFillGeneratorF = (tilePoly, index) => { int odd = ((index.x + index.y) % 2 == 0) ? 1 : 0; RasterFillPolygon solid_gen = new RasterFillPolygon(tilePoly, Settings.FillTypeFactory.Solid()) { InsetFromInputPolygon = false, PathSpacing = solidFillSpacing, ToolWidth = Settings.Machine.NozzleDiamMM, AngleDeg = LayerFillAngleF(layer_i + odd) }; return(solid_gen); }; tiled_fill.Compute(); scheduler.AppendCurveSets(tiled_fill.FillCurves); } }
static SKPath MakePaths(GeneralPolygon2d poly, Func <Vector2d, SKPoint> mapF) { SKPath p = new SKPath(); add_poly(p, poly.Outer, mapF); foreach (var h in poly.Holes) { add_poly(p, h, mapF); } return(p); }
public static CPolygonList ConvertToClipper(GeneralPolygon2d polys, double nIntScale) { List <CPolygon> clipper_polys = new List <CPolygon>(); clipper_polys.Add(ConvertToClipper(polys.Outer, nIntScale)); foreach (Polygon2d hole in polys.Holes) { clipper_polys.Add(ConvertToClipper(hole, nIntScale)); } return(clipper_polys); }