From 9620eafd4ac568f78866073cc3036d1cc4be9d7a Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Thu, 23 May 2024 12:43:53 +0200 Subject: [PATCH 1/3] Added new toolpath generator for lasers --- .../model/PartialPosition.java | 12 +- .../uielements/TextFieldUnit.java | 3 +- .../uielements/components/UnitSpinner.java | 4 +- .../model/PartialPositionTest.java | 9 +- .../actions/ChangeCutSettingsAction.java | 33 +-- .../actions/OpenToolSettingsAction.java | 9 +- .../nbp/designer/entities/EntitySetting.java | 34 ++- .../entities/cuttable/AbstractCuttable.java | 76 ++++++- .../designer/entities/cuttable/CutType.java | 29 ++- .../designer/entities/cuttable/Cuttable.java | 50 +++++ .../cuttable/CuttableEntitySettings.java | 40 ++++ .../nbp/designer/entities/cuttable/Group.java | 69 +++++- .../ugs/nbp/designer/gui/CutTypeIcon.java | 20 +- .../nbp/designer/gui/ToolSettingsPanel.java | 75 +++++-- .../FieldActionDispatcher.java | 19 +- .../FieldEventDispatcher.java | 6 + .../SelectionSettingsModel.java | 83 ++++--- .../SelectionSettingsPanel.java | 128 ++++++++--- .../designer/io/gcode/GcodeDesignWriter.java | 13 +- .../designer/io/gcode/SimpleGcodeRouter.java | 202 ++++++------------ .../nbp/designer/io/gcode/path/GcodePath.java | 11 +- .../designer/io/gcode/path/PathGenerator.java | 10 +- .../nbp/designer/io/gcode/path/Segment.java | 26 +-- .../io/gcode/toolpaths/AbstractToolPath.java | 60 ++---- .../gcode/toolpaths/DrillCenterToolPath.java | 38 ++-- .../io/gcode/toolpaths/LaserFillToolPath.java | 86 ++++++++ .../gcode/toolpaths/LaserOutlineToolPath.java | 55 +++++ .../io/gcode/toolpaths/OutlineToolPath.java | 30 +-- .../io/gcode/toolpaths/PocketToolPath.java | 26 +-- .../nbp/designer/io/ugsd/UgsDesignWriter.java | 4 + .../nbp/designer/io/ugsd/v1/CutTypeV1.java | 14 +- .../designer/io/ugsd/v1/CuttableEntityV1.java | 27 ++- .../nbp/designer/io/ugsd/v1/SettingsV1.java | 6 +- .../ugs/nbp/designer/model/Settings.java | 44 +++- .../src/main/resources/img/cutfill.svg | 89 ++++++++ .../src/main/resources/img/cutfill24.svg | 89 ++++++++ .../src/main/resources/img/cutfill32.svg | 89 ++++++++ .../designer/entities/cuttable/GroupTest.java | 32 +++ .../SelectionSettingsModelTest.java | 101 +-------- .../toolpaths/DrillCenterToolPathTest.java | 14 +- .../gcode/toolpaths/LaserOutlinePathTest.java | 11 + .../gcode/toolpaths/OutlineToolPathTest.java | 6 +- .../gcode/toolpaths/PocketToolPathTest.java | 38 ++-- 43 files changed, 1263 insertions(+), 557 deletions(-) create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CuttableEntitySettings.java create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill.svg create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill24.svg create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill32.svg create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/cuttable/GroupTest.java create mode 100644 ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlinePathTest.java diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/PartialPosition.java b/ugs-core/src/com/willwinder/universalgcodesender/model/PartialPosition.java index 97b8e9453b..690160e355 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/PartialPosition.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/PartialPosition.java @@ -94,27 +94,27 @@ public static PartialPosition fromXY(Position position) { public boolean hasX() { - return x != null; + return x != null && !x.isNaN(); } public boolean hasY() { - return y != null; + return y != null && !y.isNaN(); } public boolean hasZ() { - return z != null; + return z != null && !z.isNaN(); } public boolean hasA() { - return a != null; + return a != null && !a.isNaN(); } public boolean hasB() { - return b != null; + return b != null && !b.isNaN(); } public boolean hasC() { - return c != null; + return c != null && !c.isNaN(); } public Double getX() { diff --git a/ugs-core/src/com/willwinder/universalgcodesender/uielements/TextFieldUnit.java b/ugs-core/src/com/willwinder/universalgcodesender/uielements/TextFieldUnit.java index dac7c4bc5e..7df6cc2ffa 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/uielements/TextFieldUnit.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/uielements/TextFieldUnit.java @@ -28,7 +28,8 @@ public enum TextFieldUnit { INCHES_PER_MINUTE("inch/min"), ROTATIONS_PER_MINUTE("rpm"), PERCENT("%"), - DEGREE("°"); + DEGREE("°"), + TIMES("times"); private final String abbreviation; diff --git a/ugs-core/src/com/willwinder/universalgcodesender/uielements/components/UnitSpinner.java b/ugs-core/src/com/willwinder/universalgcodesender/uielements/components/UnitSpinner.java index cd708838e5..3394d3a030 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/uielements/components/UnitSpinner.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/uielements/components/UnitSpinner.java @@ -48,9 +48,9 @@ public UnitSpinner(double value, TextFieldUnit units, Double minimum, Double max } public void setUnits(TextFieldUnit units) { - if(units == TextFieldUnit.MM) { + if (units == TextFieldUnit.MM) { spinnerNumberModel.setStepSize(0.01); - } else { + } else if (units == TextFieldUnit.INCH) { spinnerNumberModel.setStepSize(0.001); } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/model/PartialPositionTest.java b/ugs-core/test/com/willwinder/universalgcodesender/model/PartialPositionTest.java index 3e5656cd63..a833cc7ed8 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/model/PartialPositionTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/model/PartialPositionTest.java @@ -1,9 +1,10 @@ package com.willwinder.universalgcodesender.model; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import org.junit.Test; -import static org.junit.Assert.*; - public class PartialPositionTest { @Test @@ -12,6 +13,10 @@ public void testFormatted() { assertEquals("X0Z0", new PartialPosition(0.0, null, 0.0, UnitUtils.Units.MM).getFormattedGCode()); assertEquals("X0Y0", new PartialPosition(0.0, 0.0, UnitUtils.Units.MM).getFormattedGCode()); + assertEquals("Y0Z0", new PartialPosition(Double.NaN, 0.0, 0.0, UnitUtils.Units.MM).getFormattedGCode()); + assertEquals("X0Z0", new PartialPosition(0.0, Double.NaN, 0.0, UnitUtils.Units.MM).getFormattedGCode()); + assertEquals("X0Y0", new PartialPosition(0.0, 0.0, Double.NaN, UnitUtils.Units.MM).getFormattedGCode()); + assertEquals("Y10Z0", new PartialPosition(null, 10.0, 0.0, UnitUtils.Units.MM).getFormattedGCode()); assertEquals("X10Z0", new PartialPosition(10.0, null, 0.0, UnitUtils.Units.MM).getFormattedGCode()); assertEquals("X0Y10", new PartialPosition(0.0, 10.0, UnitUtils.Units.MM).getFormattedGCode()); diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/ChangeCutSettingsAction.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/ChangeCutSettingsAction.java index bb749b25e1..a13320cfb6 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/ChangeCutSettingsAction.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/ChangeCutSettingsAction.java @@ -18,15 +18,14 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.nbp.designer.actions; -import com.willwinder.ugs.nbp.designer.entities.cuttable.CutType; +import com.willwinder.ugs.nbp.designer.entities.EntitySetting; import com.willwinder.ugs.nbp.designer.entities.cuttable.Cuttable; import com.willwinder.ugs.nbp.designer.logic.Controller; -import javax.swing.*; +import javax.swing.AbstractAction; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** * @author Joacim Breiler @@ -34,22 +33,16 @@ This file is part of Universal Gcode Sender (UGS). public class ChangeCutSettingsAction extends AbstractAction implements UndoableAction { private final transient Controller controller; - private final List previousStartDepth; - private final List previousCutDepth; - private final List previousCutType; - private final double newStartDepth; - private final double newCutDepth; - private final CutType newCutType; private final transient List cuttableList; + private final Object newValue; + private final List previousValue; + private final EntitySetting entitySetting; - public ChangeCutSettingsAction(Controller controller, List cuttableList, double startDepth, double targetDepth, CutType cutType) { + public ChangeCutSettingsAction(Controller controller, List cuttableList, EntitySetting entitySetting, Object value) { this.cuttableList = new ArrayList<>(cuttableList); - previousStartDepth = cuttableList.stream().map(Cuttable::getStartDepth).collect(Collectors.toList()); - previousCutDepth = cuttableList.stream().map(Cuttable::getTargetDepth).collect(Collectors.toList()); - previousCutType = cuttableList.stream().map(Cuttable::getCutType).collect(Collectors.toList()); - newStartDepth = startDepth; - newCutDepth = targetDepth; - newCutType = cutType; + this.entitySetting = entitySetting; + previousValue = cuttableList.stream().map(c -> c.getEntitySetting(entitySetting).orElse(null)).toList(); + newValue = value; this.controller = controller; putValue("menuText", "Change stock settings"); @@ -64,18 +57,14 @@ public void redo() { @Override public void undo() { for (int i = 0; i < cuttableList.size(); i++) { - cuttableList.get(i).setStartDepth(previousStartDepth.get(i)); - cuttableList.get(i).setTargetDepth(previousCutDepth.get(i)); - cuttableList.get(i).setCutType(previousCutType.get(i)); + cuttableList.get(i).setEntitySetting(entitySetting, previousValue.get(i)); } } @Override public void actionPerformed(ActionEvent e) { for (Cuttable cuttable : cuttableList) { - cuttable.setStartDepth(newStartDepth); - cuttable.setTargetDepth(newCutDepth); - cuttable.setCutType(newCutType); + cuttable.setEntitySetting(entitySetting, newValue); } this.controller.getDrawing().repaint(); } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/OpenToolSettingsAction.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/OpenToolSettingsAction.java index 0f666411fb..9bafbaba75 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/OpenToolSettingsAction.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/actions/OpenToolSettingsAction.java @@ -4,12 +4,11 @@ import com.willwinder.ugs.nbp.designer.logic.Controller; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; +import static org.openide.NotifyDescriptor.OK_OPTION; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import static org.openide.NotifyDescriptor.OK_OPTION; - public class OpenToolSettingsAction implements ActionListener { private final Controller controller; @@ -22,9 +21,9 @@ public void actionPerformed(ActionEvent e) { ToolSettingsPanel toolSettingsPanel = new ToolSettingsPanel(controller); DialogDescriptor dialogDescriptor = new DialogDescriptor(toolSettingsPanel, "Tool settings", true, null); if (DialogDisplayer.getDefault().notify(dialogDescriptor) == OK_OPTION) { - ChangeToolSettingsAction changeStockSettings = new ChangeToolSettingsAction(controller, toolSettingsPanel.getSettings()); - changeStockSettings.actionPerformed(null); - controller.getUndoManager().addAction(changeStockSettings); + ChangeToolSettingsAction changeToolSettingsAction = new ChangeToolSettingsAction(controller, toolSettingsPanel.getSettings()); + changeToolSettingsAction.actionPerformed(null); + controller.getUndoManager().addAction(changeToolSettingsAction); } } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntitySetting.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntitySetting.java index 4a608ea136..f90b8844bd 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntitySetting.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/EntitySetting.java @@ -19,6 +19,8 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.ugs.nbp.designer.entities; +import java.util.List; + /** * What settings that is possible to set on an entity. * @@ -36,7 +38,37 @@ public enum EntitySetting { TARGET_DEPTH("Target depth"), ANCHOR("Anchor"), FONT_FAMILY("Font"), - LOCK_RATIO("Lock ratio"); + LOCK_RATIO("Lock ratio"), + SPINDLE_SPEED("Spindle speed"), + PASSES("Passes"), + FEED_RATE("Feed rate"); + + public static final List DEFAULT_ENDMILL_SETTINGS = List.of( + EntitySetting.CUT_TYPE, + EntitySetting.ANCHOR, + EntitySetting.HEIGHT, + EntitySetting.WIDTH, + EntitySetting.FONT_FAMILY, + EntitySetting.LOCK_RATIO, + EntitySetting.POSITION_X, + EntitySetting.POSITION_Y, + EntitySetting.ROTATION, + EntitySetting.START_DEPTH, + EntitySetting.TARGET_DEPTH); + + public static final List DEFAULT_LASER_SETTINGS = List.of( + EntitySetting.CUT_TYPE, + EntitySetting.ANCHOR, + EntitySetting.HEIGHT, + EntitySetting.WIDTH, + EntitySetting.FONT_FAMILY, + EntitySetting.LOCK_RATIO, + EntitySetting.POSITION_X, + EntitySetting.POSITION_Y, + EntitySetting.ROTATION, + EntitySetting.SPINDLE_SPEED, + EntitySetting.PASSES, + EntitySetting.FEED_RATE); private final String label; diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/AbstractCuttable.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/AbstractCuttable.java index e1482f9268..880b047239 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/AbstractCuttable.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/AbstractCuttable.java @@ -20,8 +20,8 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.designer.entities.AbstractEntity; import com.willwinder.ugs.nbp.designer.entities.EntityEvent; -import com.willwinder.ugs.nbp.designer.entities.EventType; import com.willwinder.ugs.nbp.designer.entities.EntitySetting; +import com.willwinder.ugs.nbp.designer.entities.EventType; import com.willwinder.ugs.nbp.designer.gui.Colors; import com.willwinder.ugs.nbp.designer.gui.Drawing; import com.willwinder.ugs.nbp.designer.logic.Controller; @@ -35,14 +35,19 @@ This file is part of Universal Gcode Sender (UGS). import java.awt.geom.Rectangle2D; import java.util.Arrays; import java.util.List; +import java.util.Optional; /** * @author Joacim Breiler */ public abstract class AbstractCuttable extends AbstractEntity implements Cuttable { + private final CuttableEntitySettings entitySettings; private CutType cutType = CutType.NONE; private double targetDepth; private double startDepth; + private int spindleSpeed; + private int passes; + private int feedRate; private boolean isHidden = false; protected AbstractCuttable() { @@ -51,6 +56,7 @@ protected AbstractCuttable() { protected AbstractCuttable(double relativeX, double relativeY) { super(relativeX, relativeY); + entitySettings = new CuttableEntitySettings(this); } @Override @@ -86,6 +92,39 @@ public void setTargetDepth(double targetDepth) { notifyEvent(new EntityEvent(this, EventType.SETTINGS_CHANGED)); } + @Override + public int getSpindleSpeed() { + return spindleSpeed; + } + + @Override + public void setSpindleSpeed(int Speed) { + this.spindleSpeed = Math.abs(Speed); + notifyEvent(new EntityEvent(this, EventType.SETTINGS_CHANGED)); + } + + @Override + public int getPasses() { + return passes; + } + + @Override + public void setPasses(int passes) { + this.passes = Math.abs(passes); + notifyEvent(new EntityEvent(this, EventType.SETTINGS_CHANGED)); + } + + @Override + public int getFeedRate() { + return feedRate; + } + + @Override + public void setFeedRate(int feedRate) { + this.feedRate = Math.abs(feedRate); + notifyEvent(new EntityEvent(this, EventType.SETTINGS_CHANGED)); + } + @Override public void render(Graphics2D graphics, Drawing drawing) { if (isHidden) { @@ -106,6 +145,13 @@ public void render(Graphics2D graphics, Drawing drawing) { graphics.draw(shape); } else if (getCutType() == CutType.INSIDE_PATH || getCutType() == CutType.ON_PATH || getCutType() == CutType.OUTSIDE_PATH) { drawShape(graphics, new BasicStroke(strokeWidth), getCutColor(), shape); + } else if (getCutType() == CutType.LASER_ON_PATH) { + drawShape(graphics, new BasicStroke(strokeWidth), getLaserCutColor(), shape); + } else if (getCutType() == CutType.LASER_FILL) { + graphics.setStroke(new BasicStroke(strokeWidth)); + graphics.setColor(getLaserCutColor()); + graphics.fill(shape); + graphics.draw(shape); } else if (getCutType() == CutType.CENTER_DRILL) { drawShape(graphics, new BasicStroke(strokeWidth), Colors.SHAPE_HINT, shape); double centerX = shape.getBounds2D().getCenterX(); @@ -152,10 +198,24 @@ public List getSettings() { EntitySetting.HEIGHT, EntitySetting.CUT_TYPE, EntitySetting.START_DEPTH, - EntitySetting.TARGET_DEPTH + EntitySetting.TARGET_DEPTH, + EntitySetting.SPINDLE_SPEED, + EntitySetting.PASSES, + EntitySetting.FEED_RATE ); } + private Color getLaserCutColor() { + int color = Math.max(0, Math.min(255, (int) Math.round(255d * getLaserCutAlpha()) - 50)); + return new Color(color, color, color); + } + + private double getLaserCutAlpha() { + return 1d - Math.max(Float.MIN_VALUE, getEntitySetting(EntitySetting.SPINDLE_SPEED) + .map(v -> (Integer) v / 100d).orElse(0d)); + } + + private Color getCutColor() { int color = Math.max(0, Math.min(255, (int) Math.round(255d * getCutAlpha()) - 50)); return new Color(color, color, color); @@ -174,6 +234,18 @@ protected void copyPropertiesTo(Cuttable copy) { copy.setStartDepth(getStartDepth()); copy.setTargetDepth(getTargetDepth()); copy.setCutType(getCutType()); + copy.setSpindleSpeed(getSpindleSpeed()); + copy.setPasses(getPasses()); copy.setHidden(isHidden()); } + + @Override + public Optional getEntitySetting(EntitySetting entitySetting) { + return entitySettings.getEntitySetting(entitySetting); + } + + @Override + public void setEntitySetting(EntitySetting entitySetting, Object value) { + entitySettings.setEntitySetting(entitySetting, value); + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CutType.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CutType.java index 47b64084e6..a71db3c6e4 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CutType.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CutType.java @@ -18,24 +18,39 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.nbp.designer.entities.cuttable; +import com.willwinder.ugs.nbp.designer.entities.EntitySetting; +import static com.willwinder.ugs.nbp.designer.entities.EntitySetting.DEFAULT_ENDMILL_SETTINGS; +import static com.willwinder.ugs.nbp.designer.entities.EntitySetting.DEFAULT_LASER_SETTINGS; + +import java.util.List; + /** * @author Joacim Breiler */ public enum CutType { - NONE("None"), - POCKET("Pocket"), - ON_PATH("On path"), - INSIDE_PATH("Inside path"), - OUTSIDE_PATH("Outside path"), - CENTER_DRILL("Center drill"); + NONE("None", List.of(EntitySetting.CUT_TYPE)), + POCKET("Mill Pocket", DEFAULT_ENDMILL_SETTINGS), + ON_PATH("Mill - On path", DEFAULT_ENDMILL_SETTINGS), + INSIDE_PATH("Mill - Inside path", DEFAULT_ENDMILL_SETTINGS), + OUTSIDE_PATH("Mill - Outside path", DEFAULT_ENDMILL_SETTINGS), + LASER_ON_PATH("Laser - On path", DEFAULT_LASER_SETTINGS), + LASER_FILL("Laser - Fill", DEFAULT_LASER_SETTINGS), + CENTER_DRILL("Center drill", DEFAULT_ENDMILL_SETTINGS), + ; private final String name; - CutType(String name) { + private final List settings; + + CutType(String name, List settings) { this.name = name; + this.settings = settings; } public String getName() { return name; } + public List getSettings() { + return settings; + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Cuttable.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Cuttable.java index c50a5f9ec7..cc21d7b8bb 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Cuttable.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Cuttable.java @@ -19,6 +19,9 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.ugs.nbp.designer.entities.cuttable; import com.willwinder.ugs.nbp.designer.entities.Entity; +import com.willwinder.ugs.nbp.designer.entities.EntitySetting; + +import java.util.Optional; /** * Defines an entity that can be cut using a cut operation. @@ -68,6 +71,49 @@ public interface Cuttable extends Entity { */ void setStartDepth(double startDepth); + /** + * Returns the laser power in percent where min is 0 and max is 100. + * + * @return the laser power in percent + */ + int getSpindleSpeed(); + + /** + * Sets the laser power in percent where min is 0 and max is 100 + * + * @param power the power in percent + */ + void setSpindleSpeed(int power); + + /** + * Returns the laser feed rate in percent where min is 0 and max is 100. + * + * @return the laser feed rate in percent + */ + int getFeedRate(); + + /** + * Sets the laser feed rate in percent where min is 0 and max is 100 + * + * @param feedRate the feed rate in percent + */ + + void setFeedRate(int feedRate); + + /** + * Returns the number of passes that should be done + * + * @return the number of passes + */ + int getPasses(); + + /** + * Sets the number of passes that should be done in with laser + * + * @param laserPasses the number of passes + */ + void setPasses(int laserPasses); + /** * If the entity should be hidden in the design. When hidden the entity is not included * in the output or displayed in the editor. @@ -82,4 +128,8 @@ public interface Cuttable extends Entity { * @param hidden if the entity is hidden. */ void setHidden(boolean hidden); + + Optional getEntitySetting(EntitySetting entitySetting); + + void setEntitySetting(EntitySetting entitySetting, Object value); } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CuttableEntitySettings.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CuttableEntitySettings.java new file mode 100644 index 0000000000..79ed84008a --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/CuttableEntitySettings.java @@ -0,0 +1,40 @@ +package com.willwinder.ugs.nbp.designer.entities.cuttable; + +import com.willwinder.ugs.nbp.designer.entities.EntitySetting; + +import java.util.Optional; +import java.util.logging.Logger; + +public class CuttableEntitySettings { + private static final Logger LOGGER = Logger.getLogger(AbstractCuttable.class.getSimpleName()); + private final Cuttable cuttable; + + public CuttableEntitySettings(Cuttable cuttable) { + this.cuttable = cuttable; + } + + public Optional getEntitySetting(EntitySetting entitySetting) { + return switch (entitySetting) { + case CUT_TYPE -> Optional.of(cuttable.getCutType()); + case START_DEPTH -> Optional.of(cuttable.getStartDepth()); + case TARGET_DEPTH -> Optional.of(cuttable.getTargetDepth()); + case SPINDLE_SPEED -> Optional.of(cuttable.getSpindleSpeed()); + case PASSES -> Optional.of(cuttable.getPasses()); + case FEED_RATE -> Optional.of(cuttable.getFeedRate()); + default -> Optional.empty(); + }; + } + + public void setEntitySetting(EntitySetting entitySetting, Object value) { + switch (entitySetting) { + case CUT_TYPE -> cuttable.setCutType((CutType) value); + case START_DEPTH -> cuttable.setStartDepth((Double) value); + case TARGET_DEPTH -> cuttable.setTargetDepth((Double) value); + case SPINDLE_SPEED -> cuttable.setSpindleSpeed((Integer) value); + case PASSES -> cuttable.setPasses(Integer.parseInt(value.toString())); + case FEED_RATE -> cuttable.setFeedRate(((Double) value).intValue()); + default -> LOGGER.info("Do not know how to set " + entitySetting + " to " + value); + } + } + +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Group.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Group.java index 33355a5456..1e4d8016a8 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Group.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/cuttable/Group.java @@ -33,9 +33,12 @@ This file is part of Universal Gcode Sender (UGS). * @author Joacim Breiler */ public class Group extends EntityGroup implements Cuttable { + private final CuttableEntitySettings entitySettings; + public Group() { setName("Group"); + entitySettings = new CuttableEntitySettings(this); } @Override @@ -96,6 +99,57 @@ public void setStartDepth(double startDepth) { }); } + @Override + public int getSpindleSpeed() { + return getCuttableStream() + .mapToInt(Cuttable::getSpindleSpeed) + .max() + .orElse(0); + } + + @Override + public void setSpindleSpeed(int spindleSpeed) { + getChildren().forEach(child -> { + if (child instanceof Cuttable cuttable) { + cuttable.setSpindleSpeed(spindleSpeed); + } + }); + } + + @Override + public int getFeedRate() { + return getCuttableStream() + .mapToInt(Cuttable::getFeedRate) + .max() + .orElse(0); + } + + @Override + public void setFeedRate(int feedRate) { + getChildren().forEach(child -> { + if (child instanceof Cuttable cuttable) { + cuttable.setFeedRate(feedRate); + } + }); + } + + @Override + public int getPasses() { + return getCuttableStream() + .mapToInt(Cuttable::getPasses) + .max() + .orElse(0); + } + + @Override + public void setPasses(int passes) { + getChildren().forEach(child -> { + if (child instanceof Cuttable cuttable) { + cuttable.setPasses(passes); + } + }); + } + @Override public boolean isHidden() { return getCuttableStream() @@ -113,6 +167,16 @@ public void setHidden(boolean hidden) { }); } + @Override + public Optional getEntitySetting(EntitySetting entitySetting) { + return entitySettings.getEntitySetting(entitySetting); + } + + @Override + public void setEntitySetting(EntitySetting entitySetting, Object value) { + entitySettings.setEntitySetting(entitySetting, value); + } + private Stream getCuttableStream() { return getChildren().stream() .filter(Cuttable.class::isInstance) @@ -146,7 +210,10 @@ public List getSettings() { EntitySetting.HEIGHT, EntitySetting.CUT_TYPE, EntitySetting.START_DEPTH, - EntitySetting.TARGET_DEPTH + EntitySetting.TARGET_DEPTH, + EntitySetting.SPINDLE_SPEED, + EntitySetting.PASSES, + EntitySetting.FEED_RATE ); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/CutTypeIcon.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/CutTypeIcon.java index 9823892468..d8c9b84120 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/CutTypeIcon.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/CutTypeIcon.java @@ -30,28 +30,36 @@ public CutTypeIcon(CutType cutType, Size size) { switch (cutType) { case NONE: icon = ImageUtilities.loadImageIcon("img/cutnone" + size.value + ".svg", false); - setDescription("No cut"); + setDescription(cutType.getName()); break; case POCKET: icon = ImageUtilities.loadImageIcon("img/cutpocket" + size.value + ".svg", false); - setDescription("Pocket"); + setDescription(cutType.getName()); break; case OUTSIDE_PATH: icon = ImageUtilities.loadImageIcon("img/cutoutside" + size.value + ".svg", false); - setDescription("Outside"); + setDescription(cutType.getName()); break; case INSIDE_PATH: icon = ImageUtilities.loadImageIcon("img/cutinside" + size.value + ".svg", false); - setDescription("Inside"); + setDescription(cutType.getName()); break; case ON_PATH: + case LASER_ON_PATH: icon = ImageUtilities.loadImageIcon("img/cutonpath" + size.value + ".svg", false); - setDescription("On path"); + setDescription(cutType.getName()); break; case CENTER_DRILL: icon = ImageUtilities.loadImageIcon("img/centerdrill" + size.value + ".svg", false); - setDescription("Center drill"); + setDescription(cutType.getName()); break; + case LASER_FILL: + icon = ImageUtilities.loadImageIcon("img/cutfill" + size.value + ".svg", false); + setDescription(cutType.getName()); + break; + default: + icon = ImageUtilities.loadImageIcon("img/cutnone" + size.value + ".svg", false); + setDescription(cutType.getName()); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/ToolSettingsPanel.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/ToolSettingsPanel.java index 31c2775fe2..22fe4eecf2 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/ToolSettingsPanel.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/ToolSettingsPanel.java @@ -25,8 +25,12 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.uielements.TextFieldWithUnit; import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import java.awt.Dimension; import java.text.ParseException; /** @@ -41,6 +45,8 @@ public class ToolSettingsPanel extends JPanel { private JTextField stepOver; private JTextField safeHeight; private JTextField spindleSpeed; + private TextFieldWithUnit laserDiameter; + private TextFieldWithUnit maxSpindleSpeed; public ToolSettingsPanel(Controller controller) { this.controller = controller; @@ -50,37 +56,48 @@ public ToolSettingsPanel(Controller controller) { } private void initComponents() { - setLayout(new MigLayout("fill", "[20%][80%]")); + setLayout(new MigLayout("fill", "[20%][80%]" )); - add(new JLabel("Tool diameter")); + add(new JLabel("Tool diameter" )); toolDiameter = new TextFieldWithUnit(TextFieldUnit.MM, 3, controller.getSettings().getToolDiameter()); - add(toolDiameter, "grow, wrap"); + add(toolDiameter, "grow, wrap" ); - add(new JLabel("Feed speed")); + add(new JLabel("Feed speed" )); feedSpeed = new TextFieldWithUnit(TextFieldUnit.MM_PER_MINUTE, 0, controller.getSettings().getFeedSpeed()); - add(feedSpeed, "grow, wrap"); + add(feedSpeed, "grow, wrap" ); - add(new JLabel("Plunge speed")); + add(new JLabel("Plunge speed" )); plungeSpeed = new TextFieldWithUnit(TextFieldUnit.MM_PER_MINUTE, 0, controller.getSettings().getPlungeSpeed()); - add(plungeSpeed, "grow, wrap"); + add(plungeSpeed, "grow, wrap" ); - add(new JLabel("Depth per pass")); + add(new JLabel("Depth per pass" )); depthPerPass = new TextFieldWithUnit(TextFieldUnit.MM, 2, controller.getSettings().getDepthPerPass()); - add(depthPerPass, "grow, wrap"); + add(depthPerPass, "grow, wrap" ); - add(new JLabel("Step over")); - stepOver = new TextFieldWithUnit(TextFieldUnit.PERCENT, 2, + add(new JLabel("Step over" )); + stepOver = new TextFieldWithUnit(TextFieldUnit.PERCENT, 2, controller.getSettings().getToolStepOver()); - add(stepOver, "grow, wrap"); + add(stepOver, "grow, wrap" ); - add(new JLabel("Safe height")); + add(new JLabel("Safe height" )); safeHeight = new TextFieldWithUnit(TextFieldUnit.MM, 2, controller.getSettings().getSafeHeight()); - add(safeHeight, "grow, wrap"); + add(safeHeight, "grow, wrap" ); - add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap"); - add(new JLabel("Spindle speed")); - spindleSpeed = new TextFieldWithUnit(TextFieldUnit.ROTATIONS_PER_MINUTE, 0, controller.getSettings().getSpindleSpeed()); - add(spindleSpeed, "grow, wrap"); + add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap" ); + add(new JLabel("Spindle speed" )); + spindleSpeed = new TextFieldWithUnit(TextFieldUnit.ROTATIONS_PER_MINUTE, 0, controller.getSettings().getSpindleSpeed()); + add(spindleSpeed, "grow, wrap" ); + + add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap" ); + add(new JLabel("Max spindle speed" )); + maxSpindleSpeed = new TextFieldWithUnit(TextFieldUnit.ROTATIONS_PER_MINUTE, 0, controller.getSettings().getMaxSpindleSpeed()); + add(maxSpindleSpeed, "grow, wrap" ); + + add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap" ); + + add(new JLabel("Laser diameter" )); + laserDiameter = new TextFieldWithUnit(TextFieldUnit.MM, 3, controller.getSettings().getLaserDiameter()); + add(laserDiameter, "grow, wrap" ); } public double getToolDiameter() { @@ -139,6 +156,22 @@ public double getSpindleSpeed() { } } + private double getLaserDiameter() { + try { + return Utils.formatter.parse(laserDiameter.getText()).doubleValue(); + } catch (ParseException e) { + return controller.getSettings().getLaserDiameter(); + } + } + + private double getMaxSpindleSpeed() { + try { + return Utils.formatter.parse(maxSpindleSpeed.getText()).doubleValue(); + } catch (ParseException e) { + return controller.getSettings().getMaxSpindleSpeed(); + } + } + public Settings getSettings() { Settings settings = new Settings(); settings.applySettings(controller.getSettings()); @@ -149,6 +182,8 @@ public Settings getSettings() { settings.setToolStepOver(getStepOver()); settings.setPlungeSpeed(getPlungeSpeed()); settings.setSpindleSpeed(getSpindleSpeed()); + settings.setLaserDiameter(getLaserDiameter()); + settings.setMaxSpindleSpeed((int) getMaxSpindleSpeed()); return settings; } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldActionDispatcher.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldActionDispatcher.java index 7d6381b567..a03f72347f 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldActionDispatcher.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldActionDispatcher.java @@ -10,7 +10,6 @@ import com.willwinder.ugs.nbp.designer.actions.UndoableAction; import com.willwinder.ugs.nbp.designer.entities.EntitySetting; import com.willwinder.ugs.nbp.designer.entities.controls.Location; -import com.willwinder.ugs.nbp.designer.entities.cuttable.CutType; import com.willwinder.ugs.nbp.designer.entities.cuttable.Cuttable; import com.willwinder.ugs.nbp.designer.entities.cuttable.Group; import com.willwinder.ugs.nbp.designer.entities.cuttable.Text; @@ -59,23 +58,19 @@ public void onFieldUpdate(EntitySetting entitySetting, Object object) { List actionList = new ArrayList<>(); if (entitySetting == EntitySetting.WIDTH || entitySetting == EntitySetting.HEIGHT) { actionList.addAll(handleSizeChange(entitySetting, (Double) object, selection)); - } else if (entitySetting == EntitySetting.CUT_TYPE) { - actionList.add(new ChangeCutSettingsAction(controller, selection.getChildren().stream().filter(Cuttable.class::isInstance).map(Cuttable.class::cast).toList(), model.getStartDepth(), model.getTargetDepth(), (CutType) object)); } else if (entitySetting == EntitySetting.POSITION_X) { actionList.add(createMovePositionXAction((Double) object, selection)); } else if (entitySetting == EntitySetting.POSITION_Y) { actionList.add(createMovePositionYAction((Double) object, selection)); } else if (entitySetting == EntitySetting.ROTATION) { actionList.add(new RotateAction(selection.getChildren(), selection.getCenter(), (Double) object - model.getRotation())); - } else if (entitySetting == EntitySetting.START_DEPTH) { - actionList.add(createChangeStartDepthAction((Double) object, selection)); - } else if (entitySetting == EntitySetting.TARGET_DEPTH) { - actionList.add(createChangeTargetDepthAction((Double) object, selection)); } else if (entitySetting == EntitySetting.FONT_FAMILY) { actionList.addAll(createFontChangeActions((String) object, selection)); } else if (entitySetting == EntitySetting.TEXT) { List changeTextActions = selection.getChildren().stream().filter(Text.class::isInstance).map(Text.class::cast).map(text -> new ChangeTextAction(text, (String) object)).toList(); actionList.addAll(changeTextActions); + } else { + actionList.add(createChangeSettingAction(selection, entitySetting, object)); } if (actionList.isEmpty()) { @@ -120,11 +115,9 @@ private MoveAction createMovePositionYAction(Double object, Group selection) { return new MoveAction(selection.getChildren(), delta); } - private ChangeCutSettingsAction createChangeStartDepthAction(Double object, Group selection) { - return new ChangeCutSettingsAction(controller, selection.getChildren().stream().filter(Cuttable.class::isInstance).map(Cuttable.class::cast).toList(), object, model.getTargetDepth(), model.getCutType()); - } - - private ChangeCutSettingsAction createChangeTargetDepthAction(Double object, Group selection) { - return new ChangeCutSettingsAction(controller, selection.getChildren().stream().filter(Cuttable.class::isInstance).map(Cuttable.class::cast).toList(), model.getStartDepth(), object, model.getCutType()); + private ChangeCutSettingsAction createChangeSettingAction(Group selection, EntitySetting entitySetting, Object object) { + return new ChangeCutSettingsAction(controller, selection.getChildren().stream().filter(Cuttable.class::isInstance).map(Cuttable.class::cast).toList(), + entitySetting, + object); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java index dbe5d4dab9..bb825bb83e 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java @@ -24,6 +24,7 @@ This file is part of Universal Gcode Sender (UGS). import javax.swing.JComboBox; import javax.swing.JComponent; +import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.JTextField; @@ -156,4 +157,9 @@ public void updateValue(EntitySetting entitySetting, Object object) { public void addListener(FieldEventListener listener) { listeners.add(listener); } + + public void registerListener(EntitySetting entitySetting, JSlider slider) { + componentsMap.put(entitySetting, slider); + slider.addChangeListener(l -> updateValue(entitySetting, slider.getValue())); + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModel.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModel.java index ca638f18b9..4926fa1fd6 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModel.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModel.java @@ -37,34 +37,6 @@ public void addListener(SelectionSettingsModelListener listener) { listeners.add(listener); } - public void put(EntitySetting key, Object object) { - if (key == EntitySetting.WIDTH) { - setWidth(parseDouble(object)); - } else if (key == EntitySetting.HEIGHT) { - setHeight(parseDouble(object)); - } else if (key == EntitySetting.ROTATION) { - setRotation(parseDouble(object)); - } else if (key == EntitySetting.POSITION_X) { - setPositionX(parseDouble(object)); - } else if (key == EntitySetting.POSITION_Y) { - setPositionY(parseDouble(object)); - } else if (key == EntitySetting.CUT_TYPE) { - setCutType(parseCutType(object)); - } else if (key == EntitySetting.TARGET_DEPTH) { - setTargetDepth(parseDouble(object)); - } else if (key == EntitySetting.START_DEPTH) { - setStartDepth(parseDouble(object)); - } else if (key == EntitySetting.TEXT) { - setText(object.toString()); - } else if (key == EntitySetting.FONT_FAMILY) { - setFontFamily(object.toString()); - } else if (key == EntitySetting.ANCHOR) { - setAnchor((Anchor) object); - } else if (key == EntitySetting.LOCK_RATIO) { - setLockRatio((Boolean) object); - } - } - public Object get(EntitySetting key) { return switch (key) { case WIDTH -> getWidth(); @@ -79,26 +51,13 @@ public Object get(EntitySetting key) { case FONT_FAMILY -> getFontFamily(); case ANCHOR -> getAnchor(); case LOCK_RATIO -> getLockRatio(); + case SPINDLE_SPEED -> getSpindleSpeed(); + case PASSES -> getPasses(); + case FEED_RATE -> getFeedRate(); default -> throw new SelectionSettingsModelException("Unknown setting " + key); }; } - private CutType parseCutType(Object object) { - if (object instanceof CutType cutType) { - return cutType; - } - - throw new SelectionSettingsModelException("Incorrect type"); - } - - private double parseDouble(Object object) { - if (object instanceof Double doubleValue) { - return doubleValue; - } - - throw new SelectionSettingsModelException("Incorrect type"); - } - public double getWidth() { return (Double) settings.getOrDefault(EntitySetting.WIDTH, 0d); } @@ -171,6 +130,7 @@ public void reset() { setCutType(CutType.NONE); setStartDepth(0); setTargetDepth(0); + setSpindleSpeed(0); setText(""); setFontFamily(Font.SANS_SERIF); } @@ -219,6 +179,41 @@ public void setStartDepth(double startDepth) { } } + public int getSpindleSpeed() { + return (Integer) settings.getOrDefault(EntitySetting.SPINDLE_SPEED, 0); + } + + public void setSpindleSpeed(Integer speed) { + if (!valuesEquals(getSpindleSpeed(), speed)) { + settings.put(EntitySetting.SPINDLE_SPEED, speed); + notifyListeners(EntitySetting.SPINDLE_SPEED); + } + } + + public int getPasses() { + return (Integer) settings.getOrDefault(EntitySetting.PASSES, 1); + } + + public void setPasses(Integer passes) { + if (!valuesEquals(getPasses(), passes)) { + passes = Math.max(1, passes); + settings.put(EntitySetting.PASSES, passes); + notifyListeners(EntitySetting.PASSES); + } + } + + public int getFeedRate() { + return (Integer) settings.getOrDefault(EntitySetting.FEED_RATE, 50); + } + + public void setFeedRate(Integer feedRate) { + if (!valuesEquals(getFeedRate(), feedRate)) { + feedRate = Math.max(50, feedRate); + settings.put(EntitySetting.FEED_RATE, feedRate); + notifyListeners(EntitySetting.FEED_RATE); + } + } + public String getFontFamily() { return (String) settings.getOrDefault(EntitySetting.FONT_FAMILY, Font.SANS_SERIF); } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java index bd816a4e51..09ca9f2dcb 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java @@ -41,6 +41,7 @@ This file is part of Universal Gcode Sender (UGS). import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSeparator; +import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.SwingConstants; @@ -58,6 +59,7 @@ public class SelectionSettingsPanel extends JPanel implements SelectionListener, private TextFieldWithUnit rotation; private TextFieldWithUnit posXTextField; private TextFieldWithUnit posYTextField; + private JSlider spindleSpeedSlider; private JLabel startDepthLabel; private JLabel targetDepthLabel; private CutTypeCombo cutTypeComboBox; @@ -73,10 +75,15 @@ public class SelectionSettingsPanel extends JPanel implements SelectionListener, private JLabel widthLabel; private JLabel heightLabel; private JToggleButton lockRatioButton; + private JLabel spindleSpeedLabel; + private JLabel laserPassesLabel; + private JSlider passesSlider; + private JLabel feedRateLabel; + private UnitSpinner feedRateSpinner; public SelectionSettingsPanel(Controller controller) { fieldEventDispatcher = new FieldEventDispatcher(); - setLayout(new MigLayout("fill, hidemode 3, insets 5", "[sg label] 5 [grow] 5 [60px]")); + setLayout(new MigLayout("fill, hidemode 3, insets 5", "[sg label] 5 [grow] 5 [60px]" )); addTextSettingFields(); addPositionFields(); addCutFields(); @@ -86,13 +93,19 @@ public SelectionSettingsPanel(Controller controller) { fieldEventDispatcher.addListener(fieldActionDispatcher); } + private static Boolean firstChildHasSetting(Group selectionGroup, EntitySetting entitySetting) { + return selectionGroup.getFirstChild() + .map(firstChild -> firstChild.getSettings().contains(entitySetting)) + .orElse(false); + } + private void addPositionFields() { createAndAddLabel(EntitySetting.POSITION_X); posXTextField = createAndAddField(EntitySetting.POSITION_X, TextFieldUnit.MM, false); anchorSelector = new AnchorSelectorPanel(); anchorSelector.setAnchor(model.getAnchor()); anchorSelector.addListener((model::setAnchor)); - add(anchorSelector, "span 1 2, grow, wrap"); + add(anchorSelector, "span 1 2, grow, wrap" ); createAndAddLabel(EntitySetting.POSITION_Y); posYTextField = createAndAddField(EntitySetting.POSITION_Y, TextFieldUnit.MM, true); @@ -102,26 +115,26 @@ private void addPositionFields() { lockRatioButton = new JToggleButton(ImageUtilities.loadImageIcon("img/link.svg", false)); lockRatioButton.setSelectedIcon(ImageUtilities.loadImageIcon("img/link-off.svg", false)); lockRatioButton.addActionListener(l -> model.setLockRatio(!lockRatioButton.isSelected())); - add(lockRatioButton, "span 1 2, growy, wrap"); + add(lockRatioButton, "span 1 2, growy, wrap" ); heightLabel = createAndAddLabel(EntitySetting.HEIGHT); heightTextField = createAndAddField(EntitySetting.HEIGHT, TextFieldUnit.MM, true); createAndAddLabel(EntitySetting.ROTATION); rotation = createAndAddField(EntitySetting.ROTATION, TextFieldUnit.DEGREE, true); - add(new JSeparator(), "grow, spanx, wrap"); + add(new JSeparator(), "grow, spanx, wrap" ); } private JLabel createAndAddLabel(EntitySetting entitySetting) { JLabel label = new JLabel(entitySetting.getLabel(), SwingConstants.RIGHT); - add(label, "grow"); + add(label, "grow" ); return label; } private TextFieldWithUnit createAndAddField(EntitySetting setting, TextFieldUnit units, boolean wrap) { TextFieldWithUnit field = new TextFieldWithUnit(units, 4, 0); fieldEventDispatcher.registerListener(setting, field); - add(field, wrap ? "grow, wrap" : "grow"); + add(field, wrap ? "grow, wrap" : "grow" ); return field; } @@ -130,25 +143,56 @@ private void addCutFields() { fieldEventDispatcher.registerListener(EntitySetting.CUT_TYPE, cutTypeComboBox); JLabel cutTypeLabel = new JLabel("Cut type", SwingConstants.RIGHT); - add(cutTypeLabel, "grow"); - add(cutTypeComboBox, "grow, wrap"); + add(cutTypeLabel, "grow" ); + add(cutTypeComboBox, "grow, wrap" ); + + feedRateLabel = new JLabel("Feed rate", SwingConstants.RIGHT); + add(feedRateLabel, "grow" ); + + feedRateSpinner = new UnitSpinner(50, TextFieldUnit.MM_PER_MINUTE, 50d, 10000d, 10d); + add(feedRateSpinner, "grow, wrap" ); + fieldEventDispatcher.registerListener(EntitySetting.FEED_RATE, feedRateSpinner); + + spindleSpeedLabel = new JLabel("Power", SwingConstants.RIGHT); + add(spindleSpeedLabel, "grow" ); + + spindleSpeedSlider = new JSlider(0, 100, 0); + spindleSpeedSlider.setPaintLabels(true); + spindleSpeedSlider.setPaintTicks(true); + spindleSpeedSlider.setMinorTickSpacing(5); + spindleSpeedSlider.setMajorTickSpacing(20); + + add(spindleSpeedSlider, "grow, wrap" ); + fieldEventDispatcher.registerListener(EntitySetting.SPINDLE_SPEED, spindleSpeedSlider); + + laserPassesLabel = new JLabel("Passes", SwingConstants.RIGHT); + add(laserPassesLabel, "grow" ); + + passesSlider = new JSlider(0, 10, 1); + passesSlider.setPaintLabels(true); + passesSlider.setPaintTicks(true); + passesSlider.setMinorTickSpacing(1); + passesSlider.setMajorTickSpacing(5); + + add(passesSlider, "grow, wrap" ); + fieldEventDispatcher.registerListener(EntitySetting.PASSES, passesSlider); startDepthLabel = new JLabel("Start depth", SwingConstants.RIGHT); - add(startDepthLabel, "grow"); + add(startDepthLabel, "grow" ); startDepthSpinner = new UnitSpinner(0, TextFieldUnit.MM, null, null, 0.1d); startDepthSpinner.setPreferredSize(startDepthSpinner.getPreferredSize()); fieldEventDispatcher.registerListener(EntitySetting.START_DEPTH, startDepthSpinner); - add(startDepthSpinner, "grow, wrap"); + add(startDepthSpinner, "grow, wrap" ); targetDepthLabel = new JLabel("Target depth", SwingConstants.RIGHT); - add(targetDepthLabel, "grow"); + add(targetDepthLabel, "grow" ); targetDepthSpinner = new UnitSpinner(0, TextFieldUnit.MM, 0d, null, 0.1d); targetDepthSpinner.setPreferredSize(targetDepthSpinner.getPreferredSize()); fieldEventDispatcher.registerListener(EntitySetting.TARGET_DEPTH, targetDepthSpinner); - add(targetDepthSpinner, "grow, wrap"); + add(targetDepthSpinner, "grow, wrap" ); setEnabled(false); } @@ -158,29 +202,28 @@ private void setController(Controller controller) { this.controller.getSelectionManager().addListener(this); } - private void addTextSettingFields() { textLabel = new JLabel("Text", SwingConstants.RIGHT); textLabel.setVisible(false); - add(textLabel, "grow"); + add(textLabel, "grow" ); textTextField = new JTextField(); textTextField.setVisible(false); fieldEventDispatcher.registerListener(EntitySetting.TEXT, textTextField); - add(textTextField, "grow, wrap"); + add(textTextField, "grow, wrap" ); fontLabel = new JLabel("Font", SwingConstants.RIGHT); fontLabel.setVisible(false); - add(fontLabel, "grow"); + add(fontLabel, "grow" ); fontDropDown = new FontCombo(); fieldEventDispatcher.registerListener(EntitySetting.FONT_FAMILY, fontDropDown); fontDropDown.setVisible(false); - add(fontDropDown, "grow, wrap"); + add(fontDropDown, "grow, wrap" ); fontSeparator = new JSeparator(SwingConstants.HORIZONTAL); fontSeparator.setVisible(false); - add(fontSeparator, "grow, spanx, wrap"); + add(fontSeparator, "grow, spanx, wrap" ); } @Override @@ -217,7 +260,6 @@ public void onEvent(EntityEvent entityEvent) { model.setText(textEntity.getText()); model.setFontFamily(textEntity.getFontFamily()); } - model.setPositionX(selectionGroup.getPosition(model.getAnchor()).getX()); model.setPositionY(selectionGroup.getPosition(model.getAnchor()).getY()); model.setWidth(selectionGroup.getSize().getWidth()); @@ -226,6 +268,9 @@ public void onEvent(EntityEvent entityEvent) { model.setStartDepth(selectionGroup.getStartDepth()); model.setTargetDepth(selectionGroup.getTargetDepth()); model.setCutType(selectionGroup.getCutType()); + model.setSpindleSpeed(selectionGroup.getSpindleSpeed()); + model.setPasses(selectionGroup.getPasses()); + model.setFeedRate(selectionGroup.getFeedRate()); controller.getDrawing().invalidate(); } @@ -277,15 +322,30 @@ public void onModelUpdate(EntitySetting entitySetting) { } } else if (entitySetting == EntitySetting.LOCK_RATIO) { lockRatioButton.setSelected(!model.getLockRatio()); + } else if (entitySetting == EntitySetting.SPINDLE_SPEED) { + spindleSpeedSlider.setValue(model.getSpindleSpeed()); + selectionGroup.setSpindleSpeed(model.getSpindleSpeed()); + } else if (entitySetting == EntitySetting.PASSES) { + passesSlider.setValue(model.getPasses()); + selectionGroup.setPasses(model.getPasses()); + } else if (entitySetting == EntitySetting.FEED_RATE) { + feedRateSpinner.setValue(model.getFeedRate()); + selectionGroup.setFeedRate(model.getFeedRate()); } - final boolean hasCutTypeSelection = selectionGroup.getCutType() != CutType.NONE; + handleComponentVisibility(selectionGroup); + } + + private void handleComponentVisibility(Group selectionGroup) { + CutType cutType = selectionGroup.getCutType(); + + final boolean hasCutTypeSelection = cutType != CutType.NONE; startDepthSpinner.setEnabled(hasCutTypeSelection); startDepthLabel.setEnabled(hasCutTypeSelection); targetDepthSpinner.setEnabled(hasCutTypeSelection); targetDepthLabel.setEnabled(hasCutTypeSelection); - boolean isTextCuttable = firstChildHasSetting(selectionGroup, EntitySetting.TEXT); + boolean isTextCuttable = cutType.getSettings().contains(EntitySetting.TEXT); textTextField.setVisible(isTextCuttable); textLabel.setVisible(isTextCuttable); fontLabel.setVisible(isTextCuttable); @@ -303,12 +363,26 @@ public void onModelUpdate(EntitySetting entitySetting) { boolean hasAnchor = firstChildHasSetting(selectionGroup, EntitySetting.ANCHOR); anchorSelector.setVisible(hasAnchor); - lockRatioButton.setVisible(hasWidth && hasHeight); - } + boolean hasStartDepth = cutType.getSettings().contains(EntitySetting.START_DEPTH); + startDepthSpinner.setVisible(hasStartDepth); + startDepthLabel.setVisible(hasStartDepth); - private static Boolean firstChildHasSetting(Group selectionGroup, EntitySetting entitySetting) { - return selectionGroup.getFirstChild() - .map(firstChild -> firstChild.getSettings().contains(entitySetting)) - .orElse(false); + boolean hasTargetDepth = cutType.getSettings().contains(EntitySetting.TARGET_DEPTH); + targetDepthSpinner.setVisible(hasTargetDepth); + targetDepthLabel.setVisible(hasTargetDepth); + + boolean hasLaserPower = cutType.getSettings().contains(EntitySetting.SPINDLE_SPEED); + spindleSpeedLabel.setVisible(hasLaserPower); + spindleSpeedSlider.setVisible(hasLaserPower); + + boolean hasLaserPasses = cutType.getSettings().contains(EntitySetting.PASSES); + laserPassesLabel.setVisible(hasLaserPasses); + passesSlider.setVisible(hasLaserPasses); + + boolean hasLaserFeedRate = cutType.getSettings().contains(EntitySetting.FEED_RATE); + feedRateLabel.setVisible(hasLaserFeedRate); + feedRateSpinner.setVisible(hasLaserFeedRate); + + lockRatioButton.setVisible(hasWidth && hasHeight); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/GcodeDesignWriter.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/GcodeDesignWriter.java index e2b77201fa..dc97acc5ee 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/GcodeDesignWriter.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/GcodeDesignWriter.java @@ -52,16 +52,7 @@ public void write(File file, Controller controller) { @Override public void write(OutputStream outputStream, Controller controller) { try { - SimpleGcodeRouter gcodeRouter = new SimpleGcodeRouter(); - gcodeRouter.setSafeHeight(controller.getSettings().getSafeHeight()); - gcodeRouter.setDepthPerPass(controller.getSettings().getDepthPerPass()); - gcodeRouter.setToolDiameter(controller.getSettings().getToolDiameter()); - gcodeRouter.setToolStepOver(controller.getSettings().getToolStepOver()); - gcodeRouter.setPlungeSpeed(controller.getSettings().getPlungeSpeed()); - gcodeRouter.setSafeHeight(controller.getSettings().getSafeHeight()); - gcodeRouter.setFeedSpeed(controller.getSettings().getFeedSpeed()); - gcodeRouter.setSpindleSpeed(controller.getSettings().getSpindleSpeed()); - + SimpleGcodeRouter gcodeRouter = new SimpleGcodeRouter(controller.getSettings()); List cuttables = controller.getDrawing().getEntities().stream() .filter(Cuttable.class::isInstance) .map(Cuttable.class::cast) @@ -69,7 +60,7 @@ public void write(OutputStream outputStream, Controller controller) { .filter(cuttable -> cuttable.getCutType() != CutType.NONE) .collect(Collectors.toList()); - String gcode = gcodeRouter.toGcode(cuttables); + String gcode = gcodeRouter.toGcode( cuttables); IOUtils.write(gcode, outputStream, StandardCharsets.UTF_8); } catch (IOException e) { throw new DesignWriterException("Could not write gcode to stream", e); 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 147cf4dc0a..4dbc62fe37 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 @@ -21,10 +21,13 @@ 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.gcode.toolpaths.DrillCenterToolPath; +import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.LaserFillToolPath; +import com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.LaserOutlineToolPath; 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.ugs.nbp.designer.model.Settings; import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.utils.Version; import org.apache.commons.lang3.StringUtils; @@ -42,103 +45,15 @@ 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"; + private final Settings settings; - /** - * The feed rate to move tool in material as mm/min - */ - private int feedSpeed = 1000; - - /** - * A plunge feed for moving in Z-axis into the material as mm/min - */ - private int plungeSpeed = 400; - - /** - * The diameter of the tool in millimeters - */ - private double toolDiameter = 3; - - /** - * The percentage of tool step over to make each pass, the smaller the value the finer the results. - * Should be larger than 0 and smaller than 1 where 0.1 would cut 10% of the tool diameter for each - * pass and 1 would cut 100% of the tool diameter. - */ - private double toolStepOver = 0.3; - - /** - * The depth to plunge into the material for each pass in millimeters - */ - private double depthPerPass = 1; - - /** - * The safe height over the material in millimeters which allow the machine to move freely without scratching it - */ - private double safeHeight = 1; - - /** - * The spindle speed in RPM - */ - private double spindleSpeed = 1000; - - public int getFeedSpeed() { - return feedSpeed; - } - - public void setFeedSpeed(int feedSpeed) { - this.feedSpeed = feedSpeed; - } - - public int getPlungeSpeed() { - return plungeSpeed; - } - - public void setPlungeSpeed(int plungeSpeed) { - this.plungeSpeed = plungeSpeed; - } - - public double getSafeHeight() { - return safeHeight; - } - - public void setSafeHeight(double safeHeight) { - this.safeHeight = safeHeight; - } - - public double getToolDiameter() { - return toolDiameter; - } - - public void setToolDiameter(double toolDiameter) { - this.toolDiameter = toolDiameter; - } - - public double getToolStepOver() { - return toolStepOver; - } - - public void setToolStepOver(double toolStepOver) { - this.toolStepOver = toolStepOver; - } - - public double getDepthPerPass() { - return depthPerPass; - } - - public void setDepthPerPass(double depthPerPass) { - this.depthPerPass = depthPerPass; - } - - private double getSpindleSpeed() { - return this.spindleSpeed; - } - - public void setSpindleSpeed(double spindleSpeed) { - this.spindleSpeed = spindleSpeed; + public SimpleGcodeRouter(Settings settings) { + this.settings = settings; } 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"); + 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); @@ -152,18 +67,26 @@ public String toGcode(List entities) { Code.G21.name() + " ; millimeters\n" + Code.G90.name() + " ; absolute coordinate\n" + Code.G17.name() + " ; XY plane\n" + - Code.G94.name() + " ; units per minute feed rate mode\n" + - Code.M3.name() + " S" + Math.round(getSpindleSpeed()) + " ; Turning on spindle\n\n" + Code.G94.name() + " ; units per minute feed rate mode\n" ); + if (settings.getSpindleSpeed() > 0) { + result.append(Code.M3.name()) + .append(" S" ) + .append(Math.round(settings.getSpindleSpeed())) + .append(" ; Turning on spindle\n" ); + } + + result.append("\n" ); + try { result.append(toGcode(getGcodePathFromCuttables(entities))); } catch (IOException e) { throw new RuntimeException("An error occured while trying to generate gcode", e); } - result.append("\n" + "; Turning off spindle\n") - .append(Code.M5.name()).append("\n"); + result.append("\n" + "; Turning off spindle\n" ) + .append(Code.M5.name()).append("\n" ); return result.toString(); } @@ -172,56 +95,47 @@ private GcodePath getGcodePathFromCuttables(List cuttables) { int index = 0; for (Cuttable cuttable : cuttables) { index++; - gcodePath.addSegment(new Segment(" " + cuttable.getName() + " - " + cuttable.getCutType().getName() + " (" + index + "/" + cuttables.size() + ")")); + gcodePath.addSegment(new Segment(" " + cuttable.getName() + " - " + cuttable.getCutType().getName() + " (" + index + "/" + cuttables.size() + ")" )); switch (cuttable.getCutType()) { case POCKET: - PocketToolPath simplePocket = new PocketToolPath(cuttable); + PocketToolPath simplePocket = new PocketToolPath(settings, cuttable); simplePocket.setStartDepth(cuttable.getStartDepth()); simplePocket.setTargetDepth(cuttable.getTargetDepth()); - simplePocket.setToolDiameter(toolDiameter); - simplePocket.setDepthPerPass(depthPerPass); - simplePocket.setSafeHeight(safeHeight); - simplePocket.setStepOver(toolStepOver); - - gcodePath.appendGcodePath(simplePocket.toGcodePath()); + simplePocket.appendGcodePath(gcodePath, settings); break; case OUTSIDE_PATH: - OutlineToolPath simpleOutsidePath = new OutlineToolPath(cuttable); - simpleOutsidePath.setOffset(toolDiameter / 2d); + OutlineToolPath simpleOutsidePath = new OutlineToolPath(settings, cuttable); + simpleOutsidePath.setOffset(settings.getToolDiameter() / 2d); simpleOutsidePath.setStartDepth(cuttable.getStartDepth()); simpleOutsidePath.setTargetDepth(cuttable.getTargetDepth()); - simpleOutsidePath.setToolDiameter(toolDiameter); - simpleOutsidePath.setDepthPerPass(depthPerPass); - simpleOutsidePath.setSafeHeight(safeHeight); - gcodePath.appendGcodePath(simpleOutsidePath.toGcodePath()); + simpleOutsidePath.appendGcodePath(gcodePath, settings); break; case INSIDE_PATH: - OutlineToolPath simpleInsidePath = new OutlineToolPath(cuttable); - simpleInsidePath.setOffset(-toolDiameter / 2d); + OutlineToolPath simpleInsidePath = new OutlineToolPath(settings, cuttable); + simpleInsidePath.setOffset(-settings.getToolDiameter() / 2d); simpleInsidePath.setStartDepth(cuttable.getStartDepth()); simpleInsidePath.setTargetDepth(cuttable.getTargetDepth()); - simpleInsidePath.setToolDiameter(toolDiameter); - simpleInsidePath.setDepthPerPass(depthPerPass); - simpleInsidePath.setSafeHeight(safeHeight); - gcodePath.appendGcodePath(simpleInsidePath.toGcodePath()); + simpleInsidePath.appendGcodePath(gcodePath, settings); break; case ON_PATH: - OutlineToolPath simpleOnPath = new OutlineToolPath(cuttable); + OutlineToolPath simpleOnPath = new OutlineToolPath(settings, cuttable); simpleOnPath.setStartDepth(cuttable.getStartDepth()); simpleOnPath.setTargetDepth(cuttable.getTargetDepth()); - simpleOnPath.setToolDiameter(toolDiameter); - simpleOnPath.setDepthPerPass(depthPerPass); - simpleOnPath.setSafeHeight(safeHeight); - gcodePath.appendGcodePath(simpleOnPath.toGcodePath()); + simpleOnPath.appendGcodePath(gcodePath, settings); break; case CENTER_DRILL: - DrillCenterToolPath drillToolPath = new DrillCenterToolPath(cuttable); + DrillCenterToolPath drillToolPath = new DrillCenterToolPath(settings, cuttable); drillToolPath.setStartDepth(cuttable.getStartDepth()); drillToolPath.setTargetDepth(cuttable.getTargetDepth()); - drillToolPath.setToolDiameter(toolDiameter); - drillToolPath.setDepthPerPass(depthPerPass); - drillToolPath.setSafeHeight(safeHeight); - gcodePath.appendGcodePath(drillToolPath.toGcodePath()); + drillToolPath.appendGcodePath(gcodePath, settings); + break; + case LASER_ON_PATH: + LaserOutlineToolPath laserOutlineToolPath = new LaserOutlineToolPath(settings, cuttable); + laserOutlineToolPath.appendGcodePath(gcodePath, settings); + break; + case LASER_FILL: + LaserFillToolPath laserFillToolPath = new LaserFillToolPath(settings, cuttable); + laserFillToolPath.appendGcodePath(gcodePath, settings); break; default: } @@ -230,13 +144,13 @@ private GcodePath getGcodePathFromCuttables(List cuttables) { } private String generateToolHeader() { - return "; Tool: " + getToolDiameter() + "mm\n" + - "; Depth per pass: " + getDepthPerPass() + "mm\n" + - "; Feed speed: " + getFeedSpeed() + "mm/min\n" + - "; Plunge speed: " + getPlungeSpeed() + "mm/min\n" + - "; Safe height: " + getSafeHeight() + "mm\n" + - "; Tool step over: " + getToolStepOver() + "mm\n" + - "; Spindle speed: " + Math.round(getSpindleSpeed()) + "rpm\n"; + return "; Tool: " + settings.getToolDiameter() + "mm\n" + + "; Depth per pass: " + settings.getDepthPerPass() + "mm\n" + + "; Feed speed: " + settings.getFeedSpeed() + "mm/min\n" + + "; Plunge speed: " + settings.getPlungeSpeed() + "mm/min\n" + + "; Safe height: " + settings.getSafeHeight() + "mm\n" + + "; Tool step over: " + settings.getToolStepOver() + "mm\n" + + "; Spindle speed: " + Math.round(settings.getSpindleSpeed()) + "rpm\n"; } protected void toGcode(Writer writer, GcodePath path) throws IOException { @@ -251,7 +165,11 @@ protected void runPath(Writer writer, List segments) throws IOException for (Segment s : segments) { // Write any label if (StringUtils.isNotEmpty(s.getLabel())) { - writer.write(";" + s.getLabel() + "\n"); + writer.write(";" + s.getLabel() + "\n" ); + } + + if (s.spindleSpeed != null) { + writer.write("M3 S" + s.spindleSpeed + "\n" ); } switch (s.type) { @@ -265,19 +183,19 @@ protected void runPath(Writer writer, List segments) throws IOException // The rapid over target point is skipped when we do multiple passes // and the end point is the same as the starting point. writer.write(SegmentType.MOVE.gcode); - writer.write(" "); + writer.write(" " ); writer.write(s.point.getFormattedGCode()); - writer.write("\n"); + writer.write("\n" ); hasFeedRateSet = false; break; // Drill down using the plunge speed case POINT: writer.write(SegmentType.POINT.gcode); - writer.write(" "); - writer.write("F" + plungeSpeed + " "); + writer.write(" " ); + writer.write("F" + settings.getPlungeSpeed() + " " ); writer.write(s.point.getFormattedGCode()); - writer.write("\n"); + writer.write("\n" ); break; // Motion at feed rate @@ -288,14 +206,14 @@ protected void runPath(Writer writer, List segments) throws IOException writer.write(' '); if (!hasFeedRateSet) { - writer.write("F"); - writer.write(String.valueOf(feedSpeed)); + writer.write("F" ); + writer.write(String.valueOf(settings.getFeedSpeed())); writer.write(' '); hasFeedRateSet = true; } writer.write(s.point.getFormattedGCode()); - writer.write("\n"); + writer.write("\n" ); break; default: throw new RuntimeException("BUG! Unhandled segment type " + s.type); diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/GcodePath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/GcodePath.java index 113c448dd3..482f0eb56e 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/GcodePath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/GcodePath.java @@ -16,6 +16,7 @@ */ package com.willwinder.ugs.nbp.designer.io.gcode.path; +import com.willwinder.ugs.nbp.designer.model.Settings; import com.willwinder.universalgcodesender.model.PartialPosition; import java.util.ArrayList; @@ -30,7 +31,7 @@ */ public class GcodePath implements PathGenerator { - private List segments; + private final List segments; public GcodePath() { segments = new ArrayList<>(); @@ -80,10 +81,8 @@ public boolean isEmpty() { return segments.isEmpty(); } - /** - * @return this - */ - public GcodePath toGcodePath() { - return this; + @Override + public void appendGcodePath(GcodePath gcodePath, Settings settings) { + segments.forEach(gcodePath::addSegment); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/PathGenerator.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/PathGenerator.java index f646fc1ec5..d5033420ca 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/PathGenerator.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/PathGenerator.java @@ -16,6 +16,8 @@ */ package com.willwinder.ugs.nbp.designer.io.gcode.path; +import com.willwinder.ugs.nbp.designer.model.Settings; + /** * Interface for classes that generate paths * @@ -23,8 +25,10 @@ */ public interface PathGenerator { /** - * Generate the path - * @return path + * Appends any gcode to the current gcode path + * + * @param gcodePath + * @param settings */ - GcodePath toGcodePath(); + void appendGcodePath(GcodePath gcodePath, Settings settings); } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/Segment.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/Segment.java index 582d58dd3f..dacacc20c5 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/Segment.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/path/Segment.java @@ -22,6 +22,7 @@ * Path segment * * @author Calle Laakkonen + * @author Joacim Breiler */ public final class Segment { /** @@ -39,6 +40,18 @@ public final class Segment { */ public final String label; + /** + * The current spindle speed + */ + public final Integer spindleSpeed; + + public Segment(SegmentType type, PartialPosition point, String label, Integer spindleSpeed) { + this.type = type; + this.point = point; + this.label = label; + this.spindleSpeed = spindleSpeed; + } + public Segment(SegmentType type, PartialPosition point) { this(type, point, null); } @@ -46,19 +59,8 @@ public Segment(SegmentType type, PartialPosition point) { public Segment(String label) { this(SegmentType.SEAM, null, label); } - public Segment(SegmentType type, PartialPosition point, String label) { - this.type = type; - this.point = point; - this.label = label; - } - - @Override - public String toString() { - if (label == null) - return type.name() + " " + point; - else - return type.name() + " " + point + '(' + label + ')'; + this(type, point, label, null); } /** diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/AbstractToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/AbstractToolPath.java index c9413a5778..7ae6f22b5a 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/AbstractToolPath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/AbstractToolPath.java @@ -21,6 +21,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.designer.io.gcode.path.GcodePath; import com.willwinder.ugs.nbp.designer.io.gcode.path.PathGenerator; import com.willwinder.ugs.nbp.designer.io.gcode.path.SegmentType; +import com.willwinder.ugs.nbp.designer.model.Settings; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.UnitUtils; @@ -30,33 +31,20 @@ This file is part of Universal Gcode Sender (UGS). public abstract class AbstractToolPath implements PathGenerator { + protected final Settings settings; + private final GeometryFactory geometryFactory = new GeometryFactory(); /** * The depth to start from in millimeters */ private double startDepth = 0; - /** * The depth that we are targeting for in millimeters */ private double targetDepth = 0; - /** - * The tool diameter in millimeters - */ - private double toolDiameter = 3; - - /** - * The depth to plunge for each pass in millimeters - */ - private double depthPerPass = 1; - - /** - * A safe height above the material in millimeters - */ - private double safeHeight = 1; - - private final GeometryFactory geometryFactory = new GeometryFactory(); - + protected AbstractToolPath(Settings settings) { + this.settings = settings; + } public double getStartDepth() { return Math.abs(startDepth); @@ -66,40 +54,16 @@ public void setStartDepth(double startDepth) { this.startDepth = Math.abs(startDepth); } - public void setTargetDepth(double targetDepth) { - this.targetDepth = Math.abs(targetDepth); - } - - public void setToolDiameter(double toolDiameter) { - this.toolDiameter = Math.abs(toolDiameter); - } - - public void setDepthPerPass(double depthPerPass) { - this.depthPerPass = Math.abs(depthPerPass); - } - - public double getDepthPerPass() { - return depthPerPass; - } - - public void setSafeHeight(double safeHeight) { - this.safeHeight = safeHeight; - } - - public double getSafeHeight() { - return safeHeight; - } - public double getTargetDepth() { return targetDepth; } - public double getToolDiameter() { - return toolDiameter; + public void setTargetDepth(double targetDepth) { + this.targetDepth = Math.abs(targetDepth); } protected void addSafeHeightSegment(GcodePath gcodePath) { - PartialPosition safeHeightCoordinate = PartialPosition.from(Axis.Z, getSafeHeight(), UnitUtils.Units.MM); + PartialPosition safeHeightCoordinate = PartialPosition.from(Axis.Z, settings.getSafeHeight(), UnitUtils.Units.MM); gcodePath.addSegment(SegmentType.MOVE, safeHeightCoordinate); } @@ -128,4 +92,10 @@ protected GcodePath toGcodePath(List> coordinateList) { } return gcodePath; } + + public GcodePath toGcodePath() { + GcodePath gcodePath = new GcodePath(); + appendGcodePath(gcodePath, settings); + return gcodePath; + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPath.java index 57e179a26f..ec2fe2695e 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPath.java @@ -21,6 +21,7 @@ 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.ugs.nbp.designer.io.gcode.path.SegmentType; +import com.willwinder.ugs.nbp.designer.model.Settings; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.UnitUtils; @@ -34,20 +35,34 @@ This file is part of Universal Gcode Sender (UGS). public class DrillCenterToolPath extends AbstractToolPath { private final Cuttable source; - public DrillCenterToolPath(Cuttable source) { + public DrillCenterToolPath(Settings settings, Cuttable source) { + super(settings); this.source = source; } + private void addDepthSegment(GcodePath gcodePath, double depth) { + gcodePath.addSegment(SegmentType.POINT, PartialPosition.builder(UnitUtils.Units.MM) + .setZ(depth) + .build()); + } + + private PartialPosition getCenterPosition() { + Point2D center = source.getCenter(); + return PartialPosition.builder(UnitUtils.Units.MM) + .setX(center.getX()) + .setY(center.getY()) + .build(); + } + @Override - public GcodePath toGcodePath() { + public void appendGcodePath(GcodePath gcodePath, Settings settings) { PartialPosition centerPosition = getCenterPosition(); - GcodePath gcodePath = new GcodePath(); addSafeHeightSegmentTo(gcodePath, centerPosition); addDepthSegment(gcodePath, getStartDepth()); double currentDepth = getStartDepth(); while (currentDepth < getTargetDepth()) { - currentDepth += getDepthPerPass(); + currentDepth += settings.getDepthPerPass(); if (currentDepth > getTargetDepth()) { currentDepth = getTargetDepth(); } @@ -61,20 +76,5 @@ public GcodePath toGcodePath() { } addSafeHeightSegment(gcodePath); - return gcodePath; - } - - private void addDepthSegment(GcodePath gcodePath, double depth) { - gcodePath.addSegment(SegmentType.POINT, PartialPosition.builder(UnitUtils.Units.MM) - .setZ(depth) - .build()); - } - - private PartialPosition getCenterPosition() { - Point2D center = source.getCenter(); - return PartialPosition.builder(UnitUtils.Units.MM) - .setX(center.getX()) - .setY(center.getY()) - .build(); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java new file mode 100644 index 0000000000..efae416f15 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java @@ -0,0 +1,86 @@ +package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; + +import com.google.common.collect.Lists; +import com.willwinder.ugs.nbp.designer.entities.cuttable.Cuttable; +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.model.Settings; +import com.willwinder.universalgcodesender.model.PartialPosition; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateXY; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; + +import java.awt.geom.Area; +import java.util.ArrayList; +import java.util.List; + +public class LaserFillToolPath extends AbstractToolPath { + private final Cuttable source; + + public LaserFillToolPath(Settings settings, Cuttable source) { + super(settings); + this.source = source; + } + + private static void addGeometriesToCoordinateList(ArrayList> coordinateList, List geometryCoordinates) { + coordinateList.add(geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()) + .toList()); + } + + public void appendGcodePath(GcodePath gcodePath, Settings settings) { + gcodePath.addSegment(new Segment(SegmentType.SEAM, null, null, (int) Math.round(settings.getMaxSpindleSpeed() * (source.getSpindleSpeed() / 100d)))); + + List geometries; + if (ToolPathUtils.isClosedGeometry(source.getShape())) { + Geometry geometry = ToolPathUtils.convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); + geometries = List.of(geometry); + } else { + geometries = ToolPathUtils.convertShapeToGeometry(source.getShape(), getGeometryFactory()); + } + + + geometries.forEach(g -> { + Envelope envelope = g.getEnvelopeInternal(); + + + int currentPass = 0; + while (currentPass < source.getPasses()) { + currentPass++; + + boolean reverse = false; + double currentY = envelope.getMinY(); + while (currentY <= envelope.getMaxY()) { + LineString lineString = getGeometryFactory().createLineString(new Coordinate[]{ + new CoordinateXY(envelope.getMinX(), currentY), + new CoordinateXY(envelope.getMaxX(), currentY), + }); + + Geometry intersection = g.intersection(lineString); + List geometryCoordinates = ToolPathUtils.geometryToCoordinates(intersection); + List partialPosition = geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()).toList(); + + if (partialPosition.size() > 1) { + if (reverse) { + partialPosition = Lists.reverse(partialPosition); + } + + for (int i = 0; i < partialPosition.size(); i += 2) { + gcodePath.addSegment(SegmentType.MOVE, partialPosition.get(i)); + gcodePath.addSegment(SegmentType.LINE, partialPosition.get(i + 1)); + } + } + + + currentY += settings.getLaserDiameter(); + reverse = !reverse; + } + } + }); + + } +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java new file mode 100644 index 0000000000..4d1580e954 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java @@ -0,0 +1,55 @@ +package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; + +import com.willwinder.ugs.nbp.designer.entities.cuttable.Cuttable; +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.model.Settings; +import com.willwinder.universalgcodesender.model.PartialPosition; +import org.locationtech.jts.geom.Geometry; + +import java.awt.geom.Area; +import java.util.ArrayList; +import java.util.List; + +public class LaserOutlineToolPath extends AbstractToolPath { + private final Cuttable source; + + public LaserOutlineToolPath(Settings settings, Cuttable source) { + super(settings); + this.source = source; + } + + private static void addGeometriesToCoordinateList(ArrayList> coordinateList, List geometryCoordinates) { + coordinateList.add(geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()) + .toList()); + } + public void appendGcodePath(GcodePath gcodePath, Settings settings) { + gcodePath.addSegment(new Segment(SegmentType.SEAM, null, null, (int) Math.round(settings.getMaxSpindleSpeed() * (source.getSpindleSpeed() / 100d)))); + + List geometries; + if (ToolPathUtils.isClosedGeometry(source.getShape())) { + Geometry geometry = ToolPathUtils.convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); + geometries = ToolPathUtils.toGeometryList(geometry); + } else { + geometries = ToolPathUtils.convertShapeToGeometry(source.getShape(), getGeometryFactory()); + } + + + geometries.forEach(g -> { + List geometryCoordinates = ToolPathUtils.geometryToCoordinates(g); + + int currentPass = 0; + while (currentPass < source.getPasses()) { + currentPass++; + List partialPosition = geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()).toList(); + + gcodePath.addSegment(SegmentType.MOVE, partialPosition.get(0), " Pass " + currentPass + " of " + source.getPasses()); + partialPosition.forEach(c -> gcodePath.addSegment(SegmentType.LINE, c)); + } + }); + + } +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPath.java index 669a044ea3..9763a96cd1 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPath.java @@ -20,6 +20,7 @@ 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.ugs.nbp.designer.model.Settings; import com.willwinder.universalgcodesender.model.PartialPosition; import org.locationtech.jts.geom.Geometry; @@ -35,12 +36,23 @@ public class OutlineToolPath extends AbstractToolPath { private double offset; - public OutlineToolPath(Cuttable source) { + public OutlineToolPath(Settings settings, Cuttable source) { + super(settings); this.source = source; } + private static void addGeometriesToCoordinateList(ArrayList> coordinateList, List geometryCoordinates, double depth) { + coordinateList.add(geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).setZ(-depth).build()) + .toList()); + } + + public void setOffset(double offset) { + this.offset = offset; + } + @Override - public GcodePath toGcodePath() { + public void appendGcodePath(GcodePath gcodePath, Settings settings) { List geometries; if (ToolPathUtils.isClosedGeometry(source.getShape())) { Geometry geometry = ToolPathUtils.convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); @@ -59,7 +71,7 @@ public GcodePath toGcodePath() { double currentDepth = getStartDepth(); while (currentDepth < getTargetDepth()) { - currentDepth += getDepthPerPass(); + currentDepth += settings.getDepthPerPass(); if (currentDepth > getTargetDepth()) { currentDepth = getTargetDepth(); } @@ -68,16 +80,6 @@ public GcodePath toGcodePath() { } }); - return toGcodePath(coordinateList); - } - - private static void addGeometriesToCoordinateList(ArrayList> coordinateList, List geometryCoordinates, double depth) { - coordinateList.add(geometryCoordinates.stream() - .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).setZ(-depth).build()) - .toList()); - } - - public void setOffset(double offset) { - this.offset = offset; + gcodePath.appendGcodePath(toGcodePath(coordinateList)); } } 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 6696779c44..fb096c21b4 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 @@ -23,6 +23,7 @@ This file is part of Universal Gcode Sender (UGS). import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.addGeometriesToCoordinatesList; import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.bufferAndCollectGeometries; import static com.willwinder.ugs.nbp.designer.io.gcode.toolpaths.ToolPathUtils.convertAreaToGeometry; +import com.willwinder.ugs.nbp.designer.model.Settings; import com.willwinder.universalgcodesender.model.PartialPosition; import org.locationtech.jts.geom.Geometry; @@ -36,28 +37,24 @@ This file is part of Universal Gcode Sender (UGS). public class PocketToolPath extends AbstractToolPath { private final Cuttable source; - /** - * How much should the tool cut for each pass. Should be larger than 0 and smaller than 1. - * 0.1 would cut 10% of the tool diameter for each pass and 1 would cut 100% of the tool diameter. - */ - private double stepOver = 0.3; - - public PocketToolPath(Cuttable source) { + public PocketToolPath(Settings settings, Cuttable source) { + super(settings); this.source = source; } @Override - public GcodePath toGcodePath() { + public void appendGcodePath(GcodePath gcodePath, Settings settings) { + double stepOver = Math.min(Math.max(0.01, Math.abs(settings.getToolStepOver())), 1.0); Geometry geometryCollection = convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); - Geometry shell = geometryCollection.buffer(-getToolDiameter() / 2d); - List geometries = bufferAndCollectGeometries(geometryCollection, getToolDiameter(), stepOver); + Geometry shell = geometryCollection.buffer(-settings.getToolDiameter() / 2d); + List geometries = bufferAndCollectGeometries(geometryCollection, settings.getToolDiameter(), stepOver); List> coordinateList = new ArrayList<>(); addGeometriesToCoordinatesList(shell, geometries, coordinateList, getStartDepth()); double currentDepth = getStartDepth(); while (currentDepth < getTargetDepth()) { - currentDepth += getDepthPerPass(); + currentDepth += settings.getDepthPerPass(); if (currentDepth > getTargetDepth()) { currentDepth = getTargetDepth(); } @@ -65,11 +62,6 @@ public GcodePath toGcodePath() { addGeometriesToCoordinatesList(shell, geometries, coordinateList, currentDepth); } - return toGcodePath(coordinateList); - } - - - public void setStepOver(double stepOver) { - this.stepOver = Math.min(Math.max(0.01, Math.abs(stepOver)), 1.0); + gcodePath.appendGcodePath(toGcodePath(coordinateList)); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/UgsDesignWriter.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/UgsDesignWriter.java index 2e72656c60..03cf8a1d99 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/UgsDesignWriter.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/UgsDesignWriter.java @@ -94,6 +94,9 @@ private EntityV1 convertToEntity(Entity entity) { cuttableEntity.setCutDepth(cuttable.getTargetDepth()); cuttableEntity.setCutType(CutTypeV1.fromCutType(cuttable.getCutType())); cuttableEntity.setHidden(cuttable.isHidden()); + cuttableEntity.setSpindleSpeed(cuttable.getSpindleSpeed()); + cuttableEntity.setPasses(cuttable.getPasses()); + cuttableEntity.setFeedRate(cuttable.getFeedRate()); } return result; } @@ -166,6 +169,7 @@ private SettingsV1 convertSettings(Controller controller) { settings.setToolDiameter(controller.getSettings().getToolDiameter()); settings.setToolStepOver(controller.getSettings().getToolStepOver()); settings.setSpindleSpeed(controller.getSettings().getSpindleSpeed()); + settings.setLaserDiameter(controller.getSettings().getLaserDiameter()); return settings; } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CutTypeV1.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CutTypeV1.java index dc8f70ca9b..1144b11dea 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CutTypeV1.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CutTypeV1.java @@ -29,7 +29,9 @@ public enum CutTypeV1 { ON_PATH, INSIDE_PATH, OUTSIDE_PATH, - CENTER_DRILL; + CENTER_DRILL, + LASER_ON_PATH, + LASER_FILL; public static CutTypeV1 fromCutType(CutType cutType) { if (cutType == CutType.POCKET) { @@ -42,7 +44,11 @@ public static CutTypeV1 fromCutType(CutType cutType) { return OUTSIDE_PATH; } else if (cutType == CutType.CENTER_DRILL) { return CENTER_DRILL; - }else { + } else if (cutType == CutType.LASER_ON_PATH) { + return LASER_ON_PATH; + } else if (cutType == CutType.LASER_FILL) { + return LASER_FILL; + } else { return NONE; } } @@ -58,6 +64,10 @@ public static CutType toCutType(CutTypeV1 cutType) { return CutType.OUTSIDE_PATH; } else if (cutType == CENTER_DRILL) { return CutType.CENTER_DRILL; + } else if (cutType == LASER_ON_PATH) { + return CutType.LASER_ON_PATH; + } else if (cutType == LASER_FILL) { + return CutType.LASER_FILL; } else { return CutType.NONE; } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CuttableEntityV1.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CuttableEntityV1.java index 2095547c43..59be9744ac 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CuttableEntityV1.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/CuttableEntityV1.java @@ -34,6 +34,15 @@ public class CuttableEntityV1 extends EntityV1 { @Expose private double cutDepth; + @Expose + private int spindleSpeed; + + @Expose + private int passes; + + @Expose + private int feedRate; + @Expose private CutTypeV1 cutType; @@ -79,6 +88,18 @@ public void setHidden(boolean hidden) { isHidden = hidden; } + public void setSpindleSpeed(int spindleSpeed) { + this.spindleSpeed = spindleSpeed; + } + + public void setPasses(int passes) { + this.passes = passes; + } + + public void setFeedRate(int feedRate) { + this.feedRate = feedRate; + } + @Override protected void applyCommonAttributes(Entity entity) { super.applyCommonAttributes(entity); @@ -86,12 +107,14 @@ protected void applyCommonAttributes(Entity entity) { // We need to make a copy of the transformation to set the affine transformation state and type which is not serialized entity.setTransform(new AffineTransform(transform.getScaleX(), transform.getShearY(), transform.getShearX(), transform.getScaleY(), transform.getTranslateX(), transform.getTranslateY())); - if (entity instanceof Cuttable) { - Cuttable cuttable = ((Cuttable) entity); + if (entity instanceof Cuttable cuttable) { cuttable.setStartDepth(startDepth); cuttable.setTargetDepth(cutDepth); cuttable.setCutType(CutTypeV1.toCutType(cutType)); cuttable.setHidden(isHidden); + cuttable.setSpindleSpeed(spindleSpeed); + cuttable.setPasses(passes); + cuttable.setFeedRate(feedRate); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/SettingsV1.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/SettingsV1.java index 42f1ccd9d1..43480b2ffc 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/SettingsV1.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/ugsd/v1/SettingsV1.java @@ -15,6 +15,7 @@ public class SettingsV1 implements Serializable { private double toolStepOver = 0.3; private double depthPerPass = 1; private double spindleSpeed = 0; + private double laserDiameter = 0.2; public int getPlungeSpeed() { return plungeSpeed; @@ -87,7 +88,9 @@ public double getSpindleSpeed() { public void setSpindleSpeed(double spindleSpeed) { this.spindleSpeed = spindleSpeed; } - + public void setLaserDiameter(double laserDiameter) { + this.laserDiameter = laserDiameter; + } public Settings toInternal() { Settings settings = new Settings(); settings.setSafeHeight(safeHeight); @@ -99,6 +102,7 @@ public Settings toInternal() { settings.setDepthPerPass(depthPerPass); settings.setFeedSpeed(feedSpeed); settings.setSpindleSpeed(spindleSpeed); + settings.setLaserDiameter(laserDiameter); return settings; } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/model/Settings.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/model/Settings.java index aa0ceaceac..b3ab7c2250 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/model/Settings.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/model/Settings.java @@ -36,6 +36,8 @@ public class Settings { private double toolStepOver = 0.3; private double depthPerPass = 1; private double spindleSpeed; + private double laserDiameter = 0.2; + private int maxSpindleSpeed = 255; public Settings() { } @@ -180,16 +182,38 @@ public void setSpindleSpeed(double spindleSpeed) { } public void applySettings(Settings settings) { - if (settings != null) { - setDepthPerPass(settings.getDepthPerPass()); - setFeedSpeed(settings.getFeedSpeed()); - setPlungeSpeed(settings.getPlungeSpeed()); - setStockThickness(settings.getStockThickness()); - setToolDiameter(settings.getToolDiameter()); - setToolStepOver(settings.getToolStepOver()); - setPreferredUnits(settings.getPreferredUnits()); - setSafeHeight(settings.getSafeHeight()); - setSpindleSpeed(settings.getSpindleSpeed()); + if (settings == null) { + return; } + + setDepthPerPass(settings.getDepthPerPass()); + setFeedSpeed(settings.getFeedSpeed()); + setPlungeSpeed(settings.getPlungeSpeed()); + setStockThickness(settings.getStockThickness()); + setToolDiameter(settings.getToolDiameter()); + setToolStepOver(settings.getToolStepOver()); + setPreferredUnits(settings.getPreferredUnits()); + setSafeHeight(settings.getSafeHeight()); + setSpindleSpeed(settings.getSpindleSpeed()); + setLaserDiameter(settings.getLaserDiameter()); + setMaxSpindleSpeed(settings.getMaxSpindleSpeed()); + } + + public double getLaserDiameter() { + return laserDiameter; + } + + public void setLaserDiameter(double laserDiameter) { + this.laserDiameter = laserDiameter; + notifyListeners(); + } + + public int getMaxSpindleSpeed() { + return maxSpindleSpeed; + } + + public void setMaxSpindleSpeed(int maxSpindleSpeed) { + this.maxSpindleSpeed = Math.abs(maxSpindleSpeed); + notifyListeners(); } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill.svg b/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill.svg new file mode 100644 index 0000000000..82fe1ce025 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill24.svg b/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill24.svg new file mode 100644 index 0000000000..9785277afe --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill24.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill32.svg b/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill32.svg new file mode 100644 index 0000000000..264212f794 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/resources/img/cutfill32.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/cuttable/GroupTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/cuttable/GroupTest.java new file mode 100644 index 0000000000..8b163f9681 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/entities/cuttable/GroupTest.java @@ -0,0 +1,32 @@ +package com.willwinder.ugs.nbp.designer.entities.cuttable; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class GroupTest { + @Test + public void setLaserPowerShoouldSetTheLaserPowerOnAllChildren() { + Rectangle rectangle = new Rectangle(1, 1); + + Group group = new Group(); + group.addChild(rectangle); + group.setSpindleSpeed(10); + + assertEquals(10, rectangle.getSpindleSpeed(), 0.1); + } + + @Test + public void getLaserPowerShouldGetTheHighestValue() { + Rectangle rectangle1 = new Rectangle(1, 1); + rectangle1.setSpindleSpeed(11); + + Rectangle rectangle2 = new Rectangle(1, 1); + rectangle2.setSpindleSpeed(10); + + Group group = new Group(); + group.addChild(rectangle1); + group.addChild(rectangle2); + + assertEquals(11, group.getSpindleSpeed(), 0.1); + } +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModelTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModelTest.java index 66740c0e92..178dbabc29 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModelTest.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsModelTest.java @@ -18,24 +18,12 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.nbp.designer.gui.selectionsettings; -import com.willwinder.ugs.nbp.designer.entities.Anchor; import com.willwinder.ugs.nbp.designer.entities.EntitySetting; -import com.willwinder.ugs.nbp.designer.entities.cuttable.CutType; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; -import java.awt.Font; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - public class SelectionSettingsModelTest { private SelectionSettingsModel target; @@ -45,40 +33,6 @@ public void setUp() { target = new SelectionSettingsModel(); } - @Test - public void putShouldSetThePropertyAndNotify() { - Thread mainThread = Thread.currentThread(); - AtomicBoolean hasBeenNotified = new AtomicBoolean(false); - target.addListener((entitySetting) -> { - if (entitySetting == EntitySetting.WIDTH) { - hasBeenNotified.set(true); - } else { - fail("Got unwanted update: " + entitySetting); - } - - assertEquals("The notification must be done on the same thread", mainThread, Thread.currentThread()); - }); - - target.put(EntitySetting.WIDTH, 10.0); - - assertEquals(10.0, target.getWidth(), 0.01); - assertEquals(10.0, (Double) target.get(EntitySetting.WIDTH), 0.01); - assertTrue(hasBeenNotified.get()); - } - - @Test - public void putShouldNotSetThePropertyIfNotChanged() { - target.setHeight(10.0); - target.addListener((entitySetting) -> { - fail("Got unwanted update: " + entitySetting); - }); - - target.put(EntitySetting.HEIGHT, 10.0); - - assertEquals(10.0, target.getHeight(), 0.01); - assertEquals(10.0, (Double) target.get(EntitySetting.HEIGHT), 0.01); - } - @Test public void setSizeMayNotNotifyBeforeBothValuesHaveChanged() { target.addListener((entitySetting) -> { @@ -98,53 +52,18 @@ public void setSizeMayNotNotifyBeforeBothValuesHaveChanged() { } @Test - public void putValueWithWrongTypeShouldThrowException() { - target.addListener((e) -> { - fail("Should not notify any change"); + public void setLaserPower() { + target.addListener((entitySetting) -> { + if (entitySetting != EntitySetting.SPINDLE_SPEED) { + fail("Got unknown update " + entitySetting); + } + if (target.getSpindleSpeed() != 10.0) { + fail("Set laser power notified before value where set"); + } }); - assertThrows(SelectionSettingsModelException.class, () -> target.put(EntitySetting.POSITION_X, "1.0")); - - assertEquals(0.0, target.getPositionX(), 0.01); - } - - @Test - public void putAllValues() { - List notifiedSettings = new ArrayList<>(); - target.addListener(notifiedSettings::add); - - target.put(EntitySetting.POSITION_X, 1.0); - target.put(EntitySetting.POSITION_Y, 2.0); - target.put(EntitySetting.WIDTH, 3.0); - target.put(EntitySetting.HEIGHT, 4.0); - target.put(EntitySetting.ROTATION, 5.0); - target.put(EntitySetting.CUT_TYPE, CutType.POCKET); - target.put(EntitySetting.TEXT, "Banana"); - target.put(EntitySetting.START_DEPTH, 6.0); - target.put(EntitySetting.TARGET_DEPTH, 7.0); - target.put(EntitySetting.ANCHOR, Anchor.TOP_RIGHT); - target.put(EntitySetting.FONT_FAMILY, Font.SERIF); - target.put(EntitySetting.LOCK_RATIO, false); - - assertEquals(1.0, target.getPositionX(), 0.01); - assertEquals(2.0, target.getPositionY(), 0.01); - assertEquals(3.0, target.getWidth(), 0.01); - assertEquals(4.0, target.getHeight(), 0.01); - assertEquals(5.0, target.getRotation(), 0.01); - assertEquals(CutType.POCKET, target.getCutType()); - assertEquals("Banana", target.getText()); - assertEquals(6.0, target.getStartDepth(), 0.01); - assertEquals(7.0, target.getTargetDepth(), 0.01); - assertEquals(Anchor.TOP_RIGHT, target.getAnchor()); - assertEquals(Font.SERIF, target.getFontFamily()); - assertFalse(target.getLockRatio()); - - EntitySetting[] expectedSettings = EntitySetting.values(); - Arrays.sort(expectedSettings); - - EntitySetting[] currentSettings = notifiedSettings.toArray(new EntitySetting[0]); - Arrays.sort(currentSettings); + target.setSpindleSpeed(10); - assertArrayEquals("All settings have not been set properly", expectedSettings, currentSettings); + assertEquals(10, target.getSpindleSpeed()); } } \ No newline at end of file diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPathTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPathTest.java index 393ddd62ee..08dbebffdf 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPathTest.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/DrillCenterToolPathTest.java @@ -3,14 +3,14 @@ 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.SegmentType; +import com.willwinder.ugs.nbp.designer.model.Settings; import com.willwinder.ugs.nbp.designer.model.Size; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import org.junit.Test; import java.awt.geom.Point2D; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - public class DrillCenterToolPathTest { @Test public void drillCenterShouldDrillInCenterOfShape() { @@ -18,10 +18,12 @@ public void drillCenterShouldDrillInCenterOfShape() { rectangle.setSize(new Size(15, 15)); rectangle.setPosition(new Point2D.Double(10, 10)); - DrillCenterToolPath drillCenterToolPath = new DrillCenterToolPath(rectangle); - drillCenterToolPath.setDepthPerPass(5); + Settings settings = new Settings(); + settings.setSafeHeight(11); + settings.setDepthPerPass(5); + + DrillCenterToolPath drillCenterToolPath = new DrillCenterToolPath(settings, rectangle); drillCenterToolPath.setTargetDepth(10); - drillCenterToolPath.setSafeHeight(11); GcodePath gcodePath = drillCenterToolPath.toGcodePath(); assertEquals(9, gcodePath.getSegments().size()); diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlinePathTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlinePathTest.java new file mode 100644 index 0000000000..8456b21a32 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlinePathTest.java @@ -0,0 +1,11 @@ +package com.willwinder.ugs.nbp.designer.io.gcode.toolpaths; + +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class LaserOutlinePathTest { + @Test + public void pocketOnRectangleWithHole() { + assertTrue(true); + } +} diff --git a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPathTest.java b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPathTest.java index 55d68225f1..64e5fd298a 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPathTest.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/test/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/OutlineToolPathTest.java @@ -22,6 +22,7 @@ This file is part of Universal Gcode Sender (UGS). 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.model.Settings; import com.willwinder.ugs.nbp.designer.model.Size; import static org.junit.Assert.*; import org.junit.Test; @@ -34,11 +35,12 @@ public class OutlineToolPathTest { public void toGcodePathShouldGenerateGcodeFromStartDepth() { Rectangle rectangle = new Rectangle(0,0); rectangle.setSize(new Size(10, 10)); + Settings settings = new Settings(); + settings.setSafeHeight(10); - OutlineToolPath toolPath = new OutlineToolPath(rectangle); + OutlineToolPath toolPath = new OutlineToolPath(settings, rectangle); toolPath.setStartDepth(-1); toolPath.setTargetDepth(-1); - toolPath.setSafeHeight(10); GcodePath gcodePath = toolPath.toGcodePath(); List segments = gcodePath.getSegments(); 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 8980f2d140..cb57cabfaa 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 @@ -8,6 +8,7 @@ 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.Settings; import com.willwinder.ugs.nbp.designer.model.Size; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.PartialPosition; @@ -31,12 +32,14 @@ public void pocketShouldNotExceedTheGeometry() { Rectangle rectangle = new Rectangle(); rectangle.setSize(new Size(geometrySize, geometrySize)); - PocketToolPath simplePocket = new PocketToolPath(rectangle); + Settings settings = new Settings(); + settings.setSafeHeight(safeHeight); + settings.setToolStepOver(1); + settings.setToolDiameter(toolRadius * 2); + settings.setDepthPerPass(1); + + PocketToolPath simplePocket = new PocketToolPath(settings, rectangle); simplePocket.setTargetDepth(targetDepth); - simplePocket.setDepthPerPass(depthPerPass); - simplePocket.setToolDiameter(toolRadius * 2); - simplePocket.setStepOver(1); - simplePocket.setSafeHeight(safeHeight); List segmentList = simplePocket.toGcodePath().getSegments(); @@ -83,12 +86,15 @@ public void pocketOnRectangleWithHole() { Rectangle rectangle = new Rectangle(); rectangle.setSize(new Size(geometrySize, geometrySize)); - PocketToolPath simplePocket = new PocketToolPath(rectangle); + + Settings settings = new Settings(); + settings.setToolDiameter(toolRadius * 2); + settings.setSafeHeight(safeHeight); + settings.setToolStepOver(1); + settings.setDepthPerPass(depthPerPass); + + PocketToolPath simplePocket = new PocketToolPath(settings, rectangle); simplePocket.setTargetDepth(targetDepth); - simplePocket.setDepthPerPass(depthPerPass); - simplePocket.setToolDiameter(toolRadius * 2); - simplePocket.setStepOver(1); - simplePocket.setSafeHeight(safeHeight); List segmentList = simplePocket.toGcodePath().getSegments(); @@ -137,14 +143,16 @@ public void pocketOnTestFileCheckLengths() { double totalLength = 0; double totalRapidLength = 0; + Settings settings = new Settings(); + settings.setToolStepOver(0.5); + settings.setSafeHeight(safeHeight); + settings.setToolDiameter(toolDiameter); + settings.setDepthPerPass(depthPerPass); + for (Entity entity : design.getEntities()) { - PocketToolPath simplePocket = new PocketToolPath((Cuttable) entity); + PocketToolPath simplePocket = new PocketToolPath(settings, (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); From f6ad4fc5a9a6895022156c2450008d513c47f954 Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Fri, 24 May 2024 06:43:25 +0200 Subject: [PATCH 2/3] Minor refactoring --- .../io/gcode/toolpaths/LaserFillToolPath.java | 56 +++++++++---------- .../gcode/toolpaths/LaserOutlineToolPath.java | 40 ++++++------- 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java index efae416f15..16b137f4aa 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserFillToolPath.java @@ -14,7 +14,6 @@ import org.locationtech.jts.geom.LineString; import java.awt.geom.Area; -import java.util.ArrayList; import java.util.List; public class LaserFillToolPath extends AbstractToolPath { @@ -25,28 +24,22 @@ public LaserFillToolPath(Settings settings, Cuttable source) { this.source = source; } - private static void addGeometriesToCoordinateList(ArrayList> coordinateList, List geometryCoordinates) { - coordinateList.add(geometryCoordinates.stream() - .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()) - .toList()); - } - - public void appendGcodePath(GcodePath gcodePath, Settings settings) { - gcodePath.addSegment(new Segment(SegmentType.SEAM, null, null, (int) Math.round(settings.getMaxSpindleSpeed() * (source.getSpindleSpeed() / 100d)))); - - List geometries; + private List getGeometries() { if (ToolPathUtils.isClosedGeometry(source.getShape())) { Geometry geometry = ToolPathUtils.convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); - geometries = List.of(geometry); + return List.of(geometry); } else { - geometries = ToolPathUtils.convertShapeToGeometry(source.getShape(), getGeometryFactory()); + return ToolPathUtils.convertShapeToGeometry(source.getShape(), getGeometryFactory()); } + } + public void appendGcodePath(GcodePath gcodePath, Settings settings) { + gcodePath.addSegment(new Segment(SegmentType.SEAM, null, null, (int) Math.round(settings.getMaxSpindleSpeed() * (source.getSpindleSpeed() / 100d)))); + List geometries = getGeometries(); geometries.forEach(g -> { Envelope envelope = g.getEnvelopeInternal(); - int currentPass = 0; while (currentPass < source.getPasses()) { currentPass++; @@ -59,28 +52,29 @@ public void appendGcodePath(GcodePath gcodePath, Settings settings) { new CoordinateXY(envelope.getMaxX(), currentY), }); - Geometry intersection = g.intersection(lineString); - List geometryCoordinates = ToolPathUtils.geometryToCoordinates(intersection); - List partialPosition = geometryCoordinates.stream() - .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()).toList(); - - if (partialPosition.size() > 1) { - if (reverse) { - partialPosition = Lists.reverse(partialPosition); - } - - for (int i = 0; i < partialPosition.size(); i += 2) { - gcodePath.addSegment(SegmentType.MOVE, partialPosition.get(i)); - gcodePath.addSegment(SegmentType.LINE, partialPosition.get(i + 1)); - } - } - - + addLineIntersectionSegments(gcodePath, g, lineString, reverse); currentY += settings.getLaserDiameter(); reverse = !reverse; } } }); + } + + private static void addLineIntersectionSegments(GcodePath gcodePath, Geometry geometry, LineString lineString, boolean reverse) { + Geometry intersection = geometry.intersection(lineString); + List geometryCoordinates = ToolPathUtils.geometryToCoordinates(intersection); + List partialPosition = geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()).toList(); + if (partialPosition.size() > 1) { + if (reverse) { + partialPosition = Lists.reverse(partialPosition); + } + + for (int i = 0; i < partialPosition.size(); i += 2) { + gcodePath.addSegment(SegmentType.MOVE, partialPosition.get(i)); + gcodePath.addSegment(SegmentType.LINE, partialPosition.get(i + 1)); + } + } } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java index 4d1580e954..3eceb56600 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/io/gcode/toolpaths/LaserOutlineToolPath.java @@ -9,7 +9,6 @@ import org.locationtech.jts.geom.Geometry; import java.awt.geom.Area; -import java.util.ArrayList; import java.util.List; public class LaserOutlineToolPath extends AbstractToolPath { @@ -20,36 +19,33 @@ public LaserOutlineToolPath(Settings settings, Cuttable source) { this.source = source; } - private static void addGeometriesToCoordinateList(ArrayList> coordinateList, List geometryCoordinates) { - coordinateList.add(geometryCoordinates.stream() - .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()) - .toList()); - } public void appendGcodePath(GcodePath gcodePath, Settings settings) { gcodePath.addSegment(new Segment(SegmentType.SEAM, null, null, (int) Math.round(settings.getMaxSpindleSpeed() * (source.getSpindleSpeed() / 100d)))); - List geometries; + List geometries = getGeometries(); + geometries.forEach(g -> addGeometrySegments(g, gcodePath)); + } + + private List getGeometries() { if (ToolPathUtils.isClosedGeometry(source.getShape())) { Geometry geometry = ToolPathUtils.convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory()); - geometries = ToolPathUtils.toGeometryList(geometry); + return ToolPathUtils.toGeometryList(geometry); } else { - geometries = ToolPathUtils.convertShapeToGeometry(source.getShape(), getGeometryFactory()); + return ToolPathUtils.convertShapeToGeometry(source.getShape(), getGeometryFactory()); } + } + private void addGeometrySegments(Geometry geometry, GcodePath gcodePath) { + List geometryCoordinates = ToolPathUtils.geometryToCoordinates(geometry); - geometries.forEach(g -> { - List geometryCoordinates = ToolPathUtils.geometryToCoordinates(g); - - int currentPass = 0; - while (currentPass < source.getPasses()) { - currentPass++; - List partialPosition = geometryCoordinates.stream() - .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()).toList(); - - gcodePath.addSegment(SegmentType.MOVE, partialPosition.get(0), " Pass " + currentPass + " of " + source.getPasses()); - partialPosition.forEach(c -> gcodePath.addSegment(SegmentType.LINE, c)); - } - }); + int currentPass = 0; + while (currentPass < source.getPasses()) { + currentPass++; + List partialPosition = geometryCoordinates.stream() + .map(numericCoordinate -> PartialPosition.builder(numericCoordinate).build()).toList(); + gcodePath.addSegment(SegmentType.MOVE, partialPosition.get(0), " Pass " + currentPass + " of " + source.getPasses()); + partialPosition.forEach(c -> gcodePath.addSegment(SegmentType.LINE, c)); + } } } From 37a5383053061c62b5c8bd3a13d72b17d95a8f6f Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Fri, 24 May 2024 07:07:04 +0200 Subject: [PATCH 3/3] Fixed selection bug overwriting settigns --- .../gui/selectionsettings/FieldEventDispatcher.java | 11 +++++++++-- .../gui/selectionsettings/SelectionSettingsPanel.java | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java index bb825bb83e..6650375a71 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/FieldEventDispatcher.java @@ -41,6 +41,7 @@ This file is part of Universal Gcode Sender (UGS). import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; /** @@ -51,8 +52,8 @@ This file is part of Universal Gcode Sender (UGS). */ public class FieldEventDispatcher { private final Set listeners = ConcurrentHashMap.newKeySet(); - private final EnumMap componentsMap = new EnumMap<>(EntitySetting.class); + private AtomicBoolean enabled = new AtomicBoolean(false); /** * Installs a listener to receive notification when the text of any @@ -151,7 +152,9 @@ private void valueUpdated(PropertyChangeEvent propertyChangeEvent) { public void updateValue(EntitySetting entitySetting, Object object) { - listeners.forEach(l -> l.onFieldUpdate(entitySetting, object)); + if (this.enabled.get()) { + listeners.forEach(l -> l.onFieldUpdate(entitySetting, object)); + } } public void addListener(FieldEventListener listener) { @@ -162,4 +165,8 @@ public void registerListener(EntitySetting entitySetting, JSlider slider) { componentsMap.put(entitySetting, slider); slider.addChangeListener(l -> updateValue(entitySetting, slider.getValue())); } + + public void setEnabled(boolean enabled) { + this.enabled.set(enabled); + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java index 09ca9f2dcb..cb2d9f5057 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/selectionsettings/SelectionSettingsPanel.java @@ -241,7 +241,10 @@ public void setEnabled(boolean enabled) { @Override public void onSelectionEvent(SelectionEvent selectionEvent) { + // Temporarily disable the field event dispatcher so that it won't triggers updates on select + fieldEventDispatcher.setEnabled(false); onEvent(new EntityEvent(controller.getSelectionManager(), EventType.SELECTED)); + fieldEventDispatcher.setEnabled(true); } @Override