Skip to content

Commit 1a08b8d

Browse files
committed
Merge branch 'master' into dev
2 parents 6e56803 + f8ecee5 commit 1a08b8d

17 files changed

Lines changed: 107 additions & 124 deletions

File tree

Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
1011
### Fixed
1112
- First travel move was appearing in white instead of the travel color.
1213

@@ -16,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1617
- Drag + drop a file now adds that file to the "Recent Files" list.
1718

1819
### Fixed
20+
21+
- Infill of closed loops should now be properly filled. Was occasionally failing the in/out test and creating a mess.
1922
- MickeyMoe1992 now properly obeys the margin limits, same as the other converters.
2023
- Render hints are now forced to enable antialiasing, etc.
2124

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.marginallyclever</groupId>
88
<artifactId>Makelangelo</artifactId>
9-
<version>7.78.4</version>
9+
<version>7.78.5</version>
1010
<name>Makelangelo</name>
1111
<description>Makelangelo Software is a Java program that prepares art for CNC plotters. It is especially designed for the Makelangelo Robot.
1212
It pairs really well with Marlin-polargraph, the code in the brain of the robot that receives instructions and moves the motors.</description>

src/main/java/com/marginallyclever/convenience/ColorHSB.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.awt.*;
44

55
/**
6-
* HSB color class. Each component is 0...1
6+
* {@link ColorHSB} stores a color by its Hue, Saturation, and Brightness. Each component is 0...1
77
*
88
* @author Dan Royer
99
* @since 2022-08-31

src/main/java/com/marginallyclever/convenience/ColorPalette.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,7 @@ public void addColor(Color c) {
4242
*/
4343
@Deprecated(forRemoval = true)
4444
public void removeColor(Color c) {
45-
for (final Iterator<Color> colorsIterator = colors.iterator(); colorsIterator.hasNext(); ) {
46-
final Color nextColor = colorsIterator.next();
47-
if (nextColor.equals(c)) {
48-
colorsIterator.remove();
49-
}
50-
}
45+
colors.removeIf(nextColor -> nextColor.equals(c));
5146
}
5247

5348

