| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import FreeCAD |
| | import FreeCADGui |
| | import Path |
| | import Path.Base.Gui.GetPoint as PathGetPoint |
| | import Path.Base.Gui.Util as PathGuiUtil |
| | import Path.Base.SetupSheet as PathSetupSheet |
| | 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 PathScripts.PathUtils as PathUtils |
| | import importlib |
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| |
|
| | from PySide import QtCore, QtGui, QtWidgets |
| |
|
| | __title__ = "CAM Operation UI base classes" |
| | __author__ = "sliptonic (Brad Collette)" |
| | __url__ = "https://www.freecad.org" |
| | __doc__ = "Base classes and framework for CAM operation's UI" |
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | class ViewProvider(object): |
| | """ |
| | Generic view provider for path objects. |
| | Deducts the icon name from operation name, brings up the TaskPanel |
| | with pages corresponding to the operation's opFeatures() and forwards |
| | property change notifications to the page controllers. |
| | """ |
| |
|
| | def __init__(self, vobj, resources): |
| | Path.Log.track() |
| | self.deleteOnReject = True |
| | self.OpIcon = ":/icons/%s.svg" % resources.pixmap |
| | self.OpName = resources.name |
| | self.OpPageModule = resources.opPageClass.__module__ |
| | self.OpPageClass = resources.opPageClass.__name__ |
| |
|
| | |
| | self.vobj = vobj |
| | self.Object = None |
| | self.panel = None |
| |
|
| | def attach(self, vobj): |
| | Path.Log.track() |
| | self.vobj = vobj |
| | self.Object = vobj.Object |
| | self.panel = None |
| | return |
| |
|
| | def deleteObjectsOnReject(self): |
| | """ |
| | deleteObjectsOnReject() ... return true if all objects should |
| | be created if the user hits cancel. This is used during the initial |
| | edit session, if the user does not press OK, it is assumed they've |
| | changed their mind about creating the operation. |
| | """ |
| | Path.Log.track() |
| | return hasattr(self, "deleteOnReject") and self.deleteOnReject |
| |
|
| | def setDeleteObjectsOnReject(self, state=False): |
| | Path.Log.track() |
| | self.deleteOnReject = state |
| | return self.deleteOnReject |
| |
|
| | def setEdit(self, vobj=None, mode=0): |
| | """setEdit(vobj, mode=0) ... initiate editing of receivers model.""" |
| | Path.Log.track() |
| | if 0 == mode: |
| | if vobj is None: |
| | vobj = self.vobj |
| | page = self.getTaskPanelOpPage(vobj.Object) |
| | page.setTitle(self.OpName) |
| | page.setIcon(self.OpIcon) |
| | selection = self.getSelectionFactory() |
| | self.setupTaskPanel( |
| | TaskPanel(vobj.Object, self.deleteObjectsOnReject(), page, selection) |
| | ) |
| | self.deleteOnReject = False |
| | return True |
| | |
| | return False |
| |
|
| | def setupTaskPanel(self, panel): |
| | """setupTaskPanel(panel) ... internal function to start the editor.""" |
| | self.panel = panel |
| | FreeCADGui.Control.closeDialog() |
| | FreeCADGui.Control.showDialog(panel) |
| | panel.setupUi() |
| | job = self.Object.Proxy.getJob(self.Object) |
| | if job: |
| | job.ViewObject.Proxy.setupEditVisibility(job) |
| | else: |
| | Path.Log.info("did not find no job") |
| |
|
| | def clearTaskPanel(self): |
| | """clearTaskPanel() ... internal callback function when editing has finished.""" |
| | self.panel = None |
| | job = self.Object.Proxy.getJob(self.Object) |
| | if job: |
| | job.ViewObject.Proxy.resetEditVisibility(job) |
| |
|
| | def unsetEdit(self, arg1, arg2): |
| | if self.panel: |
| | self.panel.reject(False) |
| |
|
| | def dumps(self): |
| | """dumps() ... callback before receiver is saved to a file. |
| | Returns a dictionary with the receiver's resources as strings.""" |
| | Path.Log.track() |
| | state = {} |
| | state["OpName"] = self.OpName |
| | state["OpIcon"] = self.OpIcon |
| | state["OpPageModule"] = self.OpPageModule |
| | state["OpPageClass"] = self.OpPageClass |
| | return state |
| |
|
| | def loads(self, state): |
| | """loads(state) ... callback on restoring a saved instance, pendant to dumps() |
| | state is the dictionary returned by dumps().""" |
| | self.OpName = state["OpName"] |
| | self.OpIcon = state["OpIcon"] |
| | self.OpPageModule = state["OpPageModule"] |
| | self.OpPageClass = state["OpPageClass"] |
| |
|
| | def getIcon(self): |
| | """getIcon() ... the icon used in the object tree""" |
| | if self.Object.Active: |
| | return self.OpIcon |
| | else: |
| | return ":/icons/CAM_OpActive.svg" |
| |
|
| | def getTaskPanelOpPage(self, obj): |
| | """getTaskPanelOpPage(obj) ... use the stored information to instantiate the receiver op's page controller.""" |
| | mod = importlib.import_module(self.OpPageModule) |
| | cls = getattr(mod, self.OpPageClass) |
| | return cls(obj, 0) |
| |
|
| | def getSelectionFactory(self): |
| | """getSelectionFactory() ... return a factory function that can be used to create the selection observer.""" |
| | return PathSelection.select(self.OpName) |
| |
|
| | def updateData(self, obj, prop): |
| | """updateData(obj, prop) ... callback whenever a property of the receiver's model is assigned. |
| | The callback is forwarded to the task panel - in case an editing session is ongoing.""" |
| | |
| | if self.panel: |
| | self.panel.updateData(obj, prop) |
| |
|
| | def onDelete(self, vobj, arg2=None): |
| | PathUtil.clearExpressionEngine(vobj.Object) |
| | return True |
| |
|
| | def setupContextMenu(self, vobj, menu): |
| | Path.Log.track() |
| | for action in menu.actions(): |
| | menu.removeAction(action) |
| | action = QtGui.QAction(translate("PathOp", "Edit"), menu) |
| | action.triggered.connect(self._editInContextMenuTriggered) |
| | menu.addAction(action) |
| |
|
| | def _editInContextMenuTriggered(self, checked): |
| | self.setEdit() |
| |
|
| |
|
| | class TaskPanelPage(object): |
| | """Base class for all task panel pages.""" |
| |
|
| | |
| | def __init__(self, obj, features): |
| | """__init__(obj, features) ... framework initialisation. |
| | Do not overwrite, implement initPage(obj) instead.""" |
| | self.obj = obj |
| | self.job = PathUtils.findParentJob(obj) |
| | self.form = self.getForm() |
| | self.signalDirtyChanged = None |
| | self.setClean() |
| | self.setTitle("-") |
| | self.setIcon(None) |
| | self.features = features |
| | 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() |
| | msg_box.setIcon(QtGui.QMessageBox.Critical) |
| | msg_box.setWindowTitle(title) |
| | msg_box.setText(message) |
| | msg_box.setStandardButtons(QtGui.QMessageBox.Ok) |
| | msg_box.exec_() |
| |
|
| | def _installTCUpdate(self): |
| | return hasattr(self.form, "toolController") |
| |
|
| | def setParent(self, parent): |
| | """setParent() ... used to transfer parent object link to child class. |
| | Do not overwrite.""" |
| | self.parent = parent |
| |
|
| | def onDirtyChanged(self, callback): |
| | """onDirtyChanged(callback) ... set callback when dirty state changes.""" |
| | self.signalDirtyChanged = callback |
| |
|
| | def setDirty(self): |
| | """setDirty() ... mark receiver as dirty, causing the model to be recalculated if OK or Apply is pressed.""" |
| | self.isdirty = True |
| | if self.signalDirtyChanged: |
| | self.signalDirtyChanged(self) |
| |
|
| | def setClean(self): |
| | """setClean() ... mark receiver as clean, indicating there is no need to recalculate the model even if the user presses OK or Apply.""" |
| | self.isdirty = False |
| | if self.signalDirtyChanged: |
| | self.signalDirtyChanged(self) |
| |
|
| | def pageGetFields(self): |
| | """pageGetFields() ... internal callback. |
| | Do not overwrite, implement getFields(obj) instead.""" |
| | self.getFields(self.obj) |
| | self.setDirty() |
| |
|
| | def pageSetFields(self): |
| | """pageSetFields() ... internal callback. |
| | Do not overwrite, implement setFields(obj) instead.""" |
| | self.setFields(self.obj) |
| |
|
| | def pageCleanup(self): |
| | """pageCleanup() ... internal callback. |
| | Do not overwrite, implement cleanupPage(obj) instead.""" |
| | if self._installTCUpdate(): |
| | PathJob.Notification.updateTC.disconnect(self.resetToolController) |
| | self.cleanupPage(self.obj) |
| |
|
| | def pageRegisterSignalHandlers(self): |
| | """pageRegisterSignalHandlers() .. internal callback. |
| | Registers a callback for all signals returned by getSignalsForUpdate(obj). |
| | Do not overwrite, implement getSignalsForUpdate(obj) and/or registerSignalHandlers(obj) instead. |
| | """ |
| | for signal in self.getSignalsForUpdate(self.obj): |
| | signal.connect(self.pageGetFields) |
| | self.registerSignalHandlers(self.obj) |
| |
|
| | def pageUpdateData(self, obj, prop): |
| | """pageUpdateData(obj, prop) ... internal callback. |
| | Do not overwrite, implement updateData(obj) instead.""" |
| | self.updateData(obj, prop) |
| |
|
| | def setTitle(self, title): |
| | """setTitle(title) ... sets a title for the page.""" |
| | self.title = title |
| |
|
| | def getTitle(self, obj): |
| | """getTitle(obj) ... return title to be used for the receiver page. |
| | The default implementation returns what was previously set with setTitle(title). |
| | Can safely be overwritten by subclasses.""" |
| | return self.title |
| |
|
| | def setIcon(self, icon): |
| | """setIcon(icon) ... sets the icon for the page.""" |
| | self.icon = icon |
| |
|
| | def getIcon(self, obj): |
| | """getIcon(obj) ... return icon for page or None. |
| | Can safely be overwritten by subclasses.""" |
| | return self.icon |
| |
|
| | |
| | def initPage(self, obj): |
| | """initPage(obj) ... overwrite to customize UI for specific model. |
| | Note that this function is invoked after all page controllers have been created. |
| | Should be overwritten by subclasses.""" |
| | pass |
| |
|
| | def cleanupPage(self, obj): |
| | """cleanupPage(obj) ... overwrite to perform any cleanup tasks before page is destroyed. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def modifyStandardButtons(self, buttonBox): |
| | """modifyStandardButtons(buttonBox) ... overwrite if the task panel standard buttons need to be modified. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def getForm(self): |
| | """getForm() ... return UI form for this page. |
| | Must be overwritten by subclasses.""" |
| | pass |
| |
|
| | def getFields(self, obj): |
| | """getFields(obj) ... overwrite to transfer values from UI to obj's properties. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def setFields(self, obj): |
| | """setFields(obj) ... overwrite to transfer obj's property values to UI. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def getSignalsForUpdate(self, obj): |
| | """getSignalsForUpdate(obj) ... return signals which, when triggered, cause the receiver to update the model. |
| | See also registerSignalHandlers(obj) |
| | Can safely be overwritten by subclasses.""" |
| | return [] |
| |
|
| | def registerSignalHandlers(self, obj): |
| | """registerSignalHandlers(obj) ... overwrite to register custom signal handlers. |
| | In case an update of a model is not the desired operation of a signal invocation |
| | (see getSignalsForUpdate(obj)) this function can be used to register signal handlers |
| | manually. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def updateData(self, obj, prop): |
| | """updateData(obj, prop) ... overwrite if the receiver needs to react to property changes that might not have been caused by the receiver itself. |
| | Sometimes a model will recalculate properties based on a change of another property. In order to keep the UI up to date with such changes this |
| | function can be used. |
| | Please note that the callback is synchronous with the property assignment operation. Also note that the notification is invoked regardless of the |
| | actual value of the property assignment. In other words it also fires if a property gets assigned the same value it already has. |
| | Taking above observations into account the implementation has to take care that it doesn't overwrite modified UI values by invoking setFields(obj). |
| | This can happen if a subclass unconditionally transfers all values in getFields(obj) to the model and just calls setFields(obj) in this callback. |
| | In such a scenario the first property assignment will cause all changes in the UI of the other fields to be overwritten by setFields(obj). |
| | You have been warned.""" |
| | pass |
| |
|
| | def updateSelection(self, obj, sel): |
| | """updateSelection(obj, sel) ... |
| | overwrite to customize UI depending on current selection. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def selectInComboBox(self, name, combo): |
| | """selectInComboBox(name, combo) ... |
| | helper function to select a specific value in a combo box.""" |
| | try: |
| | combo.blockSignals(True) |
| | index = combo.currentIndex() |
| |
|
| | |
| | newindex = combo.findData(name) |
| | if newindex >= 0: |
| | combo.setCurrentIndex(newindex) |
| | return |
| |
|
| | |
| | newindex = combo.findText(name, QtCore.Qt.MatchFixedString) |
| | if newindex >= 0: |
| | combo.setCurrentIndex(newindex) |
| | return |
| |
|
| | |
| | combo.setCurrentIndex(index) |
| | finally: |
| | combo.blockSignals(False) |
| |
|
| | def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): |
| | """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: |
| | |
| | dock = ToolBitLibraryDock(self.job, True) |
| | dock.open() |
| | self.resetTCCombo() |
| | elif newIndex == self.combo.count() - 2: |
| | |
| | self.copyToolController() |
| | 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 None: |
| | return |
| | self.obj.ToolController = tc |
| | self.setupToolController() |
| |
|
| | def copyToolController(self): |
| | oldTc = self.tcEditor.obj |
| | self.tcEditor.updateToolController() |
| | job = self.obj.Proxy.getJob(self.obj) |
| | self.obj.ToolController = PathToolController.copyTC(oldTc, job) |
| | 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.""" |
| | obj = obj or self.obj |
| | combo = combo or self.combo |
| | self.obj, self.combo = obj, combo |
| |
|
| | self.resetTCCombo() |
| |
|
| | if hasattr(self.form, "editToolController"): |
| | layout = self.form.editToolController.parent().layout() |
| | oldEditor = self.tcEditor |
| |
|
| | |
| | |
| | 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) |
| |
|
| | |
| | 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() |
| | checkbox = self.form.editToolController |
| | checkboxSignal = ( |
| | checkbox.checkStateChanged |
| | if hasattr(checkbox, "checkStateChanged") |
| | else checkbox.stateChanged |
| | ) |
| | checkboxSignal.connect(self.updateToolControllerEditorVisibility) |
| |
|
| | if oldEditor: |
| | oldEditor.updateToolController() |
| | oldEditor.controller.hide() |
| | layout.removeWidget(oldEditor.controller) |
| |
|
| | def updateToolController(self, obj, combo): |
| | """updateToolController(obj, combo) ... |
| | helper function to update obj's ToolController property if a different |
| | one has been selected in the combo box.""" |
| | 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) ... |
| | helper function to setup obj's Coolant option.""" |
| | job = PathUtils.findParentJob(obj) |
| | options = job.SetupSheet.CoolantModes |
| | combo.blockSignals(True) |
| | combo.clear() |
| | combo.addItems(options) |
| | combo.blockSignals(False) |
| |
|
| | if hasattr(obj, "CoolantMode"): |
| | self.selectInComboBox(obj.CoolantMode, combo) |
| |
|
| | def updateCoolant(self, obj, combo): |
| | """updateCoolant(obj, combo) ... |
| | helper function to update obj's Coolant property if a different |
| | one has been selected in the combo box.""" |
| | option = combo.currentText() |
| | if hasattr(obj, "CoolantMode") and option: |
| | if obj.CoolantMode != option: |
| | obj.CoolantMode = option |
| |
|
| | def updatePanelVisibility(self, panelTitle, obj): |
| | """updatePanelVisibility(panelTitle, obj) ... |
| | Function to call the `updateVisibility()` GUI method of the |
| | page whose panel title is as indicated.""" |
| | if hasattr(self, "parent"): |
| | parent = getattr(self, "parent") |
| | if parent and hasattr(parent, "featurePages"): |
| | for page in parent.featurePages: |
| | if hasattr(page, "panelTitle"): |
| | if page.panelTitle == panelTitle and hasattr(page, "updateVisibility"): |
| | page.updateVisibility() |
| | break |
| |
|
| |
|
| | class TaskPanelBaseGeometryPage(TaskPanelPage): |
| | """Page controller for the base geometry.""" |
| |
|
| | DataObject = QtCore.Qt.ItemDataRole.UserRole |
| | DataObjectSub = QtCore.Qt.ItemDataRole.UserRole + 1 |
| |
|
| | def __init__(self, obj, features): |
| | super(TaskPanelBaseGeometryPage, self).__init__(obj, features) |
| |
|
| | self.panelTitle = "Base Geometry" |
| | self.OpIcon = ":/icons/CAM_BaseGeometry.svg" |
| | self.setIcon(self.OpIcon) |
| |
|
| | def getForm(self): |
| | panel = FreeCADGui.PySideUic.loadUi(":/panels/PageBaseGeometryEdit.ui") |
| | self.modifyPanel(panel) |
| | return panel |
| |
|
| | def modifyPanel(self, panel): |
| | """modifyPanel(self, panel) ... |
| | Helper method to modify the current form immediately after |
| | it is loaded.""" |
| | |
| | availableOps = list() |
| | ops = self.job.Operations.Group |
| | for op in ops: |
| | if hasattr(op, "Base") and isinstance(op.Base, list): |
| | if len(op.Base) > 0: |
| | availableOps.append(op.Label) |
| |
|
| | |
| | if len(availableOps) > 0: |
| | |
| | try: |
| | panel.geometryImportList.blockSignals(True) |
| | panel.geometryImportList.clear() |
| | availableOps.sort() |
| | for opLbl in availableOps: |
| | panel.geometryImportList.addItem(opLbl) |
| | panel.geometryImportList.blockSignals(False) |
| | except (AttributeError, RuntimeError): |
| | |
| | pass |
| | else: |
| | try: |
| | panel.geometryImportList.hide() |
| | panel.geometryImportButton.hide() |
| | except (AttributeError, RuntimeError): |
| | |
| | pass |
| |
|
| | def getTitle(self, obj): |
| | return translate("PathOp", "Base Geometry") |
| |
|
| | def getFields(self, obj): |
| | pass |
| |
|
| | def setFields(self, obj): |
| | self.form.baseList.blockSignals(True) |
| | self.form.baseList.clear() |
| | for base in self.obj.Base: |
| | for sub in base[1]: |
| | item = QtGui.QListWidgetItem("%s.%s" % (base[0].Label, sub)) |
| | item.setData(self.DataObject, base[0]) |
| | item.setData(self.DataObjectSub, sub) |
| | self.form.baseList.addItem(item) |
| | self.form.baseList.blockSignals(False) |
| | self.resizeBaseList() |
| |
|
| | def itemActivated(self): |
| | FreeCADGui.Selection.clearSelection() |
| | for item in self.form.baseList.selectedItems(): |
| | obj = item.data(self.DataObject) |
| | sub = item.data(self.DataObjectSub) |
| | if sub: |
| | FreeCADGui.Selection.addSelection(obj, sub) |
| | else: |
| | FreeCADGui.Selection.addSelection(obj) |
| | |
| |
|
| | def supportsVertexes(self): |
| | return self.features & PathOp.FeatureBaseVertexes |
| |
|
| | def supportsEdges(self): |
| | return self.features & PathOp.FeatureBaseEdges |
| |
|
| | def supportsFaces(self): |
| | return self.features & PathOp.FeatureBaseFaces |
| |
|
| | def supportsPanels(self): |
| | return self.features & PathOp.FeatureBasePanels |
| |
|
| | def featureName(self): |
| | if self.supportsEdges() and self.supportsFaces(): |
| | return "features" |
| | if self.supportsFaces(): |
| | return "faces" |
| | if self.supportsEdges(): |
| | return "edges" |
| | return "nothing" |
| |
|
| | def selectionSupportedAsBaseGeometry(self, sel, ignoreErrors): |
| | if sel.HasSubObjects: |
| | if not self.supportsVertexes() and sel.SubObjects[0].ShapeType == "Vertex": |
| | return False |
| | if not self.supportsEdges() and sel.SubObjects[0].ShapeType == "Edge": |
| | return False |
| | if not self.supportsFaces() and sel.SubObjects[0].ShapeType == "Face": |
| | return False |
| | else: |
| | if not self.supportsPanels() or "Panel" not in sel.Object.Name: |
| | return False |
| | return True |
| |
|
| | def addBaseGeometry(self, selection): |
| | Path.Log.track(selection) |
| | for sel in selection: |
| | |
| | if self.selectionSupportedAsBaseGeometry(sel, False): |
| | for sub in sel.SubElementNames: |
| | self.obj.Proxy.addBase(self.obj, sel.Object, sub) |
| | return False |
| |
|
| | def addBase(self): |
| | Path.Log.track() |
| | if self.addBaseGeometry(FreeCADGui.Selection.getSelectionEx()): |
| | |
| | self.setFields(self.obj) |
| | self.setDirty() |
| | self.updatePanelVisibility("Operation", self.obj) |
| |
|
| | def deleteBase(self): |
| | Path.Log.track() |
| | selected = self.form.baseList.selectedItems() |
| | for item in selected: |
| | self.form.baseList.takeItem(self.form.baseList.row(item)) |
| | self.setDirty() |
| | self.updateBase() |
| | self.updatePanelVisibility("Operation", self.obj) |
| | self.resizeBaseList() |
| |
|
| | def updateBase(self): |
| | newlist = [] |
| | for i in range(self.form.baseList.count()): |
| | item = self.form.baseList.item(i) |
| | obj = item.data(self.DataObject) |
| | sub = item.data(self.DataObjectSub) |
| | if sub: |
| | base = (obj, str(sub)) |
| | newlist.append(base) |
| | Path.Log.debug("Setting new base: %s -> %s" % (self.obj.Base, newlist)) |
| | self.obj.Base = newlist |
| |
|
| | def clearBase(self): |
| | self.obj.Base = [] |
| | self.setDirty() |
| | self.updatePanelVisibility("Operation", self.obj) |
| | self.resizeBaseList() |
| |
|
| | def importBaseGeometry(self): |
| | opLabel = str(self.form.geometryImportList.currentText()) |
| | ops = FreeCAD.ActiveDocument.getObjectsByLabel(opLabel) |
| | if len(ops) > 1: |
| | msg = translate("PathOp", "Multiple operations are labeled as") |
| | msg += " {}\n".format(opLabel) |
| | FreeCAD.Console.PrintWarning(msg) |
| | for base, subList in ops[0].Base: |
| | FreeCADGui.Selection.clearSelection() |
| | FreeCADGui.Selection.addSelection(base, subList) |
| | self.addBase() |
| |
|
| | def registerSignalHandlers(self, obj): |
| | self.form.baseList.itemSelectionChanged.connect(self.itemActivated) |
| | self.form.addBase.clicked.connect(self.addBase) |
| | self.form.deleteBase.clicked.connect(self.deleteBase) |
| | self.form.clearBase.clicked.connect(self.clearBase) |
| | self.form.geometryImportButton.clicked.connect(self.importBaseGeometry) |
| |
|
| | def pageUpdateData(self, obj, prop): |
| | if prop in ["Base"]: |
| | self.setFields(obj) |
| |
|
| | def updateSelection(self, obj, selection): |
| | for sel in selection: |
| | if self.selectionSupportedAsBaseGeometry(sel, True): |
| | self.form.addBase.setEnabled(True) |
| | else: |
| | self.form.addBase.setEnabled(False) |
| |
|
| | def resizeBaseList(self): |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | qList = self.form.baseList |
| | row = (qList.count() + qList.frameWidth()) * 15 |
| | |
| | Path.Log.debug( |
| | "baseList({}, {}) {} * {}".format( |
| | qList.size(), row, qList.count(), qList.sizeHintForRow(0) |
| | ) |
| | ) |
| |
|
| |
|
| | class TaskPanelBaseLocationPage(TaskPanelPage): |
| | """Page controller for base locations. Uses PathGetPoint.""" |
| |
|
| | DataLocation = QtCore.Qt.ItemDataRole.UserRole |
| |
|
| | def __init__(self, obj, features): |
| | super(TaskPanelBaseLocationPage, self).__init__(obj, features) |
| |
|
| | |
| | self.editRow = None |
| | self.panelTitle = "Base Location" |
| |
|
| | def getForm(self): |
| | self.formLoc = FreeCADGui.PySideUic.loadUi(":/panels/PageBaseLocationEdit.ui") |
| | if QtCore.qVersion()[0] == "4": |
| | self.formLoc.baseList.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) |
| | else: |
| | self.formLoc.baseList.horizontalHeader().setSectionResizeMode(QtGui.QHeaderView.Stretch) |
| | self.getPoint = PathGetPoint.TaskPanel(self.formLoc.addRemoveEdit) |
| | return self.formLoc |
| |
|
| | def modifyStandardButtons(self, buttonBox): |
| | self.getPoint.buttonBox = buttonBox |
| |
|
| | def getTitle(self, obj): |
| | return translate("PathOp", "Base Location") |
| |
|
| | def getFields(self, obj): |
| | pass |
| |
|
| | def setFields(self, obj): |
| | self.formLoc.baseList.blockSignals(True) |
| | self.formLoc.baseList.clearContents() |
| | self.formLoc.baseList.setRowCount(0) |
| | for location in self.obj.Locations: |
| | self.formLoc.baseList.insertRow(self.formLoc.baseList.rowCount()) |
| |
|
| | item = QtGui.QTableWidgetItem("%.2f" % location.x) |
| | item.setData(self.DataLocation, location.x) |
| | self.formLoc.baseList.setItem(self.formLoc.baseList.rowCount() - 1, 0, item) |
| |
|
| | item = QtGui.QTableWidgetItem("%.2f" % location.y) |
| | item.setData(self.DataLocation, location.y) |
| | self.formLoc.baseList.setItem(self.formLoc.baseList.rowCount() - 1, 1, item) |
| | self.formLoc.baseList.resizeColumnToContents(0) |
| | self.formLoc.baseList.blockSignals(False) |
| | self.itemActivated() |
| |
|
| | def removeLocation(self): |
| | deletedRows = [] |
| | selected = self.formLoc.baseList.selectedItems() |
| | for item in selected: |
| | row = self.formLoc.baseList.row(item) |
| | if row not in deletedRows: |
| | deletedRows.append(row) |
| | self.formLoc.baseList.removeRow(row) |
| | self.updateLocations() |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| | def updateLocations(self): |
| | Path.Log.track() |
| | locations = [] |
| | for i in range(self.formLoc.baseList.rowCount()): |
| | x = self.formLoc.baseList.item(i, 0).data(self.DataLocation) |
| | y = self.formLoc.baseList.item(i, 1).data(self.DataLocation) |
| | location = FreeCAD.Vector(x, y, 0) |
| | locations.append(location) |
| | self.obj.Locations = locations |
| |
|
| | def addLocation(self): |
| | self.getPoint.getPoint(self.addLocationAt) |
| |
|
| | def addLocationAt(self, point, obj): |
| | if point: |
| | locations = self.obj.Locations |
| | locations.append(point) |
| | self.obj.Locations = locations |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| | def editLocation(self): |
| | selected = self.formLoc.baseList.selectedItems() |
| | if selected: |
| | row = self.formLoc.baseList.row(selected[0]) |
| | self.editRow = row |
| | x = self.formLoc.baseList.item(row, 0).data(self.DataLocation) |
| | y = self.formLoc.baseList.item(row, 1).data(self.DataLocation) |
| | start = FreeCAD.Vector(x, y, 0) |
| | self.getPoint.getPoint(self.editLocationAt, start) |
| |
|
| | def editLocationAt(self, point, obj): |
| | if point: |
| | self.formLoc.baseList.item(self.editRow, 0).setData(self.DataLocation, point.x) |
| | self.formLoc.baseList.item(self.editRow, 1).setData(self.DataLocation, point.y) |
| | self.updateLocations() |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| | def itemActivated(self): |
| | if self.formLoc.baseList.selectedItems(): |
| | self.form.removeLocation.setEnabled(True) |
| | self.form.editLocation.setEnabled(True) |
| | else: |
| | self.form.removeLocation.setEnabled(False) |
| | self.form.editLocation.setEnabled(False) |
| |
|
| | def registerSignalHandlers(self, obj): |
| | self.form.baseList.itemSelectionChanged.connect(self.itemActivated) |
| | self.formLoc.addLocation.clicked.connect(self.addLocation) |
| | self.formLoc.removeLocation.clicked.connect(self.removeLocation) |
| | self.formLoc.editLocation.clicked.connect(self.editLocation) |
| |
|
| | def pageUpdateData(self, obj, prop): |
| | if prop in ["Locations"]: |
| | self.setFields(obj) |
| |
|
| |
|
| | class TaskPanelHeightsPage(TaskPanelPage): |
| | """Page controller for heights.""" |
| |
|
| | def __init__(self, obj, features): |
| | super(TaskPanelHeightsPage, self).__init__(obj, features) |
| |
|
| | |
| | self.clearanceHeight = None |
| | self.safeHeight = None |
| | self.panelTitle = "Heights" |
| | self.OpIcon = ":/icons/CAM_Heights.svg" |
| | self.setIcon(self.OpIcon) |
| |
|
| | def getForm(self): |
| | return FreeCADGui.PySideUic.loadUi(":/panels/PageHeightsEdit.ui") |
| |
|
| | def initPage(self, obj): |
| | self.safeHeight = PathGuiUtil.QuantitySpinBox(self.form.safeHeight, obj, "SafeHeight") |
| | self.clearanceHeight = PathGuiUtil.QuantitySpinBox( |
| | self.form.clearanceHeight, obj, "ClearanceHeight" |
| | ) |
| |
|
| | def getTitle(self, obj): |
| | return translate("PathOp", "Heights") |
| |
|
| | def getFields(self, obj): |
| | self.safeHeight.updateProperty() |
| | self.clearanceHeight.updateProperty() |
| |
|
| | def setFields(self, obj): |
| | self.safeHeight.updateWidget() |
| | self.clearanceHeight.updateWidget() |
| |
|
| | def getSignalsForUpdate(self, obj): |
| | signals = [] |
| | signals.append(self.form.safeHeight.editingFinished) |
| | signals.append(self.form.clearanceHeight.editingFinished) |
| | return signals |
| |
|
| | def pageUpdateData(self, obj, prop): |
| | if prop in ["SafeHeight", "ClearanceHeight"]: |
| | self.setFields(obj) |
| |
|
| |
|
| | class TaskPanelDepthsPage(TaskPanelPage): |
| | """Page controller for depths.""" |
| |
|
| | def __init__(self, obj, features): |
| | super(TaskPanelDepthsPage, self).__init__(obj, features) |
| |
|
| | |
| | self.startDepth = None |
| | self.finalDepth = None |
| | self.finishDepth = None |
| | self.stepDown = None |
| | self.panelTitle = "Depths" |
| | self.OpIcon = ":/icons/CAM_Depths.svg" |
| | self.setIcon(self.OpIcon) |
| |
|
| | def getForm(self): |
| | return FreeCADGui.PySideUic.loadUi(":/panels/PageDepthsEdit.ui") |
| |
|
| | def haveStartDepth(self): |
| | return PathOp.FeatureDepths & self.features |
| |
|
| | def haveFinalDepth(self): |
| | return ( |
| | PathOp.FeatureDepths & self.features and not PathOp.FeatureNoFinalDepth & self.features |
| | ) |
| |
|
| | def haveFinishDepth(self): |
| | return PathOp.FeatureDepths & self.features and PathOp.FeatureFinishDepth & self.features |
| |
|
| | def haveStepDown(self): |
| | return PathOp.FeatureStepDown & self.features |
| |
|
| | def initPage(self, obj): |
| |
|
| | if self.haveStartDepth(): |
| | self.startDepth = PathGuiUtil.QuantitySpinBox(self.form.startDepth, obj, "StartDepth") |
| | else: |
| | self.form.startDepth.hide() |
| | self.form.startDepthLabel.hide() |
| | self.form.startDepthSet.hide() |
| |
|
| | if self.haveFinalDepth(): |
| | self.finalDepth = PathGuiUtil.QuantitySpinBox(self.form.finalDepth, obj, "FinalDepth") |
| | else: |
| | if self.haveStartDepth(): |
| | self.form.finalDepth.setEnabled(False) |
| | self.form.finalDepth.setToolTip( |
| | translate( |
| | "PathOp", |
| | "FinalDepth cannot be modified for this operation.\nIf it is necessary to set the FinalDepth manually please select a different operation.", |
| | ) |
| | ) |
| | else: |
| | self.form.finalDepth.hide() |
| | self.form.finalDepthLabel.hide() |
| | self.form.finalDepthSet.hide() |
| |
|
| | if self.haveStepDown(): |
| | self.stepDown = PathGuiUtil.QuantitySpinBox(self.form.stepDown, obj, "StepDown") |
| | else: |
| | self.form.stepDown.hide() |
| | self.form.stepDownLabel.hide() |
| |
|
| | if self.haveFinishDepth(): |
| | self.finishDepth = PathGuiUtil.QuantitySpinBox( |
| | self.form.finishDepth, obj, "FinishDepth" |
| | ) |
| | else: |
| | self.form.finishDepth.hide() |
| | self.form.finishDepthLabel.hide() |
| |
|
| | def getTitle(self, obj): |
| | return translate("PathOp", "Depths") |
| |
|
| | def getFields(self, obj): |
| | if self.haveStartDepth(): |
| | self.startDepth.updateProperty() |
| | if self.haveFinalDepth(): |
| | self.finalDepth.updateProperty() |
| | if self.haveStepDown(): |
| | self.stepDown.updateProperty() |
| | if self.haveFinishDepth(): |
| | self.finishDepth.updateProperty() |
| |
|
| | def setFields(self, obj): |
| | if self.haveStartDepth(): |
| | self.startDepth.updateWidget() |
| | if self.haveFinalDepth(): |
| | self.finalDepth.updateWidget() |
| | if self.haveStepDown(): |
| | self.stepDown.updateWidget() |
| | if self.haveFinishDepth(): |
| | self.finishDepth.updateWidget() |
| | self.updateSelection(obj, FreeCADGui.Selection.getSelectionEx()) |
| |
|
| | def getSignalsForUpdate(self, obj): |
| | signals = [] |
| | if self.haveStartDepth(): |
| | signals.append(self.form.startDepth.editingFinished) |
| | if self.haveFinalDepth(): |
| | signals.append(self.form.finalDepth.editingFinished) |
| | if self.haveStepDown(): |
| | signals.append(self.form.stepDown.editingFinished) |
| | if self.haveFinishDepth(): |
| | signals.append(self.form.finishDepth.editingFinished) |
| | return signals |
| |
|
| | def registerSignalHandlers(self, obj): |
| | if self.haveStartDepth(): |
| | self.form.startDepthSet.clicked.connect( |
| | lambda: self.depthSet(obj, self.startDepth, "StartDepth") |
| | ) |
| | if self.haveFinalDepth(): |
| | self.form.finalDepthSet.clicked.connect( |
| | lambda: self.depthSet(obj, self.finalDepth, "FinalDepth") |
| | ) |
| |
|
| | def pageUpdateData(self, obj, prop): |
| | if prop in ["StartDepth", "FinalDepth", "StepDown", "FinishDepth"]: |
| | self.setFields(obj) |
| |
|
| | def depthSet(self, obj, spinbox, prop): |
| | z = self.selectionZLevel(FreeCADGui.Selection.getSelectionEx()) |
| | if z is not None: |
| | Path.Log.debug("depthSet(%s, %s, %.2f)" % (obj.Label, prop, z)) |
| | if spinbox.expression(): |
| | obj.setExpression(prop, None) |
| | self.setDirty() |
| | spinbox.updateWidget(FreeCAD.Units.Quantity(z, FreeCAD.Units.Length)) |
| | if spinbox.updateProperty(): |
| | self.setDirty() |
| | else: |
| | Path.Log.info("depthSet(-)") |
| |
|
| | def selectionZLevel(self, sel): |
| | if len(sel) == 1 and len(sel[0].SubObjects) == 1: |
| | sub = sel[0].SubObjects[0] |
| | if "Vertex" == sub.ShapeType: |
| | return sub.Z |
| | if Path.Geom.isHorizontal(sub): |
| | if "Edge" == sub.ShapeType: |
| | return sub.Vertexes[0].Z |
| | if "Face" == sub.ShapeType: |
| | return sub.BoundBox.ZMax |
| | return None |
| |
|
| | def updateSelection(self, obj, sel): |
| | if self.selectionZLevel(sel) is not None: |
| | self.form.startDepthSet.setEnabled(True) |
| | self.form.finalDepthSet.setEnabled(True) |
| | else: |
| | self.form.startDepthSet.setEnabled(False) |
| | self.form.finalDepthSet.setEnabled(False) |
| |
|
| |
|
| | class TaskPanelDiametersPage(TaskPanelPage): |
| | """Page controller for diameters.""" |
| |
|
| | def __init__(self, obj, features): |
| | super(TaskPanelDiametersPage, self).__init__(obj, features) |
| |
|
| | |
| | self.clearanceHeight = None |
| | self.safeHeight = None |
| |
|
| | def getForm(self): |
| | return FreeCADGui.PySideUic.loadUi(":/panels/PageDiametersEdit.ui") |
| |
|
| | def initPage(self, obj): |
| | self.minDiameter = PathGuiUtil.QuantitySpinBox(self.form.minDiameter, obj, "MinDiameter") |
| | self.maxDiameter = PathGuiUtil.QuantitySpinBox(self.form.maxDiameter, obj, "MaxDiameter") |
| |
|
| | def getTitle(self, obj): |
| | return translate("PathOp", "Diameters") |
| |
|
| | def getFields(self, obj): |
| | self.minDiameter.updateProperty() |
| | self.maxDiameter.updateProperty() |
| |
|
| | def setFields(self, obj): |
| | self.minDiameter.updateWidget() |
| | self.maxDiameter.updateWidget() |
| |
|
| | def getSignalsForUpdate(self, obj): |
| | signals = [] |
| | signals.append(self.form.minDiameter.editingFinished) |
| | signals.append(self.form.maxDiameter.editingFinished) |
| | return signals |
| |
|
| | def pageUpdateData(self, obj, prop): |
| | if prop in ["MinDiameter", "MaxDiameter"]: |
| | self.setFields(obj) |
| |
|
| |
|
| | class TaskPanel(object): |
| | """ |
| | Generic TaskPanel implementation handling the standard Path operation layout. |
| | This class only implements the framework and takes care of bringing all pages up and down in a controller fashion. |
| | It implements the standard editor behaviour for OK, Cancel and Apply and tracks if the model is still in sync with |
| | the UI. |
| | However, all display and processing of fields is handled by the page controllers which are managed in a list. All |
| | event callbacks and framework actions are forwarded to the page controllers in turn and each page controller is |
| | expected to process all events concerning the data it manages. |
| | """ |
| |
|
| | def __init__(self, obj, deleteOnReject, opPage, selectionFactory): |
| | Path.Log.track(obj.Label, deleteOnReject, opPage, selectionFactory) |
| | FreeCAD.ActiveDocument.openTransaction(translate("PathOp", "AreaOp Operation")) |
| | self.obj = obj |
| | self.deleteOnReject = deleteOnReject |
| | self.featurePages = [] |
| | self.parent = None |
| |
|
| | |
| | self.clearanceHeight = None |
| | self.safeHeight = None |
| | self.startDepth = None |
| | self.finishDepth = None |
| | self.finalDepth = None |
| | self.stepDown = None |
| | self.buttonBox = None |
| | self.minDiameter = None |
| | self.maxDiameter = None |
| |
|
| | features = obj.Proxy.opFeatures(obj) |
| | opPage.features = features |
| |
|
| | if PathOp.FeatureBaseGeometry & features: |
| | if hasattr(opPage, "taskPanelBaseGeometryPage"): |
| | self.featurePages.append(opPage.taskPanelBaseGeometryPage(obj, features)) |
| | else: |
| | self.featurePages.append(TaskPanelBaseGeometryPage(obj, features)) |
| |
|
| | if PathOp.FeatureLocations & features: |
| | if hasattr(opPage, "taskPanelBaseLocationPage"): |
| | self.featurePages.append(opPage.taskPanelBaseLocationPage(obj, features)) |
| | else: |
| | self.featurePages.append(TaskPanelBaseLocationPage(obj, features)) |
| |
|
| | if PathOp.FeatureDepths & features or PathOp.FeatureStepDown & features: |
| | if hasattr(opPage, "taskPanelDepthsPage"): |
| | self.featurePages.append(opPage.taskPanelDepthsPage(obj, features)) |
| | else: |
| | self.featurePages.append(TaskPanelDepthsPage(obj, features)) |
| |
|
| | if PathOp.FeatureHeights & features: |
| | if hasattr(opPage, "taskPanelHeightsPage"): |
| | self.featurePages.append(opPage.taskPanelHeightsPage(obj, features)) |
| | else: |
| | self.featurePages.append(TaskPanelHeightsPage(obj, features)) |
| |
|
| | if PathOp.FeatureDiameters & features: |
| | if hasattr(opPage, "taskPanelDiametersPage"): |
| | self.featurePages.append(opPage.taskPanelDiametersPage(obj, features)) |
| | else: |
| | self.featurePages.append(TaskPanelDiametersPage(obj, features)) |
| |
|
| | self.featurePages.append(opPage) |
| |
|
| | for page in self.featurePages: |
| | page.parent = self |
| | page.initPage(obj) |
| | page.onDirtyChanged(self.pageDirtyChanged) |
| |
|
| | taskPanelLayout = Path.Preferences.defaultTaskPanelLayout() |
| |
|
| | if taskPanelLayout < 2: |
| | opTitle = opPage.getTitle(obj) |
| | opPage.setTitle(translate("PathOp", "Operation")) |
| | toolbox = QtGui.QToolBox() |
| | if taskPanelLayout == 0: |
| | for page in self.featurePages: |
| | toolbox.addItem(page.form, page.getTitle(obj)) |
| | itemIdx = toolbox.count() - 1 |
| | if page.icon: |
| | toolbox.setItemIcon(itemIdx, QtGui.QIcon(page.icon)) |
| | toolbox.setCurrentIndex(len(self.featurePages) - 1) |
| | else: |
| | for page in reversed(self.featurePages): |
| | toolbox.addItem(page.form, page.getTitle(obj)) |
| | itemIdx = toolbox.count() - 1 |
| | if page.icon: |
| | toolbox.setItemIcon(itemIdx, QtGui.QIcon(page.icon)) |
| | toolbox.setWindowTitle(opTitle) |
| | if opPage.getIcon(obj): |
| | toolbox.setWindowIcon(QtGui.QIcon(opPage.getIcon(obj))) |
| |
|
| | self.form = toolbox |
| | elif taskPanelLayout == 2: |
| | forms = [] |
| | for page in self.featurePages: |
| | page.form.setWindowTitle(page.getTitle(obj)) |
| | forms.append(page.form) |
| | self.form = forms |
| | elif taskPanelLayout == 3: |
| | forms = [] |
| | for page in reversed(self.featurePages): |
| | page.form.setWindowTitle(page.getTitle(obj)) |
| | forms.append(page.form) |
| | self.form = forms |
| |
|
| | self.selectionFactory = selectionFactory |
| | self.obj = obj |
| | self.isdirty = deleteOnReject |
| | self.visibility = obj.ViewObject.Visibility |
| | obj.ViewObject.Visibility = True |
| |
|
| | def isDirty(self): |
| | """isDirty() ... returns true if the model is not in sync with the UI anymore.""" |
| | for page in self.featurePages: |
| | if page.isdirty: |
| | return True |
| | return self.isdirty |
| |
|
| | def setClean(self): |
| | """setClean() ... set the receiver and all its pages clean.""" |
| | self.isdirty = False |
| | for page in self.featurePages: |
| | page.setClean() |
| |
|
| | def accept(self, resetEdit=True): |
| | """accept() ... callback invoked when user presses the task panel OK button.""" |
| | self.preCleanup() |
| | if self.isDirty(): |
| | self.panelGetFields() |
| | FreeCAD.ActiveDocument.commitTransaction() |
| | self.cleanup(resetEdit) |
| |
|
| | def reject(self, resetEdit=True): |
| | """reject() ... callback invoked when user presses the task panel Cancel button.""" |
| | self.preCleanup() |
| | FreeCAD.ActiveDocument.abortTransaction() |
| | if self.deleteOnReject: |
| | FreeCAD.ActiveDocument.openTransaction(translate("PathOp", "Uncreate AreaOp Operation")) |
| | try: |
| | PathUtil.clearExpressionEngine(self.obj) |
| | FreeCAD.ActiveDocument.removeObject(self.obj.Name) |
| | except Exception as ee: |
| | Path.Log.debug("{}\n".format(ee)) |
| | FreeCAD.ActiveDocument.commitTransaction() |
| | self.cleanup(resetEdit) |
| | return True |
| |
|
| | def preCleanup(self): |
| | for page in self.featurePages: |
| | page.onDirtyChanged(None) |
| | PathSelection.clear() |
| | FreeCADGui.Selection.removeObserver(self) |
| | self.obj.ViewObject.Proxy.clearTaskPanel() |
| | self.obj.ViewObject.Visibility = self.visibility |
| |
|
| | def cleanup(self, resetEdit): |
| | """cleanup() ... implements common cleanup tasks.""" |
| | self.panelCleanup() |
| | FreeCADGui.Control.closeDialog() |
| | if resetEdit: |
| | FreeCADGui.ActiveDocument.resetEdit() |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| | def pageDirtyChanged(self, page): |
| | """pageDirtyChanged(page) ... internal callback""" |
| | self.buttonBox.button(QtGui.QDialogButtonBox.Apply).setEnabled(self.isDirty()) |
| |
|
| | def clicked(self, button): |
| | """clicked(button) ... callback invoked when the user presses any of the task panel buttons.""" |
| | if button == QtGui.QDialogButtonBox.Apply: |
| | self.panelGetFields() |
| | self.setClean() |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| | def modifyStandardButtons(self, buttonBox): |
| | """modifyStandarButtons(buttonBox) ... callback in case the task panel buttons need to be modified.""" |
| | self.buttonBox = buttonBox |
| | for page in self.featurePages: |
| | page.modifyStandardButtons(buttonBox) |
| | self.pageDirtyChanged(None) |
| |
|
| | def panelGetFields(self): |
| | """panelGetFields() ... invoked to trigger a complete transfer of UI data to the model.""" |
| | Path.Log.track() |
| | for page in self.featurePages: |
| | page.pageGetFields() |
| |
|
| | def panelSetFields(self): |
| | """panelSetFields() ... invoked to trigger a complete transfer of the model's properties to the UI.""" |
| | Path.Log.track() |
| | self.obj.Proxy.sanitizeBase(self.obj) |
| | for page in self.featurePages: |
| | page.pageSetFields() |
| |
|
| | def panelCleanup(self): |
| | """panelCleanup() ... invoked before the receiver is destroyed.""" |
| | Path.Log.track() |
| | for page in self.featurePages: |
| | page.pageCleanup() |
| |
|
| | def open(self): |
| | """open() ... callback invoked when the task panel is opened.""" |
| | self.selectionFactory() |
| | FreeCADGui.Selection.addObserver(self) |
| |
|
| | def getStandardButtons(self): |
| | """getStandardButtons() ... returns the Buttons for the task panel.""" |
| | return ( |
| | QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel |
| | ) |
| |
|
| | def setupUi(self): |
| | """setupUi() ... internal function to initialise all pages.""" |
| | Path.Log.track(self.deleteOnReject) |
| |
|
| | if self.deleteOnReject and PathOp.FeatureBaseGeometry & self.obj.Proxy.opFeatures(self.obj): |
| | sel = FreeCADGui.Selection.getSelectionEx() |
| | for page in self.featurePages: |
| | if getattr(page, "InitBase", True) and hasattr(page, "addBase"): |
| | page.clearBase() |
| | page.addBaseGeometry(sel) |
| |
|
| | |
| | for prp, expr in self.obj.ExpressionEngine: |
| | evalExpr = self.obj.evalExpression(expr) |
| | if not isinstance(evalExpr, (int, float, FreeCAD.Units.Quantity)): |
| | continue |
| | val = FreeCAD.Units.Quantity(evalExpr) |
| | value = val.Value if hasattr(val, "Value") else val |
| | prop = getattr(self.obj, prp) |
| | if hasattr(prop, "Value"): |
| | prop.Value = value |
| | else: |
| | prop = value |
| |
|
| | self.panelSetFields() |
| |
|
| | for page in self.featurePages: |
| | page.pageRegisterSignalHandlers() |
| |
|
| | def updateData(self, obj, prop): |
| | """updateDate(obj, prop) ... callback invoked whenever a model's property is assigned a value.""" |
| | |
| | for page in self.featurePages: |
| | page.pageUpdateData(obj, prop) |
| |
|
| | def needsFullSpace(self): |
| | return True |
| |
|
| | def updateSelection(self): |
| | sel = FreeCADGui.Selection.getSelectionEx() |
| | for page in self.featurePages: |
| | page.updateSelection(self.obj, sel) |
| |
|
| | |
| | def addSelection(self, doc, obj, sub, pnt): |
| | self.updateSelection() |
| |
|
| | def removeSelection(self, doc, obj, sub): |
| | self.updateSelection() |
| |
|
| | def setSelection(self, doc): |
| | self.updateSelection() |
| |
|
| | def clearSelection(self, doc): |
| | self.updateSelection() |
| |
|
| |
|
| | class CommandSetStartPoint: |
| | """Command to set the start point for an operation.""" |
| |
|
| | def GetResources(self): |
| | return { |
| | "Pixmap": "CAM_StartPoint", |
| | "MenuText": QT_TRANSLATE_NOOP("PathOp", "Start Point Selection"), |
| | "ToolTip": QT_TRANSLATE_NOOP("PathOp", "Selects the start point"), |
| | } |
| |
|
| | def IsActive(self): |
| | if FreeCAD.ActiveDocument is None: |
| | return False |
| | sel = FreeCADGui.Selection.getSelection() |
| | if not sel: |
| | return False |
| | obj = sel[0] |
| | return obj and hasattr(obj, "StartPoint") |
| |
|
| | def setpoint(self, point, o): |
| | FreeCADGui.Snapper.grid.off() |
| | obj = self.obj |
| | obj.StartPoint.x = point.x |
| | obj.StartPoint.y = point.y |
| | obj.StartPoint.z = obj.ClearanceHeight.Value |
| | obj.UseStartPoint = True |
| | obj.recompute() |
| | textPoint = f"{obj.StartPoint.x:.2f}, {obj.StartPoint.y:.2f}, {obj.StartPoint.z:.2f}" |
| | print(f"Set start point for operation {obj.Label} >>> {textPoint}") |
| |
|
| | def Activated(self): |
| | self.obj = FreeCADGui.Selection.getSelection()[0] |
| | if not hasattr(FreeCADGui, "Snapper"): |
| | import DraftTools |
| | FreeCADGui.Snapper.getPoint(callback=self.setpoint) |
| |
|
| |
|
| | def Create(res): |
| | """Create(res) ... generic implementation of a create function. |
| | res is an instance of CommandResources. It is not expected that the user invokes |
| | this function directly, but calls the Activated() function of the Command object |
| | that is created in each operations Gui implementation.""" |
| | FreeCAD.ActiveDocument.openTransaction("Create %s" % res.name) |
| | if res.job is None: |
| | FreeCAD.ActiveDocument.abortTransaction() |
| | raise ValueError("No job selected. Operation creation aborted.") |
| | try: |
| | obj = res.objFactory(res.name, obj=None, parentJob=res.job) |
| | if obj.Proxy: |
| | obj.ViewObject.Proxy = ViewProvider(obj.ViewObject, res) |
| | obj.ViewObject.Visibility = True |
| | FreeCAD.ActiveDocument.commitTransaction() |
| |
|
| | obj.ViewObject.Document.setEdit(obj.ViewObject, 0) |
| | return obj |
| | except PathUtils.PathNoTCExistsException: |
| | msg = translate("PathOp", "No suitable tool controller found.\nAborting op creation") |
| | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "Error", msg) |
| | diag.setWindowModality(QtCore.Qt.ApplicationModal) |
| | diag.exec_() |
| | except PathOp.PathNoTCException: |
| | Path.Log.warning(translate("PathOp", "No tool controller, aborting op creation")) |
| |
|
| | FreeCAD.ActiveDocument.abortTransaction() |
| | FreeCAD.ActiveDocument.recompute() |
| | return None |
| |
|
| |
|
| | class CommandPathOp: |
| | """Generic, data driven implementation of a Path operation creation command. |
| | Instances of this class are stored in all Path operation Gui modules and can |
| | be used to create said operations with view providers and all.""" |
| |
|
| | def __init__(self, resources): |
| | self.res = resources |
| |
|
| | def GetResources(self): |
| | ress = { |
| | "Pixmap": self.res.pixmap, |
| | "MenuText": self.res.menuText, |
| | "ToolTip": self.res.toolTip, |
| | } |
| | if self.res.accelKey: |
| | ress["Accel"] = self.res.accelKey |
| | return ress |
| |
|
| | def IsActive(self): |
| | if FreeCAD.ActiveDocument is not None: |
| | for o in FreeCAD.ActiveDocument.Objects: |
| | if o.Name[:3] == "Job": |
| | return True |
| | return False |
| |
|
| | def Activated(self): |
| | jobs = PathUtils.GetJobs() |
| | if not jobs: |
| | return |
| | job = PathUtils.UserInput.chooseJob(jobs) |
| | if job is None: |
| | return |
| | self.res.job = job |
| | return Create(self.res) |
| |
|
| | def setJob(self, job): |
| | self.res.job = job |
| | return Create(self.res) |
| |
|
| |
|
| | class CommandResources: |
| | """POD class to hold command specific resources.""" |
| |
|
| | def __init__(self, name, objFactory, opPageClass, pixmap, menuText, accelKey, toolTip): |
| | self.name = name |
| | self.objFactory = objFactory |
| | self.opPageClass = opPageClass |
| | self.pixmap = pixmap |
| | self.menuText = menuText |
| | self.accelKey = accelKey |
| | self.toolTip = toolTip |
| | self.job = None |
| |
|
| |
|
| | def SetupOperation(name, objFactory, opPageClass, pixmap, menuText, toolTip, setupProperties=None): |
| | """SetupOperation(name, objFactory, opPageClass, pixmap, menuText, toolTip, setupProperties=None) |
| | Creates an instance of CommandPathOp with the given parameters and registers the command with FreeCAD. |
| | When activated it creates a model with proxy (by invoking objFactory), assigns a view provider to it |
| | (see ViewProvider in this module) and starts the editor specifically for this operation (driven by opPageClass). |
| | This is an internal function that is automatically called by the initialisation code for each operation. |
| | It is not expected to be called manually. |
| | """ |
| |
|
| | res = CommandResources(name, objFactory, opPageClass, pixmap, menuText, None, toolTip) |
| |
|
| | command = CommandPathOp(res) |
| | FreeCADGui.addCommand("CAM_%s" % name.replace(" ", "_"), command) |
| |
|
| | if setupProperties is not None: |
| | PathSetupSheet.RegisterOperation(name, objFactory, setupProperties) |
| |
|
| | return command |
| |
|
| |
|
| | FreeCADGui.addCommand("CAM_SetStartPoint", CommandSetStartPoint()) |
| |
|
| | FreeCAD.Console.PrintLog("Loading PathOpGui… done\n") |
| |
|