From 79823a8494cdbb8fd6374547fa1d053a08c2b6f0 Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Sun, 3 Nov 2019 14:45:19 -0800 Subject: [PATCH] Integrate gsSlicerPro extensions into base gsSlicer classes. --- generators/PrintMeshAssembly.cs | 3 + generators/SingleMaterialFFFPrintGenPro.cs | 303 --------------------- generators/ThreeAxisPrintGenerator.cs | 43 +++ gsSlicer.csproj | 4 +- slicing/MeshPlanarSlicer.cs | 24 ++ slicing/MeshPlanarSlicerPro.cs | 78 ------ slicing/PlanarSlice.cs | 122 ++++++--- slicing/PlanarSlicePro.cs | 116 -------- utility/PolygonDecomposer.cs | 222 +++++++++++++++ 9 files changed, 375 insertions(+), 540 deletions(-) delete mode 100644 generators/SingleMaterialFFFPrintGenPro.cs delete mode 100644 slicing/MeshPlanarSlicerPro.cs delete mode 100644 slicing/PlanarSlicePro.cs create mode 100644 utility/PolygonDecomposer.cs diff --git a/generators/PrintMeshAssembly.cs b/generators/PrintMeshAssembly.cs index a1b946c..f6b3592 100644 --- a/generators/PrintMeshAssembly.cs +++ b/generators/PrintMeshAssembly.cs @@ -14,6 +14,9 @@ public class PrintMeshOptions public bool IsCropRegion = false; // treat as crop region public bool IsOpen = false; // treat as open mesh (ie do not fill) + public double ClearanceXY = 0; + public double OffsetXY = 0; + public enum OpenPathsModes { Embedded = 0, Clipped = 1, Ignored = 2, Default = 10 diff --git a/generators/SingleMaterialFFFPrintGenPro.cs b/generators/SingleMaterialFFFPrintGenPro.cs deleted file mode 100644 index e436230..0000000 --- a/generators/SingleMaterialFFFPrintGenPro.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using g3; - -namespace gs -{ - public class SingleMaterialFFFPrintGenPro : SingleMaterialFFFPrintGenerator - { - public SingleMaterialFFFPrintGenPro(PrintMeshAssembly meshes, - PlanarSliceStack slices, - SingleMaterialFFFSettings settings, - AssemblerFactoryF overrideAssemblerF = null) - : base(meshes, slices, settings, overrideAssemblerF) - { - } - - - - - /// - /// Fill a bridge region. Goal is to use shortest paths possible. - /// So, instead of just using fixed angle, we fit bounding box and - /// use the shorter axis. - /// - protected override void fill_bridge_region(GeneralPolygon2d poly, IFillPathScheduler2d scheduler, PrintLayerData layer_data) - { - base.fill_bridge_region(poly, scheduler, layer_data); - //fill_bridge_region_decompose(poly, scheduler, layer_data); - } - - - - - 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 polys = decompose_mesh_recursive(mesh); - List 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); - //} - } - - - - - List decompose_cluster_up(DMesh3 mesh) - { - optimize_mesh(mesh); - mesh.CompactInPlace(); - mesh.DiscardTriangleGroups(); mesh.EnableTriangleGroups(0); - - double minLength = Settings.MaxBridgeWidthMM * 0.75; - double minArea = minLength * minLength; - - Dictionary areas = new Dictionary(); - Dictionary> trisets = new Dictionary>(); - HashSet active_groups = new HashSet(); - - Action add_tri_to_group = (tid, gid) => { - mesh.SetTriangleGroup(tid, gid); - areas[gid] = areas[gid] + mesh.GetTriArea(tid); - trisets[gid].Add(tid); - }; - Action add_group_to_group = (gid, togid) => { - var set = trisets[togid]; - foreach (int tid in trisets[gid]) { - mesh.SetTriangleGroup(tid, togid); - set.Add(tid); - } - areas[togid] += areas[gid]; - active_groups.Remove(gid); - }; - Func, int> find_min_area_group = (tri_itr) => { - int min_gid = -1; double min_area = double.MaxValue; - foreach (int tid in tri_itr) { - int gid = mesh.GetTriangleGroup(tid); - double a = areas[gid]; - if (a < min_area) { - min_area = a; - min_gid = gid; - } - } - return min_gid; - }; - - - foreach (int eid in MeshIterators.InteriorEdges(mesh)) { - Index2i et = mesh.GetEdgeT(eid); - if (mesh.GetTriangleGroup(et.a) != 0 || mesh.GetTriangleGroup(et.b) != 0) - continue; - int gid = mesh.AllocateTriangleGroup(); - areas[gid] = 0; - trisets[gid] = new HashSet(); - active_groups.Add(gid); - add_tri_to_group(et.a, gid); - add_tri_to_group(et.b, gid); - } - foreach (int tid in mesh.TriangleIndices()) { - if (mesh.GetTriangleGroup(tid) != 0) - continue; - int gid = find_min_area_group(mesh.TriTrianglesItr(tid)); - add_tri_to_group(tid, gid); - } - - - IndexPriorityQueue pq = new IndexPriorityQueue(mesh.MaxGroupID); - foreach (var pair in areas) { - pq.Insert(pair.Key, (float)pair.Value); - } - while (pq.Count > 0) { - int gid = pq.First; - pq.Remove(gid); - if (areas[gid] > minArea) // ?? - break; - - List nbr_groups = find_neighbour_groups(mesh, gid, trisets[gid]); - int min_gid = -1; double min_area = double.MaxValue; - foreach (int ngid in nbr_groups) { - double a = areas[ngid]; - if (a < min_area) { - min_area = a; - min_gid = ngid; - } - } - if (min_gid != -1) { - add_group_to_group(gid, min_gid); - pq.Remove(min_gid); - pq.Insert(min_gid, (float)areas[min_gid]); - } - } - - - - List result = new List(); - int[][] sets = FaceGroupUtil.FindTriangleSetsByGroup(mesh); - foreach (var set in sets) - result.Add(make_poly(mesh, set)); - return result; - } - - - List find_neighbour_groups(DMesh3 mesh, int gid, HashSet group_tris) - { - List result = new List(); - foreach (int tid in group_tris) { - foreach (int ntid in mesh.TriTrianglesItr(tid)) { - int ngid = mesh.GetTriangleGroup(ntid); - if (ngid != gid && result.Contains(ngid) == false) - result.Add(ngid); - } - } - return result; - } - - - - - - - - - - Polygon2d make_poly(DMesh3 mesh, IEnumerable triangles) - { - DSubmesh3 submesh = new DSubmesh3(mesh, triangles); - MeshBoundaryLoops loops = new MeshBoundaryLoops(submesh.SubMesh); - Util.gDevAssert(loops.Loops.Count == 1); - return make_poly(submesh.SubMesh, loops.Loops[0]); - } - Polygon2d make_poly(DMesh3 mesh, EdgeLoop loop) - { - Polygon2d poly = new Polygon2d(); - for (int k = 0; k < loop.VertexCount; ++k) { - Vector3d v = mesh.GetVertex(loop.Vertices[k]); - poly.AppendVertex(v.xy); - } - return poly; - } - - - - - - - void optimize_mesh(DMesh3 mesh) - { - Reducer reducer = new Reducer(mesh); - MeshConstraints constraints = new MeshConstraints(); - MeshConstraintUtil.FixAllBoundaryEdges(constraints, mesh); - reducer.SetExternalConstraints(constraints); - reducer.ReduceToTriangleCount(1); - - Vector3d a, b, c, d; - a = b = c = d = Vector3d.Zero; - - bool done = false; - while (!done) { - done = true; - - for (int eid = 0; eid < mesh.MaxEdgeID; ++eid) { - if (mesh.IsEdge(eid) == false) - continue; - - Index4i evt = mesh.GetEdge(eid); - if (evt.d == DMesh3.InvalidID) - continue; - a = mesh.GetVertex(evt.a); b = mesh.GetVertex(evt.b); - Index2i ov = mesh.GetEdgeOpposingV(eid); - c = mesh.GetVertex(ov.a); d = mesh.GetVertex(ov.b); - - if (c.DistanceSquared(d) > a.DistanceSquared(b)) - continue; - if (MeshUtil.CheckIfEdgeFlipCreatesFlip(mesh, eid)) - continue; - - DMesh3.EdgeFlipInfo flipInfo; - if (mesh.FlipEdge(eid, out flipInfo) == MeshResult.Ok) - done = false; - } - } - - - } - - - - - int find_shortest_internal_edge(DMesh3 mesh) - { - double shortSqr = double.MaxValue; - int short_eid = -1; - Vector3d va, vb; va = vb = Vector3d.Zero; - foreach (int eid in mesh.VertexIndices()) { - if (mesh.IsBoundaryEdge(eid)) - continue; - mesh.GetEdgeV(eid, ref va, ref vb); - double lenSqr = va.DistanceSquared(ref vb); - if (lenSqr < shortSqr) { - shortSqr = lenSqr; - short_eid = eid; - } - } - return short_eid; - } - - - } -} diff --git a/generators/ThreeAxisPrintGenerator.cs b/generators/ThreeAxisPrintGenerator.cs index 150b839..f9cc916 100644 --- a/generators/ThreeAxisPrintGenerator.cs +++ b/generators/ThreeAxisPrintGenerator.cs @@ -717,11 +717,54 @@ protected virtual void fill_bridge_region(GeneralPolygon2d poly, IFillPathSchedu scheduler.AppendCurveSets(fill_gen.GetFillCurves()); } + protected virtual void fill_bridge_region_decompose(GeneralPolygon2d poly, IFillPathScheduler2d scheduler, PrintLayerData layer_data) + { + poly.Simplify(0.1, 0.01, true); + + double minLength = Settings.MaxBridgeWidthMM * 0.75; + double minArea = minLength * minLength; + + var polys = PolygonDecomposer.Compute(poly, minArea); + + 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()); + } + } + } /// /// Determine the sparse infill and solid fill regions for a layer, given the input regions that diff --git a/gsSlicer.csproj b/gsSlicer.csproj index 5f6e9e3..8483e3d 100644 --- a/gsSlicer.csproj +++ b/gsSlicer.csproj @@ -46,11 +46,8 @@ - - - @@ -60,6 +57,7 @@ + diff --git a/slicing/MeshPlanarSlicer.cs b/slicing/MeshPlanarSlicer.cs index 838a5e2..c9fc6fd 100644 --- a/slicing/MeshPlanarSlicer.cs +++ b/slicing/MeshPlanarSlicer.cs @@ -349,6 +349,18 @@ protected virtual void add_support_polygons(PlanarSlice slice, List polygons, PrintMeshOptions options) { slice.AddCavityPolygons(polygons); + + if (options.ClearanceXY != 0) + { + foreach (var poly in polygons) + slice.Cavity_Clearances.Add(poly, options.ClearanceXY); + } + + if (options.OffsetXY != 0) + { + foreach (var poly in polygons) + slice.Cavity_Offsets.Add(poly, options.OffsetXY); + } } protected virtual void add_crop_region_polygons(PlanarSlice slice, List polygons, PrintMeshOptions options) @@ -359,6 +371,18 @@ protected virtual void add_crop_region_polygons(PlanarSlice slice, List polygons, PrintMeshOptions options) { slice.AddPolygons(polygons); + + if (options.ClearanceXY != 0) + { + foreach (var poly in polygons) + slice.Clearances.Add(poly, options.ClearanceXY); + } + + if (options.OffsetXY != 0) + { + foreach (var poly in polygons) + slice.Offsets.Add(poly, options.OffsetXY); + } } diff --git a/slicing/MeshPlanarSlicerPro.cs b/slicing/MeshPlanarSlicerPro.cs deleted file mode 100644 index 0500610..0000000 --- a/slicing/MeshPlanarSlicerPro.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using g3; - -namespace gs -{ - - - public class ExtendedPrintMeshOptions - { - public double ClearanceXY = 0; - public double OffsetXY = 0; - } - - - - public class MeshPlanarSlicerPro : MeshPlanarSlicer - { - public MeshPlanarSlicerPro() : base() - { - } - - - protected override void add_solid_polygons(PlanarSlice slice, List polygons, PrintMeshOptions options) - { - slice.AddPolygons(polygons); - - if (options.Extended != null && options.Extended is ExtendedPrintMeshOptions) { - ExtendedPrintMeshOptions ext = options.Extended as ExtendedPrintMeshOptions; - - if (slice is PlanarSlicePro) { - PlanarSlicePro sp = slice as PlanarSlicePro; - - if (ext.ClearanceXY != 0) { - foreach (var poly in polygons) - sp.Clearances.Add(poly, ext.ClearanceXY); - } - - if (ext.OffsetXY != 0) { - foreach (var poly in polygons) - sp.Offsets.Add(poly, ext.OffsetXY); - } - } - } - } - - - - - protected override void add_cavity_polygons(PlanarSlice slice, List polygons, PrintMeshOptions options) - { - slice.AddCavityPolygons(polygons); - - if (options.Extended != null && options.Extended is ExtendedPrintMeshOptions) { - ExtendedPrintMeshOptions ext = options.Extended as ExtendedPrintMeshOptions; - - if (slice is PlanarSlicePro) { - PlanarSlicePro sp = slice as PlanarSlicePro; - - if (ext.ClearanceXY != 0) { - foreach (var poly in polygons) - sp.Cavity_Clearances.Add(poly, ext.ClearanceXY); - } - - if (ext.OffsetXY != 0) { - foreach (var poly in polygons) - sp.Cavity_Offsets.Add(poly, ext.OffsetXY); - } - } - } - } - - - } -} diff --git a/slicing/PlanarSlice.cs b/slicing/PlanarSlice.cs index 6c1616a..e53c560 100644 --- a/slicing/PlanarSlice.cs +++ b/slicing/PlanarSlice.cs @@ -34,7 +34,15 @@ public class PlanarSlice public List InputSupportSolids = new List(); public List InputCropRegions = new List(); - public List InputSupportPoints = new List(); + public Dictionary Clearances = new Dictionary(); + public Dictionary Offsets = new Dictionary(); + + public Dictionary Cavity_Clearances = new Dictionary(); + public Dictionary Cavity_Offsets = new Dictionary(); + + protected Dictionary> Thickened; + + public List InputSupportPoints = new List(); /* * Output geometry, produced by Resolve(). These should not have any intersections. @@ -47,8 +55,6 @@ public class PlanarSlice public List Paths = new List(); public List SupportSolids = new List(); - - // allow integer tags on polygons, which we can use for arbitrary stuff public IntTagSet Tags { get { @@ -59,28 +65,25 @@ public IntTagSet Tags { } IntTagSet tags; - public PlanarSlice() { } - public bool IsEmpty { get { return Solids.Count == 0 && Paths.Count == 0 && SupportSolids.Count == 0; } } - public void AddPolygon(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) poly.Reverse(); InputSolids.Add(poly); } + public void AddPolygons(IEnumerable polys) { foreach (GeneralPolygon2d p in polys) AddPolygon(p); } - public void AddEmbeddedPath(PolyLine2d pline) { EmbeddedPaths.Add(pline); } @@ -88,36 +91,32 @@ public void AddClippedPath(PolyLine2d pline) { ClippedPaths.Add(pline); } - - public void AddSupportPolygon(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) poly.Reverse(); SupportSolids.Add(poly); } + public void AddSupportPolygons(IEnumerable polys) { foreach (GeneralPolygon2d p in polys) AddSupportPolygon(p); } - - public void AddCavityPolygon(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) poly.Reverse(); InputCavities.Add(poly); } + public void AddCavityPolygons(IEnumerable polys) { foreach (GeneralPolygon2d p in polys) AddCavityPolygon(p); } - - public void AddCropRegion(GeneralPolygon2d poly) { if (poly.Outer.IsClockwise) @@ -129,8 +128,6 @@ public void AddCropRegions(IEnumerable polys) { AddCropRegion(p); } - - /// /// Convert assembly of polygons, polylines, etc, into a set of printable solids and paths /// @@ -246,18 +243,46 @@ public virtual void Resolve() } - - /* * functions for subclasses to override to customize behavior */ - - protected virtual GeneralPolygon2d[] process_input_polys_before_sort(GeneralPolygon2d[] solids) { - return solids; + protected virtual GeneralPolygon2d[] process_input_polys_before_sort(GeneralPolygon2d[] polys) { + if (Offsets.Count == 0) + return polys; + List newPolys = new List(); + bool modified = false; + foreach (var poly in polys) + { + double offset; + if (Offsets.TryGetValue(poly, out offset) && Math.Abs(offset) > MathUtil.ZeroTolerancef) + { + List 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 GeneralPolygon2d[] process_input_polys_after_sort(GeneralPolygon2d[] solids) { + // construct thickened solids + Thickened = new Dictionary>(); + 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; } @@ -265,7 +290,6 @@ protected virtual double sorting_weight(GeneralPolygon2d poly) { return poly.Outer.Area; } - protected virtual List make_solid(GeneralPolygon2d poly, bool bIsSupportSolid) { // always do a self-union of outer polygon. This allows Clipper to clean up many @@ -281,8 +305,17 @@ protected virtual List make_solid(GeneralPolygon2d poly, bool 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); - //return resolvedSolid; } protected virtual List combine_solids(List all_solids, List new_solids) @@ -290,13 +323,28 @@ protected virtual List combine_solids(List a return ClipperUtil.PolygonBoolean(all_solids, new_solids, ClipperUtil.BooleanOp.Union, MIN_AREA); } - protected virtual List remove_cavity(List solids, GeneralPolygon2d cavity) { - return ClipperUtil.Difference(solids, cavity, MIN_AREA); + 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); + } } - protected virtual List filter_solids(List solids) { if (MIN_AREA > 0) { @@ -305,10 +353,9 @@ protected virtual List filter_solids(List so return solids; } - /// - /// use during resolve() processing to transfer tags/metadata to child polygons - /// created by processing ops + /// Use during Resolve() processing to transfer tags/metadata to child polygons + /// created by processing operations. /// protected virtual void transfer_tags(GeneralPolygon2d oldPoly, GeneralPolygon2d newPoly) { @@ -316,10 +363,15 @@ protected virtual void transfer_tags(GeneralPolygon2d oldPoly, GeneralPolygon2d int t = Tags.Get(oldPoly); Tags.Add(newPoly, t); } - } - + double clearance; + if (Clearances.TryGetValue(oldPoly, out clearance)) + Clearances[newPoly] = clearance; + double offset; + if (Offsets.TryGetValue(oldPoly, out offset)) + Offsets[newPoly] = offset; + } protected virtual Polygon2d make_thickened_path(PolyLine2d path, double width) { @@ -333,8 +385,6 @@ protected virtual Polygon2d make_thickened_path(PolyLine2d path, double width) return poly; } - - public AxisAlignedBox2d Bounds { get { AxisAlignedBox2d box = AxisAlignedBox2d.Empty; @@ -353,7 +403,6 @@ public AxisAlignedBox2d Bounds { } } - /// /// Returns the unsigned minimum distance to the solid/path polylines. /// Must call BuildSpatialCaches() first, then it is safe to call @@ -390,7 +439,6 @@ public double DistanceSquared(Vector2d pt, double max_dist = double.MaxValue, bo return dist_sqr; } - /// /// Precompute spatial caching information. This is not thread-safe. /// (Currently just list of bboxes for each solid/path.) @@ -415,8 +463,6 @@ public void BuildSpatialCaches() AxisAlignedBox2d[] path_bounds; bool spatial_caches_available = false; - - public void Store(BinaryWriter writer) { writer.Write(LayerIndex); @@ -454,7 +500,6 @@ public void Store(BinaryWriter writer) gSerialization.Store(SupportSolids[k], writer); } - public void Restore(BinaryReader reader) { LayerIndex = reader.ReadInt32(); @@ -500,8 +545,5 @@ public void Restore(BinaryReader reader) for (int k = 0; k < nSupportSolids; ++k) gSerialization.Restore(SupportSolids[k], reader); } - - - } } diff --git a/slicing/PlanarSlicePro.cs b/slicing/PlanarSlicePro.cs deleted file mode 100644 index 988616c..0000000 --- a/slicing/PlanarSlicePro.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using g3; - -namespace gs -{ - public class PlanarSlicePro : PlanarSlice - { - public static PlanarSlice FactoryF(Interval1d ZSpan, double Zheight, int idx) - { - return new PlanarSlicePro() { LayerZSpan = ZSpan, Z = Zheight, LayerIndex = idx }; - } - - - public Dictionary Clearances = new Dictionary(); - public Dictionary Offsets = new Dictionary(); - - - public Dictionary Cavity_Clearances = new Dictionary(); - public Dictionary Cavity_Offsets = new Dictionary(); - - - Dictionary> Thickened; - - - protected override void transfer_tags(GeneralPolygon2d oldPoly, GeneralPolygon2d newPoly) - { - base.transfer_tags(oldPoly, newPoly); - - double clearance; - if (Clearances.TryGetValue(oldPoly, out clearance)) - Clearances[newPoly] = clearance; - double offset; - if (Offsets.TryGetValue(oldPoly, out offset)) - Offsets[newPoly] = offset; - } - - - protected override GeneralPolygon2d[] process_input_polys_before_sort(GeneralPolygon2d[] polys) - { - if (Offsets.Count == 0) - return polys; - List newPolys = new List(); - bool modified = false; - foreach ( var poly in polys ) { - double offset; - if (Offsets.TryGetValue(poly, out offset) && Math.Abs(offset) > MathUtil.ZeroTolerancef) { - List 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 override GeneralPolygon2d[] process_input_polys_after_sort(GeneralPolygon2d[] solids) - { - // construct thickened solids - Thickened = new Dictionary>(); - 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; - } - - - protected override List make_solid(GeneralPolygon2d poly, bool bIsSupportSolid) - { - List 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 override List remove_cavity(List 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); - } - } - - - - } -} diff --git a/utility/PolygonDecomposer.cs b/utility/PolygonDecomposer.cs new file mode 100644 index 0000000..7879b02 --- /dev/null +++ b/utility/PolygonDecomposer.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using g3; + +namespace gs +{ + public class PolygonDecomposer + { + public static List Compute(GeneralPolygon2d poly, double minArea) + { + TriangulatedPolygonGenerator generator = new TriangulatedPolygonGenerator() + { + Polygon = poly, + Subdivisions = 16 + }; + DMesh3 mesh = generator.Generate().MakeDMesh(); + return decompose_cluster_up(mesh, minArea); + } + + private static List decompose_cluster_up(DMesh3 mesh, double minArea) + { + optimize_mesh(mesh); + mesh.CompactInPlace(); + mesh.DiscardTriangleGroups(); + mesh.EnableTriangleGroups(0); + + Dictionary areas = new Dictionary(); + Dictionary> trisets = new Dictionary>(); + HashSet active_groups = new HashSet(); + + Action add_tri_to_group = (tid, gid) => { + mesh.SetTriangleGroup(tid, gid); + areas[gid] = areas[gid] + mesh.GetTriArea(tid); + trisets[gid].Add(tid); + }; + Action add_group_to_group = (gid, togid) => { + var set = trisets[togid]; + foreach (int tid in trisets[gid]) + { + mesh.SetTriangleGroup(tid, togid); + set.Add(tid); + } + areas[togid] += areas[gid]; + active_groups.Remove(gid); + }; + Func, int> find_min_area_group = (tri_itr) => { + int min_gid = -1; double min_area = double.MaxValue; + foreach (int tid in tri_itr) + { + int gid = mesh.GetTriangleGroup(tid); + double a = areas[gid]; + if (a < min_area) + { + min_area = a; + min_gid = gid; + } + } + return min_gid; + }; + + + foreach (int eid in MeshIterators.InteriorEdges(mesh)) + { + Index2i et = mesh.GetEdgeT(eid); + if (mesh.GetTriangleGroup(et.a) != 0 || mesh.GetTriangleGroup(et.b) != 0) + continue; + int gid = mesh.AllocateTriangleGroup(); + areas[gid] = 0; + trisets[gid] = new HashSet(); + active_groups.Add(gid); + add_tri_to_group(et.a, gid); + add_tri_to_group(et.b, gid); + } + foreach (int tid in mesh.TriangleIndices()) + { + if (mesh.GetTriangleGroup(tid) != 0) + continue; + int gid = find_min_area_group(mesh.TriTrianglesItr(tid)); + add_tri_to_group(tid, gid); + } + + + IndexPriorityQueue pq = new IndexPriorityQueue(mesh.MaxGroupID); + foreach (var pair in areas) + { + pq.Insert(pair.Key, (float)pair.Value); + } + while (pq.Count > 0) + { + int gid = pq.First; + pq.Remove(gid); + if (areas[gid] > minArea) // ?? + break; + + List nbr_groups = find_neighbour_groups(mesh, gid, trisets[gid]); + int min_gid = -1; double min_area = double.MaxValue; + foreach (int ngid in nbr_groups) + { + double a = areas[ngid]; + if (a < min_area) + { + min_area = a; + min_gid = ngid; + } + } + if (min_gid != -1) + { + add_group_to_group(gid, min_gid); + pq.Remove(min_gid); + pq.Insert(min_gid, (float)areas[min_gid]); + } + } + + + + List result = new List(); + int[][] sets = FaceGroupUtil.FindTriangleSetsByGroup(mesh); + foreach (var set in sets) + result.Add(make_poly(mesh, set)); + return result; + } + + private static List find_neighbour_groups(DMesh3 mesh, int gid, HashSet group_tris) + { + List result = new List(); + foreach (int tid in group_tris) + { + foreach (int ntid in mesh.TriTrianglesItr(tid)) + { + int ngid = mesh.GetTriangleGroup(ntid); + if (ngid != gid && result.Contains(ngid) == false) + result.Add(ngid); + } + } + return result; + } + + private static Polygon2d make_poly(DMesh3 mesh, IEnumerable triangles) + { + DSubmesh3 submesh = new DSubmesh3(mesh, triangles); + MeshBoundaryLoops loops = new MeshBoundaryLoops(submesh.SubMesh); + Util.gDevAssert(loops.Loops.Count == 1); + return make_poly(submesh.SubMesh, loops.Loops[0]); + } + + private static Polygon2d make_poly(DMesh3 mesh, EdgeLoop loop) + { + Polygon2d poly = new Polygon2d(); + for (int k = 0; k < loop.VertexCount; ++k) + { + Vector3d v = mesh.GetVertex(loop.Vertices[k]); + poly.AppendVertex(v.xy); + } + return poly; + } + + private static void optimize_mesh(DMesh3 mesh) + { + Reducer reducer = new Reducer(mesh); + MeshConstraints constraints = new MeshConstraints(); + MeshConstraintUtil.FixAllBoundaryEdges(constraints, mesh); + reducer.SetExternalConstraints(constraints); + reducer.ReduceToTriangleCount(1); + + Vector3d a, b, c, d; + a = b = c = d = Vector3d.Zero; + + bool done = false; + while (!done) + { + done = true; + + for (int eid = 0; eid < mesh.MaxEdgeID; ++eid) + { + if (mesh.IsEdge(eid) == false) + continue; + + Index4i evt = mesh.GetEdge(eid); + if (evt.d == DMesh3.InvalidID) + continue; + a = mesh.GetVertex(evt.a); b = mesh.GetVertex(evt.b); + Index2i ov = mesh.GetEdgeOpposingV(eid); + c = mesh.GetVertex(ov.a); d = mesh.GetVertex(ov.b); + + if (c.DistanceSquared(d) > a.DistanceSquared(b)) + continue; + if (MeshUtil.CheckIfEdgeFlipCreatesFlip(mesh, eid)) + continue; + + DMesh3.EdgeFlipInfo flipInfo; + if (mesh.FlipEdge(eid, out flipInfo) == MeshResult.Ok) + done = false; + } + } + + + } + + private static int find_shortest_internal_edge(DMesh3 mesh) + { + double shortSqr = double.MaxValue; + int short_eid = -1; + Vector3d va, vb; va = vb = Vector3d.Zero; + foreach (int eid in mesh.VertexIndices()) + { + if (mesh.IsBoundaryEdge(eid)) + continue; + mesh.GetEdgeV(eid, ref va, ref vb); + double lenSqr = va.DistanceSquared(ref vb); + if (lenSqr < shortSqr) + { + shortSqr = lenSqr; + short_eid = eid; + } + } + return short_eid; + } + } +}