コード例 #1
0
        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);
        }
コード例 #2
0
        /// <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));
        }
コード例 #3
0
        /// <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>();
        }
コード例 #4
0
        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);
        }
コード例 #5
0
        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);
        }
コード例 #6
0
        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);
        }
コード例 #7
0
ファイル: SortingScheduler2d.cs プロジェクト: qubick/gsSlicer
        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;
        }
コード例 #8
0
        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;
        }
コード例 #9
0
        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);
        }
コード例 #10
0
        /// <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);
        }
コード例 #11
0
        /// <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);
        }
コード例 #12
0
        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);
        }
コード例 #13
0
        /// <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);
        }