From 6eff2d293997f1e8759b796d5b7bc171aa510b98 Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Sun, 5 Feb 2023 15:28:41 +0100 Subject: [PATCH] Optimized the toolpath for pocket operations (#2152) --- .../designer/io/gcode/SimpleGcodeRouter.java | 20 +- .../toolpaths/GeometryToolpathComparator.java | 37 + .../io/gcode/toolpaths/PocketToolPath.java | 122 ++- .../io/gcode/toolpaths/ToolPathStats.java | 37 + .../io/gcode/toolpaths/ToolPathUtils.java | 93 +- .../gcode/toolpaths/PocketToolPathTest.java | 94 +- .../src/test/resources/pocket-test.ugsd | 925 ++++++++++++++++++ 7 files changed, 1289 insertions(+), 39 deletions(-) create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/GeometryToolpathComparator.java create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathStats.java create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/test/resources/pocket-test.ugsd diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/SimpleGcodeRouter.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/SimpleGcodeRouter.java index 670fab9020..147cf4dc0a 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/SimpleGcodeRouter.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/SimpleGcodeRouter.java @@ -23,6 +23,8 @@ import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.DrillCenterToolPath; import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.OutlineToolPath; import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.PocketToolPath; +import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathStats; +import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils; import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.utils.Version; import org.apache.commons.lang3.StringUtils; @@ -31,13 +33,14 @@ import java.io.StringWriter; import java.io.Writer; import java.util.List; -import java.util.stream.Collectors; +import java.util.logging.Logger; /** * @author Calle Laakkonen * @author Joacim Breiler */ public class SimpleGcodeRouter { + private static final Logger LOGGER = Logger.getLogger(SimpleGcodeRouter.class.getSimpleName()); private static final String HEADER = "; This file was generated with \"Universal Gcode Sender " + Version.getVersionString() + "\"\n;\n"; /** @@ -134,6 +137,9 @@ public void setSpindleSpeed(double spindleSpeed) { } protected String toGcode(GcodePath gcodePath) throws IOException { + ToolPathStats toolPathStats = ToolPathUtils.getToolPathStats(gcodePath); + LOGGER.info("Generated a tool path with total length of " + Math.round(toolPathStats.getTotalFeedLength()) + "mm and " + Math.round(toolPathStats.getTotalRapidLength()) + "mm of rapid movement"); + StringWriter stringWriter = new StringWriter(); toGcode(stringWriter, gcodePath); stringWriter.flush(); @@ -141,15 +147,6 @@ protected String toGcode(GcodePath gcodePath) throws IOException { } public String toGcode(List entities) { - // Try to figure out the size of the drawing - double width = entities.stream().map(e -> e.getBounds().getMaxX()).max(Double::compareTo).orElse((double) 0); - double height = entities.stream().map(e -> e.getBounds().getMaxX()).max(Double::compareTo).orElse((double) 0); - - - List cuttables = entities.stream() - .sorted(new EntityComparator(width, height)) - .collect(Collectors.toList()); - StringBuilder result = new StringBuilder(HEADER + generateToolHeader() + "\n" + Code.G21.name() + " ; millimeters\n" + @@ -160,7 +157,7 @@ public String toGcode(List entities) { ); try { - result.append(toGcode(getGcodePathFromCuttables(cuttables))); + result.append(toGcode(getGcodePathFromCuttables(entities))); } catch (IOException e) { throw new RuntimeException("An error occured while trying to generate gcode", e); } @@ -272,7 +269,6 @@ protected void runPath(Writer writer, List segments) throws IOException writer.write(s.point.getFormattedGCode()); writer.write("\n"); hasFeedRateSet = false; - break; // Drill down using the plunge speed diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/GeometryToolpathComparator.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/GeometryToolpathComparator.java new file mode 100644 index 0000000000..e63db217b8 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/GeometryToolpathComparator.java @@ -0,0 +1,37 @@ +/* + Copyright 2023 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; + +import org.locationtech.jts.geom.Geometry; + +import java.util.Comparator; + +/** + * @author Joacim Breiler + */ +public class GeometryToolpathComparator implements Comparator { + @Override + public int compare(Geometry o1, Geometry o2) { + if (o2.contains(o1)) { + return 1; + } + + return o2.compareTo(o1); + } +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPath.java index 1436858d1c..d9d8c745e7 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPath.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Will Winder + Copyright 2023 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -21,19 +21,27 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.designer.entities.cuttable.Cuttable; import com.willwinder.ugs.nbp.designer.io.gcode.path.GcodePath; import com.willwinder.universalgcodesender.model.PartialPosition; +import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.awt.geom.Area; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Collections; import java.util.List; -import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.*; +import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.convertAreaToGeometry; +import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.geometryToCoordinates; +import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.toPartialPosition; /** * @author Joacim Breiler */ public class PocketToolPath extends AbstractToolPath { + public static final double DISTANCE_TOLERANCE = 0.1; private final Cuttable source; /** @@ -48,40 +56,106 @@ public PocketToolPath(Cuttable source) { @Override public GcodePath toGcodePath() { + Geometry geometryCollection = convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); + Geometry shell = geometryCollection.buffer(-getToolDiameter() / 2d); + List geometries = bufferAndCollectGeometries(geometryCollection); + List> coordinateList = new ArrayList<>(); + double currentDepth = getStartDepth() - getDepthPerPass(); + while (currentDepth < getTargetDepth()) { + currentDepth += getDepthPerPass(); + if (currentDepth > getTargetDepth()) { + currentDepth = getTargetDepth(); + } - Geometry geometryCollection = convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); - for (int i = 0; i < geometryCollection.getNumGeometries(); i++) { - List geometries = bufferAndCollectGeometries(geometryCollection.getGeometryN(i)); - double currentDepth = getStartDepth() - getDepthPerPass(); - while (currentDepth < getTargetDepth()) { - currentDepth += getDepthPerPass(); - if (currentDepth > getTargetDepth()) { - currentDepth = getTargetDepth(); + addGeometriesToCoordinatesList(shell, geometries, coordinateList, currentDepth); + } + + return toGcodePath(coordinateList); + } + + private void addGeometriesToCoordinatesList(Geometry shell, List geometries, List> coordinateList, double currentDepth) { + Geometry previousGeometry = null; + List geometryLine = new ArrayList<>(); + for (int x = 0; x < geometries.size(); x++) { + Geometry geometry = geometries.get(x); + + if (x > 0) { + PartialPosition fromPosition = ToolPathUtils.toPartialPosition(previousGeometry.getCoordinates()[0], currentDepth); + int newStartIndex = ToolPathUtils.findNearestCoordinateIndex(geometry.getCoordinates(), new Coordinate(fromPosition.getX(), fromPosition.getY(), fromPosition.getZ())); + + if (geometry instanceof LinearRing) { + geometry = rotateCoordinates((LinearRing) geometry, newStartIndex); } - List> geometriesCoordinates = geometriesToCoordinates(geometries, currentDepth); - coordinateList.addAll(geometriesCoordinates); + Coordinate firstCoordinate = geometry.getCoordinates()[0]; + PartialPosition nextPosition = toPartialPosition(firstCoordinate, currentDepth); + + LineString lineString = ToolPathUtils.createLineString(fromPosition, nextPosition); + if (shell.crosses(lineString)) { + coordinateList.add(geometryLine); + geometryLine = new ArrayList<>(); + } } + + geometryLine.addAll(geometryToCoordinates(geometry, currentDepth)); + previousGeometry = geometry; } - return toGcodePath(coordinateList); + if (!geometryLine.isEmpty()) { + coordinateList.add(geometryLine); + } + } + + private LinearRing rotateCoordinates(LinearRing nextGeometry, int newStartIndex) { + Coordinate[] geomCoordinates = nextGeometry.getCoordinates(); + Coordinate[] newCoordinates = new Coordinate[geomCoordinates.length]; + int newIndex = 0; + for (int coordIndex = newStartIndex; coordIndex < newCoordinates.length; coordIndex++) { + newCoordinates[newIndex] = geomCoordinates[coordIndex]; + newIndex++; + } + + for (int coordIndex = 1; coordIndex < newStartIndex; coordIndex++) { + newCoordinates[newIndex] = geomCoordinates[coordIndex]; + newIndex++; + } + + newCoordinates[newCoordinates.length - 1] = geomCoordinates[newStartIndex]; + nextGeometry = ToolPathUtils.createLinearRing(newCoordinates); + return nextGeometry; } private List bufferAndCollectGeometries(Geometry geometry) { - List geometries = new ArrayList<>(); double buffer = getToolDiameter() / 2d; - while (true) { - Geometry bufferedGeometry = geometry.buffer(-buffer); - List bufferedGeometries = toGeometryList(bufferedGeometry); - if(bufferedGeometries.isEmpty()) { - geometries.sort(Comparator.comparingDouble(o -> o.getEnvelope().getArea())); - return geometries; - } + List geometries = bufferAndCollectGeometries(geometry, buffer); + geometries.sort(new GeometryToolpathComparator()); + return geometries; + } - geometries.addAll(bufferedGeometries); - buffer = buffer + (getToolDiameter() * stepOver); + private List bufferAndCollectGeometries(Geometry geometry, double buffer) { + Geometry bufferedGeometry = geometry.buffer(-buffer); + if (bufferedGeometry.getNumGeometries() <= 0 || bufferedGeometry.isEmpty()) { + return Collections.emptyList(); } + + List result = new ArrayList<>(); + for (int i = 0; i < bufferedGeometry.getNumGeometries(); i++) { + Geometry geom = bufferedGeometry.getGeometryN(i); + result.addAll(bufferAndCollectGeometries(geom, getToolDiameter() * stepOver)); + + if (geom instanceof Polygon) { + Polygon polygon = (Polygon) geom; + result.add(DouglasPeuckerSimplifier.simplify(polygon.getExteriorRing(), DISTANCE_TOLERANCE)); + for (int j = 0; j < polygon.getNumInteriorRing(); j++) { + result.add(DouglasPeuckerSimplifier.simplify(polygon.getInteriorRingN(j), DISTANCE_TOLERANCE)); + } + } else { + result.add(DouglasPeuckerSimplifier.simplify(geom, DISTANCE_TOLERANCE)); + } + } + + return result; } public void setStepOver(double stepOver) { diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathStats.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathStats.java new file mode 100644 index 0000000000..9eef2ffd8a --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathStats.java @@ -0,0 +1,37 @@ +/* + Copyright 2023 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; + +public class ToolPathStats { + private final double totalFeedLength; + private final double totalRapidLength; + + public ToolPathStats(double totalFeedLength, double totalRapidLength) { + this.totalFeedLength = totalFeedLength; + this.totalRapidLength = totalRapidLength; + } + + public double getTotalRapidLength() { + return totalRapidLength; + } + + public double getTotalFeedLength() { + return totalFeedLength; + } +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathUtils.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathUtils.java index 1c88c0a595..c23e2c3ad3 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathUtils.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/ToolPathUtils.java @@ -1,10 +1,37 @@ +/* + Copyright 2023 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; +import com.willwinder.ugs.nbp.designer.io.gcode.path.GcodePath; +import com.willwinder.ugs.nbp.designer.io.gcode.path.Segment; +import com.willwinder.ugs.nbp.designer.io.gcode.path.SegmentType; +import com.willwinder.universalgcodesender.model.CNCPoint; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.UnitUtils; import org.locationtech.jts.awt.ShapeReader; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; -import org.locationtech.jts.geom.*; +import org.locationtech.jts.geom.PrecisionModel; import org.locationtech.jts.operation.polygonize.Polygonizer; import java.awt.*; @@ -20,6 +47,8 @@ public class ToolPathUtils { public static final double FLATNESS_PRECISION = 0.1d; + public static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + private ToolPathUtils() { } @@ -49,7 +78,7 @@ public static void recursivlyCollectGeometries(Geometry geometry, List } public static List geometryToCoordinates(Geometry geometry) { - org.locationtech.jts.geom.Coordinate[] coordinates = geometry.getCoordinates(); + Coordinate[] coordinates = geometry.getCoordinates(); return Arrays.stream(coordinates) .map(c -> new PartialPosition(c.getX(), c.getY(), c.getZ(), UnitUtils.Units.MM)) .collect(Collectors.toList()); @@ -73,6 +102,13 @@ public static List> geometriesToCoordinates(List .collect(Collectors.toList()); } + public static List geometryToCoordinates(Geometry geometry, double depth) { + org.locationtech.jts.geom.Coordinate[] coordinates = geometry.getCoordinates(); + return Arrays.stream(coordinates) + .map(c -> toPartialPosition(c, depth)) + .collect(Collectors.toList()); + } + public static Geometry convertAreaToGeometry(final Area area, final GeometryFactory factory) { PathIterator iter = area.getPathIterator(null, FLATNESS_PRECISION); @@ -122,4 +158,57 @@ public static boolean isClosedGeometry(Shape shape) { return false; } + + public static PartialPosition toPartialPosition(Coordinate coordinate, double depth) { + return new PartialPosition(coordinate.getX(), coordinate.getY(), -depth, UnitUtils.Units.MM); + } + + public static Coordinate toCoordinate(PartialPosition position) { + return new Coordinate(position.getX(), position.getY(), position.getZ()); + } + + public static LineString createLineString(PartialPosition fromPosition, PartialPosition toPosition) { + return GEOMETRY_FACTORY.createLineString(new Coordinate[]{toCoordinate(fromPosition), toCoordinate(toPosition)}); + } + + public static LinearRing createLinearRing(Coordinate[] points) { + return GEOMETRY_FACTORY.createLinearRing(points); + } + + public static int findNearestCoordinateIndex(Coordinate[] coordinates, Coordinate coordinate) { + int index = 0; + double shortestDistance = Double.MAX_VALUE; + for (int i = 0; i < coordinates.length; i++) { + double distance = coordinates[i].distance(coordinate); + if (distance < shortestDistance) { + index = i; + shortestDistance = distance; + } + } + return index; + } + + + private static double distanceBetween(PartialPosition position, PartialPosition point) { + CNCPoint point1 = new CNCPoint(position.getX(), position.getY(), position.getZ(), 0, 0, 0); + CNCPoint point2 = new CNCPoint(point.hasX() ? point.getX() : position.getX(), point.hasY() ? point.getY() : position.getY(), point.hasZ() ? point.getZ() : position.getZ(), 0, 0, 0); + return point1.distanceXYZ(point2); + } + + public static ToolPathStats getToolPathStats(GcodePath gcodePath) { + PartialPosition position = new PartialPosition(0d, 0d, 0d, UnitUtils.Units.MM); + double totalRapidLength = 0; + double totalFeedLength = 0; + for (Segment segment : gcodePath.getSegments()) { + if (segment.getType() == SegmentType.SEAM) { + // Do nothing + } else if (segment.getType() == SegmentType.MOVE) { + totalRapidLength += distanceBetween(position, segment.getPoint()); + } else { + totalFeedLength += distanceBetween(position, segment.getPoint()); + } + } + + return new ToolPathStats(totalFeedLength, totalRapidLength); + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPathTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPathTest.java index 173fea95a9..5b34fc0c9e 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPathTest.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/PocketToolPathTest.java @@ -1,8 +1,13 @@ package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; +import com.willwinder.ugs.nbp.designer.entities.Entity; +import com.willwinder.ugs.nbp.designer.entities.cuttable.Cuttable; import com.willwinder.ugs.nbp.designer.entities.cuttable.Rectangle; +import com.willwinder.ugs.nbp.designer.io.gcode.path.GcodePath; import com.willwinder.ugs.nbp.designer.io.gcode.path.Segment; import com.willwinder.ugs.nbp.designer.io.gcode.path.SegmentType; +import com.willwinder.ugs.nbp.designer.io.ugsd.UgsDesignReader; +import com.willwinder.ugs.nbp.designer.model.Design; import com.willwinder.ugs.nbp.designer.model.Size; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.PartialPosition; @@ -11,7 +16,9 @@ import java.util.List; import java.util.stream.Collectors; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class PocketToolPathTest { @@ -65,4 +72,89 @@ public void pocketShouldNotExceedTheGeometry() { PartialPosition point = drillOperations.get(drillOperations.size() - 1).getPoint(); assertEquals("Last operation should reach the target depth", targetDepth, point.getAxis(Axis.Z), 0.1); } + + + @Test + public void pocketOnRectangleWithHole() { + double toolRadius = 2.5; + double geometrySize = 10d; + double safeHeight = 1; + double targetDepth = -10; + int depthPerPass = 1; + + Rectangle rectangle = new Rectangle(); + rectangle.setSize(new Size(geometrySize, geometrySize)); + + PocketToolPath simplePocket = new PocketToolPath(rectangle); + simplePocket.setTargetDepth(targetDepth); + simplePocket.setDepthPerPass(depthPerPass); + simplePocket.setToolDiameter(toolRadius * 2); + simplePocket.setStepOver(1); + simplePocket.setSafeHeight(safeHeight); + + List segmentList = simplePocket.toGcodePath().getSegments(); + + Segment firstSegment = segmentList.get(0); + assertEquals("The first segment should move to safe height", safeHeight, firstSegment.point.getAxis(Axis.Z), 0.1); + assertFalse("The first segment should not move X", firstSegment.point.hasAxis(Axis.X)); + assertFalse("The first segment should not move Y", firstSegment.point.hasAxis(Axis.Y)); + + Segment secondSegment = segmentList.get(1); + assertEquals("The second segment should move to safe height", safeHeight, firstSegment.point.getAxis(Axis.Z), 0.1); + assertEquals("The second segment should move to first X position", safeHeight, secondSegment.point.getAxis(Axis.X), toolRadius); + assertEquals("The second segment should move to first Y position", safeHeight, secondSegment.point.getAxis(Axis.Y), toolRadius); + + // Make sure that we don't move outside the boundary of the geometry + segmentList.stream() + .filter(segment -> segment.type == SegmentType.LINE || segment.type == SegmentType.POINT) + .forEach(segment -> { + assertTrue("Point was outside boundary of 10x10 shape: X=" + segment.getPoint().getAxis(Axis.X), segment.getPoint().getAxis(Axis.X) >= toolRadius); + assertTrue("Point was outside boundary of 10x10 shape: Y=" + segment.getPoint().getAxis(Axis.Y), segment.getPoint().getAxis(Axis.Y) >= toolRadius); + assertTrue("Point was outside boundary of 10x10 shape: X=" + segment.getPoint().getAxis(Axis.X), segment.getPoint().getAxis(Axis.X) <= geometrySize - toolRadius); + assertTrue("Point was outside boundary of 10x10 shape: Y=" + segment.getPoint().getAxis(Axis.Y), segment.getPoint().getAxis(Axis.Y) <= geometrySize - toolRadius); + assertTrue("Point was outside boundary of 10x10 shape: Z=" + segment.getPoint().getAxis(Axis.Z), segment.getPoint().getAxis(Axis.Z) <= 0); + assertTrue("Point was outside boundary of 10x10 shape: Z=" + segment.getPoint().getAxis(Axis.Z), segment.getPoint().getAxis(Axis.Z) >= targetDepth); + }); + + List drillOperations = segmentList.stream() + .filter(segment -> segment.type == SegmentType.POINT) + .collect(Collectors.toList()); + assertEquals("There should be a number of drill operations when making a pocket", Math.abs((targetDepth - depthPerPass) / depthPerPass), drillOperations.size(), 0.1); + + PartialPosition point = drillOperations.get(drillOperations.size() - 1).getPoint(); + assertEquals("Last operation should reach the target depth", targetDepth, point.getAxis(Axis.Z), 0.1); + } + + @Test + public void pocketOnTestFileCheckLengths() { + UgsDesignReader reader = new UgsDesignReader(); + Design design = reader.read(PocketToolPathTest.class.getResourceAsStream("/pocket-test.ugsd")).orElseThrow(RuntimeException::new); + + double toolDiameter = 1; + double safeHeight = 5; + double startDepth = -1; + double targetDepth = -1; + int depthPerPass = 1; + + double totalLength = 0; + double totalRapidLength = 0; + + for (Entity entity : design.getEntities()) { + PocketToolPath simplePocket = new PocketToolPath((Cuttable) entity); + simplePocket.setTargetDepth(targetDepth); + simplePocket.setStartDepth(startDepth); + simplePocket.setDepthPerPass(depthPerPass); + simplePocket.setToolDiameter(toolDiameter); + simplePocket.setStepOver(0.5); + simplePocket.setSafeHeight(safeHeight); + + GcodePath gcodePath = simplePocket.toGcodePath(); + ToolPathStats toolPathStats = ToolPathUtils.getToolPathStats(gcodePath); + totalLength += toolPathStats.getTotalFeedLength(); + totalRapidLength += toolPathStats.getTotalRapidLength(); + } + + assertTrue("The tool path was " + Math.round(totalLength) + "mm long but should have been shorter", totalLength < 22144); + assertTrue("The tool path rapids was " + Math.round(totalRapidLength) + "mm long but should have been shorter", totalRapidLength < 676); + } } \ No newline at end of file diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/resources/pocket-test.ugsd b/ugs-platform/ugs-platform-plugin-designer/src/test/resources/pocket-test.ugsd new file mode 100644 index 0000000000..20a661a610 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/resources/pocket-test.ugsd @@ -0,0 +1,925 @@ +{ + "settings": { + "feedSpeed": 1000, + "plungeSpeed": 400, + "toolDiameter": 1.0, + "stockThickness": 10.0, + "safeHeight": 5.0, + "preferredUnits": "MM", + "toolStepOver": 0.5, + "depthPerPass": 1.0, + "spindleSpeed": 0.0 + }, + "entities": [ + { + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 10.0, + "m10": 0.0, + "m01": 0.0, + "m11": 10.0, + "m02": 10.0, + "m12": 10.0 + }, + "name": "Rectangle", + "type": "RECTANGLE" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 1.0, + "m10": 0.0, + "m01": 0.0, + "m11": 1.0, + "m02": 0.0, + "m12": 0.0 + }, + "name": "Rectangle", + "type": "PATH" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": -0.9999999999999999, + "m10": -5.551115123125783E-17, + "m01": 5.551115123125783E-17, + "m11": -0.9999999999999999, + "m02": 90.00000000000001, + "m12": 30.000000000000004 + }, + "name": "Rectangle", + "type": "PATH" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 17.5, + 32.5 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 17.5, + 37.5 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 12.5, + 37.5 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 12.5, + 32.5 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 17.5, + 32.5 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + }, + { + "type": "MOVE_TO", + "coordinates": [ + [ + 10.0, + 30.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 10.0, + 40.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 20.0, + 40.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 20.0, + 30.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 10.0, + 30.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 1.0, + "m10": 0.0, + "m01": 0.0, + "m11": 1.0, + "m02": 0.0, + "m12": 0.0 + }, + "name": "Rectangle", + "type": "PATH" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 5.551115123125783E-17, + "m10": -1.0, + "m01": 1.0, + "m11": 5.551115123125783E-17, + "m02": 20.0, + "m12": 70.0 + }, + "name": "Rectangle", + "type": "PATH" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 20.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 37.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 15.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 10.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 2.7755575615628914E-16, + "m10": 1.0, + "m01": -1.0, + "m11": 2.7755575615628914E-16, + "m02": 70.0, + "m12": -3.552713678800501E-15 + }, + "name": "Rectangle", + "type": "PATH" + }, + { + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 10.0, + "m10": 0.0, + "m01": 0.0, + "m11": 10.0, + "m02": 50.0, + "m12": 50.0 + }, + "name": "Rectangle", + "type": "RECTANGLE" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 15.0, + 52.5 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 16.38071187457698, + 52.5 + ], + [ + 17.5, + 53.61928812542302 + ], + [ + 17.5, + 55.0 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 17.5, + 56.38071187457698 + ], + [ + 16.38071187457698, + 57.5 + ], + [ + 15.0, + 57.5 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 13.619288125423017, + 57.5 + ], + [ + 12.5, + 56.38071187457698 + ], + [ + 12.5, + 55.0 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 12.5, + 53.61928812542302 + ], + [ + 13.619288125423017, + 52.5 + ], + [ + 15.0, + 52.5 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + }, + { + "type": "MOVE_TO", + "coordinates": [ + [ + 10.0, + 50.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 10.0, + 60.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 20.0, + 60.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 20.0, + 50.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 1.0, + "m10": 0.0, + "m01": 0.0, + "m11": 1.0, + "m02": 0.0, + "m12": 0.0 + }, + "name": "Rectangle", + "type": "PATH" + }, + { + "segments": [ + { + "type": "MOVE_TO", + "coordinates": [ + [ + 30.0, + 57.5 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 60.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 32.5, + 60.0 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 31.119288125423015, + 60.0 + ], + [ + 30.0, + 58.88071187457698 + ], + [ + 30.0, + 57.5 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + }, + { + "type": "MOVE_TO", + "coordinates": [ + [ + 30.0, + 50.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 30.0, + 57.5 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 30.0, + 56.11928812542302 + ], + [ + 31.119288125423015, + 55.0 + ], + [ + 32.5, + 55.0 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 33.88071187457698, + 55.0 + ], + [ + 35.0, + 56.11928812542302 + ], + [ + 35.0, + 57.5 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 35.0, + 58.88071187457698 + ], + [ + 33.88071187457698, + 60.0 + ], + [ + 32.5, + 60.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 60.0 + ] + ] + }, + { + "type": "LINE_TO", + "coordinates": [ + [ + 40.0, + 52.5 + ] + ] + }, + { + "type": "CUBIC_TO", + "coordinates": [ + [ + 38.61928812542302, + 52.5 + ], + [ + 37.5, + 51.38071187457698 + ], + [ + 37.5, + 50.0 + ] + ] + }, + { + "type": "CLOSE", + "coordinates": [] + } + ], + "startDepth": 1.0, + "cutDepth": 1.0, + "cutType": "POCKET", + "isHidden": false, + "transform": { + "m00": 1.0, + "m10": 0.0, + "m01": 0.0, + "m11": 1.0, + "m02": 0.0, + "m12": 0.0 + }, + "name": "Rectangle", + "type": "PATH" + } + ], + "version": "1" +} \ No newline at end of file