Skip to content

Commit d32819b

Browse files
authored
Allow using tool path angle on laser fill (#3137)
1 parent 82cc19b commit d32819b

2 files changed

Lines changed: 75 additions & 11 deletions

File tree

ugs-designer/src/main/java/com/willwinder/ugs/designer/io/gcode/toolpaths/LaserFillToolPath.java

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
import com.willwinder.ugs.designer.io.gcode.path.Segment;
77
import com.willwinder.ugs.designer.io.gcode.path.SegmentType;
88
import com.willwinder.ugs.designer.model.Settings;
9+
import static com.willwinder.ugs.designer.utils.GeometryUtils.generateLineString;
910
import com.willwinder.universalgcodesender.model.PartialPosition;
10-
import org.locationtech.jts.geom.Coordinate;
11-
import org.locationtech.jts.geom.CoordinateXY;
1211
import org.locationtech.jts.geom.Envelope;
1312
import org.locationtech.jts.geom.Geometry;
1413
import org.locationtech.jts.geom.LineString;
1514
import org.locationtech.jts.geom.MultiPoint;
1615

1716
import java.awt.geom.Area;
17+
import java.util.Arrays;
1818
import java.util.List;
1919

2020
public class LaserFillToolPath extends AbstractToolPath {
@@ -37,30 +37,57 @@ private List<Geometry> getGeometries() {
3737
public void appendGcodePath(GcodePath gcodePath, Settings settings) {
3838
gcodePath.addSegment(new Segment(SegmentType.SEAM, null, null, (int) Math.round(settings.getMaxSpindleSpeed() * (source.getSpindleSpeed() / 100d)), source.getFeedRate()));
3939

40+
double toolPathAngle = source.getToolPathAngle();
4041
List<Geometry> geometries = getGeometries();
4142
geometries.forEach(g -> {
4243
Envelope envelope = g.getEnvelopeInternal();
44+
double[] offsetRange = getOffsetRange(envelope, toolPathAngle);
4345

4446
int currentPass = 0;
4547
while (currentPass < source.getPasses()) {
4648
currentPass++;
4749

4850
boolean reverse = false;
49-
double currentY = envelope.getMinY();
50-
while (currentY <= envelope.getMaxY()) {
51-
LineString lineString = getGeometryFactory().createLineString(new Coordinate[]{
52-
new CoordinateXY(envelope.getMinX(), currentY),
53-
new CoordinateXY(envelope.getMaxX(), currentY),
54-
});
55-
56-
addLineIntersectionSegments(gcodePath, g, lineString, reverse);
57-
currentY += settings.getLaserDiameter();
51+
double currentOffset = offsetRange[0];
52+
while (currentOffset <= offsetRange[1]) {
53+
LineString lineString = generateLineString(envelope, currentOffset, toolPathAngle);
54+
if (lineString != null) {
55+
addLineIntersectionSegments(gcodePath, g, lineString, reverse);
56+
}
57+
currentOffset += settings.getLaserDiameter();
5858
reverse = !reverse;
5959
}
6060
}
6161
});
6262
}
6363

64+
/**
65+
* Returns the range of offsets along the pass normal that covers the whole envelope for the given angle.
66+
* The offset is measured from the envelope's minimum corner, matching {@link com.willwinder.ugs.designer.utils.GeometryUtils#generateLineString}.
67+
*/
68+
private static double[] getOffsetRange(Envelope envelope, double angleInDegrees) {
69+
double radians = Math.toRadians(-angleInDegrees);
70+
double dx = Math.cos(radians);
71+
double dy = Math.sin(radians);
72+
73+
double normalX = -dy;
74+
double normalY = dx;
75+
76+
double width = envelope.getWidth();
77+
double height = envelope.getHeight();
78+
79+
double[] projections = {
80+
0,
81+
width * normalX,
82+
height * normalY,
83+
width * normalX + height * normalY
84+
};
85+
86+
double min = Arrays.stream(projections).min().orElse(0);
87+
double max = Arrays.stream(projections).max().orElse(0);
88+
return new double[]{min, max};
89+
}
90+
6491
private static void addLineIntersectionSegments(GcodePath gcodePath, Geometry geometry, LineString lineString, boolean reverse) {
6592
Geometry intersection = geometry.intersection(lineString);
6693

ugs-designer/src/test/java/com/willwinder/ugs/designer/io/gcode/toolpaths/LaserFillToolPathTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import static com.willwinder.ugs.designer.io.gcode.path.SegmentType.MOVE;
1414
import static com.willwinder.ugs.designer.io.gcode.path.SegmentType.SEAM;
1515
import static org.junit.Assert.assertEquals;
16+
import static org.junit.Assert.assertFalse;
1617

1718
public class LaserFillToolPathTest {
1819

@@ -44,6 +45,42 @@ public void fillToolPathShouldNotConnectThePointEdgesWithALineSegment() {
4445
assertSegment(segments.get(4), LINE, 0.0, 0.2);
4546
}
4647

48+
@Test
49+
public void fillToolPathShouldFillAlongToolPathAngle() {
50+
Path path = new Path();
51+
path.setPasses(1);
52+
path.setToolPathAngle(90);
53+
path.moveTo(0, 0);
54+
path.lineTo(0, 1);
55+
path.lineTo(1, 1);
56+
path.lineTo(1, 0);
57+
path.lineTo(0, 0);
58+
path.close();
59+
60+
Settings settings = new Settings();
61+
settings.setMaxSpindleSpeed(10000);
62+
63+
LaserFillToolPath toolPath = new LaserFillToolPath(settings, path);
64+
toolPath.setStartDepth(0);
65+
toolPath.setTargetDepth(0);
66+
67+
GcodePath gcodePath = toolPath.toGcodePath();
68+
List<Segment> segments = gcodePath.getSegments();
69+
assertEquals(SEAM, segments.get(0).type);
70+
71+
List<Segment> lineSegments = segments.stream().filter(s -> s.type == LINE).toList();
72+
assertFalse("Expected the fill to produce line segments", lineSegments.isEmpty());
73+
74+
// A 90 degree angle fills with vertical passes, so every line moves along Y while keeping X constant
75+
lineSegments.forEach(segment ->
76+
assertEquals(segment.getPoint().getX(), findMovePreceding(segments, segment).getPoint().getX(), 0.01));
77+
}
78+
79+
private Segment findMovePreceding(List<Segment> segments, Segment lineSegment) {
80+
int index = segments.indexOf(lineSegment);
81+
return segments.get(index - 1);
82+
}
83+
4784
private void assertSegment(Segment segment, SegmentType segmentType, double x, double y) {
4885
assertEquals(segmentType, segment.getType());
4986
assertEquals(x, segment.getPoint().getX(), 0.01);

0 commit comments

Comments
 (0)