From db9a615985d8497cb8075cfd702916fbfd4a400f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 2 Sep 2025 11:19:33 -0400 Subject: [PATCH] [CAM] Add UI elements for viewing and editing tool controller parameters from within the operation edit UI (#23180) * [CAM] extract tool controller ui elements into their own file * [CAM] make the changes in the tool controller UI only apply when ok is clicked * [CAM] Add tool controller edit panel to the Profile operation * [CAM] Add copy button to in-operation tool controller editor * [CAM] clean up changes * [CAM] Add tool controller edit UI to all operations Notes on changes that were not a simple copy/paste job from the changes I made for Profile: - Deburr: changed TC/coolant rows from 1 and 2 to 0 and 1 - Probe: didn't work at all initially due to bug in main where ShapeName changed to ShapeType. I added a utility for reading either a ShapeType or a ShapeName (check for both properties, convert ShapeType to lower case) and applied it to probe and camotics - Drilling: moved Keep Tool Down checkbox up from row 8 to row 2 (all intermediate rows were missing) and added the edit checkbox in row 3 below it - VBit, Probe (or anything else that requires a specific tool type): in Base.py setupToolController(), I added a check to see if the currently selected tool is an invalid type, and if so and there is a valid tool, then change to that one. This fixes two UI bugs. Plausibly pre-existing, if there is one valid tool and an invalid tool is selected, it's impossible to switch to the valid one because you can't generate a combo box change event for the new tool. Definitely new: if an invalid tool is selected and there are no valid tools, the combo box will be empty but the new tool controller edit utility will let you edit the current TC anyway. - Thread Milling: replaced the Tool Controller GroupBox with the standard QFrame layout, and added the checkbox. Note that this operation doesn't have a UI element for coolant -- likely a bug, but I didn't look into it - Surface: Changed from form layout to grid layout. Deleted an old SurfaceEdit.ui file -- it was replaced with PageOpSurfaceEdit.ui in 2017 but not deleted (commit 77af19e7489e1fc637a68cdad220e5dd430dc2b9) - Waterline: Changed from form layout to grid layout * [CAM] Bug fixes setupUi() wasn't called on the tool controller editor, preventing changes in its UI from being written back to the object immediately. This caused weird behavior where if you edited a field twice it would reset the second time it was focused Added a hook to automatically update the TC combo box when the TC name (or anything else about it, since that was easier) changes * Fix usage of QSignalBlockers * [CAM] Block scroll events on tool number and spindle direction when not focused Specifically, if you mouse over either of these UI elements and use the scroll wheel, it used to focus the element and change its value. This commit makes it do neither of those things, for these specific elements, as a measure against users accidentally changing these values. * disable tcNumber edit field in operations panel * Add "New tool controller" option to TC combo box When selected, it opens (toggles, technically) the tool bit dock and returns to the previous selection. Adding a new tool controller using the dock (already, before this commit) automatically switches the operation's tool controller to the new one * Add "Copy" option to tool controller combo box * Copy TC function only in combo box, no button * [CAM] update in-operation "new tool controller" function to use a dialog * [CAM] make the tool selector always a dialog and never a dock * remove spacer from ToolControllerEdit.ui to fix exces white space * [CAM] change tool dialog default sizing/spacing * [CAM] fix bug where copying tool controller doesn't copy all values --- src/Mod/CAM/Gui/Resources/Path.qrc | 1 + .../Resources/panels/DlgToolControllerEdit.ui | 304 +-------------- .../Resources/panels/PageOpAdaptiveEdit.ui | 9 +- .../Gui/Resources/panels/PageOpCustomEdit.ui | 7 + .../Gui/Resources/panels/PageOpDeburrEdit.ui | 15 +- .../Resources/panels/PageOpDrillingEdit.ui | 11 +- .../Gui/Resources/panels/PageOpEngraveEdit.ui | 7 + .../Gui/Resources/panels/PageOpHelixEdit.ui | 7 + .../Resources/panels/PageOpPocketFullEdit.ui | 7 + .../Gui/Resources/panels/PageOpProbeEdit.ui | 7 + .../Resources/panels/PageOpProfileFullEdit.ui | 7 + .../Gui/Resources/panels/PageOpSlotEdit.ui | 9 +- .../Gui/Resources/panels/PageOpSurfaceEdit.ui | 9 +- .../Gui/Resources/panels/PageOpTappingEdit.ui | 7 + .../panels/PageOpThreadMillingEdit.ui | 33 +- .../Gui/Resources/panels/PageOpVcarveEdit.ui | 7 + .../Resources/panels/PageOpWaterlineEdit.ui | 9 +- .../CAM/Gui/Resources/panels/SurfaceEdit.ui | 351 ------------------ .../Resources/panels/ToolControllerEdit.ui | 240 ++++++++++++ src/Mod/CAM/Path/Main/Gui/Camotics.py | 7 +- src/Mod/CAM/Path/Op/Gui/Adaptive.py | 6 +- src/Mod/CAM/Path/Op/Gui/Base.py | 149 +++++++- src/Mod/CAM/Path/Op/Probe.py | 2 +- src/Mod/CAM/Path/Tool/Gui/Controller.py | 131 ++++--- src/Mod/CAM/Path/Tool/library/ui/cmd.py | 6 +- src/Mod/CAM/Path/Tool/library/ui/dock.py | 43 ++- src/Mod/CAM/PathScripts/PathUtils.py | 8 + 27 files changed, 637 insertions(+), 762 deletions(-) delete mode 100644 src/Mod/CAM/Gui/Resources/panels/SurfaceEdit.ui create mode 100644 src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui diff --git a/src/Mod/CAM/Gui/Resources/Path.qrc b/src/Mod/CAM/Gui/Resources/Path.qrc index 287d7a2a61..071ae97d00 100644 --- a/src/Mod/CAM/Gui/Resources/Path.qrc +++ b/src/Mod/CAM/Gui/Resources/Path.qrc @@ -124,6 +124,7 @@ panels/SetupOp.ui panels/ToolBitEditor.ui panels/ToolBitLibraryEdit.ui + panels/ToolControllerEdit.ui panels/TaskPathCamoticsSim.ui panels/TaskPathSimulator.ui panels/TaskCAMSimulator.ui diff --git a/src/Mod/CAM/Gui/Resources/panels/DlgToolControllerEdit.ui b/src/Mod/CAM/Gui/Resources/panels/DlgToolControllerEdit.ui index 511aafc20e..ce26f9dc62 100644 --- a/src/Mod/CAM/Gui/Resources/panels/DlgToolControllerEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/DlgToolControllerEdit.ui @@ -15,293 +15,8 @@ - - - - - 0 - 0 - 543 - 638 - - - - Controller - - - - - - Controller Name / Tool Number - - - - - - false - - - - - - - 99999 - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - Horizontal feed - - - - - - - - 0 - 0 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.000000000000000 - - - 9999999.000000000000000 - - - mm/s - - - - - - - Vertical feed - - - - - - - - 0 - 0 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.000000000000000 - - - 9999999.000000000000000 - - - mm/s - - - - - - - Horizontal rapid - - - - - - - - 0 - 0 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.000000000000000 - - - 9999999.000000000000000 - - - mm/s - - - - - - - Vertical rapid - - - - - - - - 0 - 0 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.000000000000000 - - - 9999999.000000000000000 - - - mm/s - - - - - - - - - - Spindle - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - - Forward - - - - - Reverse - - - - - - - - 0.000000000000000 - - - 100000.000000000000000 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - 0 - 0 - 543 - 638 - - - - Tool - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - QFrame::WinPanel - - - Any modifications only affect this ToolController! - - - Qt::AlignCenter - - - true - - - - - + + @@ -316,22 +31,7 @@ - - - Gui::QuantitySpinBox - QDoubleSpinBox -
Gui/QuantitySpinBox.h
-
-
- tcName - tcNumber - horizFeed - vertFeed - horizRapid - vertRapid - spindleSpeed - spindleDirection buttonBox diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui index 31d6ff4b93..667bc2755a 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui @@ -25,7 +25,7 @@ - + @@ -37,6 +37,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpCustomEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpCustomEdit.ui index e7f084e0fc..ab8d05a783 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpCustomEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpCustomEdit.ui @@ -53,6 +53,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpDeburrEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpDeburrEdit.ui index 8b0ae3bdb8..0fe4be6b3d 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpDeburrEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpDeburrEdit.ui @@ -29,7 +29,7 @@ QFrame::Raised - + @@ -54,14 +54,14 @@ - + The tool and its settings to be used for this operation - + @@ -86,13 +86,20 @@ - + The tool and its settings to be used for this operation + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui index d8920da0f7..d6a0330d5f 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui @@ -56,8 +56,8 @@ The tool and its settings to be used for this operation - - + + Do not retract after every hole @@ -67,6 +67,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpEngraveEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpEngraveEdit.ui index 3296627d78..e2e5579a33 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpEngraveEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpEngraveEdit.ui @@ -51,6 +51,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpHelixEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpHelixEdit.ui index 02d656535a..574d8371b2 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpHelixEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpHelixEdit.ui @@ -51,6 +51,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui index ecff4f3a4c..e08bdac8d1 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui @@ -51,6 +51,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpProbeEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpProbeEdit.ui index d3dc72f8e9..9306803c0b 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpProbeEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpProbeEdit.ui @@ -43,6 +43,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui index 6b215e43c1..f907d6397a 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui @@ -51,6 +51,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpSlotEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpSlotEdit.ui index 85f7cd7aa8..b1ece310ef 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpSlotEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpSlotEdit.ui @@ -25,7 +25,7 @@ QFrame::Raised - + 3 @@ -53,6 +53,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui index 9aea7bc7b4..4fe64dbbaf 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -22,7 +22,7 @@ QFrame::Raised - + @@ -51,6 +51,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpTappingEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpTappingEdit.ui index 242221bf33..133ed66cd6 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpTappingEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpTappingEdit.ui @@ -111,6 +111,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpThreadMillingEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpThreadMillingEdit.ui index 71f59ac8c0..7368d2c161 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpThreadMillingEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpThreadMillingEdit.ui @@ -15,13 +15,34 @@ - - - Tool Controller + + + QFrame::StyledPanel - - - + + QFrame::Raised + + + + + + Tool Controller + + + + + + + The tool and its settings to be used for this operation. + + + + + + + Edit Tool Controller + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui index 3115241023..e403fa39bc 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -51,6 +51,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui index 1e7b8c6cf4..173f0d78b4 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -22,7 +22,7 @@ QFrame::Raised - + @@ -47,6 +47,13 @@ + + + + Edit Tool Controller + + + diff --git a/src/Mod/CAM/Gui/Resources/panels/SurfaceEdit.ui b/src/Mod/CAM/Gui/Resources/panels/SurfaceEdit.ui deleted file mode 100644 index 7bb41cbd01..0000000000 --- a/src/Mod/CAM/Gui/Resources/panels/SurfaceEdit.ui +++ /dev/null @@ -1,351 +0,0 @@ - - - TaskPanel - - - - 0 - 0 - 363 - 523 - - - - - 0 - 400 - - - - Surface - - - - - - 3 - - - - true - - - - 0 - 0 - 347 - 391 - - - - - :/icons/CAM_BaseGeometry.svg:/icons/CAM_BaseGeometry.svg - - - Base geometry - - - - - - Drag to reorder, then update - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - false - - - - - - - Add item selected in window - - - Add - - - - - - - Remove item selected in list, then update - - - Remove - - - - - - - Update the path with the removed and reordered items - - - Update - - - - - - - All objects will be profiled using the same depth and speed settings - - - Qt::AutoText - - - true - - - - - - - - - 0 - 0 - 347 - 391 - - - - - :/icons/CAM_Depths.svg:/icons/CAM_Depths.svg - - - Depths - - - - - - mm - - - - - - - Start depth - - - - - - - mm - - - - - - - Final depth - - - - - - - mm - - - - - - - Step down - - - - - - - mm - - - - - - - Finish step down - - - - - - - - - 0 - 0 - 347 - 391 - - - - - :/icons/CAM_Heights.svg:/icons/CAM_Heights.svg - - - Heights - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - mm - - - - - - - Safe height - - - - - - - mm - - - - - - - Clearance height - - - - - - - - - 0 - 0 - 347 - 391 - - - - - :/icons/CAM_OperationB.svg:/icons/CAM_OperationB.svg - - - Operation - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - Tool controller - - - - - - - - - - Coolant mode - - - - - - - The tool and its settings to be used for this operation. - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - Algorithm - - - - - - - - OCL dropcutter - - - - - OCL waterline - - - - - - - - - - - Qt::Vertical - - - - 20 - 274 - - - - - - - - - - - - - Gui::InputField - QLineEdit -
Gui/InputField.h
-
-
- - - - - -
diff --git a/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui b/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui new file mode 100644 index 0000000000..bbb94b24b2 --- /dev/null +++ b/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui @@ -0,0 +1,240 @@ + + + Frame + + + + 0 + 0 + 561 + 739 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + + Controller Name / Tool Number + + + + + + false + + + + + + + Qt::StrongFocus + + + QAbstractSpinBox::NoButtons + + + 99999 + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Horizontal feed + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + + + + Vertical feed + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + + + + Horizontal rapid + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + + + + Vertical rapid + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + + + + + + + Spindle + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Qt::StrongFocus + + + + Forward + + + + + Reverse + + + + + + + + 0.000000000000000 + + + 100000.000000000000000 + + + + + + + + + + + Gui::QuantitySpinBox + QDoubleSpinBox +
Gui/QuantitySpinBox.h
+
+
+ + tcName + tcNumber + horizFeed + vertFeed + horizRapid + vertRapid + spindleSpeed + spindleDirection + + + +
diff --git a/src/Mod/CAM/Path/Main/Gui/Camotics.py b/src/Mod/CAM/Path/Main/Gui/Camotics.py index 2be847ed63..3c28187ef0 100644 --- a/src/Mod/CAM/Path/Main/Gui/Camotics.py +++ b/src/Mod/CAM/Path/Main/Gui/Camotics.py @@ -28,6 +28,7 @@ import FreeCADGui import Mesh import Path import PathScripts +import PathScripts.PathUtils as PathUtils import Path.Post.Command as PathPost import camotics import io @@ -191,7 +192,7 @@ class CamoticsSimulation(QtCore.QObject): self.SIM.set_tool( t.ToolNumber, metric=True, - shape=self.SHAPEMAP.get(t.Tool.ShapeName, "Cylindrical"), + shape=self.SHAPEMAP.get(PathUtils.getToolShapeName(t.Tool), "Cylindrical"), length=t.Tool.Length.Value, diameter=t.Tool.Diameter.Value, ) @@ -283,7 +284,9 @@ class CamoticsSimulation(QtCore.QObject): if hasattr(t.Tool, "Camotics"): toolitem["shape"] = t.Tool.Camotics else: - toolitem["shape"] = self.SHAPEMAP.get(t.Tool.ShapeName, "Cylindrical") + toolitem["shape"] = self.SHAPEMAP.get( + PathUtils.getToolShapeName(t.Tool), "Cylindrical" + ) toolitem["length"] = t.Tool.Length.Value toolitem["diameter"] = t.Tool.Diameter.Value diff --git a/src/Mod/CAM/Path/Op/Gui/Adaptive.py b/src/Mod/CAM/Path/Op/Gui/Adaptive.py index d16b6ace33..58abd359ba 100644 --- a/src/Mod/CAM/Path/Op/Gui/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Gui/Adaptive.py @@ -57,7 +57,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals = [] signals.append(self.form.Side.currentIndexChanged) signals.append(self.form.OperationType.currentIndexChanged) - signals.append(self.form.ToolController.currentIndexChanged) + signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.stepOverPercent.valueChanged) signals.append(self.form.Tolerance.valueChanged) signals.append(self.form.HelixAngle.valueChanged) @@ -113,7 +113,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.FinishingProfile.setChecked(obj.FinishingProfile) self.form.useOutline.setChecked(obj.UseOutline) self.form.orderCutsByRegion.setChecked(obj.OrderCutsByRegion) - self.setupToolController(obj, self.form.ToolController) + self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) self.form.StopButton.setChecked(obj.Stopped) obj.setEditorMode("AdaptiveInputState", 2) # hide this property @@ -155,7 +155,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.StopButton.setChecked(False) # reset the button obj.StopProcessing = True - self.updateToolController(obj, self.form.ToolController) + self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) obj.setEditorMode("AdaptiveInputState", 2) # hide this property obj.setEditorMode("AdaptiveOutputState", 2) # hide this property diff --git a/src/Mod/CAM/Path/Op/Gui/Base.py b/src/Mod/CAM/Path/Op/Gui/Base.py index 141b79bc75..2cda1f989e 100644 --- a/src/Mod/CAM/Path/Op/Gui/Base.py +++ b/src/Mod/CAM/Path/Op/Gui/Base.py @@ -30,12 +30,14 @@ import Path.Base.Util as PathUtil import Path.Main.Job as PathJob import Path.Op.Base as PathOp import Path.Op.Gui.Selection as PathSelection +import Path.Tool.Controller as PathToolController +from Path.Tool.library.ui.dock import ToolBitLibraryDock import PathGui import PathScripts.PathUtils as PathUtils import importlib from PySide.QtCore import QT_TRANSLATE_NOOP -from PySide import QtCore, QtGui +from PySide import QtCore, QtGui, QtWidgets __title__ = "CAM Operation UI base classes" __author__ = "sliptonic (Brad Collette)" @@ -212,9 +214,12 @@ class TaskPanelPage(object): self.isdirty = False self.parent = None self.panelTitle = "Operation" + self.tcEditor = None + self.combo = None if self._installTCUpdate(): PathJob.Notification.updateTC.connect(self.resetToolController) + self.form.toolController.currentIndexChanged.connect(self.tcComboChanged) def show_error_message(self, title, message): msg_box = QtGui.QMessageBox() @@ -389,27 +394,141 @@ class TaskPanelPage(object): """populateCombobox(form, enumTups, comboBoxesPropertyMap) ... proxy for PathGuiUtil.populateCombobox()""" PathGuiUtil.populateCombobox(form, enumTups, comboBoxesPropertyMap) + def tcComboChanged(self, newIndex): + if self.obj is not None and self.tcEditor: + if newIndex == self.combo.count() - 1: + # Special entry: new tool controller. Show the tool dock and reset combo + dock = ToolBitLibraryDock(self.job, True) + dock.open() + self.resetTCCombo() + elif newIndex == self.combo.count() - 2: + # Special entry: copy tool controller + self.copyToolController() # this function also rebuilds the combo + self.resetTCCombo() + else: + tc = PathUtils.findToolController( + self.obj, self.obj.Proxy, self.form.toolController.currentText() + ) + self.obj.ToolController = tc + self.setupToolController() + + def updateToolControllerEditorVisibility(self): + if self.form.editToolController.isChecked(): + self.tcEditor.controller.show() + else: + self.tcEditor.controller.hide() + def resetToolController(self, job, tc): if self.obj is not None: self.obj.ToolController = tc - combo = self.form.toolController - self.setupToolController(self.obj, combo) + self.setupToolController() - def setupToolController(self, obj, combo): + def copyToolController(self): + oldTc = self.tcEditor.obj + self.tcEditor.updateToolController() + tc = PathToolController.Create( + name=oldTc.Label, tool=oldTc.Tool, toolNumber=oldTc.ToolNumber + ) + job = self.obj.Proxy.getJob(self.obj) + job.Proxy.addToolController(tc) + + tc.HorizFeed = oldTc.HorizFeed + tc.VertFeed = oldTc.VertFeed + tc.HorizRapid = oldTc.HorizRapid + tc.VertRapid = oldTc.VertRapid + tc.SpindleSpeed = oldTc.SpindleSpeed + tc.SpindleDir = oldTc.SpindleDir + for attr, expr in oldTc.ExpressionEngine: + tc.setExpression(attr, expr) + + self.obj.ToolController = tc + self.setupToolController() + + def tcEditorChanged(self): + self.setDirty() + self.resetTCCombo() + + def resetTCCombo(self): + controllers = PathUtils.getToolControllers(self.obj) + + if self.obj.ToolController is None: + self.obj.ToolController = PathUtils.findToolController(self.obj, self.obj.Proxy) + if len(controllers) > 0 and not self.obj.Proxy.isToolSupported( + self.obj, self.obj.ToolController.Tool + ): + self.obj.ToolController = controllers[0] + + tcName = self.obj.ToolController.Label if self.obj.ToolController else "" + labels = [c.Label for c in controllers] + labels.append(FreeCAD.Qt.translate("CAM_Operation", "Copy {0}…").format(tcName)) + labels.append(FreeCAD.Qt.translate("CAM_Operation", "New tool controller…")) + self.combo.blockSignals(True) + self.combo.clear() + self.combo.addItems(labels) + self.combo.insertSeparator(len(controllers)) + self.combo.blockSignals(False) + + if self.obj.ToolController is not None: + self.selectInComboBox(self.obj.ToolController.Label, self.combo) + + def setupToolController(self, obj=None, combo=None): """setupToolController(obj, combo) ... helper function to setup obj's ToolController in the given combo box.""" - controllers = PathUtils.getToolControllers(self.obj) - labels = [c.Label for c in controllers] - combo.blockSignals(True) - combo.clear() - combo.addItems(labels) - combo.blockSignals(False) + obj = obj or self.obj + combo = combo or self.combo + self.obj, self.combo = obj, combo - if obj.ToolController is None: - obj.ToolController = PathUtils.findToolController(obj, obj.Proxy) - if obj.ToolController is not None: - self.selectInComboBox(obj.ToolController.Label, combo) + self.resetTCCombo() + + if hasattr(self.form, "editToolController"): + layout = self.form.editToolController.parent().layout() + oldEditor = self.tcEditor + + # Count the number of times the tool controller is used in other operations + # If it is used in other operations, we will offer the "copy tool controller" button + tcCount = 0 + for job in PathUtils.GetJobs(): + for op in job.Operations.Group: + if op == self.obj: + continue + elif hasattr(op, "ToolController") and op.ToolController == obj.ToolController: + tcCount += 1 + + self.tcEditor = Path.Tool.Gui.Controller.ToolControllerEditor( + obj.ToolController, + False, + self.tcEditorChanged, + True, + True, + ) + self.tcEditor.setupUi() + + labelStr = FreeCAD.Qt.translate( + "CAM_Operation", "This tool controller is used by {0} other operations." + ).format(tcCount) + self.tcEditor.controller.tcOperationCountLabel.setText(labelStr) + + # add to layout -- requires a grid layout + if isinstance(layout, QtWidgets.QGridLayout): + layout.addWidget( + self.tcEditor.controller, layout.rowCount(), 0, 1, layout.columnCount() + ) + else: + Path.Log.error( + "Panel uses a layout incompatible with editing tool controllers. Report a bug: it should be a QGridLayout" + ) + + self.updateToolControllerEditorVisibility() + self.tcEditor.updateUi() + self.form.editToolController.checkStateChanged.connect( + self.updateToolControllerEditorVisibility + ) + + if oldEditor: + oldEditor.updateToolController() + oldEditor.controller.hide() + layout.removeWidget(oldEditor.controller) def updateToolController(self, obj, combo): """updateToolController(obj, combo) ... @@ -418,6 +537,8 @@ class TaskPanelPage(object): tc = PathUtils.findToolController(obj, obj.Proxy, combo.currentText()) if obj.ToolController != tc: obj.ToolController = tc + if self.tcEditor: + self.tcEditor.updateToolController() def setupCoolant(self, obj, combo): """setupCoolant(obj, combo) ... diff --git a/src/Mod/CAM/Path/Op/Probe.py b/src/Mod/CAM/Path/Op/Probe.py index d1523f4473..b8ae6edcea 100644 --- a/src/Mod/CAM/Path/Op/Probe.py +++ b/src/Mod/CAM/Path/Op/Probe.py @@ -134,7 +134,7 @@ class ObjectProbing(PathOp.ObjectOp): def isToolSupported(self, obj, tool): """Probe operation requires a probe tool""" - support = hasattr(tool, "ShapeName") and (tool.ShapeName == "probe") + support = PathUtils.getToolShapeName(tool) == "probe" Path.Log.track(tool.Label, support) return support diff --git a/src/Mod/CAM/Path/Tool/Gui/Controller.py b/src/Mod/CAM/Path/Tool/Gui/Controller.py index bc40b82154..f5712e1c71 100644 --- a/src/Mod/CAM/Path/Tool/Gui/Controller.py +++ b/src/Mod/CAM/Path/Tool/Gui/Controller.py @@ -188,80 +188,104 @@ class CommandPathToolController(object): FreeCAD.ActiveDocument.recompute() +class BlockScrollWheel(QtCore.QObject): + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.Type.Wheel: + if not obj.hasFocus(): + return True + return super().eventFilter(obj, event) + + class ToolControllerEditor(object): - def __init__(self, obj, asDialog): + + def __init__( + self, obj, asDialog, notifyChanged=None, showCountLabel=False, disableToolNumber=False + ): + self.notifyChanged = notifyChanged self.form = FreeCADGui.PySideUic.loadUi(":/panels/DlgToolControllerEdit.ui") + self.controller = FreeCADGui.PySideUic.loadUi(":/panels/ToolControllerEdit.ui") + self.form.tc_layout.addWidget(self.controller) if not asDialog: self.form.buttonBox.hide() + if not showCountLabel: + self.controller.tcOperationCountLabel.hide() self.obj = obj comboToPropertyMap = [("spindleDirection", "SpindleDir")] enumTups = PathToolController.ToolController.propertyEnumerations(dataType="raw") - PathGuiUtil.populateCombobox(self.form, enumTups, comboToPropertyMap) - self.vertFeed = PathGuiUtil.QuantitySpinBox(self.form.vertFeed, obj, "VertFeed") - self.horizFeed = PathGuiUtil.QuantitySpinBox(self.form.horizFeed, obj, "HorizFeed") - self.vertRapid = PathGuiUtil.QuantitySpinBox(self.form.vertRapid, obj, "VertRapid") - self.horizRapid = PathGuiUtil.QuantitySpinBox(self.form.horizRapid, obj, "HorizRapid") + PathGuiUtil.populateCombobox(self.controller, enumTups, comboToPropertyMap) + self.vertFeed = PathGuiUtil.QuantitySpinBox(self.controller.vertFeed, obj, "VertFeed") + self.horizFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "HorizFeed") + self.vertRapid = PathGuiUtil.QuantitySpinBox(self.controller.vertRapid, obj, "VertRapid") + self.horizRapid = PathGuiUtil.QuantitySpinBox(self.controller.horizRapid, obj, "HorizRapid") + + self.blockScrollWheel = BlockScrollWheel() + self.controller.tcNumber.installEventFilter(self.blockScrollWheel) + self.controller.spindleDirection.installEventFilter(self.blockScrollWheel) + self.controller.tcNumber.setReadOnly(disableToolNumber) self.editor = None - self.form.toolBox.widget(1).hide() - self.form.toolBox.removeItem(1) def selectInComboBox(self, name, combo): """selectInComboBox(name, combo) ... helper function to select a specific value in a combo box.""" - blocker = QtCore.QSignalBlocker(combo) - index = combo.currentIndex() # Save initial index + with QtCore.QSignalBlocker(combo): + index = combo.currentIndex() # Save initial index - # Search using currentData and return if found - newindex = combo.findData(name) - if newindex >= 0: - combo.setCurrentIndex(newindex) - return + # Search using currentData and return if found + newindex = combo.findData(name) + if newindex >= 0: + combo.setCurrentIndex(newindex) + return - # if not found, search using current text - newindex = combo.findText(name, QtCore.Qt.MatchFixedString) - if newindex >= 0: - combo.setCurrentIndex(newindex) - return + # if not found, search using current text + newindex = combo.findText(name, QtCore.Qt.MatchFixedString) + if newindex >= 0: + combo.setCurrentIndex(newindex) + return - # not found, return unchanged - combo.setCurrentIndex(index) + # not found, return unchanged + combo.setCurrentIndex(index) return def updateUi(self): tc = self.obj - self.form.tcName.setText(tc.Label) - self.form.tcNumber.setValue(tc.ToolNumber) - self.horizFeed.updateWidget() - self.horizRapid.updateWidget() - self.vertFeed.updateWidget() - self.vertRapid.updateWidget() - self.form.spindleSpeed.setValue(tc.SpindleSpeed) - self.selectInComboBox(tc.SpindleDir, self.form.spindleDirection) + with ( + QtCore.QSignalBlocker(self.controller.tcName), + QtCore.QSignalBlocker(self.controller.tcNumber), + QtCore.QSignalBlocker(self.horizFeed.widget), + QtCore.QSignalBlocker(self.horizRapid.widget), + QtCore.QSignalBlocker(self.vertFeed.widget), + QtCore.QSignalBlocker(self.vertRapid.widget), + QtCore.QSignalBlocker(self.controller.spindleSpeed), + QtCore.QSignalBlocker(self.controller.spindleDirection), + ): + self.controller.tcName.setText(tc.Label) + self.controller.tcNumber.setValue(tc.ToolNumber) + self.horizFeed.updateWidget() + self.horizRapid.updateWidget() + self.vertFeed.updateWidget() + self.vertRapid.updateWidget() + self.controller.spindleSpeed.setValue(tc.SpindleSpeed) - # index = self.form.spindleDirection.findText( - # tc.SpindleDir, QtCore.Qt.MatchFixedString - # ) - # if index >= 0: - # self.form.spindleDirection.setCurrentIndex(index) + self.selectInComboBox(tc.SpindleDir, self.controller.spindleDirection) - if self.editor: - self.editor.updateUI() + if self.editor: + self.editor.updateUI() def updateToolController(self): tc = self.obj try: - tc.Label = self.form.tcName.text() - tc.ToolNumber = self.form.tcNumber.value() + tc.Label = self.controller.tcName.text() + tc.ToolNumber = self.controller.tcNumber.value() self.horizFeed.updateProperty() self.vertFeed.updateProperty() self.horizRapid.updateProperty() self.vertRapid.updateProperty() - tc.SpindleSpeed = self.form.spindleSpeed.value() - tc.SpindleDir = self.form.spindleDirection.currentData() + tc.SpindleSpeed = self.controller.spindleSpeed.value() + tc.SpindleDir = self.controller.spindleDirection.currentData() if self.editor: self.editor.updateTool() @@ -270,23 +294,29 @@ class ToolControllerEditor(object): except Exception as e: Path.Log.error("Error updating TC: {}".format(e)) - def refresh(self): + def changed(self): self.form.blockSignals(True) + self.controller.blockSignals(True) self.updateToolController() self.updateUi() + self.controller.blockSignals(False) self.form.blockSignals(False) + if self.notifyChanged: + self.notifyChanged() + def setupUi(self): if self.editor: self.editor.setupUI() - self.form.tcName.editingFinished.connect(self.refresh) - self.form.horizFeed.editingFinished.connect(self.refresh) - self.form.vertFeed.editingFinished.connect(self.refresh) - self.form.horizRapid.editingFinished.connect(self.refresh) - self.form.vertRapid.editingFinished.connect(self.refresh) - self.form.spindleSpeed.editingFinished.connect(self.refresh) - self.form.spindleDirection.currentIndexChanged.connect(self.refresh) + self.controller.tcName.textChanged.connect(self.changed) + self.controller.tcNumber.editingFinished.connect(self.changed) + self.vertFeed.widget.textChanged.connect(self.changed) + self.horizFeed.widget.textChanged.connect(self.changed) + self.vertRapid.widget.textChanged.connect(self.changed) + self.horizRapid.widget.textChanged.connect(self.changed) + self.controller.spindleSpeed.editingFinished.connect(self.changed) + self.controller.spindleDirection.currentIndexChanged.connect(self.changed) class TaskPanel: @@ -359,6 +389,9 @@ class DlgToolControllerEdit: Path.Log.info("revert") self.obj.Proxy.setFromTemplate(self.obj, restoreTC) rc = True + else: + self.editor.updateToolController() + self.obj.Proxy.execute(self.obj) return rc diff --git a/src/Mod/CAM/Path/Tool/library/ui/cmd.py b/src/Mod/CAM/Path/Tool/library/ui/cmd.py index a1f9f94f99..c1dc9d0798 100644 --- a/src/Mod/CAM/Path/Tool/library/ui/cmd.py +++ b/src/Mod/CAM/Path/Tool/library/ui/cmd.py @@ -48,8 +48,10 @@ class CommandToolBitLibraryDockOpen: def GetResources(self): return { "Pixmap": "CAM_ToolTable", - "MenuText": QT_TRANSLATE_NOOP("CAM_ToolBitDock", "Toolbit Dock"), - "ToolTip": QT_TRANSLATE_NOOP("CAM_ToolBitDock", "Toggles the toolbit dock"), + "MenuText": QT_TRANSLATE_NOOP("CAM_ToolBitSelection", "Add toolbit…"), + "ToolTip": QT_TRANSLATE_NOOP( + "CAM_ToolBitSelection", "Opens the toolbit selection dialog" + ), "Accel": "P, T", "CmdType": "ForEdit", } diff --git a/src/Mod/CAM/Path/Tool/library/ui/dock.py b/src/Mod/CAM/Path/Tool/library/ui/dock.py index 2667af7a64..6606bfeb44 100644 --- a/src/Mod/CAM/Path/Tool/library/ui/dock.py +++ b/src/Mod/CAM/Path/Tool/library/ui/dock.py @@ -28,7 +28,7 @@ import FreeCADGui import Path import Path.Tool.Gui.Controller as PathToolControllerGui import PathScripts.PathUtilsGui as PathUtilsGui -from PySide import QtGui, QtCore +from PySide import QtGui, QtCore, QtWidgets from functools import partial from typing import List, Tuple from ...camassets import cam_assets, ensure_assets_initialized @@ -50,12 +50,20 @@ translate = FreeCAD.Qt.translate class ToolBitLibraryDock(object): """Controller for displaying a library and creating ToolControllers""" - def __init__(self): + def __init__(self, defaultJob=None, autoClose=False): ensure_assets_initialized(cam_assets) # Create the main form widget directly - self.form = QtGui.QDockWidget() + self.defaultJob = defaultJob + self.autoClose = autoClose + self.form = QtWidgets.QDialog() self.form.setObjectName("ToolSelector") self.form.setWindowTitle(translate("CAM_ToolBit", "Tool Selector")) + self.form.setMinimumSize(600, 400) + self.form.resize(800, 600) + self.form.adjustSize() + self.form_layout = QtGui.QVBoxLayout(self.form) + self.form_layout.setContentsMargins(4, 4, 4, 4) + self.form_layout.setSpacing(4) # Create the browser widget self.browser_widget = LibraryBrowserWidget(asset_manager=cam_assets) @@ -69,6 +77,8 @@ class ToolBitLibraryDock(object): # Create a main widget and layout for the dock main_widget = QtGui.QWidget() main_layout = QtGui.QVBoxLayout(main_widget) + main_layout.setContentsMargins(4, 4, 4, 4) + main_layout.setSpacing(4) # Add the browser widget to the layout main_layout.addWidget(self.browser_widget) @@ -88,7 +98,7 @@ class ToolBitLibraryDock(object): main_layout.addLayout(button_layout) # Set the main widget as the dock's widget - self.form.setWidget(main_widget) + self.form.layout().addWidget(main_widget) # Connect signals from the browser widget and buttons self.browser_widget.toolSelected.connect(self._update_state) @@ -152,11 +162,12 @@ class ToolBitLibraryDock(object): translate("CAM_ToolBit", "Please create a Job first."), ) return - elif len(jobs) == 1: - job = jobs[0] + elif self.defaultJob or len(jobs) == 1: + job = self.defaultJob or jobs[0] else: userinput = PathUtilsGui.PathUtilsUserInput() job = userinput.chooseJob(jobs) + self.defaultJob = job if job is None: # user may have canceled return @@ -169,21 +180,9 @@ class ToolBitLibraryDock(object): job.Proxy.addToolController(tc) FreeCAD.ActiveDocument.recompute() + if self.autoClose: + self.form.accept() + def open(self, path=None): """load library stored in path and bring up ui""" - docs = FreeCADGui.getMainWindow().findChildren(QtGui.QDockWidget) - for doc in docs: - if doc.objectName() == "ToolSelector": - if doc.isVisible(): - doc.deleteLater() - return - else: - doc.setVisible(True) - return - - mw = FreeCADGui.getMainWindow() - mw.addDockWidget( - QtCore.Qt.RightDockWidgetArea, - self.form, - QtCore.Qt.Orientation.Vertical, - ) + self.form.exec_() diff --git a/src/Mod/CAM/PathScripts/PathUtils.py b/src/Mod/CAM/PathScripts/PathUtils.py index 10a846456c..367d03c63e 100644 --- a/src/Mod/CAM/PathScripts/PathUtils.py +++ b/src/Mod/CAM/PathScripts/PathUtils.py @@ -357,6 +357,14 @@ def getToolControllers(obj, proxy=None): return [] +def getToolShapeName(tool): + if hasattr(tool, "ShapeName"): + return tool.ShapeName.lower() + if hasattr(tool, "ShapeType"): + return tool.ShapeType.lower() + return "" + + def findToolController(obj, proxy, name=None): """returns a tool controller with a given name. If no name is specified, returns the first controller.