Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2026 Damian Nikodem

This file is part of Universal Gcode Sender (UGS).

UGS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

UGS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with UGS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.willwinder.ugs.designer.actions;

import com.willwinder.ugs.designer.gui.toollibrary.ToolLibraryDialog;
import com.willwinder.ugs.designer.logic.Controller;
import com.willwinder.universalgcodesender.uielements.DialogUtils;

import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class OpenToolLibraryAction implements ActionListener {
private final Controller controller;

public OpenToolLibraryAction(Controller controller) {
this.controller = controller;
}

@Override
public void actionPerformed(ActionEvent e) {
Window parent = DialogUtils.getParentWindow(controller.getDrawing());
ToolLibraryDialog.show(parent, controller.getSettings().getPreferredUnits());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
Copyright 2021-2024 Will Winder
Copyright 2026 Damian Nikodem

This file is part of Universal Gcode Sender (UGS).

Expand All @@ -18,120 +19,253 @@ This file is part of Universal Gcode Sender (UGS).
*/
package com.willwinder.ugs.designer.gui;

import com.willwinder.ugs.designer.gui.toollibrary.DeviationHighlighter;
import com.willwinder.ugs.designer.gui.toollibrary.ToolLibraryPickerDialog;
import com.willwinder.ugs.designer.logic.Controller;
import com.willwinder.ugs.designer.logic.ToolLibraryService;
import com.willwinder.ugs.designer.model.Settings;
import com.willwinder.ugs.designer.model.toollibrary.ToolDefinition;
import com.willwinder.universalgcodesender.Utils;
import com.willwinder.universalgcodesender.model.Unit;
import com.willwinder.universalgcodesender.model.UnitUtils;
import com.willwinder.universalgcodesender.services.LookupService;
import com.willwinder.universalgcodesender.uielements.TextFieldWithUnit;
import net.miginfocom.swing.MigLayout;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import java.awt.Dimension;
import java.text.ParseException;
import java.util.Optional;

/**
* @author Joacim Breiler
*/
public class ToolSettingsPanel extends JPanel {
public static final String TOOL_FIELD_CONSTRAINT = "grow, wrap";
private final transient Controller controller;
private JTextField toolDiameter;
private JTextField feedSpeed;
private JTextField plungeSpeed;
private JTextField depthPerPass;
private JTextField stepOver;
private final transient ToolLibraryService libraryService;

private JButton pickFromLibraryButton;
private JLabel selectedToolLabel;
private JPanel diameterSlot;
private TextFieldWithUnit toolDiameter;
private UnitUtils.Units diameterDisplayUnit = UnitUtils.Units.MM;
private TextFieldWithUnit feedSpeed;
private TextFieldWithUnit plungeSpeed;
private TextFieldWithUnit depthPerPass;
private TextFieldWithUnit stepOver;
private JTextField safeHeight;
private JCheckBox detectMaxSpindleSpeed;
private TextFieldWithUnit laserDiameter;
private TextFieldWithUnit maxSpindleSpeed;
private JComboBox<String> spindleDirection;
private TextFieldWithUnit flatnessPrecision;

private transient ToolDefinition librarySnapshot;

public ToolSettingsPanel(Controller controller) {
this.controller = controller;
this.libraryService = LookupService.lookupOptional(ToolLibraryService.class).orElse(null);
initComponents();
setMinimumSize(new Dimension(300, 400));
setPreferredSize(new Dimension(300, 400));
setMinimumSize(new Dimension(360, 480));
setPreferredSize(new Dimension(360, 480));
initialiseLibrarySnapshot();
attachDeviationHighlighters();
}

private void initComponents() {
setLayout(new MigLayout("fill", "[20%][80%]" ));
setLayout(new MigLayout("fill", "[pref!][grow,fill]"));

pickFromLibraryButton = new JButton("Pick from Library…");
pickFromLibraryButton.addActionListener(e -> onPickFromLibrary());
add(pickFromLibraryButton, "spanx, growx, split 2");
selectedToolLabel = new JLabel(" ");
add(selectedToolLabel, "wrap, growx");

add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2");

add(new JLabel("Tool diameter" ));
toolDiameter = new TextFieldWithUnit(Unit.MM, 3, controller.getSettings().getToolDiameter());
add(toolDiameter, TOOL_FIELD_CONSTRAINT);
add(new JLabel("Tool diameter"));
diameterSlot = new JPanel(new MigLayout("insets 0, fill"));
rebuildDiameterField(UnitUtils.Units.MM, controller.getSettings().getToolDiameter());
add(diameterSlot, TOOL_FIELD_CONSTRAINT);

add(new JLabel("Tool step over" ));
add(new JLabel("Tool step over (%)"));
stepOver = new TextFieldWithUnit(Unit.PERCENT, 2,
controller.getSettings().getToolStepOver());
add(stepOver, TOOL_FIELD_CONSTRAINT);

add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2" );
add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2");

add(new JLabel("Default feed speed" ));
add(new JLabel("Default feed speed (" + Unit.MM_PER_MINUTE.getAbbreviation() + ")"));
feedSpeed = new TextFieldWithUnit(Unit.MM_PER_MINUTE, 0, controller.getSettings().getFeedSpeed());
add(feedSpeed, TOOL_FIELD_CONSTRAINT);

add(new JLabel("Plunge speed" ));
add(new JLabel("Plunge speed (" + Unit.MM_PER_MINUTE.getAbbreviation() + ")"));
plungeSpeed = new TextFieldWithUnit(Unit.MM_PER_MINUTE, 0, controller.getSettings().getPlungeSpeed());
add(plungeSpeed, TOOL_FIELD_CONSTRAINT);

add(new JLabel("Depth per pass" ));
add(new JLabel("Depth per pass (" + Unit.MM.getAbbreviation() + ")"));
depthPerPass = new TextFieldWithUnit(Unit.MM, 2, controller.getSettings().getDepthPerPass());
add(depthPerPass, TOOL_FIELD_CONSTRAINT);

add(new JLabel("Safe height" ));
add(new JLabel("Safe height (" + Unit.MM.getAbbreviation() + ")"));
safeHeight = new TextFieldWithUnit(Unit.MM, 2, controller.getSettings().getSafeHeight());
add(safeHeight, TOOL_FIELD_CONSTRAINT);

add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2" );
add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2");

add(new JLabel("Detect max spindle speed" ));
add(new JLabel("Detect max spindle speed"));
detectMaxSpindleSpeed = new JCheckBox("", controller.getSettings().getDetectMaxSpindleSpeed());
add(detectMaxSpindleSpeed, TOOL_FIELD_CONSTRAINT);

add(new JLabel("Max spindle speed" ));
add(new JLabel("Max spindle speed"));
maxSpindleSpeed = new TextFieldWithUnit(Unit.REVOLUTIONS_PER_MINUTE, 0, controller.getSettings().getMaxSpindleSpeed());
add(maxSpindleSpeed, TOOL_FIELD_CONSTRAINT);

add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2" );
add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2");

add(new JLabel("Laser diameter" ));
add(new JLabel("Laser diameter"));
laserDiameter = new TextFieldWithUnit(Unit.MM, 3, controller.getSettings().getLaserDiameter());
add(laserDiameter, TOOL_FIELD_CONSTRAINT);

add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2" );
add(new JSeparator(SwingConstants.HORIZONTAL), "spanx, grow, wrap, hmin 2");

add(new JLabel("Spindle Start Command" ));
spindleDirection = new JComboBox<>(new DefaultComboBoxModel<>(new String[]{"M3","M4","M5"}));
add(new JLabel("Spindle Start Command"));
spindleDirection = new JComboBox<>(new DefaultComboBoxModel<>(new String[]{"M3", "M4", "M5"}));
spindleDirection.setSelectedItem(controller.getSettings().getSpindleDirection());
add(spindleDirection, TOOL_FIELD_CONSTRAINT);

add(new JLabel("Arc precision" ));
add(new JLabel("Arc precision"));
flatnessPrecision = new TextFieldWithUnit(Unit.MM, 3, controller.getSettings().getFlatnessPrecision());
add(flatnessPrecision, TOOL_FIELD_CONSTRAINT);
}

private void rebuildDiameterField(UnitUtils.Units unit, double valueInFieldUnit) {
diameterDisplayUnit = unit == null ? UnitUtils.Units.MM : unit;
Unit fieldUnit = diameterDisplayUnit == UnitUtils.Units.INCH ? Unit.INCH : Unit.MM;
int decimals = diameterDisplayUnit == UnitUtils.Units.INCH ? 4 : 3;
diameterSlot.removeAll();
toolDiameter = new TextFieldWithUnit(fieldUnit, decimals, valueInFieldUnit);
diameterSlot.add(toolDiameter, "grow");
diameterSlot.revalidate();
diameterSlot.repaint();
}

private void initialiseLibrarySnapshot() {
Settings settings = controller.getSettings();
String activeId = settings.getCurrentToolId();
if (activeId != null && libraryService != null) {
Optional<ToolDefinition> tool = libraryService.getById(activeId);
if (tool.isPresent() && !tool.get().isCustomSentinel()) {
librarySnapshot = tool.get();
SwingUtilities.invokeLater(() -> applyLibrarySnapshotToFields(false));
return;
}
}
ToolDefinition snapshot = settings.getCurrentToolSnapshot();
if (snapshot != null && !snapshot.isCustomSentinel()) {
librarySnapshot = new ToolDefinition(snapshot);
}
updateSelectedToolLabel();
}

private void attachDeviationHighlighters() {
DeviationHighlighter.attachDouble(toolDiameter, () -> librarySnapshot == null ? null
: valueInDisplayUnit(librarySnapshot.getDiameterInMm()));
DeviationHighlighter.attachDouble(maxSpindleSpeed,
() -> librarySnapshot == null ? null : (double) librarySnapshot.getMaxSpindleSpeed());
DeviationHighlighter.attachDouble(flatnessPrecision,
() -> librarySnapshot == null ? null : null);
DeviationHighlighter.attachDouble(laserDiameter,
() -> librarySnapshot == null ? null : null);

DeviationHighlighter.attachDouble(feedSpeed,
() -> librarySnapshot == null ? null : (double) librarySnapshot.getFeedSpeed());
DeviationHighlighter.attachDouble(plungeSpeed,
() -> librarySnapshot == null ? null : (double) librarySnapshot.getPlungeSpeed());
DeviationHighlighter.attachDouble(depthPerPass,
() -> librarySnapshot == null ? null : librarySnapshot.getDepthPerPass());
DeviationHighlighter.attachDouble(stepOver,
() -> librarySnapshot == null ? null : librarySnapshot.getStepOverPercent());
DeviationHighlighter.attachCombo(spindleDirection,
() -> librarySnapshot == null ? null : librarySnapshot.getSpindleDirection());
}

private Double valueInDisplayUnit(double mm) {
if (diameterDisplayUnit == UnitUtils.Units.INCH) {
return mm * UnitUtils.scaleUnits(UnitUtils.Units.MM, UnitUtils.Units.INCH);
}
return mm;
}

private void onPickFromLibrary() {
if (libraryService == null) {
return;
}
Optional<ToolDefinition> picked = ToolLibraryPickerDialog.pick(
SwingUtilities.getWindowAncestor(this),
controller.getSettings().getPreferredUnits());
picked.ifPresent(tool -> {
if (tool.isCustomSentinel()) {
librarySnapshot = null;
updateSelectedToolLabel();
} else {
librarySnapshot = tool;
applyLibrarySnapshotToFields(true);
}
});
}

private void applyLibrarySnapshotToFields(boolean populateAll) {
if (librarySnapshot == null) return;
rebuildDiameterField(librarySnapshot.getDiameterUnit(), librarySnapshot.getDiameter());
// Re-attach highlighter to the new field
DeviationHighlighter.attachDouble(toolDiameter, () -> librarySnapshot == null ? null
: valueInDisplayUnit(librarySnapshot.getDiameterInMm()));
if (populateAll) {
try {
feedSpeed.setDoubleValue(librarySnapshot.getFeedSpeed());
plungeSpeed.setDoubleValue(librarySnapshot.getPlungeSpeed());
depthPerPass.setDoubleValue(librarySnapshot.getDepthPerPass());
stepOver.setDoubleValue(librarySnapshot.getStepOverPercent());
maxSpindleSpeed.setDoubleValue(librarySnapshot.getMaxSpindleSpeed());
spindleDirection.setSelectedItem(librarySnapshot.getSpindleDirection());
} catch (RuntimeException ignored) {
// Bad format — leave field as-is
}
}
updateSelectedToolLabel();
}

private void updateSelectedToolLabel() {
if (librarySnapshot == null) {
selectedToolLabel.setText("— Custom —");
} else {
selectedToolLabel.setText(librarySnapshot.getName() == null
? "— Custom —" : librarySnapshot.getName());
}
}

public double getToolDiameter() {
try {
return Utils.formatter.parse(toolDiameter.getText()).doubleValue();
double displayed = Utils.formatter.parse(toolDiameter.getText()).doubleValue();
return displayed * UnitUtils.scaleUnits(diameterDisplayUnit, UnitUtils.Units.MM);
} catch (ParseException e) {
return controller.getSettings().getToolDiameter();
}
}

public double getStepOver() {
try {
return Utils.formatter.parse(stepOver.getText()).doubleValue() / 100;
} catch (ParseException e) {
return controller.getSettings().getToolStepOver();
}
return stepOver.getDoubleValue();
}

public double getDepthPerPass() {
Expand Down Expand Up @@ -212,6 +346,13 @@ public Settings getSettings() {
settings.setDetectMaxSpindleSpeed(getDetectMaxSpindleSpeed());
settings.setSpindleDirection(getSpindleDirection());
settings.setFlatnessPrecision(getFlatnessPrecision());
if (librarySnapshot != null) {
settings.setCurrentToolId(librarySnapshot.getId());
settings.setCurrentToolSnapshot(new ToolDefinition(librarySnapshot));
} else {
settings.setCurrentToolId(null);
settings.setCurrentToolSnapshot(null);
}
return settings;
}
}
Loading
Loading