CAM: Refactor LeadInOut task panel to use QuantitySpinBox and improve migration/visibility logic

- Replace QDoubleSpinBox widgets with Gui::QuantitySpinBox in DressUpLeadInOutEdit.ui for all lead-in/out numeric fields, enabling unit/expressions support.
- Register QuantitySpinBox as a custom widget in the .ui file.
- Refactor TaskDressupLeadInOut panel setup:
  - Add setupSpinBoxes, setupGroupBoxes, setupDynamicVisibility for cleaner UI initialization.
  - Use PathGuiUtil.QuantitySpinBox for all numeric fields and ensure updateWidget() is called for each.
  - Centralize signal registration and field updates using getSignalsForUpdate and pageGetFields.
  - Move group box signal handler to a class method.
  - Share hideModes dictionary for field visibility logic.
  - Add dynamic label switching for "Radius"/"Length" with translation placeholders.
  - Remove the Include layers Check Box
- Improve ObjectDressup migration:
  - Use shared hideModes from TaskDressupLeadInOut.
  - Set default angles to 90 instead of 45.
  - Preserve previous style values when migrating StyleOn/StyleOff.
  - Ensure field visibility is updated after migration.
- Add Perpendicular and Tangent to lead_styles in correct order.
This commit is contained in:
Billy Huddleston
2025-10-23 13:37:21 -04:00
committed by Chris Hennes
parent d92c8fbfa3
commit b1437fe189
2 changed files with 148 additions and 123 deletions

View File

@@ -62,20 +62,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="dspRadiusIn">
<property name="toolTip">
<string>Length of the Lead-in</string>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>999999.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
<widget class="Gui::QuantitySpinBox" name="dspRadiusIn"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_1">
@@ -85,20 +72,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="dspAngleIn">
<property name="toolTip">
<string>Angular extent of the lead in arc (degrees)</string>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="singleStep">
<double>10.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
<widget class="Gui::QuantitySpinBox" name="dspAngleIn"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
@@ -108,17 +82,7 @@
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="dspOffsetIn">
<property name="minimum">
<double>-999999.000000000000000</double>
</property>
<property name="maximum">
<double>999999.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
<widget class="Gui::QuantitySpinBox" name="dspOffsetIn"/>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="chkInvertDirectionIn">
@@ -174,20 +138,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="dspRadiusOut">
<property name="toolTip">
<string>Length of the Lead-out</string>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>999999.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
<widget class="Gui::QuantitySpinBox" name="dspRadiusOut"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
@@ -197,20 +148,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="dspAngleOut">
<property name="toolTip">
<string>Angular extent of the lead out arc (degrees)</string>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="singleStep">
<double>10.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
<widget class="Gui::QuantitySpinBox" name="dspAngleOut"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_17">
@@ -220,17 +158,7 @@
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="dspOffsetOut">
<property name="minimum">
<double>-999999.000000000000000</double>
</property>
<property name="maximum">
<double>999999.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
<widget class="Gui::QuantitySpinBox" name="dspOffsetOut"/>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="chkInvertDirectionOut">
@@ -262,16 +190,6 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="chkLayers">
<property name="toolTip">
<string>Apply lead-in/out on all layers</string>
</property>
<property name="text">
<string>Include layers</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
@@ -280,10 +198,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="dspRetractThreshold">
<property name="maximum">
<double>999999.000000000000000</double>
</property>
<widget class="Gui::QuantitySpinBox" name="dspRetractThreshold">
<property name="unit" stdset="0">
<string notr="true"/>
</property>
@@ -308,4 +223,11 @@
</widget>
<resources/>
<connections/>
<customwidgets>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
</customwidgets>
</ui>

View File

