Skip to content

Commit

Permalink
Added possibility to offset the surfacing tool path (#2701)
Browse files Browse the repository at this point in the history
  • Loading branch information
breiler authored Feb 17, 2025
1 parent e66e9bb commit 46f2a82
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ This file is part of Universal Gcode Sender (UGS).
*/
package com.willwinder.ugs.nbp.designer;

import static com.willwinder.ugs.nbp.designer.DesignerMain.PROPERTY_IS_STANDALONE;
import org.apache.commons.lang3.StringUtils;

import java.awt.geom.Point2D;
import java.text.ParseException;

import static com.willwinder.ugs.nbp.designer.DesignerMain.PROPERTY_IS_STANDALONE;

/**
* @author Joacim Breiler
*/
Expand Down Expand Up @@ -71,7 +70,7 @@ public static String toString(double value) {
* you will need to call SwingUtilities.convertPointToScreen or equivalent
* on all arguments before passing them to this function.
* <p>
* Source: https://stackoverflow.com/a/16340752
* Source: <a href="https://stackoverflow.com/a/16340752">Source on stack overflow</a>
*
* @param centerPt Point we are rotating around.
* @param targetPt Point we want to calculate the angle to.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public enum EntitySetting {
LOCK_RATIO("Lock ratio"),
SPINDLE_SPEED("Spindle speed"),
PASSES("Passes"),
FEED_RATE("Feed rate");
FEED_RATE("Feed rate"),
OFFSET_TOOL_PERCENT("Tool offset percent"),;

public static final List<EntitySetting> DEFAULT_ENDMILL_SETTINGS = List.of(
EntitySetting.CUT_TYPE,
Expand All @@ -59,6 +60,23 @@ public enum EntitySetting {
EntitySetting.FEED_RATE,
EntitySetting.TEXT);

public static final List<EntitySetting> DEFAULT_SURFACE_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,
EntitySetting.SPINDLE_SPEED,
EntitySetting.FEED_RATE,
EntitySetting.TEXT,
EntitySetting.OFFSET_TOOL_PERCENT);

public static final List<EntitySetting> DEFAULT_LASER_SETTINGS = List.of(
EntitySetting.CUT_TYPE,
EntitySetting.ANCHOR,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ This file is part of Universal Gcode Sender (UGS).
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.List;
Expand All @@ -45,6 +47,7 @@ public abstract class AbstractCuttable extends AbstractEntity implements Cuttabl
private CutType cutType = CutType.NONE;
private double targetDepth;
private double startDepth;
private int offsetToolPercent;
private int spindleSpeed;
private int passes;
private int feedRate;
Expand Down Expand Up @@ -125,6 +128,26 @@ public void setFeedRate(int feedRate) {
notifyEvent(new EntityEvent(this, EventType.SETTINGS_CHANGED));
}

@Override
public void setOffsetToolPercent(int percent) {
this.offsetToolPercent = percent;
notifyEvent(new EntityEvent(this, EventType.SETTINGS_CHANGED));
}

@Override
public int getOffsetToolPercent() {
return offsetToolPercent;
}

@Override
public boolean isWithin(Point2D point) {
if (cutType != CutType.SURFACE) {
return super.isWithin(point);
}

return getSurfacingShape().contains(point) || getSurfacingShape().intersects(point.getX() - 1, point.getY() - 1, 2, 2);
}

@Override
public void render(Graphics2D graphics, Drawing drawing) {
if (isHidden) {
Expand All @@ -138,11 +161,16 @@ public void render(Graphics2D graphics, Drawing drawing) {
Shape shape = getShape();
if (getCutType() == CutType.NONE) {
drawShape(graphics, dashedStroke, Colors.SHAPE_HINT, shape);
} else if (getCutType() == CutType.POCKET || getCutType() == CutType.SURFACE) {
} else if (getCutType() == CutType.POCKET) {
graphics.setStroke(new BasicStroke(strokeWidth));
graphics.setColor(getCutColor());
graphics.fill(shape);
graphics.draw(shape);
} else if (getCutType() == CutType.SURFACE) {
drawShape(graphics, dashedStroke, Colors.SHAPE_HINT, shape);
graphics.setStroke(new BasicStroke(strokeWidth));
graphics.setColor(getCutColor());
graphics.fill(getSurfacingShape());
} 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) {
Expand All @@ -164,6 +192,21 @@ public void render(Graphics2D graphics, Drawing drawing) {
}
}

private Shape getBufferedShape(double offsetInMillimeters) {
Shape shape = getShape().getBounds2D();
if (offsetInMillimeters == 0) {
return shape;
}

BasicStroke stroke = new BasicStroke((float) (offsetInMillimeters * 2d)); // Multiply by 2 to expand evenly
return new Area(stroke.createStrokedShape(shape).getBounds2D());
}

private Shape getSurfacingShape() {
double offsetInMillimeters = ControllerFactory.getController().getSettings().getToolDiameter() * ((double) getOffsetToolPercent() / 100d);
return getBufferedShape(offsetInMillimeters);
}

private void drawShape(Graphics2D graphics, BasicStroke strokeWidth, Color shapeHint, Shape shape) {
graphics.setStroke(strokeWidth);
graphics.setColor(shapeHint);
Expand All @@ -183,7 +226,6 @@ public void setHidden(boolean hidden) {

@Override
public Rectangle2D getBounds() {
// Make sure that the shape bounds are not zero to make it possible to select the entity
Rectangle2D bounds = super.getBounds();
return new Rectangle2D.Double(bounds.getX(), bounds.getY(), Math.max(bounds.getWidth(), 0.001), Math.max(bounds.getHeight(), 0.001));
}
Expand All @@ -202,7 +244,8 @@ public List<EntitySetting> getSettings() {
EntitySetting.TARGET_DEPTH,
EntitySetting.SPINDLE_SPEED,
EntitySetting.PASSES,
EntitySetting.FEED_RATE
EntitySetting.FEED_RATE,
EntitySetting.OFFSET_TOOL_PERCENT
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This file is part of Universal Gcode Sender (UGS).
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 static com.willwinder.ugs.nbp.designer.entities.EntitySetting.DEFAULT_SURFACE_SETTINGS;

import java.util.List;

Expand All @@ -30,7 +31,7 @@ This file is part of Universal Gcode Sender (UGS).
public enum CutType {
NONE("None", List.of(EntitySetting.CUT_TYPE)),
POCKET("Mill - Pocket", DEFAULT_ENDMILL_SETTINGS),
SURFACE("Mill - Surface", DEFAULT_ENDMILL_SETTINGS),
SURFACE("Mill - Surface", DEFAULT_SURFACE_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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,18 @@ public interface Cuttable extends Entity {
Optional<Object> getEntitySetting(EntitySetting entitySetting);

void setEntitySetting(EntitySetting entitySetting, Object value);

/**
* Sets the tool offset in percent (ie: 100 for 100%)
*
* @param value the percentage value
*/
void setOffsetToolPercent(int value);

/**
* Returns the tool offset in percent (ie: 100 for 100%)
*
* @return the tool offset
*/
int getOffsetToolPercent();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public Optional<Object> getEntitySetting(EntitySetting entitySetting) {
case SPINDLE_SPEED -> Optional.of(cuttable.getSpindleSpeed());
case PASSES -> Optional.of(cuttable.getPasses());
case FEED_RATE -> Optional.of(cuttable.getFeedRate());
case OFFSET_TOOL_PERCENT -> Optional.of(cuttable.getOffsetToolPercent());
default -> Optional.empty();
};
}
Expand All @@ -33,6 +34,7 @@ public void setEntitySetting(EntitySetting entitySetting, Object value) {
case SPINDLE_SPEED -> cuttable.setSpindleSpeed((Integer) value);
case PASSES -> cuttable.setPasses(Integer.parseInt(value.toString()));
case FEED_RATE -> cuttable.setFeedRate(((Double) value).intValue());
case OFFSET_TOOL_PERCENT -> cuttable.setOffsetToolPercent(((Integer) value));
default -> LOGGER.info("Do not know how to set " + entitySetting + " to " + value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ public void setPasses(int passes) {
});
}

@Override
public int getOffsetToolPercent() {
return getCuttableStream()
.mapToInt(Cuttable::getOffsetToolPercent)
.max()
.orElse(0);
}

@Override
public void setOffsetToolPercent(int value) {
getChildren().forEach(child -> {
if (child instanceof Cuttable cuttable) {
cuttable.setOffsetToolPercent(value);
}
});
}

@Override
public boolean isHidden() {
return getCuttableStream()
Expand Down Expand Up @@ -230,6 +247,11 @@ public List<EntitySetting> getSettings() {
result.remove(EntitySetting.FEED_RATE);
}

if (getCuttableStream().map(Cuttable::getOffsetToolPercent).distinct().toList().size() > 1) {
result = new ArrayList<>(result);
result.remove(EntitySetting.OFFSET_TOOL_PERCENT);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public Object get(EntitySetting key) {
case SPINDLE_SPEED -> getSpindleSpeed();
case PASSES -> getPasses();
case FEED_RATE -> getFeedRate();
case OFFSET_TOOL_PERCENT -> getToolOffsetPercent();
default -> throw new SelectionSettingsModelException("Unknown setting " + key);
};
}
Expand Down Expand Up @@ -122,6 +123,17 @@ public void setRotation(double rotation) {
}
}

public int getToolOffsetPercent() {
return (Integer) settings.getOrDefault(EntitySetting.OFFSET_TOOL_PERCENT, 0);
}

public void setToolOffsetPercent(int offsetPercent) {
if (!valuesEquals(getToolOffsetPercent(), offsetPercent)) {
settings.put(EntitySetting.OFFSET_TOOL_PERCENT, offsetPercent);
notifyListeners(EntitySetting.OFFSET_TOOL_PERCENT);
}
}

private void notifyListeners(EntitySetting setting) {
listeners.forEach(l -> l.onModelUpdate(setting));
}
Expand All @@ -137,6 +149,7 @@ public void reset() {
setTargetDepth(0);
setSpindleSpeed(0);
setFeedRate(0);
setToolOffsetPercent(0);
setText("");
setFontFamily(Font.SANS_SERIF);
}
Expand Down Expand Up @@ -342,5 +355,9 @@ public void updateFromEntity(Group selectionGroup) {
if (settings.contains(EntitySetting.FEED_RATE)) {
setFeedRate(selectionGroup.getFeedRate());
}

if (settings.contains(EntitySetting.OFFSET_TOOL_PERCENT)) {
setToolOffsetPercent(selectionGroup.getOffsetToolPercent());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ public class SelectionSettingsPanel extends JPanel implements SelectionListener,
private JLabel feedRateLabel;
private UnitSpinner feedRateSpinner;
private JLabel cutTypeLabel;
private JLabel offsetToolPercentSliderLabel;
private JSlider offsetToolPercentSlider;

public SelectionSettingsPanel(Controller controller) {
fieldEventDispatcher = new FieldEventDispatcher();
Expand Down Expand Up @@ -153,6 +155,17 @@ private void addCutFields() {
add(feedRateSpinner, FIELD_CONSTRAINTS + ", spanx");
fieldEventDispatcher.registerListener(EntitySetting.FEED_RATE, feedRateSpinner);

offsetToolPercentSliderLabel = createAndAddLabel(EntitySetting.OFFSET_TOOL_PERCENT);
offsetToolPercentSlider = new JSlider(0, 300, 0);
offsetToolPercentSlider.setPaintLabels(true);
offsetToolPercentSlider.setPaintTicks(true);
offsetToolPercentSlider.setSnapToTicks(true);
offsetToolPercentSlider.setMinorTickSpacing(50);
offsetToolPercentSlider.setMajorTickSpacing(100);

add(offsetToolPercentSlider, SLIDER_FIELD_CONSTRAINTS + ", spanx");
fieldEventDispatcher.registerListener(EntitySetting.OFFSET_TOOL_PERCENT, offsetToolPercentSlider);

spindleSpeedLabel = createAndAddLabel(EntitySetting.SPINDLE_SPEED);
spindleSpeedSlider = new JSlider(0, 100, 0);
spindleSpeedSlider.setPaintLabels(true);
Expand Down Expand Up @@ -310,6 +323,9 @@ public void onModelUpdate(EntitySetting entitySetting) {
} else if (entitySetting == EntitySetting.FEED_RATE) {
feedRateSpinner.setValue(model.getFeedRate());
selectionGroup.setFeedRate(model.getFeedRate());
} else if (entitySetting == EntitySetting.OFFSET_TOOL_PERCENT) {
offsetToolPercentSlider.setValue(model.getToolOffsetPercent());
selectionGroup.setOffsetToolPercent(model.getToolOffsetPercent());
}

handleComponentVisibility(selectionGroup);
Expand Down Expand Up @@ -372,6 +388,11 @@ private void handleComponentVisibility(Group selectionGroup) {
feedRateLabel.setVisible(hasFeedRate);
feedRateSpinner.setVisible(hasFeedRate);

boolean hasOffsetTool = selectionHasSetting(selectionGroup, EntitySetting.OFFSET_TOOL_PERCENT) &&
cutType.getSettings().contains(EntitySetting.OFFSET_TOOL_PERCENT);
offsetToolPercentSlider.setVisible(hasOffsetTool);
offsetToolPercentSliderLabel.setVisible(hasOffsetTool);

lockRatioButton.setVisible(hasWidth && hasHeight);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public SurfaceToolPath(Settings settings, Cuttable source) {

private List<Geometry> getGeometries() {
Geometry geometry = ToolPathUtils.convertAreaToGeometry(new Area(source.getShape()), getGeometryFactory());
Geometry shell = geometry.buffer(-settings.getToolDiameter() / 2d);
return List.of(shell);
Geometry shell = geometry.buffer((settings.getToolDiameter() * (source.getOffsetToolPercent() / 100d)));
return List.of(shell.getEnvelope());
}

public void appendGcodePath(GcodePath gcodePath, Settings settings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ private EntityV1 convertToEntity(Entity entity) {
cuttableEntity.setSpindleSpeed(cuttable.getSpindleSpeed());
cuttableEntity.setPasses(cuttable.getPasses());
cuttableEntity.setFeedRate(cuttable.getFeedRate());
cuttableEntity.setOffsetToolPercent(cuttable.getOffsetToolPercent());
}
return result;
}
Expand Down
Loading

0 comments on commit 46f2a82

Please sign in to comment.