Skip to content

Commit

Permalink
Optimized the toolpath for pocket operations (#2152)
Browse files Browse the repository at this point in the history
  • Loading branch information
breiler authored Feb 5, 2023
1 parent a905020 commit 6eff2d2
Show file tree
Hide file tree
Showing 7 changed files with 1,289 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

/**
Expand Down Expand Up @@ -134,22 +137,16 @@ 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();
return stringWriter.toString();
}

public String toGcode(List<Cuttable> 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<Cuttable> cuttables = entities.stream()
.sorted(new EntityComparator(width, height))
.collect(Collectors.toList());

StringBuilder result = new StringBuilder(HEADER +
generateToolHeader() + "\n" +
Code.G21.name() + " ; millimeters\n" +
Expand All @@ -160,7 +157,7 @@ public String toGcode(List<Cuttable> 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);
}
Expand Down Expand Up @@ -272,7 +269,6 @@ protected void runPath(Writer writer, List<Segment> segments) throws IOException
writer.write(s.point.getFormattedGCode());
writer.write("\n");
hasFeedRateSet = false;

break;

// Drill down using the plunge speed
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Geometry> {
@Override
public int compare(Geometry o1, Geometry o2) {
if (o2.contains(o1)) {
return 1;
}

return o2.compareTo(o1);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2021 Will Winder
Copyright 2023 Will Winder
This file is part of Universal Gcode Sender (UGS).
Expand All @@ -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;

/**
Expand All @@ -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<Geometry> geometries = bufferAndCollectGeometries(geometryCollection);

List<List<PartialPosition>> 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<Geometry> 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<Geometry> geometries, List<List<PartialPosition>> coordinateList, double currentDepth) {
Geometry previousGeometry = null;
List<PartialPosition> 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<List<PartialPosition>> 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<Geometry> bufferAndCollectGeometries(Geometry geometry) {
List<Geometry> geometries = new ArrayList<>();
double buffer = getToolDiameter() / 2d;
while (true) {
Geometry bufferedGeometry = geometry.buffer(-buffer);
List<Geometry> bufferedGeometries = toGeometryList(bufferedGeometry);
if(bufferedGeometries.isEmpty()) {
geometries.sort(Comparator.comparingDouble(o -> o.getEnvelope().getArea()));
return geometries;
}
List<Geometry> geometries = bufferAndCollectGeometries(geometry, buffer);
geometries.sort(new GeometryToolpathComparator());
return geometries;
}

geometries.addAll(bufferedGeometries);
buffer = buffer + (getToolDiameter() * stepOver);
private List<Geometry> bufferAndCollectGeometries(Geometry geometry, double buffer) {
Geometry bufferedGeometry = geometry.buffer(-buffer);
if (bufferedGeometry.getNumGeometries() <= 0 || bufferedGeometry.isEmpty()) {
return Collections.emptyList();
}

List<Geometry> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}
Loading

0 comments on commit 6eff2d2

Please sign in to comment.