@@ -27,6 +27,7 @@ import Path
import Path.Base.Language as PathLanguage
import Path.Dressup.Utils as PathDressup
import PathScripts.PathUtils as PathUtils
import Path.Base.Gui.Util as PathGuiUtil
from Path.Base.Util import toolControllerForOp
import copy
import math
@@ -49,6 +50,8 @@ lead_styles = (
# common options first
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Arc"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Line"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Perpendicular"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Tangent"),
# additional options, alphabetical order
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Arc3d"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "ArcZ"),
@@ -56,8 +59,6 @@ lead_styles = (
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Line3d"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "LineZ"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "No Retract"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Perpendicular"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Tangent"),
QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Vertical"),
)
@@ -170,8 +171,8 @@ class ObjectDressup:
def setup(self, obj):
obj.LeadIn = True
obj.LeadOut = True
obj.AngleIn = 45
obj.AngleOut = 45
obj.AngleIn = 90
obj.AngleOut = 90
obj.InvertIn = False
obj.InvertOut = False
obj.RapidPlunge = False
@@ -227,13 +228,8 @@ class ObjectDressup:
if obj.AngleOut < limit_angle_out:
obj.AngleOut = limit_angle_out
hideModes = {
"Angle": ("No Retract", "Perpendicular", "Tangent", "Vertical"),
"Invert": ("No Retract", "ArcZ", "LineZ", "Vertical"),
"Offset": ("No Retract"),
"Radius": ("No Retract", "Vertical"),
}
for k, v in hideModes.items():
# Use shared hideModes from TaskDressupLeadInOut
for k, v in TaskDressupLeadInOut.hideModes.items():
obj.setEditorMode(k + "In", 2 if obj.StyleIn in v else 0)
obj.setEditorMode(k + "Out", 2 if obj.StyleOut in v else 0)
@@ -268,7 +264,12 @@ class ObjectDressup:
)
obj.StyleIn = lead_styles
obj.removeProperty("StyleOn")
obj.StyleIn = "Arc"
# Set previous value if possible
if styleOn in lead_styles:
obj.StyleIn = styleOn
elif styleOn == "Arc":
obj.StyleIn = "Arc"
obj.AngleIn = 90
if hasattr(obj, "StyleOff"):
# Replace StyleOff by StyleOut
styleOff = obj.StyleOff
@@ -280,7 +281,12 @@ class ObjectDressup:
)
obj.StyleOut = lead_styles
obj.removeProperty("StyleOff")
obj.StyleOut = "Arc"
# Set previous value if possible
if styleOff in lead_styles:
obj.StyleOut = styleOff
elif styleOff == "Arc":
obj.StyleOut = "Arc"
obj.AngleOut = 90
if not hasattr(obj, "AngleIn"):
obj.addProperty(
@@ -289,7 +295,7 @@ class ObjectDressup:
"Path Lead-in",
QT_TRANSLATE_NOOP("App::Property", "Angle of the Lead-In (1..90)"),
)
obj.AngleIn = 45
obj.AngleIn = 90
if not hasattr(obj, "AngleOut"):
obj.addProperty(
"App::PropertyAngle",
@@ -297,7 +303,7 @@ class ObjectDressup:
"Path Lead-out",
QT_TRANSLATE_NOOP("App::Property", "Angle of the Lead-Out (1..90)"),
)
obj.AngleOut = 45
obj.AngleOut = 90
if styleOn:
if styleOn == "Arc":
@@ -402,6 +408,11 @@ class ObjectDressup:
obj.RetractThreshold = 999999
obj.removeProperty("KeepToolDown")
# Ensure correct initial visibility of fields after defaults are set
for k, v in TaskDressupLeadInOut.hideModes.items():
obj.setEditorMode(k + "In", 2 if obj.StyleIn in v else 0)
obj.setEditorMode(k + "Out", 2 if obj.StyleOut in v else 0)
# Get direction for lead-in/lead-out in XY plane
def getLeadDir(self, obj, invert=False):
output = math.pi / 2
@@ -587,7 +598,6 @@ class ObjectDressup:
return commands
def getLeadStart(self, obj, move, first, inInstrPrev, outInstrPrev):
# tangent begin move
# <----_-----x-------------------x
# / |
@@ -734,7 +744,6 @@ class ObjectDressup:
return lead
def getLeadEnd(self, obj, move, last, outInstrPrev):
# move end tangent
# x-------------------x-----_---->
# | \
@@ -1052,7 +1061,6 @@ class ObjectDressup:
# Process all instructions
for i, instr in enumerate(source):
# Process not mill instruction
if not self.isCuttingMove(instr):
if not instr.isMove():
@@ -1165,28 +1173,123 @@ class TaskDressupLeadInOut(SimpleEditPanel):
_ui_file = ":/panels/DressUpLeadInOutEdit.ui"
def setupUi(self):
self.setupSpinBoxes()
self.setupGroupBoxes()
self.setupDynamicVisibility()
self.setFields()
self.pageRegisterSignalHandlers()
def setupSpinBoxes(self):
self.connectWidget("InvertIn", self.form.chkInvertDirectionIn)
self.connectWidget("InvertOut", self.form.chkInvertDirectionOut)
self.connectWidget("RadiusIn", self.form.dspRadiusIn)
self.connectWidget("RadiusOut", self.form.dspRadiusOut)
self.connectWidget("StyleIn", self.form.cboStyleIn)
self.connectWidget("StyleOut", self.form.cboStyleOut)
self.connectWidget("AngleIn", self.form.dspAngleIn)
self.connectWidget("AngleOut", self.form.dspAngleOut)
self.connectWidget("OffsetIn", self.form.dspOffsetIn)
self.connectWidget("OffsetOut", self.form.dspOffsetOut)
self.radiusIn = PathGuiUtil.QuantitySpinBox(self.form.dspRadiusIn, self.obj, "RadiusIn")
self.radiusOut = PathGuiUtil.QuantitySpinBox(self.form.dspRadiusOut, self.obj, "RadiusOut")
self.angleIn = PathGuiUtil.QuantitySpinBox(self.form.dspAngleIn, self.obj, "AngleIn")
self.angleOut = PathGuiUtil.QuantitySpinBox(self.form.dspAngleOut, self.obj, "AngleOut")
self.offsetIn = PathGuiUtil.QuantitySpinBox(self.form.dspOffsetIn, self.obj, "OffsetIn")
self.offsetOut = PathGuiUtil.QuantitySpinBox(self.form.dspOffsetOut, self.obj, "OffsetOut")
self.connectWidget("RapidPlunge", self.form.chkRapidPlunge)
self.connectWidget("RetractThreshold", self.form.dspRetractThreshold)
self.setFields()
self.retractThreshold = PathGuiUtil.QuantitySpinBox(
self.form.dspRetractThreshold, self.obj, "RetractThreshold"
)
def handleGroupBoxCheck():
self.obj.LeadIn = self.form.groupBoxIn.isChecked()
self.obj.LeadOut = self.form.groupBoxOut.isChecked()
self.radiusIn.updateWidget()
self.radiusOut.updateWidget()
self.angleIn.updateWidget()
self.angleOut.updateWidget()
self.offsetIn.updateWidget()
self.offsetOut.updateWidget()
self.retractThreshold.updateWidget()
def setupGroupBoxes(self):
self.form.groupBoxIn.setChecked(self.obj.LeadIn)
self.form.groupBoxOut.setChecked(self.obj.LeadOut)
self.form.groupBoxIn.clicked.connect(handleGroupBoxCheck)
self.form.groupBoxOut.clicked.connect(handleGroupBoxCheck)
self.form.groupBoxIn.clicked.connect(self.handleGroupBoxCheck)
self.form.groupBoxOut.clicked.connect(self.handleGroupBoxCheck)
def handleGroupBoxCheck(self):
self.obj.LeadIn = self.form.groupBoxIn.isChecked()
self.obj.LeadOut = self.form.groupBoxOut.isChecked()
def setupDynamicVisibility(self):
self.form.cboStyleIn.currentIndexChanged.connect(self.updateLeadInVisibility)
self.form.cboStyleOut.currentIndexChanged.connect(self.updateLeadOutVisibility)
self.updateLeadInVisibility()
self.updateLeadOutVisibility()
def getSignalsForUpdate(self):
signals = []
signals.append(self.form.dspRadiusIn.editingFinished)
signals.append(self.form.dspRadiusOut.editingFinished)
signals.append(self.form.dspAngleIn.editingFinished)
signals.append(self.form.dspAngleOut.editingFinished)
signals.append(self.form.dspOffsetIn.editingFinished)
signals.append(self.form.dspOffsetOut.editingFinished)
signals.append(self.form.dspRetractThreshold.editingFinished)
return signals
def pageGetFields(self):
PathGuiUtil.updateInputField(self.obj, "RadiusIn", self.form.dspRadiusIn)
PathGuiUtil.updateInputField(self.obj, "RadiusOut", self.form.dspRadiusOut)
PathGuiUtil.updateInputField(self.obj, "AngleIn", self.form.dspAngleIn)
PathGuiUtil.updateInputField(self.obj, "AngleOut", self.form.dspAngleOut)
PathGuiUtil.updateInputField(self.obj, "OffsetIn", self.form.dspOffsetIn)
PathGuiUtil.updateInputField(self.obj, "OffsetOut", self.form.dspOffsetOut)
PathGuiUtil.updateInputField(self.obj, "RetractThreshold", self.form.dspRetractThreshold)
def pageRegisterSignalHandlers(self):
for signal in self.getSignalsForUpdate():
signal.connect(self.pageGetFields)
# Shared hideModes for both LeadIn and LeadOut
hideModes = {
"Angle": ("No Retract", "Perpendicular", "Tangent", "Vertical"),
"Invert": ("No Retract", "ArcZ", "LineZ", "Vertical", "Perpendicular", "Tangent"),
"Offset": ("No Retract"),
"Radius": ("No Retract", "Vertical"),
}
def updateLeadVisibility(self, style, angleWidget, invertWidget, angleLabel, radiusLabel=None):
# Dynamic label for Radius/Length
arc_styles = ("Arc", "Arc3d", "ArcZ", "Helix")
if radiusLabel and hasattr(self.form, radiusLabel):
if style in arc_styles:
getattr(self.form, radiusLabel).setText("Radius")
# Will do translation later
# getattr(self.form, radiusLabel).setText(translate("CAM_DressupLeadInOut", "Radius"))
else:
getattr(self.form, radiusLabel).setText("Length")
# Will do translation later
# getattr(self.form, radiusLabel).setText(translate("CAM_DressupLeadInOut", "Length"))
# Angle
if style in self.hideModes["Angle"]:
angleWidget.hide()
if hasattr(self.form, angleLabel):
getattr(self.form, angleLabel).hide()
else:
angleWidget.show()
if hasattr(self.form, angleLabel):
getattr(self.form, angleLabel).show()
# Invert Direction
if style in self.hideModes["Invert"]:
invertWidget.hide()
else:
invertWidget.show()
def updateLeadInVisibility(self):
style = self.form.cboStyleIn.currentText()
self.updateLeadVisibility(
style, self.form.dspAngleIn, self.form.chkInvertDirectionIn, "label_1", "label_5"
)
def updateLeadOutVisibility(self):
style = self.form.cboStyleOut.currentText()
self.updateLeadVisibility(
style, self.form.dspAngleOut, self.form.chkInvertDirectionOut, "label_11", "label_15"
)
class ViewProviderDressup: