From d1bb463899ba5be08bacf5b4e5f6bc159747d66f Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Fri, 1 Nov 2019 14:30:52 -0700 Subject: [PATCH] Bring in files from gsSlicerPro --- .gitignore | 2 + generators/SingleMaterialFFFPrintGenPro.cs | 303 +++++++++++++++++++++ gsSlicer.csproj | 4 + slicing/MeshPlanarSlicerPro.cs | 78 ++++++ slicing/PlanarSlicePro.cs | 116 ++++++++ toolpathing/CorrugatedFillPolygon.cs | 69 +++++ 6 files changed, 572 insertions(+) create mode 100644 generators/SingleMaterialFFFPrintGenPro.cs create mode 100644 slicing/MeshPlanarSlicerPro.cs create mode 100644 slicing/PlanarSlicePro.cs create mode 100644 toolpathing/CorrugatedFillPolygon.cs diff --git a/.gitignore b/.gitignore index a85bfa2..fc30e34 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ thirdparty/clipper_library/obj # unity meta files *.meta + +.vs/ \ No newline at end of file diff --git a/generators/SingleMaterialFFFPrintGenPro.cs b/generators/SingleMaterialFFFPrintGenPro.cs new file mode 100644 index 0000000..e436230 --- /dev/null +++ b/generators/SingleMaterialFFFPrintGenPro.cs @@ -0,0 +1,303 @@ +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/gsSlicer.csproj b/gsSlicer.csproj index 83e4a12..5f6e9e3 100644 --- a/gsSlicer.csproj +++ b/gsSlicer.csproj @@ -46,13 +46,17 @@ + + + + diff --git a/slicing/MeshPlanarSlicerPro.cs b/slicing/MeshPlanarSlicerPro.cs new file mode 100644 index 0000000..0500610 --- /dev/null +++ b/slicing/MeshPlanarSlicerPro.cs @@ -0,0 +1,78 @@ +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/PlanarSlicePro.cs b/slicing/PlanarSlicePro.cs new file mode 100644 index 0000000..988616c --- /dev/null +++ b/slicing/PlanarSlicePro.cs @@ -0,0 +1,116 @@ +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/toolpathing/CorrugatedFillPolygon.cs b/toolpathing/CorrugatedFillPolygon.cs new file mode 100644 index 0000000..2d14be7 --- /dev/null +++ b/toolpathing/CorrugatedFillPolygon.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using g3; + +namespace gs +{ + public class CorrugatedFillPolygon : ICurvesFillPolygon + { + // polygon to fill + public GeneralPolygon2d Polygon { get; set; } + + // parameters + public double ToolWidth = 0.4; + public double PathSpacing = 0.4; + public double AngleDeg = 45.0; + public double PathShift = 0; + + // if true, we inset half of tool-width from Polygon + public bool InsetFromInputPolygon = true; + + // fill paths + public List Paths { get; set; } + public List GetFillCurves() { return Paths; } + + + SegmentSet2d BoundaryPolygonCache; + + public CorrugatedFillPolygon(GeneralPolygon2d poly) + { + Polygon = poly; + Paths = new List(); + } + + + public bool Compute() + { + if (InsetFromInputPolygon) { + BoundaryPolygonCache = new SegmentSet2d(Polygon); + List current = ClipperUtil.ComputeOffsetPolygon(Polygon, -ToolWidth / 2, true); + foreach (GeneralPolygon2d poly in current) { + SegmentSet2d polyCache = new SegmentSet2d(poly); + Paths.Add(ComputeFillPaths(poly, polyCache)); + } + + } else { + List boundary = ClipperUtil.ComputeOffsetPolygon(Polygon, ToolWidth / 2, true); + BoundaryPolygonCache = new SegmentSet2d(boundary); + + SegmentSet2d polyCache = new SegmentSet2d(Polygon); + Paths.Add(ComputeFillPaths(Polygon, polyCache)); + + } + + + return true; + } + + + + + protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly, SegmentSet2d polyCache) + { + return null; + } + + + + } +}