src/main/java/com/marginallyclever/convenience/ColorRGB.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
*
1111
* @author Dan Royer
1212
*/
13-
@Deprecated
1413
public class ColorRGB {
1514
public int red;
1615
public int green;

src/main/java/com/marginallyclever/makelangelo/MainFrame.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.marginallyclever.convenience.FileAccess;
1010
import com.marginallyclever.donatello.Donatello;
1111
import com.marginallyclever.donatello.nodefactorypanel.NodeFactoryPanel;
12-
import com.marginallyclever.makelangelo.actions.ZoomToFitMachineAction;
1312
import com.marginallyclever.makelangelo.applicationsettings.MetricsPreferences;
1413
import com.marginallyclever.makelangelo.donatelloimpl.DockableEditNodePanel;
1514
import com.marginallyclever.makelangelo.donatelloimpl.DonatelloDropTarget;
@@ -367,4 +366,8 @@ public PlotterSettingsManager getPlotterSettingsManager() {
367366
public void onPlotterSettingsUpdate(PlotterSettings lastSelectedProfile) {
368367
previewPanel.onPlotterSettingsUpdate(lastSelectedProfile);
369368
}
369+
370+
public RecentFiles getRecentFiles() {
371+
return mainMenuBar.getRecentFiles();
372+
}
370373
}

src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterScale.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ public TransformedImage filter() {
3131
for (int y = 0; y < h; ++y) {
3232
for (int x = 0; x < w; ++x) {
3333
ColorRGB diff = new ColorRGB(aa.getRGB(x, y));
34-
diff.red = (int)Math.max(0,Math.min(255,diff.red * scale));
35-
diff.green = (int)Math.max(0,Math.min(255,diff.green * scale));
36-
diff.blue = (int)Math.max(0,Math.min(255,diff.blue * scale));
34+
diff.red = (int) Math.clamp(diff.red * scale, 0, 255);
35+
diff.green = (int) Math.clamp(diff.green * scale, 0, 255);
36+
diff.blue = (int) Math.clamp(diff.blue * scale, 0, 255);
3737
rr.setRGB(x, y, diff.toInt());
3838
}
3939
}

src/main/java/com/marginallyclever/makelangelo/makeart/io/LoadSVG.java

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,7 @@ private void parsePolylineElement(Element pathNodes) {
195195
}
196196

197197
myTurtle.add(t);
198-
if(isFilled(element)) {
199-
var filler = new InfillTurtle();
200-
filler.setPenDiameter(myTurtle.getDiameter());
201-
myTurtle.add(filler.run(t));
202-
}
198+
if(isFilled(element)) fillTurtle(t);
203199
}
204200

205201
/**
@@ -402,11 +398,13 @@ private void parseRectElement(Element element) {
402398
arcTurtle(t, x1,y2, rx,ry, Math.PI * -1.5,Math.PI * -1.0,m);
403399
arcTurtle(t, x1,y1, rx,ry, Math.PI * -1.0,Math.PI * -0.5,m);
404400
myTurtle.add(t);
405-
if(isFilled(element)) {
406-
var filler = new InfillTurtle();
407-
filler.setPenDiameter(myTurtle.getDiameter());
408-
myTurtle.add(filler.run(t));
409-
}
401+
if(isFilled(element)) fillTurtle(t);
402+
}
403+
404+
private void fillTurtle(Turtle t) {
405+
var filler = new InfillTurtle();
406+
filler.setPenDiameter(myTurtle.getDiameter());
407+
myTurtle.add(filler.run(t));
410408
}
411409

412410
/**
@@ -457,11 +455,7 @@ private void parseCircleElement(Element element) {
457455
//logger.debug("circ={}", circ);
458456
printEllipse(t, m, cx, cy, r, r, circ);
459457
myTurtle.add(t);
460-
if(isFilled(element)) {
461-
var filler = new InfillTurtle();
462-
filler.setPenDiameter(myTurtle.getDiameter());
463-
myTurtle.add(filler.run(t));
464-
}
458+
if(isFilled(element)) fillTurtle(t);
465459
}
466460

467461
private void parseEllipseElement(Element element) {
@@ -482,11 +476,7 @@ private void parseEllipseElement(Element element) {
482476
steps = Math.min(60,steps);
483477
printEllipse(t, m, cx, cy, rx, ry, steps);
484478
myTurtle.add(t);
485-
if(isFilled(element)) {
486-
var filler = new InfillTurtle();
487-
filler.setPenDiameter(myTurtle.getDiameter());
488-
myTurtle.add(filler.run(t));
489-
}
479+
if(isFilled(element)) fillTurtle(t);
490480
}
491481

492482
private void printEllipse(Turtle t, Matrix3d m, double cx, double cy, double rx, double ry, double steps) {
@@ -533,11 +523,7 @@ private void parsePathElement(Element paths) throws Exception {
533523
}
534524
}
535525
myTurtle.add(t);
536-
if(isFilled(element)) {
537-
var filler = new InfillTurtle();
538-
filler.setPenDiameter(myTurtle.getDiameter());
539-
myTurtle.add(filler.run(t));
540-
}
526+
if(isFilled(element)) fillTurtle(t);
541527
}
542528

543529
private void doCubicCurveAbs(SVGPathSeg item, Matrix3d m,Turtle t) {

src/main/java/com/marginallyclever/makelangelo/makeart/turtletool/InfillTurtle.java

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package com.marginallyclever.makelangelo.makeart.turtletool;
22

3-
import com.marginallyclever.convenience.linecollection.LineCollection;
43
import com.marginallyclever.convenience.LineSegment2D;
4+
import com.marginallyclever.convenience.linecollection.LineCollection;
55
import com.marginallyclever.makelangelo.turtle.Turtle;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
68

7-
import javax.vecmath.Vector2d;
89
import javax.vecmath.Point2d;
9-
import java.awt.*;
10+
import javax.vecmath.Vector2d;
1011
import java.awt.geom.Rectangle2D;
1112
import java.util.ArrayList;
12-
import java.util.Comparator;
1313
import java.util.List;
1414

1515
/**
@@ -19,6 +19,8 @@
1919
* @since 7.31.0
2020
*/
2121
public class InfillTurtle {
22+
private static final Logger logger = LoggerFactory.getLogger(InfillTurtle.class);
23+
2224
public static final double MINIMUM_PEN_DIAMETER = 0.1;
2325

2426
private double penDiameter = 0.8;
@@ -38,10 +40,11 @@ public Turtle run(Turtle input) {
3840
List<Turtle> list = input.splitByToolChange();
3941
for(Turtle t : list) {
4042
LineCollection segments = infillFromTurtle(t);
41-
result.setStroke(t.getColor(), t.getDiameter());
43+
//result.setStroke(t.getColor(), t.getDiameter());
4244
result.addLineSegments(segments);
4345
}
4446

47+
4548
return result;
4649
}
4750

@@ -68,16 +71,19 @@ private LineCollection infillFromTurtle(Turtle input) {
6871
Vector2d minorDir = new Vector2d(Math.cos(Math.toRadians(angle+90)), Math.sin(Math.toRadians(angle+90)));
6972
Vector2d minorStart = new Vector2d(bounds.getCenterX(),bounds.getCenterY());
7073
minorStart.scaleAdd(-size,minorDir,minorStart);
74+
Vector2d minorTemp = new Vector2d();
7175
Vector2d majorStart = new Vector2d();
7276
Vector2d majorEnd = new Vector2d();
7377

74-
for(double i=0;i<size*2;i+=penDiameter) {
75-
majorStart.scaleAdd(-size,majorDir,minorStart);
76-
majorEnd.scaleAdd(size,majorDir,minorStart);
78+
var end = size*2;
79+
80+
for(double i=0;i<end;i+=penDiameter) {
81+
minorTemp.scaleAdd(i,minorDir,minorStart);
82+
majorStart.scaleAdd(-size,majorDir,minorTemp);
83+
majorEnd.scaleAdd(size,majorDir,minorTemp);
7784
line.start.set(majorStart.x,majorStart.y);
7885
line.end.set(majorEnd.x,majorEnd.y);
7986
results.addAll(trimLineToPath(line, convertedPath));
80-
minorStart.scaleAdd(penDiameter,minorDir,minorStart);
8187
}
8288

8389
return results;
@@ -87,7 +93,7 @@ private LineCollection infillFromTurtle(Turtle input) {
8793
* Add padding to a {@link Rectangle2D.Double} bounding rectangle.
8894
*
8995
* @param before the original rectangle
90-
* @param percent the added percentage.
96+
* @param percent the added percentage. 0...100
9197
* @return the larger bounds
9298
*/
9399
private Rectangle2D.Double addPaddingToBounds(Rectangle2D.Double before, double percent) {
@@ -113,72 +119,59 @@ private Rectangle2D.Double addPaddingToBounds(Rectangle2D.Double before, double
113119
* example). Then, taken in pairs, they give you the end points of the segments
114120
* of the line that lie inside the polygon.
115121
* </p>
122+
* <p>Collect intersections and compute their parametric 't' along the infill line.
123+
* Sorting by projection (t) is more robust than sorting by x or y, and we
124+
* remove nearly-duplicate intersections caused by shared segment endpoints
125+
* or floating point noise.</p>
116126
*
117127
* @param line A {@link LineSegment2D} to clip
118128
* @param convertedPath The boundary line, which must be a closed loop
119129
* @return a list of remaining {@link LineSegment2D}.
120130
*/
121131
private LineCollection trimLineToPath(LineSegment2D line, LineCollection convertedPath) {
122-
List<Point2d> intersections = new ArrayList<>();
132+
record Hit(Point2d p,double t) {}
133+
List<Hit> hits = new ArrayList<>();
134+
Vector2d s1 = new Vector2d(line.end);
135+
s1.sub(line.start);
136+
double s1_len2 = s1.lengthSquared();
123137

124138
for (LineSegment2D s : convertedPath) {
125139
Point2d p = getIntersection(line, s);
126-
if (p != null) intersections.add(p);
140+
if (p != null) {
141+
double t = 0.0;
142+
if (s1_len2 > 0) {
143+
t = ((p.x - line.start.x) * s1.x + (p.y - line.start.y) * s1.y) / s1_len2;
144+
}
145+
hits.add(new Hit(p, t));
146+
}
127147
}
128148

129149
LineCollection results = new LineCollection();
130-
int size = intersections.size();
131-
if(size%2==0) {
132-
if (size == 2) {
133-
results.add(new LineSegment2D(intersections.get(0), intersections.get(1), line.color));
134-
} else if (size > 2) {
135-
results.addAll(sortIntersectionsIntoSegments(intersections, line.color));
150+
if (hits.isEmpty()) return results;
151+
152+
// sort by t and deduplicate near-equal t values
153+
hits.sort((a,b) -> Double.compare(a.t,b.t));
154+
List<Hit> unique = new ArrayList<>();
155+
double EPS = 1e-8;
156+
for (Hit h : hits) {
157+
if (unique.isEmpty() || Math.abs(h.t - unique.getLast().t) > EPS) {
158+
unique.add(h);
136159
}
137160
}
138161

139-
return results;
140-
}
141-
142-
/**
143-
* @param intersections A list of intersections. guaranteed to be 2 or more even
144-
* number of intersections.
145-
* @param color Color to assign to line
146-
* @return return Intersections sorted by ascending x value. If x values match,
147-
* sort by ascending y value.
148-
*/
149-
private LineCollection sortIntersectionsIntoSegments(List<Point2d> intersections, Color color) {
150-
Point2d first = intersections.get(0);
151-
Point2d second = intersections.get(1);
152-
if (Double.compare(first.x, second.x) == 0) {
153-
intersections.sort(new ComparePointsByY());
162+
int size = unique.size();
163+
if (size % 2 == 0) {
164+
for (int i = 0; i < size - 1; i += 2) {
165+
results.add(new LineSegment2D(unique.get(i).p, unique.get(i+1).p, line.color));
166+
}
154167
} else {
155-
intersections.sort(new ComparePointsByX());
156-
}
157-
158-
LineCollection results = new LineCollection();
159-
int i = 0;
160-
while (i < intersections.size()-1) {
161-
results.add(new LineSegment2D(intersections.get(i), intersections.get(i + 1), color));
162-
i += 2;
168+
// odd number of intersections — something unexpected; log for debugging
169+
logger.error("infill: odd intersection count=" + size);
163170
}
164171

165172
return results;
166173
}
167174

168-
static class ComparePointsByY implements Comparator<Point2d> {
169-
@Override
170-
public int compare(Point2d o1, Point2d o2) {
171-
return Double.compare(o1.y, o2.y);
172-
}
173-
}
174-
175-
static class ComparePointsByX implements Comparator<Point2d> {
176-
@Override
177-
public int compare(Point2d o1, Point2d o2) {
178-
return Double.compare(o1.x, o2.x);
179-
}
180-
}
181-
182175
/**
183176
* It is based on an algorithm in Andre LaMothe's "Tricks of the Windows Game Programming Gurus". See
184177
* <a href="https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect">Stackoverflow</a>
@@ -194,6 +187,9 @@ private Point2d getIntersection(LineSegment2D alpha, LineSegment2D beta) {
194187
double s2_y = beta.end.y - beta.start.y;
195188

196189
double denominator = (-s2_x * s1_y + s1_x * s2_y);
190+
// avoid division by (near) zero -> parallel or nearly-parallel segments
191+
if (Math.abs(denominator) < 1e-12) return null;
192+
197193
double s = (-s1_y * (alpha.start.x - beta.start.x) + s1_x * (alpha.start.y - beta.start.y)) / denominator;
198194
double t = ( s2_x * (alpha.start.y - beta.start.y) - s2_y * (alpha.start.x - beta.start.x)) / denominator;
199195

0 commit comments

Comments
 (0)