static void generate_stacked_polygon(SingleMaterialFFFCompiler compiler, SingleMaterialFFFSettings settings) { int NLayers = 10; for (int layer_i = 0; layer_i < NLayers; ++layer_i) { // create data structures for organizing this layer ToolpathSetBuilder layer_builder = new ToolpathSetBuilder(); SequentialScheduler2d scheduler = new SequentialScheduler2d(layer_builder, settings); if (layer_i == 0) { scheduler.SpeedHint = SchedulerSpeedHint.Careful; } // initialize layer layer_builder.Initialize(compiler.NozzlePosition); // layer-up layer_builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); // schedule a circle FillPolygon2d circle_poly = new FillPolygon2d(Polygon2d.MakeCircle(25.0f, 64)); circle_poly.TypeFlags = FillTypeFlags.OuterPerimeter; scheduler.AppendPolygon2d(circle_poly); // pass paths to compiler compiler.AppendPaths(layer_builder.Paths, settings); } }
void update_polygon() { if (preview != null) { preview.Polygon = Polygon2d.MakeCircle(Radius, nSlices); } }
/// <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>(); }
static DMesh3 GenerateTubeMeshesForGCode(string sPath, double pathWidth = 0.4) { GenericGCodeParser parser = new GenericGCodeParser(); GCodeFile gcode; using (FileStream fs = new FileStream(sPath, FileMode.Open, FileAccess.Read)) { using (TextReader reader = new StreamReader(fs)) { gcode = parser.Parse(reader); } } GCodeToLayerTubeMeshes make_tubes = new GCodeToLayerTubeMeshes() { TubeProfile = Polygon2d.MakeCircle(pathWidth / 2, 12), InterpretZChangeAsLayerChange = false }; make_tubes.WantTubeTypes.Add(ToolpathTypes.Travel); ThreeAxisCNCInterpreter interpreter = new ThreeAxisCNCInterpreter(); interpreter.AddListener(make_tubes); interpreter.Interpret(gcode, new InterpretArgs()); DMesh3 tubeMesh2 = make_tubes.GetCombinedMesh(1); return(tubeMesh2); }
void enable_circle_indicator(bool enable) { if (enable == false && circle_indicator == null) { return; } if (enable && circle_indicator == null) { LineSet lines = new LineSet(); lines.UseFixedNormal = true; lines.FixedNormal = Vector3f.AxisY; DCurve3 curve = new DCurve3(Polygon2d.MakeCircle(gizmoInitialRadius, 64), 0, 2); lines.Curves.Add(curve); lines.Width = 1.0f; lines.WidthType = LineWidthType.Pixel; lines.Segments.Add( new Segment3d(Vector3d.Zero, gizmoInitialRadius * diagonals[nRotationAxis])); lines.Color = Colorf.DimGrey; circle_indicator = new fLineSetGameObject(new GameObject(), lines, "circle"); circle_indicator.SetLayer(FPlatform.WidgetOverlayLayer, true); circle_indicator.SetLocalRotation(Quaternionf.FromTo(Vector3f.AxisY, Frame3f.Identity.GetAxis(nRotationAxis))); RootGameObject.AddChild(circle_indicator, false); } circle_indicator.SetVisible(enable); }
public static void test_svg() { Polygon2d poly = Polygon2d.MakeCircle(100.0f, 10); PolyLine2d pline = new PolyLine2d(); pline.AppendVertex(Vector2d.Zero); pline.AppendVertex(200 * Vector2d.AxisX); pline.AppendVertex(200 * Vector2d.One); Circle2d circ = new Circle2d(33 * Vector2d.One, 25); Segment2d seg = new Segment2d(Vector2d.Zero, -50 * Vector2d.AxisY); SVGWriter writer = new SVGWriter(); writer.AddPolygon(poly, SVGWriter.Style.Filled("lime", "black", 0.25f)); writer.AddPolyline(pline, SVGWriter.Style.Outline("orange", 2.0f)); writer.AddCircle(circ, SVGWriter.Style.Filled("yellow", "red", 5.0f)); writer.AddLine(seg, SVGWriter.Style.Outline("blue", 10.0f)); int astep = 29; Vector2d c = new Vector2d(-200, 100); for (int k = 1; k <= 12; ++k) { Arc2d arc = new Arc2d(c + k * 45 * Vector2d.AxisX, 20, 0, k * astep); writer.AddArc(arc); writer.AddBox(arc.Bounds, SVGWriter.Style.Outline("red", 0.5f)); } c.y += 50; for (int k = 1; k <= 12; ++k) { Arc2d arc = new Arc2d(c + k * 45 * Vector2d.AxisX, 20, k * astep, (k + 5) * astep); writer.AddArc(arc); writer.AddBox(arc.Bounds, SVGWriter.Style.Outline("red", 0.5f)); } c.y += 50; for (int k = 1; k <= 12; ++k) { Arc2d arc = new Arc2d(c + k * 45 * Vector2d.AxisX, 20, k * astep, (k + 10) * astep); writer.AddArc(arc); writer.AddBox(arc.Bounds, SVGWriter.Style.Outline("red", 0.5f)); } c.y += 50; for (int k = 1; k <= 12; ++k) { Arc2d arc = new Arc2d(c + k * 45 * Vector2d.AxisX, 20, k * astep, (k + 10) * astep); arc.Reverse(); writer.AddArc(arc); writer.AddBox(arc.Bounds, SVGWriter.Style.Outline("red", 0.5f)); } writer.Write(TestUtil.GetTestOutputPath("test.svg")); }
public static void TestInflate() { Polygon2d poly = Polygon2d.MakeCircle(10.0f, 32); MeshInflater inflater = new MeshInflater(poly) { TargetEdgeLength = 1.0f }; inflater.Compute(); DebugUtil.WriteDebugMesh(inflater.ResultMesh, "c:\\scratch\\inflated.obj"); }
void CreateNewTube() { preview = new MeshTubePreview() { Polygon = Polygon2d.MakeCircle(Radius, nSlices) }; preview.Create(scene.NewSOMaterial, scene.RootGameObject); smoother = new InPlaceIterativeCurveSmooth() { Curve = preview.Curve, Alpha = 0.2f }; }
DMesh3 compute_partial_hole(Vector3d start, Vector3d end, double tol) { DMesh3 origMesh = MeshSource.GetDMeshUnsafe(); DMeshAABBTree3 origSpatial = MeshSource.GetSpatial() as DMeshAABBTree3; DMesh3 cutMesh = new DMesh3(origMesh); Polygon2d polygon = Polygon2d.MakeCircle(hole_size / 2, hole_subdivisions); Vector3f axis = (Vector3f)(start - end).Normalized; int start_tid = origSpatial.FindNearestTriangle(start); Frame3f start_frame = origMesh.GetTriFrame(start_tid); start_frame.Origin = (Vector3f)start; start_frame.AlignAxis(2, axis); int end_tid = origSpatial.FindNearestTriangle(end); //Frame3f end_frame = origMesh.GetTriFrame(end_tid); end_frame.Origin = (Vector3f)end; Frame3f end_frame = start_frame; end_frame.Origin = (Vector3f)end; // [TODO] we don't need to Simplify here...is more robust? MeshInsertProjectedPolygon start_insert = new MeshInsertProjectedPolygon(cutMesh, polygon, start_frame, start_tid); bool start_ok = start_insert.Insert(); if (start_ok == false) { throw new Exception("CutPolygonHoleOp.compute_partial_hole: start or end insertion failed!"); } EdgeLoop outLoop = start_insert.InsertedLoop; MeshExtrudeLoop extrude = new MeshExtrudeLoop(cutMesh, outLoop); extrude.PositionF = (v, n, vid) => { cutMesh.GetVertex(vid); return(end_frame.ProjectToPlane((Vector3f)v, 2)); }; extrude.Extrude(); SimpleHoleFiller filler = new SimpleHoleFiller(cutMesh, extrude.NewLoop); filler.Fill(); return(cutMesh); }
void validate_tube_meshes() { Polygon2d circle = Polygon2d.MakeCircle(Radius, Slices); for (int i = 0; i < CurveSet.Length; ++i) { TubeGenerator gen = new TubeGenerator(CurveSet[i].curve, circle) { NoSharedVertices = false }; gen.Generate(); CurveSet[i].tubeMeshGO = GameObjectFactory.CreateMeshGO("tube_" + i.ToString(), gen.MakeUnityMesh(), false, true); CurveSet[i].tubeMeshGO.SetMaterial(curveMaterial, true); parentGO.AddChild(CurveSet[i].tubeMeshGO, false); } }
static void generate_stacked_wavy_circle(SingleMaterialFFFCompiler compiler, SingleMaterialFFFSettings settings) { double height = 20.0; // mm int NLayers = (int)(height / settings.LayerHeightMM); // 20mm int NSteps = 128; double radius = 15.0; double frequency = 6; double scale = 5.0; for (int layer_i = 0; layer_i < NLayers; ++layer_i) { // create data structures for organizing this layer ToolpathSetBuilder layer_builder = new ToolpathSetBuilder(); SequentialScheduler2d scheduler = new SequentialScheduler2d(layer_builder, settings); if (layer_i == 0) { scheduler.SpeedHint = SchedulerSpeedHint.Careful; } // initialize and layer-up layer_builder.Initialize(compiler.NozzlePosition); layer_builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); // start with circle FillPolygon2d circle_poly = new FillPolygon2d(Polygon2d.MakeCircle(radius, NSteps)); // apply a wave deformation to circle, with wave height increasing with Z double layer_scale = MathUtil.Lerp(0, scale, (double)layer_i / (double)NLayers); for (int i = 0; i < NSteps; ++i) { Vector2d v = circle_poly[i]; double angle = Math.Atan2(v.y, v.x); double r = v.Length; r += layer_scale * Math.Sin(frequency * angle); circle_poly[i] = r * v.Normalized; } circle_poly.TypeFlags = FillTypeFlags.OuterPerimeter; scheduler.AppendPolygon2d(circle_poly); // pass paths to compiler compiler.AppendPaths(layer_builder.Paths, settings); } }
public static void test_arrangement_stress() { Random r = new Random(31337); Arrangement2d builder = new Arrangement2d(new AxisAlignedBox2d(1024.0)); Polygon2d circ = Polygon2d.MakeCircle(512, 33); builder.Insert(circ); // crazy stress-test for (int k = 0; k < 1000; ++k) { var pts = TestUtil.RandomPoints2(2, r, circ.Bounds.Center, 800); builder.Insert(new Segment2d(pts[0], pts[1])); } //TestUtil.WriteTestOutputGraph(builder.Graph, "graph_complex.svg"); }
public static void TestFill() { Window window = new Window("TestFill"); window.SetDefaultSize(600, 600); window.SetPosition(WindowPosition.Center); DebugViewCanvas view = new DebugViewCanvas(); GeneralPolygon2d poly = new GeneralPolygon2d( Polygon2d.MakeCircle(20, 32)); Polygon2d hole = Polygon2d.MakeCircle(15, 32); hole.Reverse(); hole.Translate(2 * Vector2d.AxisX); poly.AddHole(hole); view.AddPolygon(poly, Colorf.Black); double spacing = 0.5; double[] offsets = new double[] { 5 }; foreach (double offset in offsets) { DGraph2 graph = TopoOffset2d.QuickCompute(poly, offset, spacing); DGraph2Util.Curves c = DGraph2Util.ExtractCurves(graph); //view.AddGraph(graph, Colorf.Red); //DGraph2 perturbGraph = perturb_fill(graph, poly, 5.0f, spacing); DGraph2 perturbGraph = perturb_fill_2(graph, poly, 1.0f, spacing); //DGraph2Util.Curves c2 = DGraph2Util.ExtractCurves(perturbGraph); view.AddGraph(perturbGraph, Colorf.Orange); } window.Add(view); window.ShowAll(); Active = view; }
public static void test_winding() { Random r = new Random(31337); int NPTS = 1000; double radius = 1; Polygon2d poly = Polygon2d.MakeCircle(radius, 777); Vector2d[] testPts = TestUtil.RandomPoints2(NPTS, r, Vector2d.Zero, radius); foreach (Vector2d v in testPts) { bool really_inside = (v.Length < radius); bool inside = poly.Contains(v); double winding0 = poly.WindingIntegral(v); bool inside_winding = !(Math.Abs(winding0) < MathUtil.Epsilonf); if (really_inside != inside || really_inside != inside_winding) { System.Console.WriteLine("Failed! truth {0} inside {1} winding0 {2}", really_inside, inside, winding0); } } // test random polygons int NPOLYS = 100; for (int k = 0; k < NPOLYS; ++k) { poly = new Polygon2d(TestUtil.RandomPoints2(30, r, Vector2d.Zero, radius)); testPts = TestUtil.RandomPoints2(NPTS, r, Vector2d.Zero, radius); foreach (Vector2d v in testPts) { bool inside = poly.Contains(v); double winding0 = poly.WindingIntegral(v); bool inside_winding = !(Math.Abs(winding0) < MathUtil.Epsilonf); if (inside != inside_winding) { System.Console.WriteLine("Failed! inside {0} winding0 {1}", inside, winding0); } } } }
DMesh3 compute_through_hole(Vector3d start, Vector3d end, double tol) { DMesh3 origMesh = MeshSource.GetDMeshUnsafe(); DMeshAABBTree3 origSpatial = MeshSource.GetSpatial() as DMeshAABBTree3; DMesh3 cutMesh = new DMesh3(origMesh); Polygon2d polygon = Polygon2d.MakeCircle(hole_size / 2, hole_subdivisions); Vector3f axis = (Vector3f)(start - end).Normalized; int start_tid = origSpatial.FindNearestTriangle(start); Frame3f start_frame = origMesh.GetTriFrame(start_tid); start_frame.Origin = (Vector3f)start; start_frame.AlignAxis(2, axis); int end_tid = origSpatial.FindNearestTriangle(end); //Frame3f end_frame = origMesh.GetTriFrame(end_tid); end_frame.Origin = (Vector3f)end; Frame3f end_frame = start_frame; end_frame.Origin = (Vector3f)end; MeshInsertProjectedPolygon start_insert = new MeshInsertProjectedPolygon(cutMesh, polygon, start_frame, start_tid); bool start_ok = start_insert.Insert(); MeshInsertProjectedPolygon end_insert = new MeshInsertProjectedPolygon(cutMesh, polygon, end_frame, end_tid); bool end_ok = end_insert.Insert(); if (start_ok == false || end_ok == false) { throw new Exception("CutPolygonHoleOp.compute_through_hole: start or end insertion failed!"); } MeshEditor editor = new MeshEditor(cutMesh); EdgeLoop l0 = start_insert.InsertedLoop; EdgeLoop l1 = end_insert.InsertedLoop; l1.Reverse(); editor.StitchLoop(l0.Vertices, l1.Vertices); return(cutMesh); }
override protected void Create_internal(fMaterial useMaterial) { if (polygon == null) { polygon = Polygon2d.MakeCircle(0.3f, 8); } // generate mesh tube TubeGenerator meshGen = new TubeGenerator() { Vertices = new List <Vector3d>(curve.Vertices), Capped = true, Polygon = polygon, Frame = new Frame3f(Vector3f.Zero, Vector3f.AxisY) }; meshGen.Generate(); Mesh m = meshGen.MakeUnityMesh(false); meshGO = UnityUtil.CreateMeshGO("tube_mesh", m, useMaterial, true); AppendNewGO(meshGO, RootGameObject, false); }
static DMesh3 GenerateTubeMeshesForGCode(string sPath) { GenericGCodeParser parser = new GenericGCodeParser(); GCodeFile gcode; using (FileStream fs = new FileStream(sPath, FileMode.Open, FileAccess.Read)) { using (TextReader reader = new StreamReader(fs)) { gcode = parser.Parse(reader); } } GCodeToLayerTubeMeshes make_tubes = new GCodeToLayerTubeMeshes() { TubeProfile = Polygon2d.MakeCircle(0.2f, 12) }; MakerbotInterpreter interpreter = new MakerbotInterpreter(); interpreter.AddListener(make_tubes); interpreter.Interpret(gcode, new InterpretArgs()); DMesh3 tubeMesh2 = make_tubes.GetCombinedMesh(1); return(tubeMesh2); }
public DMesh3 Make3DTubes(Interval1i layer_range, double merge_tol, double tube_radius) { Polygon2d tube_profile = Polygon2d.MakeCircle(tube_radius, 8); Frame3f frame = Frame3f.Identity; DMesh3 full_mesh = new DMesh3(); foreach (int layer_i in layer_range) { PlanarSlice slice = Slices[layer_i]; frame.Origin = new Vector3f(0, 0, slice.Z); foreach (GeneralPolygon2d gpoly in slice.Solids) { List <Polygon2d> polys = new List <Polygon2d>() { gpoly.Outer }; polys.AddRange(gpoly.Holes); foreach (Polygon2d poly in polys) { Polygon2d simpPoly = new Polygon2d(poly); simpPoly.Simplify(merge_tol, 0.01, true); if (simpPoly.VertexCount < 3) { Util.gBreakToDebugger(); } TubeGenerator tubegen = new TubeGenerator(simpPoly, frame, tube_profile) { NoSharedVertices = true }; DMesh3 tubeMesh = tubegen.Generate().MakeDMesh(); MeshEditor.Append(full_mesh, tubeMesh); } } } return(full_mesh); }
public static void test_chamfer() { //Polygon2d poly = Polygon2d.MakeRectangle(Vector2d.Zero, 200, 200); //if (poly.IsClockwise) // poly.Reverse(); Polygon2d poly = Polygon2d.MakeCircle(100, 64); double max_offset = 50; for (int k = 0; k < poly.VertexCount; k++) { double t = (double)k / (double)poly.VertexCount; double offset = (k % 2 == 0) ? -t * max_offset : t * max_offset; poly[k] = poly[k] + offset * poly[k].Normalized; } poly.Chamfer(60, 30, 30); SVGWriter writer = new SVGWriter(); writer.AddPolygon(poly, SVGWriter.Style.Filled("lime", "black", 0.25f)); writer.Write(TestUtil.GetTestOutputPath("test.svg")); }
static void generate_vertical_wave(SingleMaterialFFFCompiler compiler, SingleMaterialFFFSettings settings) { ToolpathSetBuilder builder = new ToolpathSetBuilder(); builder.Initialize(compiler.NozzlePosition); // layer-up builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); int N = 24; Polygon2d circle = Polygon2d.MakeCircle(15.0f, N, -MathUtil.HalfPI); int REPEAT = 5; for (int ri = 0; ri < REPEAT; ++ri) { builder.AppendTravel(circle[0], settings.RapidTravelSpeed); for (int k = 1; k <= N; k++) { builder.AppendExtrude(circle[k % N], settings.CarefulExtrudeSpeed / 4); } builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); builder.AppendTravel(circle[0], settings.RapidTravelSpeed); for (int k = 1; k <= N; k++) { builder.AppendExtrude(circle[k % N], settings.CarefulExtrudeSpeed / 4); } builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); if (ri == REPEAT - 1) { break; } // make on the move up we should also move 'back' a bit, // to counteract forward pull force? double height = 1.0f; double h_fudge = 0.0f; double z_stick = -0.05f; double z_layer = builder.Position.z; double top_z = z_layer + height + h_fudge; for (int k = 0; k < N - 1; k++) { Vector2d pcur = circle[k % N], pnext = circle[(k + 1) % N]; Vector3d pUp = new Vector3d(pcur.x, pcur.y, top_z); builder.AppendExtrude(pUp, settings.CarefulExtrudeSpeed / 8); builder.AppendDwell(500, false); Vector3d pDown = new Vector3d(pnext.x, pnext.y, z_layer + z_stick); builder.AppendExtrude(pDown, settings.CarefulExtrudeSpeed / 8); } // move up to z_high Vector3d vpos = new Vector3d(circle[0].x, circle[0].y, z_layer + height); builder.AppendExtrude(vpos, settings.CarefulExtrudeSpeed / 8); } compiler.AppendPaths(builder.Paths, settings); }
/// <summary> /// Cut a "partial" hole, ie we cut the mesh with the polygon once, and then /// extrude downwards to a planar version of the cut boundary. /// /// Currently only supports extruding downwards from topmost intersection. /// /// </summary> protected bool CutPartialHole(DMesh3 mesh, HoleInfo hi, Vector3d translate, bool bUpwards) { if (hi.IsVertical == false) { throw new Exception("unsupported!"); } Vector3d basePoint = CombinedBounds.Center - CombinedBounds.Extents.y * Vector3d.AxisY + translate; // do we need to compute spatial DS for each hole? not super efficient... DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); Vector3d direction = (bUpwards) ? Vector3d.AxisY : -Vector3d.AxisY; Vector3d center = basePoint + new Vector3d(hi.XZOffset.x, 0, hi.XZOffset.y) - 10000 * direction; Ray3d ray = new Ray3d(center, direction); int hit_tid = spatial.FindNearestHitTriangle(ray); if (hit_tid == DMesh3.InvalidID) { return(false); } IntrRay3Triangle3 intersection = MeshQueries.TriangleIntersection(mesh, hit_tid, ray); Vector3d inter_pos = ray.PointAt(intersection.RayParameter); Frame3f projectFrame = new Frame3f(ray.Origin, ray.Direction); int nVerts = 32; if (hi.Vertices != 0) { nVerts = hi.Vertices; } double angleShiftRad = hi.AxisAngleD * MathUtil.Deg2Rad; Polygon2d circle = Polygon2d.MakeCircle(hi.Radius, nVerts, angleShiftRad); try { EdgeLoop loop = null; MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(mesh, circle, projectFrame, hit_tid) { SimplifyInsertion = false }; if (insert.Insert()) { loop = insert.InsertedLoop; // [RMS] do we need to simplify for this one? //if (loop.VertexCount > circle.VertexCount) // loop = simplify_loop(mesh, loop, circle.VertexCount); MeshEditor editor = new MeshEditor(mesh); Vector3d base_pos = inter_pos; base_pos.y = basePoint.y + hi.PartialHoleBaseHeight; int N = loop.VertexCount; int[] newLoop = new int[N]; for (int k = 0; k < N; ++k) { newLoop[k] = mesh.AppendVertex(mesh, loop.Vertices[k]); Vector3d cur_v = mesh.GetVertex(newLoop[k]); cur_v.y = base_pos.y; mesh.SetVertex(newLoop[k], cur_v); } int base_vid = mesh.AppendVertex(base_pos); int[] fan_tris = editor.AddTriangleFan_OrderedVertexLoop(base_vid, newLoop); FaceGroupUtil.SetGroupID(mesh, fan_tris, hi.PartialHoleGroupID); int[] stitch_tris = editor.StitchLoop(loop.Vertices, newLoop); // need to remesh fan region because otherwise we get pathological cases RegionRemesher remesh = new RegionRemesher(mesh, fan_tris); remesh.SetTargetEdgeLength(2.0); remesh.SmoothSpeedT = 1.0; remesh.PreventNormalFlips = true; for (int k = 0; k < 25; ++k) { remesh.BasicRemeshPass(); } //remesh.EnableCollapses = remesh.EnableFlips = remesh.EnableSplits = false; //for (int k = 0; k < 20; ++k) // remesh.BasicRemeshPass(); remesh.BackPropropagate(); return(true); } else { return(false); } } catch (Exception e) { f3.DebugUtil.Log("partial hole {0} failed!! {1}", hi.nHole, e.Message); return(false); } }
/// <summary> /// Cut through-hole either vertically or horizontally. /// /// One current failure mode is if we get more than two ray-hits, which /// can happen due pathological cases or unexpected mesh shape. Currently /// trying to handle the pathological cases (ie ray hits adjacent triangles cases) /// via sorting, not sure if this works spectacularly well. /// /// </summary> protected bool CutThroughHole(DMesh3 mesh, HoleInfo hi, Vector3d translate) { Vector3d basePoint = CombinedBounds.Center - CombinedBounds.Extents.y * Vector3d.AxisY + translate; // do we need to compute spatial DS for each hole? not super efficient... DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); Vector3d origin = Vector3d.Zero; Vector3d direction = Vector3d.One; if (hi.IsVertical) { direction = Vector3d.AxisY; origin = basePoint + new Vector3d(hi.XZOffset.x, 0, hi.XZOffset.y) - 100 * direction; } else { origin = basePoint + hi.Height * Vector3d.AxisY; direction = Quaterniond.AxisAngleD(Vector3d.AxisY, hi.AroundAngle) * Vector3d.AxisX; } // Find upper and lower triangles that contain center-points of // holes we want to cut. This is the most error-prone part // because we depend on ray-hits, which is not very reliable... Ray3d ray1 = new Ray3d(origin, direction); Ray3d ray2 = new Ray3d(origin + 10000 * direction, -direction); if (hi.GroupIDFilters.a > 0) { spatial.TriangleFilterF = (tid) => { return(mesh.GetTriangleGroup(tid) == hi.GroupIDFilters.a); }; } int hit_1 = spatial.FindNearestHitTriangle(ray1); spatial.TriangleFilterF = null; if (hi.GroupIDFilters.b > 0) { spatial.TriangleFilterF = (tid) => { return(mesh.GetTriangleGroup(tid) == hi.GroupIDFilters.b); }; } int hit_2 = spatial.FindNearestHitTriangle(ray2); spatial.TriangleFilterF = null; if (hit_1 == DMesh3.InvalidID || hit_2 == DMesh3.InvalidID) { return(false); } if (hit_1 == hit_2) { return(false); } List <int> hitTris = new List <int>() { hit_1, hit_2 }; Frame3f projectFrame = new Frame3f(ray1.Origin, ray1.Direction); int nVerts = 32; if (hi.Vertices != 0) { nVerts = hi.Vertices; } double angleShiftRad = hi.AxisAngleD * MathUtil.Deg2Rad; Polygon2d circle = Polygon2d.MakeCircle(hi.Radius, nVerts, angleShiftRad); List <EdgeLoop> edgeLoops = new List <EdgeLoop>(); foreach (int hit_tid in hitTris) { try { MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(mesh, circle, projectFrame, hit_tid) { SimplifyInsertion = true }; if (insert.Insert()) { // if we have extra edges just randomly collapse EdgeLoop loop = insert.InsertedLoop; if (loop.VertexCount > circle.VertexCount) { loop = simplify_loop(mesh, loop, circle.VertexCount); } edgeLoops.Add(loop); } else { f3.DebugUtil.Log("insert.Insert() failed!!"); return(false); } } catch (Exception e) { // ignore this loop but we might already be in trouble... f3.DebugUtil.Log("insert.Insert() threw exception for hole {0}!!", hi.nHole); f3.DebugUtil.Log(e.Message); } } if (edgeLoops.Count != 2) { return(false); } try { MeshEditor editor = new MeshEditor(mesh); EdgeLoop l0 = edgeLoops[0]; EdgeLoop l1 = edgeLoops[1]; l1.Reverse(); editor.StitchVertexLoops_NearestV(l0.Vertices, l1.Vertices); // split edges around the holes we cut. This is helpful // if we are going to do additional operations in these areas, // as it gives us extra rings to work with //MeshEdgeSelection edges = new MeshEdgeSelection(mesh); //edges.SelectVertexEdges(l0.Vertices); //edges.SelectVertexEdges(l1.Vertices); //DMesh3.EdgeSplitInfo splitInfo; //foreach ( int eid in edges ) // mesh.SplitEdge(eid, out splitInfo); return(true); } catch { f3.DebugUtil.Log("stitch threw exception!"); return(false); } }
public static void testInsertPolygon_PlanarProj() { double dscale = 1.0; DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj"); dscale = 0.3; //DMesh3 mesh = TestUtil.LoadTestInputMesh("cylinder.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("cube.obj"); double size = mesh.CachedBounds.MaxDim; Vector3d c = mesh.CachedBounds.Center; Vector3d fw = c + mesh.CachedBounds.DiagonalLength * 2 * Vector3d.AxisZ; Ray3d ray = new Ray3d(fw, (c - fw).Normalized); // projection frame and polygon that lives in this frame Frame3f projectFrame = new Frame3f(ray.Origin, ray.Direction); Polygon2d circle = Polygon2d.MakeCircle(dscale * size * 0.1, 6); DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); List <int> hitTris = new List <int>(); spatial.FindAllHitTriangles(ray, hitTris); while (hitTris.Count != 2) { ray.Origin += 100 * MathUtil.Epsilon * Vector3d.One; hitTris.Clear(); spatial.FindAllHitTriangles(ray, hitTris); } // insert polygons but don't simplify the result DMesh3 noTrimMesh = new DMesh3(mesh); List <int[]> noTrimPolyVerts = new List <int[]>(); List <EdgeLoop> noTrimLoops = new List <EdgeLoop>(); foreach (int hit_tid in hitTris) { MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(noTrimMesh, circle, projectFrame, hit_tid); insert.SimplifyInsertion = false; if (insert.Insert()) { noTrimPolyVerts.Add(insert.InsertedPolygonVerts); noTrimLoops.Add(insert.InsertedLoop); } else { System.Console.WriteLine("testInsertPolygon_PlanarProj: no-trim Insert() failed"); } } TestUtil.WriteTestOutputMesh(noTrimMesh, "insert_polygon_notrim.obj"); // do different-vtx-count stitch if (noTrimLoops.Count == 2) { noTrimLoops[1].Reverse(); MeshStitchLoops stitcher = new MeshStitchLoops(noTrimMesh, noTrimLoops[0], noTrimLoops[1]); stitcher.TrustLoopOrientations = false; stitcher.AddKnownCorrespondences(noTrimPolyVerts[0], noTrimPolyVerts[1]); stitcher.Stitch(); } TestUtil.WriteTestOutputMesh(noTrimMesh, "insert_polygon_notrim_joined.obj"); // now do simplified version, which we can trivially stitch List <EdgeLoop> edgeLoops = new List <EdgeLoop>(); foreach (int hit_tid in hitTris) { MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(mesh, circle, projectFrame, hit_tid); if (insert.Insert()) { edgeLoops.Add(insert.InsertedLoop); } else { System.Console.WriteLine("testInsertPolygon_PlanarProj: Insert() failed"); } } //TestUtil.WriteTestOutputMesh(mesh, "insert_polygon_before_stitch.obj"); // do stitch if (edgeLoops.Count == 2) { MeshEditor editor = new MeshEditor(mesh); EdgeLoop l0 = edgeLoops[0]; EdgeLoop l1 = edgeLoops[1]; l1.Reverse(); editor.StitchLoop(l0.Vertices, l1.Vertices); } TestUtil.WriteTestOutputMesh(mesh, "insert_polygon_joined.obj"); }
public static void test_tube_generator() { Polygon2d circle_path = Polygon2d.MakeCircle(50, 64); PolyLine2d arc_path = new PolyLine2d(circle_path.Vertices.Take(circle_path.VertexCount / 2)); Polygon2d irreg_path = new Polygon2d(); for (int k = 0; k < circle_path.VertexCount; ++k) { irreg_path.AppendVertex(circle_path[k]); k += k / 2; } PolyLine2d irreg_arc_path = new PolyLine2d(irreg_path.Vertices.Take(circle_path.VertexCount - 1)); Polygon2d square_profile = Polygon2d.MakeCircle(7, 32); square_profile.Translate(4 * Vector2d.One); //square_profile[0] = 20 * square_profile[0].Normalized; bool no_shared = true; WriteGeneratedMesh( new TubeGenerator(circle_path, Frame3f.Identity, square_profile) { WantUVs = true, NoSharedVertices = no_shared }, "tubegen_loop_standarduv.obj"); WriteGeneratedMesh( new TubeGenerator(irreg_path, Frame3f.Identity, square_profile) { WantUVs = true, NoSharedVertices = no_shared }, "tubegen_irregloop_standarduv.obj"); WriteGeneratedMesh( new TubeGenerator(arc_path, Frame3f.Identity, square_profile) { WantUVs = true, NoSharedVertices = no_shared }, "tubegen_arc_standarduv.obj"); WriteGeneratedMesh( new TubeGenerator(irreg_arc_path, Frame3f.Identity, square_profile) { WantUVs = true, NoSharedVertices = no_shared }, "tubegen_irregarc_standarduv.obj"); // append tube border around each hole of input mesh DMesh3 inMesh = TestUtil.LoadTestInputMesh("n_holed_bunny.obj"); Polygon2d bdrycirc = Polygon2d.MakeCircle(0.25, 6); MeshBoundaryLoops loops = new MeshBoundaryLoops(inMesh); foreach (EdgeLoop loop in loops) { DCurve3 curve = loop.ToCurve().ResampleSharpTurns(); TubeGenerator gen = new TubeGenerator(curve, bdrycirc) { NoSharedVertices = false }; MeshEditor.Append(inMesh, gen.Generate().MakeDMesh()); } TestUtil.WriteTestOutputMesh(inMesh, "boundary_tubes.obj"); }
public static void test_basic_generators() { TrivialDiscGenerator disc_gen = new TrivialDiscGenerator(); WriteGeneratedMesh(disc_gen, "meshgen_Disc.obj"); TrivialRectGenerator rect_gen = new TrivialRectGenerator(); WriteGeneratedMesh(rect_gen, "meshgen_Rect.obj"); GriddedRectGenerator gridrect_gen = new GriddedRectGenerator(); WriteGeneratedMesh(gridrect_gen, "meshgen_GriddedRect.obj"); PuncturedDiscGenerator punc_disc_gen = new PuncturedDiscGenerator(); WriteGeneratedMesh(punc_disc_gen, "meshgen_PuncturedDisc.obj"); TrivialBox3Generator box_gen = new TrivialBox3Generator(); Frame3f f = Frame3f.Identity; f.Rotate(Quaternionf.AxisAngleD(Vector3f.AxisY, 45.0f)); f.Rotate(Quaternionf.AxisAngleD(Vector3f.AxisZ, 45.0f)); box_gen.Box = new Box3d(f.Origin, f.X, f.Y, f.Z, new Vector3d(3, 2, 1)); WriteGeneratedMesh(box_gen, "meshgen_TrivialBox_shared.obj"); box_gen.NoSharedVertices = true; WriteGeneratedMesh(box_gen, "meshgen_TrivialBox_noshared.obj"); RoundRectGenerator roundrect_gen = new RoundRectGenerator(); roundrect_gen.Width = 2; WriteGeneratedMesh(roundrect_gen, "meshgen_RoundRect.obj"); GridBox3Generator gridbox_gen = new GridBox3Generator(); WriteGeneratedMesh(gridbox_gen, "meshgen_GridBox_shared.obj"); gridbox_gen.NoSharedVertices = true; WriteGeneratedMesh(gridbox_gen, "meshgen_GridBox_noshared.obj"); Sphere3Generator_NormalizedCube normcube_gen = new Sphere3Generator_NormalizedCube(); WriteGeneratedMesh(normcube_gen, "meshgen_Sphere_NormalizedCube_shared.obj"); normcube_gen.NoSharedVertices = true; normcube_gen.Box = new Box3d(new Frame3f(Vector3f.One, Vector3f.One), Vector3d.One * 1.3); WriteGeneratedMesh(normcube_gen, "meshgen_Sphere_NormalizedCube_noshared.obj"); TubeGenerator tube_gen = new TubeGenerator() { Vertices = new List <Vector3d>() { Vector3d.Zero, Vector3d.AxisX, 2 * Vector3d.AxisX, 3 * Vector3d.AxisX }, Polygon = Polygon2d.MakeCircle(1, 16) }; WriteGeneratedMesh(tube_gen, "meshgen_TubeGenerator.obj"); tube_gen.Polygon.Translate(Vector2d.One); tube_gen.CapCenter = Vector2d.One; WriteGeneratedMesh(tube_gen, "meshgen_TubeGenerator_shifted.obj"); }
public static void TestDGraph2() { Window window = new Window("TestDGraph2"); window.SetDefaultSize(600, 600); window.SetPosition(WindowPosition.Center); DebugViewCanvas view = new DebugViewCanvas(); GeneralPolygon2d poly = new GeneralPolygon2d( Polygon2d.MakeCircle(10, 32)); //Polygon2d hole = Polygon2d.MakeCircle(9, 32); //hole.Reverse(); //poly.AddHole(hole); Polygon2d hole = Polygon2d.MakeCircle(5, 32); hole.Translate(new Vector2d(2, 0)); hole.Reverse(); poly.AddHole(hole); Polygon2d hole2 = Polygon2d.MakeCircle(1, 32); hole2.Translate(-6 * Vector2d.AxisX); hole2.Reverse(); poly.AddHole(hole2); Polygon2d hole3 = Polygon2d.MakeCircle(1, 32); hole3.Translate(-6 * Vector2d.One); hole3.Reverse(); poly.AddHole(hole3); Polygon2d hole4 = Polygon2d.MakeCircle(1, 32); hole4.Translate(7 * Vector2d.AxisY); hole4.Reverse(); poly.AddHole(hole4); view.AddPolygon(poly, Colorf.Black); double spacing = 0.2; //double[] offsets = new double[] { 0.5, 1, 1.5, 2, 2.5 }; double[] offsets = new double[] { 0.2, 0.6 }; TopoOffset2d o = new TopoOffset2d(poly) { PointSpacing = spacing }; foreach (double offset in offsets) { o.Offset = offset; DGraph2 graph = o.Compute(); DGraph2Util.Curves c = DGraph2Util.ExtractCurves(graph); view.AddGraph(graph, Colorf.Red); } window.Add(view); window.ShowAll(); }
public static void quick_test() { DMesh3 mesh = StandardMeshReader.ReadMesh("c:\\scratch\\block.obj"); DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); Vector3d rayCenter = new Vector3d(0, 0, 1); Frame3f rayFrame = new Frame3f(rayCenter, Vector3d.AxisZ); List <Frame3f> frames = new List <Frame3f>(); // how far into surface we will inset float SurfaceOffset = 0.01f; double step = 2.5f; for (double angle = 0; angle < 360; angle += step) { double dx = Math.Cos(angle * MathUtil.Deg2Rad), dy = Math.Sin(angle * MathUtil.Deg2Rad); Vector3d dir = dx * (Vector3d)rayFrame.X + dy * (Vector3d)rayFrame.Y; Ray3d ray = new Ray3d(rayFrame.Origin, dir.Normalized); Frame3f hitFrame; if (MeshQueries.RayHitPointFrame(mesh, spatial, ray, out hitFrame)) { frames.Add(hitFrame); } } int N = frames.Count; for (int k = 0; k < N; ++k) { Frame3f f = frames[k]; int prev = (k == 0) ? N - 1 : k - 1; int next = (k + 1) % N; //Vector3f dv = frames[(k + 1) % frames.Count].Origin - f.Origin; Vector3f dv = frames[next].Origin - frames[prev].Origin; dv.Normalize(); f.ConstrainedAlignAxis(0, dv, f.Z); f.Origin = f.Origin + SurfaceOffset * f.Z; frames[k] = f; } //Frame3f f = frames[0]; //Vector3f dv = (frames[1].Origin - frames[0].Origin).Normalized; //f.ConstrainedAlignAxis(1, dv, f.Z); //for (int k = 1; k < frames.Count; ++k) { // f.Origin = frames[k].Origin; // f.AlignAxis(2, frames[k].Z); // frames[k] = f; //} List <Vector3d> vertices = frames.ConvertAll((ff) => { return((Vector3d)ff.Origin); }); TubeGenerator tubegen = new TubeGenerator() { Vertices = vertices, Polygon = Polygon2d.MakeCircle(0.05, 16), NoSharedVertices = false }; DMesh3 tubeMesh = tubegen.Generate().MakeDMesh(); TestUtil.WriteTestOutputMeshes(new List <IMesh>() { mesh, tubeMesh }, "curve_tube.obj"); SimpleQuadMesh stripMeshY = new SimpleQuadMesh(); double w = 0.1; int preva = -1, prevb = -1; for (int k = 0; k < N; ++k) { Vector3d pa = frames[k].Origin + w * (Vector3d)frames[k].Y; Vector3d pb = frames[k].Origin - w * (Vector3d)frames[k].Y; int a = stripMeshY.AppendVertex(pa); int b = stripMeshY.AppendVertex(pb); if (preva != -1) { stripMeshY.AppendQuad(preva, prevb, b, a); } preva = a; prevb = b; } stripMeshY.AppendQuad(preva, prevb, 1, 0); SimpleQuadMesh.WriteOBJ(stripMeshY, TEST_OUTPUT_PATH + "quadstripy.obj", WriteOptions.Defaults); SimpleQuadMesh stripMeshZ = new SimpleQuadMesh(); preva = -1; prevb = -1; double wz = 0.1; for (int k = 0; k < N; ++k) { Vector3d pa = frames[k].Origin + wz * (Vector3d)frames[k].Z; Vector3d pb = frames[k].Origin - wz * (Vector3d)frames[k].Z; int a = stripMeshZ.AppendVertex(pa); int b = stripMeshZ.AppendVertex(pb); if (preva != -1) { stripMeshZ.AppendQuad(preva, prevb, b, a); } preva = a; prevb = b; } stripMeshZ.AppendQuad(preva, prevb, 1, 0); SimpleQuadMesh.WriteOBJ(stripMeshZ, TEST_OUTPUT_PATH + "quadstripz.obj", WriteOptions.Defaults); }
public static void test_cells() { Polygon2d outer = Polygon2d.MakeCircle(1000, 17); Polygon2d hole = Polygon2d.MakeCircle(100, 32); hole.Reverse(); GeneralPolygon2d gpoly = new GeneralPolygon2d(outer); gpoly.AddHole(hole); DGraph2 graph = new DGraph2(); graph.AppendPolygon(gpoly); GraphSplitter2d splitter = new GraphSplitter2d(graph); splitter.InsideTestF = gpoly.Contains; for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.AxisY); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.AxisX); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.One.Normalized); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], new Vector2d(1, -1).Normalized); splitter.InsertLine(line); } GraphCells2d cells = new GraphCells2d(graph); cells.FindCells(); List <Polygon2d> polys = cells.ContainedCells(gpoly); for (int k = 0; k < polys.Count; ++k) { double offset = polys[k].IsClockwise ? 4 : 20; polys[k].PolyOffset(offset); } PlanarComplex cp = new PlanarComplex(); for (int k = 0; k < polys.Count; ++k) { cp.Add(polys[k]); } // convert back to solids var options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0; var solids = cp.FindSolidRegions(options); SVGWriter svg = new SVGWriter(); svg.AddGraph(graph, SVGWriter.Style.Outline("red", 5)); for (int k = 0; k < polys.Count; ++k) { svg.AddPolygon(polys[k], SVGWriter.Style.Outline("black", 1)); } svg.Write(TestUtil.GetTestOutputPath("cells_graph.svg")); }
public static void test_splitter() { Polygon2d poly = Polygon2d.MakeCircle(1000, 16); Polygon2d hole = Polygon2d.MakeCircle(500, 32); hole.Reverse(); GeneralPolygon2d gpoly = new GeneralPolygon2d(poly); gpoly.AddHole(hole); //Polygon2d poly = Polygon2d.MakeRectangle(Vector2d.Zero, 1000, 1000); DGraph2 graph = new DGraph2(); graph.AppendPolygon(gpoly); System.Console.WriteLine("Stats before: verts {0} edges {1} ", graph.VertexCount, graph.EdgeCount); GraphSplitter2d splitter = new GraphSplitter2d(graph); splitter.InsideTestF = gpoly.Contains; for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], Vector2d.AxisY); splitter.InsertLine(line); } System.Console.WriteLine("Stats after 1: verts {0} edges {1} ", graph.VertexCount, graph.EdgeCount); for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], Vector2d.AxisX); splitter.InsertLine(line); } for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], Vector2d.One.Normalized); splitter.InsertLine(line); } for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], new Vector2d(1, -1).Normalized); splitter.InsertLine(line); } System.Console.WriteLine("Stats after: verts {0} edges {1} ", graph.VertexCount, graph.EdgeCount); Random r = new Random(31337); foreach (int vid in graph.VertexIndices()) { Vector2d v = graph.GetVertex(vid); v += TestUtil.RandomPoints2(1, r, v, 25)[0]; graph.SetVertex(vid, v); } SVGWriter svg = new SVGWriter(); svg.AddGraph(graph); var vtx_style = SVGWriter.Style.Outline("red", 1.0f); foreach (int vid in graph.VertexIndices()) { Vector2d v = graph.GetVertex(vid); svg.AddCircle(new Circle2d(v, 10), vtx_style); } svg.Write(TestUtil.GetTestOutputPath("split_graph.svg")); }
static void generate_vertical(SingleMaterialFFFCompiler compiler, SingleMaterialFFFSettings settings) { ToolpathSetBuilder builder = new ToolpathSetBuilder(); builder.Initialize(compiler.NozzlePosition); // layer-up builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); // draw circle int N = 32; Polygon2d circle = Polygon2d.MakeCircle(25.0f, N, -MathUtil.HalfPI); builder.AppendTravel(circle[0], settings.RapidTravelSpeed); for (int k = 1; k <= N; k++) { builder.AppendExtrude(circle[k % N], settings.CarefulExtrudeSpeed); } // layer-up builder.AppendZChange(settings.LayerHeightMM, settings.ZTravelSpeed); double height = 5.0f; double z_layer = builder.Position.z; double z_high = z_layer + height; for (int k = 1; k <= N; k++) { Vector2d p2 = circle[k % N]; double z = (k % 2 == 1) ? z_high : z_layer; Vector3d p3 = new Vector3d(p2.x, p2.y, z); builder.AppendExtrude(p3, settings.CarefulExtrudeSpeed / 4); // dwell at tops if (z == z_high) { AssemblerCommandsToolpath dwell_path = new AssemblerCommandsToolpath() { AssemblerF = make_tip }; builder.AppendPath(dwell_path); } } // move up to z_high builder.AppendZChange(height, settings.ZTravelSpeed); // draw circle again builder.AppendTravel(circle[0], settings.RapidTravelSpeed); for (int k = 1; k <= N; k++) { builder.AppendExtrude(circle[k % N], settings.RapidExtrudeSpeed); } // draw teeth again z_layer = builder.Position.z; z_high = z_layer + height; for (int k = 1; k <= N; k++) { Vector2d p2 = circle[k % N]; double z = (k % 2 == 1) ? z_high : z_layer; Vector3d p3 = new Vector3d(p2.x, p2.y, z); builder.AppendExtrude(p3, settings.CarefulExtrudeSpeed / 4); // dwell at tops if (z == z_high) { AssemblerCommandsToolpath dwell_path = new AssemblerCommandsToolpath() { AssemblerF = make_tip }; builder.AppendPath(dwell_path); } } // move up to z_high builder.AppendZChange(height, settings.ZTravelSpeed); // draw circle again builder.AppendTravel(circle[0], settings.RapidTravelSpeed); for (int k = 1; k <= N; k++) { builder.AppendExtrude(circle[k % N], settings.RapidExtrudeSpeed); } compiler.AppendPaths(builder.Paths, settings); }