diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoader.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoader.java
index f39e8519d0..0ffcb4f383 100644
--- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoader.java
+++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoader.java
@@ -22,69 +22,84 @@ This file is part of Universal Gcode Sender (UGS).
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
-import com.willwinder.universalgcodesender.gcode.processors.*;
+import com.willwinder.universalgcodesender.gcode.processors.ArcExpander;
+import com.willwinder.universalgcodesender.gcode.processors.CommandLengthProcessor;
+import com.willwinder.universalgcodesender.gcode.processors.CommandProcessor;
+import com.willwinder.universalgcodesender.gcode.processors.CommentProcessor;
+import com.willwinder.universalgcodesender.gcode.processors.DecimalProcessor;
+import com.willwinder.universalgcodesender.gcode.processors.EmptyLineRemoverProcessor;
+import com.willwinder.universalgcodesender.gcode.processors.FeedOverrideProcessor;
+import com.willwinder.universalgcodesender.gcode.processors.LineSplitter;
+import com.willwinder.universalgcodesender.gcode.processors.M30Processor;
+import com.willwinder.universalgcodesender.gcode.processors.PatternRemover;
+import com.willwinder.universalgcodesender.gcode.processors.SpindleOnDweller;
+import com.willwinder.universalgcodesender.gcode.processors.WhitespaceProcessor;
import com.willwinder.universalgcodesender.i18n.Localization;
import com.willwinder.universalgcodesender.utils.ControllerSettings.ProcessorConfig;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.logging.Logger;
/**
- *
* @author wwinder
*/
public class CommandProcessorLoader {
+ private static final Logger LOGGER = Logger.getLogger(CommandProcessorLoader.class.getSimpleName());
+
/**
* Add any ICommandProcessors specified in a JSON string. Processors are
* initialized using the application settings if they are enabled.
- *
+ *
* JSON Format:
* [
- * {
- * "name":"ArcExpander",
- * "enabled": ,
- * "optional": ,
- * "args": {}
- * },{
- * "name": "CommandLenghtProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {}
- * },{
- * "name": "CommentProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {}
- * },{
- * "name": "DecimalProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {}
- * },{
- * "name": "FeedOverrideProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {}
- * },{
- * "name": "M30Processor",
- * "enabled": ,
- * "optional": ,
- * "args": {}
- * },{
- * name: "WhitespaceProcessor",
- * "enabled": ,
- * "optional": ,
- "args": {}
- },{
- name: "SpindleOnDweller",
- "enabled": ,
- * "optional": ,
- * "args": {}
- * }
- * ]
+ * {
+ * "name":"ArcExpander",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * "name": "CommandLenghtProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * "name": "CommentProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * "name": "DecimalProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * "name": "FeedOverrideProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * "name": "M30Processor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * name: "WhitespaceProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * },{
+ * name: "SpindleOnDweller",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {}
+ * }
+ * ]
*/
static private List getConfigFrom(String jsonConfig) {
List list = new ArrayList<>();
- JsonArray json = new JsonParser().parse(jsonConfig).getAsJsonArray();
+ JsonArray json = JsonParser.parseString(jsonConfig).getAsJsonArray();
for (JsonElement entry : json) {
JsonObject object = entry.getAsJsonObject();
@@ -115,64 +130,64 @@ static private List getConfigFrom(String jsonConfig) {
/**
* Add any ICommandProcessors specified in a JSON string. Processors are
* configured by properties in the JSON file.
- *
+ *
* JSON Format:
* [ {
- * "name":"ArcExpander",
- * "enabled": ,
- * "optional": ,
- * "args": {
- * "segmentLengthMM":
- * }
- * },{
- * "name": "CommandLenghtProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {
- * "commandLength":
- * }
- * },{
- * "name": "CommentProcessor",
- * "enabled":
- * "optional": ,
- * },{
- * "name": "DecimalProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {
- * "decimals":
- * }
- * },{
- * "name": "FeedOverrideProcessor",
- * "enabled": ,
- * "optional": ,
- * "args": {
- * "speed":
- * }
- * },{
- * "name": "M30Processor",
- * "enabled":
- * "optional": ,
- * },{
- * "name": "WhitespaceProcessor",
- * "enabled":
- * "optional": ,
- * },{
- * "name": "SpindleOnDweller",
- * "enabled": ,
- * "optional": ,
- * "args": {
- * "duraion":
- * }
- * },{
- * "name":"LineSplitter",
- * "enabled": ,
- * "optional": ,
- * "args": {
- * "segmentLengthMM":
- * }
- * }
- * ]
+ * "name":"ArcExpander",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {
+ * "segmentLengthMM":
+ * }
+ * },{
+ * "name": "CommandLenghtProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {
+ * "commandLength":
+ * }
+ * },{
+ * "name": "CommentProcessor",
+ * "enabled":
+ * "optional": ,
+ * },{
+ * "name": "DecimalProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {
+ * "decimals":
+ * }
+ * },{
+ * "name": "FeedOverrideProcessor",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {
+ * "speed":
+ * }
+ * },{
+ * "name": "M30Processor",
+ * "enabled":
+ * "optional": ,
+ * },{
+ * "name": "WhitespaceProcessor",
+ * "enabled":
+ * "optional": ,
+ * },{
+ * "name": "SpindleOnDweller",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {
+ * "duraion":
+ * }
+ * },{
+ * "name":"LineSplitter",
+ * "enabled": ,
+ * "optional": ,
+ * "args": {
+ * "segmentLengthMM":
+ * }
+ * }
+ * ]
*/
static public List initializeWithProcessors(String jsonConfig) {
return initializeWithProcessors(getConfigFrom(jsonConfig));
@@ -186,7 +201,7 @@ static public List initializeWithProcessors(List initializeWithProcessors(List getProcessor(ProcessorConfig pc) {
switch (pc.name) {
case "ArcExpander":
double length = pc.args.get("segmentLengthMM").getAsDouble();
- return new ArcExpander(true, length);
+ return Optional.of(new ArcExpander(true, length));
case "CommandLengthProcessor":
int commandLength = pc.args.get("commandLength").getAsInt();
- return new CommandLengthProcessor(commandLength);
+ return Optional.of(new CommandLengthProcessor(commandLength));
case "CommentProcessor":
- return new CommentProcessor();
+ return Optional.of(new CommentProcessor());
case "DecimalProcessor":
int decimals = pc.args.get("decimals").getAsInt();
- return new DecimalProcessor(decimals);
+ return Optional.of(new DecimalProcessor(decimals));
case "FeedOverrideProcessor":
double override = pc.args.get("speedOverridePercent").getAsDouble();
- return new FeedOverrideProcessor(override);
+ return Optional.of(new FeedOverrideProcessor(override));
case "M30Processor":
- return new M30Processor();
+ return Optional.of(new M30Processor());
case "PatternRemover":
String pattern = pc.args.get("pattern").getAsString();
- return new PatternRemover(pattern);
+ return Optional.of(new PatternRemover(pattern));
case "WhitespaceProcessor":
- return new WhitespaceProcessor();
+ return Optional.of(new WhitespaceProcessor());
case "SpindleOnDweller":
double duration = pc.args.get("duration").getAsDouble();
- return new SpindleOnDweller(duration);
+ return Optional.of(new SpindleOnDweller(duration));
case "LineSplitter":
- return new LineSplitter(pc.args.get("segmentLengthMM").getAsDouble());
+ return Optional.of(new LineSplitter(pc.args.get("segmentLengthMM").getAsDouble()));
case "EmptyLineRemoverProcessor":
- return new EmptyLineRemoverProcessor();
+ return Optional.of(new EmptyLineRemoverProcessor());
default:
- throw new IllegalArgumentException("Unknown processor: " + pc.name);
+ LOGGER.severe("Unknown processor: " + pc.name);
+ return Optional.empty();
}
}
}
diff --git a/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoaderTest.java b/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoaderTest.java
index efa2a20fe0..83cacb5a7c 100644
--- a/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoaderTest.java
+++ b/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/CommandProcessorLoaderTest.java
@@ -19,9 +19,7 @@ This file is part of Universal Gcode Sender (UGS).
package com.willwinder.universalgcodesender.gcode.util;
import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import com.willwinder.universalgcodesender.gcode.GcodeParser;
import com.willwinder.universalgcodesender.gcode.processors.*;
import java.util.List;
import org.junit.Test;
@@ -34,10 +32,7 @@ This file is part of Universal Gcode Sender (UGS).
public class CommandProcessorLoaderTest {
@Test
- public void testInvalidProcessors() throws Exception {
- System.out.println("InvalidProcessor");
- GcodeParser gcp = new GcodeParser();
-
+ public void testInvalidProcessors() {
JsonObject args = new JsonObject();
JsonObject object = new JsonObject();
object.addProperty("name", "DoesNotExist");
@@ -46,20 +41,12 @@ public void testInvalidProcessors() throws Exception {
JsonArray array = new JsonArray();
array.add(object);
- boolean threwException = false;
- try {
- List result = CommandProcessorLoader.initializeWithProcessors(array.toString());
- } catch (IllegalArgumentException e) {
- threwException = true;
- }
- assertTrue(threwException);
+ List result = CommandProcessorLoader.initializeWithProcessors(array.toString());
+ assertTrue("Invalid processor should be ignored", result.isEmpty());
}
@Test
- public void testInvalidParametersToValidProcessor() throws Exception {
- System.out.println("InvalidProcessor");
- GcodeParser gcp = new GcodeParser();
-
+ public void testInvalidParametersToValidProcessor() {
JsonObject args = new JsonObject();
args.addProperty("segmentLengthMM", "NotANumber");
JsonObject object = new JsonObject();
@@ -83,8 +70,7 @@ public void testInvalidParametersToValidProcessor() throws Exception {
*/
@Test
public void testAllProcessors() throws Exception {
- System.out.println("initializeWithProcessors");
- JsonObject args, name, object;
+ JsonObject args, object;
JsonArray array = new JsonArray();
@@ -167,19 +153,4 @@ public void testAllProcessors() throws Exception {
assertEquals(SpindleOnDweller.class, processors.get(8).getClass());
assertEquals(LineSplitter.class, processors.get(9).getClass());
}
-
- private static JsonElement with(String name, Boolean enabled) {
- JsonObject object = new JsonObject();
- object.addProperty("name", name);
- object.addProperty("enabled", enabled);
- return object;
- }
-
- private static JsonElement with(String name, Boolean enabled, Boolean optional) {
- JsonObject object = new JsonObject();
- object.addProperty("name", name);
- object.addProperty("enabled", enabled);
- object.addProperty("optional", optional);
- return object;
- }
}
diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/BoundsCollector.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/BoundsCollector.java
new file mode 100644
index 0000000000..a4142cc1e5
--- /dev/null
+++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/BoundsCollector.java
@@ -0,0 +1,69 @@
+package com.willwinder.ugs.nbp.designer.entities;
+
+import com.google.common.collect.Sets;
+
+import java.awt.geom.Rectangle2D;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+/**
+ * A collector for merging the bounds of several entities to one big shape.
+ *
+ * @author Joacim Breiler
+ */
+public class BoundsCollector implements Collector {
+
+ public static final HashSet CHARACTERISTICS = Sets.newHashSet(Characteristics.UNORDERED);
+
+ public static BoundsCollector toBounds() {
+ return new BoundsCollector();
+ }
+
+ private static boolean isIncomplete(Rectangle2D target) {
+ return Double.isNaN(target.getX()) || Double.isNaN(target.getY()) || Double.isNaN(target.getWidth()) || Double.isNaN(target.getHeight());
+ }
+
+ @Override
+ public Supplier supplier() {
+ return () -> new Rectangle2D.Double(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+ }
+
+ @Override
+ public BiConsumer accumulator() {
+ return (target, source) -> combiner().apply(target, source);
+ }
+
+ @Override
+ public BinaryOperator combiner() {
+ return (target, source) -> {
+ if (isIncomplete(target)) {
+ target.setRect(source.getX(), source.getY(), source.getWidth(), source.getHeight());
+ } else {
+ target.add(source);
+ }
+
+ return target;
+ };
+ }
+
+ @Override
+ public Function finisher() {
+ return (target) -> {
+ if (isIncomplete(target)) {
+ return new Rectangle2D.Double(0, 0, 0, 0);
+ } else {
+ return target;
+ }
+ };
+ }
+
+ @Override
+ public Set characteristics() {
+ return CHARACTERISTICS;
+ }
+}
diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntityGroup.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntityGroup.java
index 563e6a02f3..b557a8b29b 100644
--- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntityGroup.java
+++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntityGroup.java
@@ -25,7 +25,6 @@ This file is part of Universal Gcode Sender (UGS).
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
-import java.awt.geom.RectangularShape;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
@@ -43,7 +42,6 @@ public class EntityGroup extends AbstractEntity implements EntityListener {
private final List children;
private double groupRotation = 0;
- private Point2D cachedCenter = new Point2D.Double(0, 0);
private Rectangle2D cachedBounds = new Rectangle2D.Double(0, 0, 0, 0);
public EntityGroup() {
@@ -67,7 +65,8 @@ public void setSize(Size size) {
public void rotate(double angle) {
try {
groupRotation += angle;
- getAllChildren().forEach(entity -> entity.rotate(getCenter(), angle));
+ Point2D center = getCenter();
+ getAllChildren().forEach(entity -> entity.rotate(center, angle));
notifyEvent(new EntityEvent(this, EventType.ROTATED));
} catch (Exception e) {
throw new EntityException("Couldn't set the rotation", e);
@@ -80,7 +79,7 @@ public void rotate(Point2D center, double angle) {
groupRotation += angle;
getAllChildren().forEach(entity -> entity.rotate(center, angle));
notifyEvent(new EntityEvent(this, EventType.ROTATED));
- invalidateCenter();
+ invalidateBounds();
} catch (Exception e) {
throw new EntityException("Couldn't set the rotation", e);
}
@@ -103,12 +102,9 @@ public Rectangle2D getBounds() {
return cachedBounds;
}
- List allChildren = getAllChildren();
- double maxX = allChildren.stream().map(Entity::getBounds).mapToDouble(RectangularShape::getMaxX).max().orElse(0);
- double maxY = allChildren.stream().map(Entity::getBounds).mapToDouble(RectangularShape::getMaxY).max().orElse(0);
- double minX = allChildren.stream().map(Entity::getBounds).mapToDouble(RectangularShape::getMinX).min().orElse(0);
- double minY = allChildren.stream().map(Entity::getBounds).mapToDouble(RectangularShape::getMinY).min().orElse(0);
- cachedBounds = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
+ cachedBounds = getAllChildren().stream()
+ .map(Entity::getBounds)
+ .collect(BoundsCollector.toBounds());
return cachedBounds;
}
@@ -131,7 +127,7 @@ public void addChild(Entity entity) {
if (!containsChild(entity)) {
children.add(entity);
entity.addListener(this);
- invalidateCenter();
+ invalidateBounds();
}
}
@@ -142,22 +138,17 @@ public void addChild(Entity entity, int index) {
children.add(index, entity);
entity.addListener(this);
- invalidateCenter();
+ invalidateBounds();
}
- private void invalidateCenter() {
- cachedCenter = null;
+ private void invalidateBounds() {
cachedBounds = null;
}
@Override
public Point2D getCenter() {
- if (cachedCenter == null) {
- Rectangle2D bounds = getBounds();
- cachedCenter = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
- }
-
- return cachedCenter;
+ Rectangle2D bounds = getBounds();
+ return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
}
public void addAll(List entities) {
@@ -167,7 +158,7 @@ public void addAll(List entities) {
entity.addListener(this);
}
});
- invalidateCenter();
+ invalidateBounds();
}
@Override
@@ -175,7 +166,7 @@ public void applyTransform(AffineTransform transform) {
if (children != null) {
children.forEach(c -> c.applyTransform(transform));
}
- invalidateCenter();
+ invalidateBounds();
}
@Override
@@ -183,7 +174,7 @@ public void move(Point2D deltaMovement) {
try {
applyTransform(AffineTransform.getTranslateInstance(deltaMovement.getX(), deltaMovement.getY()));
notifyEvent(new EntityEvent(this, EventType.MOVED));
- invalidateCenter();
+ invalidateBounds();
} catch (Exception e) {
throw new EntityException("Could not make inverse transform of point", e);
}
@@ -192,7 +183,7 @@ public void move(Point2D deltaMovement) {
@Override
public void setTransform(AffineTransform transform) {
children.forEach(c -> c.setTransform(transform));
- invalidateCenter();
+ invalidateBounds();
}
/**
@@ -242,7 +233,7 @@ public Optional findParentFor(Entity entity) {
public void removeChild(Entity entity) {
entity.removeListener(this);
children.remove(entity);
- invalidateCenter();
+ invalidateBounds();
}
@Override
@@ -255,7 +246,7 @@ public void removeAll() {
this.groupRotation = 0;
this.children.forEach(entity -> entity.removeListener(this));
this.children.clear();
- invalidateCenter();
+ invalidateBounds();
}
public List getChildrenAt(Point2D p) {
@@ -316,7 +307,7 @@ public void setRotation(double rotation) {
}
groupRotation += deltaRotation;
notifyEvent(new EntityEvent(this, EventType.ROTATED));
- invalidateCenter();
+ invalidateBounds();
}
public final List getAllChildren() {
@@ -347,12 +338,13 @@ public void scale(double sx, double sy) {
child.setPosition(new Point2D.Double(originalPosition.getX() + (relativePosition.getX() * sx), originalPosition.getY() + (relativePosition.getY() * sy)));
});
notifyEvent(new EntityEvent(this, EventType.RESIZED));
- invalidateCenter();
+ invalidateBounds();
}
@Override
public void onEvent(EntityEvent entityEvent) {
notifyEvent(entityEvent);
+ invalidateBounds();
}
@Override
diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Rectangle.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Rectangle.java
index fdb7a7143a..22a8e2c084 100644
--- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Rectangle.java
+++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Rectangle.java
@@ -21,7 +21,6 @@ This file is part of Universal Gcode Sender (UGS).
import com.willwinder.ugs.nbp.designer.entities.Entity;
import java.awt.Shape;
-import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
/**
diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/EntityGroupTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/EntityGroupTest.java
index a032cb852b..1cc4111cdf 100644
--- a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/EntityGroupTest.java
+++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/EntityGroupTest.java
@@ -322,4 +322,26 @@ public void findParentForShouldNotReturnParentIfNotFound() {
Optional parentFor = entityGroup.findParentFor(point);
assertFalse(parentFor.isPresent());
}
+
+ @Test
+ public void onEventShouldUpdateBounds() {
+ EntityGroup entityGroup = new EntityGroup();
+ Rectangle rectangle1 = new Rectangle(0, 0);
+ rectangle1.setSize(new Size(10, 10));
+ entityGroup.addChild(rectangle1);
+
+ Rectangle rectangle2 = new Rectangle(5, 5);
+ entityGroup.addChild(rectangle2);
+ assertEquals(0, entityGroup.getBounds().getX(), 0.1);
+ assertEquals(0, entityGroup.getBounds().getY(), 0.1);
+ assertEquals(10, entityGroup.getBounds().getWidth(), 0.1);
+ assertEquals(10, entityGroup.getBounds().getHeight(), 0.1);
+
+ // Trigger an onEvent which should update the bounds
+ rectangle2.setSize(new Size(10, 10));
+ assertEquals(0, entityGroup.getBounds().getX(), 0.1);
+ assertEquals(0, entityGroup.getBounds().getY(), 0.1);
+ assertEquals(15, entityGroup.getBounds().getWidth(), 0.1);
+ assertEquals(15, entityGroup.getBounds().getHeight(), 0.1);
+ }
}