11/*
22 Copyright 2021-2024 Will Winder
3+ Copyright 2026 Damian Nikodem
34
45 This file is part of Universal Gcode Sender (UGS).
56
@@ -18,120 +19,253 @@ This file is part of Universal Gcode Sender (UGS).
1819 */
1920package com .willwinder .ugs .designer .gui ;
2021
22+ import com .willwinder .ugs .designer .gui .toollibrary .DeviationHighlighter ;
23+ import com .willwinder .ugs .designer .gui .toollibrary .ToolLibraryPickerDialog ;
2124import com .willwinder .ugs .designer .logic .Controller ;
25+ import com .willwinder .ugs .designer .logic .ToolLibraryService ;
2226import com .willwinder .ugs .designer .model .Settings ;
27+ import com .willwinder .ugs .designer .model .toollibrary .ToolDefinition ;
2328import com .willwinder .universalgcodesender .Utils ;
2429import com .willwinder .universalgcodesender .model .Unit ;
30+ import com .willwinder .universalgcodesender .model .UnitUtils ;
31+ import com .willwinder .universalgcodesender .services .LookupService ;
2532import com .willwinder .universalgcodesender .uielements .TextFieldWithUnit ;
2633import net .miginfocom .swing .MigLayout ;
2734
2835import javax .swing .DefaultComboBoxModel ;
36+ import javax .swing .JButton ;
2937import javax .swing .JCheckBox ;
3038import javax .swing .JComboBox ;
3139import javax .swing .JLabel ;
3240import javax .swing .JPanel ;
3341import javax .swing .JSeparator ;
3442import javax .swing .JTextField ;
3543import javax .swing .SwingConstants ;
44+ import javax .swing .SwingUtilities ;
3645import java .awt .Dimension ;
3746import java .text .ParseException ;
47+ import java .util .Optional ;
3848
3949/**
4050 * @author Joacim Breiler
4151 */
4252public class ToolSettingsPanel extends JPanel {
4353 public static final String TOOL_FIELD_CONSTRAINT = "grow, wrap" ;
4454 private final transient Controller controller ;
45- private JTextField toolDiameter ;
46- private JTextField feedSpeed ;
47- private JTextField plungeSpeed ;
48- private JTextField depthPerPass ;
49- private JTextField stepOver ;
55+ private final transient ToolLibraryService libraryService ;
56+
57+ private JButton pickFromLibraryButton ;
58+ private JLabel selectedToolLabel ;
59+ private JPanel diameterSlot ;
60+ private TextFieldWithUnit toolDiameter ;
61+ private UnitUtils .Units diameterDisplayUnit = UnitUtils .Units .MM ;
62+ private TextFieldWithUnit feedSpeed ;
63+ private TextFieldWithUnit plungeSpeed ;
64+ private TextFieldWithUnit depthPerPass ;
65+ private TextFieldWithUnit stepOver ;
5066 private JTextField safeHeight ;
5167 private JCheckBox detectMaxSpindleSpeed ;
5268 private TextFieldWithUnit laserDiameter ;
5369 private TextFieldWithUnit maxSpindleSpeed ;
5470 private JComboBox <String > spindleDirection ;
5571 private TextFieldWithUnit flatnessPrecision ;
5672
73+ private transient ToolDefinition librarySnapshot ;
74+
5775 public ToolSettingsPanel (Controller controller ) {
5876 this .controller = controller ;
77+ this .libraryService = LookupService .lookupOptional (ToolLibraryService .class ).orElse (null );
5978 initComponents ();
60- setMinimumSize (new Dimension (300 , 400 ));
61- setPreferredSize (new Dimension (300 , 400 ));
79+ setMinimumSize (new Dimension (360 , 480 ));
80+ setPreferredSize (new Dimension (360 , 480 ));
81+ initialiseLibrarySnapshot ();
82+ attachDeviationHighlighters ();
6283 }
84+
6385 private void initComponents () {
64- setLayout (new MigLayout ("fill" , "[20%][80%]" ));
86+ setLayout (new MigLayout ("fill" , "[pref!][grow,fill]" ));
87+
88+ pickFromLibraryButton = new JButton ("Pick from Library…" );
89+ pickFromLibraryButton .addActionListener (e -> onPickFromLibrary ());
90+ add (pickFromLibraryButton , "spanx, growx, split 2" );
91+ selectedToolLabel = new JLabel (" " );
92+ add (selectedToolLabel , "wrap, growx" );
93+
94+ add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
6595
66- add (new JLabel ("Tool diameter" ));
67- toolDiameter = new TextFieldWithUnit (Unit .MM , 3 , controller .getSettings ().getToolDiameter ());
68- add (toolDiameter , TOOL_FIELD_CONSTRAINT );
96+ add (new JLabel ("Tool diameter" ));
97+ diameterSlot = new JPanel (new MigLayout ("insets 0, fill" ));
98+ rebuildDiameterField (UnitUtils .Units .MM , controller .getSettings ().getToolDiameter ());
99+ add (diameterSlot , TOOL_FIELD_CONSTRAINT );
69100
70- add (new JLabel ("Tool step over" ));
101+ add (new JLabel ("Tool step over (%)" ));
71102 stepOver = new TextFieldWithUnit (Unit .PERCENT , 2 ,
72103 controller .getSettings ().getToolStepOver ());
73104 add (stepOver , TOOL_FIELD_CONSTRAINT );
74105
75- add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
106+ add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
76107
77- add (new JLabel ("Default feed speed" ));
108+ add (new JLabel ("Default feed speed (" + Unit . MM_PER_MINUTE . getAbbreviation () + ")" ));
78109 feedSpeed = new TextFieldWithUnit (Unit .MM_PER_MINUTE , 0 , controller .getSettings ().getFeedSpeed ());
79110 add (feedSpeed , TOOL_FIELD_CONSTRAINT );
80111
81- add (new JLabel ("Plunge speed" ));
112+ add (new JLabel ("Plunge speed (" + Unit . MM_PER_MINUTE . getAbbreviation () + ")" ));
82113 plungeSpeed = new TextFieldWithUnit (Unit .MM_PER_MINUTE , 0 , controller .getSettings ().getPlungeSpeed ());
83114 add (plungeSpeed , TOOL_FIELD_CONSTRAINT );
84115
85- add (new JLabel ("Depth per pass" ));
116+ add (new JLabel ("Depth per pass (" + Unit . MM . getAbbreviation () + ")" ));
86117 depthPerPass = new TextFieldWithUnit (Unit .MM , 2 , controller .getSettings ().getDepthPerPass ());
87118 add (depthPerPass , TOOL_FIELD_CONSTRAINT );
88119
89- add (new JLabel ("Safe height" ));
120+ add (new JLabel ("Safe height (" + Unit . MM . getAbbreviation () + ")" ));
90121 safeHeight = new TextFieldWithUnit (Unit .MM , 2 , controller .getSettings ().getSafeHeight ());
91122 add (safeHeight , TOOL_FIELD_CONSTRAINT );
92123
93- add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
124+ add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
94125
95- add (new JLabel ("Detect max spindle speed" ));
126+ add (new JLabel ("Detect max spindle speed" ));
96127 detectMaxSpindleSpeed = new JCheckBox ("" , controller .getSettings ().getDetectMaxSpindleSpeed ());
97128 add (detectMaxSpindleSpeed , TOOL_FIELD_CONSTRAINT );
98129
99- add (new JLabel ("Max spindle speed" ));
130+ add (new JLabel ("Max spindle speed" ));
100131 maxSpindleSpeed = new TextFieldWithUnit (Unit .REVOLUTIONS_PER_MINUTE , 0 , controller .getSettings ().getMaxSpindleSpeed ());
101132 add (maxSpindleSpeed , TOOL_FIELD_CONSTRAINT );
102133
103- add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
134+ add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
104135
105- add (new JLabel ("Laser diameter" ));
136+ add (new JLabel ("Laser diameter" ));
106137 laserDiameter = new TextFieldWithUnit (Unit .MM , 3 , controller .getSettings ().getLaserDiameter ());
107138 add (laserDiameter , TOOL_FIELD_CONSTRAINT );
108139
109- add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
140+ add (new JSeparator (SwingConstants .HORIZONTAL ), "spanx, grow, wrap, hmin 2" );
110141
111- add (new JLabel ("Spindle Start Command" ));
112- spindleDirection = new JComboBox <>(new DefaultComboBoxModel <>(new String []{"M3" ,"M4" ,"M5" }));
142+ add (new JLabel ("Spindle Start Command" ));
143+ spindleDirection = new JComboBox <>(new DefaultComboBoxModel <>(new String []{"M3" , "M4" , "M5" }));
113144 spindleDirection .setSelectedItem (controller .getSettings ().getSpindleDirection ());
114145 add (spindleDirection , TOOL_FIELD_CONSTRAINT );
115146
116- add (new JLabel ("Arc precision" ));
147+ add (new JLabel ("Arc precision" ));
117148 flatnessPrecision = new TextFieldWithUnit (Unit .MM , 3 , controller .getSettings ().getFlatnessPrecision ());
118149 add (flatnessPrecision , TOOL_FIELD_CONSTRAINT );
119150 }
120151
152+ private void rebuildDiameterField (UnitUtils .Units unit , double valueInFieldUnit ) {
153+ diameterDisplayUnit = unit == null ? UnitUtils .Units .MM : unit ;
154+ Unit fieldUnit = diameterDisplayUnit == UnitUtils .Units .INCH ? Unit .INCH : Unit .MM ;
155+ int decimals = diameterDisplayUnit == UnitUtils .Units .INCH ? 4 : 3 ;
156+ diameterSlot .removeAll ();
157+ toolDiameter = new TextFieldWithUnit (fieldUnit , decimals , valueInFieldUnit );
158+ diameterSlot .add (toolDiameter , "grow" );
159+ diameterSlot .revalidate ();
160+ diameterSlot .repaint ();
161+ }
162+
163+ private void initialiseLibrarySnapshot () {
164+ Settings settings = controller .getSettings ();
165+ String activeId = settings .getCurrentToolId ();
166+ if (activeId != null && libraryService != null ) {
167+ Optional <ToolDefinition > tool = libraryService .getById (activeId );
168+ if (tool .isPresent () && !tool .get ().isCustomSentinel ()) {
169+ librarySnapshot = tool .get ();
170+ SwingUtilities .invokeLater (() -> applyLibrarySnapshotToFields (false ));
171+ return ;
172+ }
173+ }
174+ ToolDefinition snapshot = settings .getCurrentToolSnapshot ();
175+ if (snapshot != null && !snapshot .isCustomSentinel ()) {
176+ librarySnapshot = new ToolDefinition (snapshot );
177+ }
178+ updateSelectedToolLabel ();
179+ }
180+
181+ private void attachDeviationHighlighters () {
182+ DeviationHighlighter .attachDouble (toolDiameter , () -> librarySnapshot == null ? null
183+ : valueInDisplayUnit (librarySnapshot .getDiameterInMm ()));
184+ DeviationHighlighter .attachDouble (maxSpindleSpeed ,
185+ () -> librarySnapshot == null ? null : (double ) librarySnapshot .getMaxSpindleSpeed ());
186+ DeviationHighlighter .attachDouble (flatnessPrecision ,
187+ () -> librarySnapshot == null ? null : null );
188+ DeviationHighlighter .attachDouble (laserDiameter ,
189+ () -> librarySnapshot == null ? null : null );
190+
191+ DeviationHighlighter .attachDouble (feedSpeed ,
192+ () -> librarySnapshot == null ? null : (double ) librarySnapshot .getFeedSpeed ());
193+ DeviationHighlighter .attachDouble (plungeSpeed ,
194+ () -> librarySnapshot == null ? null : (double ) librarySnapshot .getPlungeSpeed ());
195+ DeviationHighlighter .attachDouble (depthPerPass ,
196+ () -> librarySnapshot == null ? null : librarySnapshot .getDepthPerPass ());
197+ DeviationHighlighter .attachDouble (stepOver ,
198+ () -> librarySnapshot == null ? null : librarySnapshot .getStepOverPercent ());
199+ DeviationHighlighter .attachCombo (spindleDirection ,
200+ () -> librarySnapshot == null ? null : librarySnapshot .getSpindleDirection ());
201+ }
202+
203+ private Double valueInDisplayUnit (double mm ) {
204+ if (diameterDisplayUnit == UnitUtils .Units .INCH ) {
205+ return mm * UnitUtils .scaleUnits (UnitUtils .Units .MM , UnitUtils .Units .INCH );
206+ }
207+ return mm ;
208+ }
209+
210+ private void onPickFromLibrary () {
211+ if (libraryService == null ) {
212+ return ;
213+ }
214+ Optional <ToolDefinition > picked = ToolLibraryPickerDialog .pick (
215+ SwingUtilities .getWindowAncestor (this ),
216+ controller .getSettings ().getPreferredUnits ());
217+ picked .ifPresent (tool -> {
218+ if (tool .isCustomSentinel ()) {
219+ librarySnapshot = null ;
220+ updateSelectedToolLabel ();
221+ } else {
222+ librarySnapshot = tool ;
223+ applyLibrarySnapshotToFields (true );
224+ }
225+ });
226+ }
227+
228+ private void applyLibrarySnapshotToFields (boolean populateAll ) {
229+ if (librarySnapshot == null ) return ;
230+ rebuildDiameterField (librarySnapshot .getDiameterUnit (), librarySnapshot .getDiameter ());
231+ // Re-attach highlighter to the new field
232+ DeviationHighlighter .attachDouble (toolDiameter , () -> librarySnapshot == null ? null
233+ : valueInDisplayUnit (librarySnapshot .getDiameterInMm ()));
234+ if (populateAll ) {
235+ try {
236+ feedSpeed .setDoubleValue (librarySnapshot .getFeedSpeed ());
237+ plungeSpeed .setDoubleValue (librarySnapshot .getPlungeSpeed ());
238+ depthPerPass .setDoubleValue (librarySnapshot .getDepthPerPass ());
239+ stepOver .setDoubleValue (librarySnapshot .getStepOverPercent ());
240+ maxSpindleSpeed .setDoubleValue (librarySnapshot .getMaxSpindleSpeed ());
241+ spindleDirection .setSelectedItem (librarySnapshot .getSpindleDirection ());
242+ } catch (RuntimeException ignored ) {
243+ // Bad format — leave field as-is
244+ }
245+ }
246+ updateSelectedToolLabel ();
247+ }
248+
249+ private void updateSelectedToolLabel () {
250+ if (librarySnapshot == null ) {
251+ selectedToolLabel .setText ("— Custom —" );
252+ } else {
253+ selectedToolLabel .setText (librarySnapshot .getName () == null
254+ ? "— Custom —" : librarySnapshot .getName ());
255+ }
256+ }
257+
121258 public double getToolDiameter () {
122259 try {
123- return Utils .formatter .parse (toolDiameter .getText ()).doubleValue ();
260+ double displayed = Utils .formatter .parse (toolDiameter .getText ()).doubleValue ();
261+ return displayed * UnitUtils .scaleUnits (diameterDisplayUnit , UnitUtils .Units .MM );
124262 } catch (ParseException e ) {
125263 return controller .getSettings ().getToolDiameter ();
126264 }
127265 }
128266
129267 public double getStepOver () {
130- try {
131- return Utils .formatter .parse (stepOver .getText ()).doubleValue () / 100 ;
132- } catch (ParseException e ) {
133- return controller .getSettings ().getToolStepOver ();
134- }
268+ return stepOver .getDoubleValue ();
135269 }
136270
137271 public double getDepthPerPass () {
@@ -212,6 +346,13 @@ public Settings getSettings() {
212346 settings .setDetectMaxSpindleSpeed (getDetectMaxSpindleSpeed ());
213347 settings .setSpindleDirection (getSpindleDirection ());
214348 settings .setFlatnessPrecision (getFlatnessPrecision ());
349+ if (librarySnapshot != null ) {
350+ settings .setCurrentToolId (librarySnapshot .getId ());
351+ settings .setCurrentToolSnapshot (new ToolDefinition (librarySnapshot ));
352+ } else {
353+ settings .setCurrentToolId (null );
354+ settings .setCurrentToolSnapshot (null );
355+ }
215356 return settings ;
216357 }
217358}
0 commit comments