From bc6ff3690e46a0550ef91f4a1fa3ffe0d034986c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 14 Aug 2017 22:47:37 -0700 Subject: [PATCH] Added docstrings to Gui classes. --- .../PathScripts/PathCircularHoleBaseGui.py | 21 +++ src/Mod/Path/PathScripts/PathDrillingGui.py | 12 +- src/Mod/Path/PathScripts/PathEngraveGui.py | 15 +- src/Mod/Path/PathScripts/PathGetPoint.py | 40 ++++- src/Mod/Path/PathScripts/PathHelixGui.py | 9 +- src/Mod/Path/PathScripts/PathMillFaceGui.py | 9 +- src/Mod/Path/PathScripts/PathOpGui.py | 153 +++++++++++++++--- src/Mod/Path/PathScripts/PathPocketBaseGui.py | 21 +++ src/Mod/Path/PathScripts/PathPocketGui.py | 9 +- .../Path/PathScripts/PathProfileBaseGui.py | 21 +++ .../Path/PathScripts/PathProfileContourGui.py | 10 +- .../Path/PathScripts/PathProfileEdgesGui.py | 10 +- .../Path/PathScripts/PathProfileFacesGui.py | 10 +- 13 files changed, 304 insertions(+), 36 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py index 289631db1b..33becd0a17 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py @@ -29,6 +29,11 @@ import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui +__title__ = "Base for Circular Hole based operations' UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Implementation of circular hole specific base geometry page controller." + if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) @@ -36,14 +41,21 @@ else: PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): + '''Controller class to be used for the BaseGeomtery page. + Circular holes don't just disply the feature, they also add a column + displaying the radius the feature describes. This page provides that + UI and functionality for all circular hole based operations.''' + DataFeatureName = QtCore.Qt.ItemDataRole.UserRole DataObject = QtCore.Qt.ItemDataRole.UserRole + 1 DataObjectSub = QtCore.Qt.ItemDataRole.UserRole + 2 def getForm(self): + '''getForm() ... load and return page''' return FreeCADGui.PySideUic.loadUi(":/panels/PageBaseHoleGeometryEdit.ui") def setFields(self, obj): + '''setFields(obj) ... fill form with values from obj''' PathLog.track() self.form.baseList.blockSignals(True) self.form.baseList.clearContents() @@ -75,6 +87,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): self.form.baseList.blockSignals(False) def itemActivated(self): + '''itemActivated() ... callback when item in table is selected''' PathLog.track() FreeCADGui.Selection.clearSelection() activatedRows = [] @@ -92,6 +105,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): #FreeCADGui.updateGui() def deleteBase(self): + '''deleteBase() ... callback for push button''' PathLog.track() deletedRows = [] selected = self.form.baseList.selectedItems() @@ -105,6 +119,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): FreeCAD.ActiveDocument.recompute() def updateBase(self): + '''updateBase() ... helper function to transfer current table to obj''' PathLog.track() newlist = [] for i in range(self.form.baseList.rowCount()): @@ -118,6 +133,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): self.obj.Base = newlist def checkedChanged(self): + '''checkeChanged() ... callback when checked status of a base feature changed''' PathLog.track() disabled = [] for i in xrange(0, self.form.baseList.rowCount()): @@ -128,6 +144,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): FreeCAD.ActiveDocument.recompute() def registerSignalHandlers(self, obj): + '''registerSignalHandlers(obj) ... setup signal handlers''' self.form.baseList.itemSelectionChanged.connect(self.itemActivated) self.form.addBase.clicked.connect(self.addBase) self.form.deleteBase.clicked.connect(self.deleteBase) @@ -135,6 +152,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): self.form.baseList.itemChanged.connect(self.checkedChanged) def resetBase(self): + '''resetBase() ... push button callback''' self.obj.Base = [] self.obj.Disabled = [] @@ -142,11 +160,14 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): FreeCAD.ActiveDocument.recompute() def updateData(self, obj, prop): + '''updateData(obj, prop) ... callback whenever a property of the model changed''' if prop in ['Base', 'Disabled']: self.setFields(obj) class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Base class for circular hole based operation's page controller.''' def taskPanelBaseGeometryPage(self, obj, features): + '''taskPanelBaseGeometryPage(obj, features) ... Return circular hole specific page controller for Base Geometry.''' return TaskPanelHoleGeometryPage(obj, features) diff --git a/src/Mod/Path/PathScripts/PathDrillingGui.py b/src/Mod/Path/PathScripts/PathDrillingGui.py index 9e06ec66c8..1345902e15 100644 --- a/src/Mod/Path/PathScripts/PathDrillingGui.py +++ b/src/Mod/Path/PathScripts/PathDrillingGui.py @@ -31,6 +31,11 @@ import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui +__title__ = "Path Drilling Operation UI." +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "UI and Command for Path Drilling Operation." + if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) @@ -39,11 +44,14 @@ else: class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): + '''Controller for the drilling operation's page''' def getForm(self): + '''getForm() ... return UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpDrillingEdit.ui") def getFields(self, obj): + '''setFields(obj) ... update obj's properties with values from the UI''' PathLog.track() self.updateInputField(obj, 'PeckDepth', self.form.peckDepth) self.updateInputField(obj, 'RetractHeight', self.form.retractHeight) @@ -59,6 +67,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.updateToolController(obj, self.form.toolController) def setFields(self, obj): + '''setFields(obj) ... update UI with obj properties' values''' PathLog.track() self.form.peckDepth.setText(FreeCAD.Units.Quantity(obj.PeckDepth.Value, FreeCAD.Units.Length).UserString) @@ -83,6 +92,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setupToolController(obj, self.form.toolController) def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals which cause the receiver to update the model''' signals = [] signals.append(self.form.retractHeight.editingFinished) @@ -95,7 +105,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): return signals -PathOpGui.SetupOperation('Drilling', +Command = PathOpGui.SetupOperation('Drilling', PathDrilling.Create, TaskPanelOpPage, 'Path-Drilling', diff --git a/src/Mod/Path/PathScripts/PathEngraveGui.py b/src/Mod/Path/PathScripts/PathEngraveGui.py index 8020ded165..bc9d25851d 100644 --- a/src/Mod/Path/PathScripts/PathEngraveGui.py +++ b/src/Mod/Path/PathScripts/PathEngraveGui.py @@ -31,33 +31,40 @@ import PathScripts.PathSelection as PathSelection from PySide import QtCore, QtGui +__title__ = "Path Engrave Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Engrave operation page controller and command implementation." + def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -FeaturePocket = 0x01 -FeatureFacing = 0x02 - class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Engrave operation.''' def getForm(self): + '''getForm() ... returns UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpEngraveEdit.ui") def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' if obj.StartVertex != self.form.startVertex.value(): obj.StartVertex = self.form.startVertex.value() self.updateToolController(obj, self.form.toolController) def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' self.form.startVertex.setValue(obj.StartVertex) self.setupToolController(obj, self.form.toolController) def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.startVertex.editingFinished) signals.append(self.form.toolController.currentIndexChanged) return signals -PathOpGui.SetupOperation('Engrave', +Command = PathOpGui.SetupOperation('Engrave', PathEngrave.Create, TaskPanelOpPage, 'Path-Engrave', diff --git a/src/Mod/Path/PathScripts/PathGetPoint.py b/src/Mod/Path/PathScripts/PathGetPoint.py index a929ad9414..66806866cc 100644 --- a/src/Mod/Path/PathScripts/PathGetPoint.py +++ b/src/Mod/Path/PathScripts/PathGetPoint.py @@ -29,16 +29,32 @@ import FreeCADGui from PySide import QtCore, QtGui from pivy import coin -class TaskPanel: +__title__ = "Path GetPoint UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Helper class to use FreeCADGUi.Snapper to let the user enter arbitray points while the task panel is active." - def __init__(self, formOrig, formPoint): - self.formOrig = formOrig - self.formPoint = formPoint +class TaskPanel: + '''Use an instance of this class in another TaskPanel to invoke the snapper. + Create the instance in the TaskPanel's constructors and invoke getPoint(whenDone, start) whenever a new point is + required or an existing point needs to be edited. The receiver is expected to have the same lifespan as the form + provided in the constructor. + The (only) public API function other than the constructor is getPoint(whenDone, start). + ''' + def __init__(self, form): + '''__init___(form) ... form will be replaced by PointEdit.ui while the Snapper is active.''' + self.formOrig = form + self.formPoint = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui") + + self.formPoint.setParent(form.parent()) + form.parent().layout().addWidget(self.formPoint) + self.formPoint.hide() self.setupUi() self.buttonBox = None def setupUi(self): + '''setupUi() ... internal function - do not call.''' self.formPoint.buttonBox.accepted.connect(self.pointAccept) self.formPoint.buttonBox.rejected.connect(self.pointReject) @@ -47,6 +63,7 @@ class TaskPanel: self.formPoint.ifValueZ.editingFinished.connect(self.updatePoint) def addEscapeShortcut(self): + '''addEscapeShortcut() ... internal function - do not call.''' # The only way I could get to intercept the escape key, or really any key was # by creating an action with a shortcut ..... self.escape = QtGui.QAction(self.formPoint) @@ -56,11 +73,19 @@ class TaskPanel: self.formPoint.addAction(self.escape) def removeEscapeShortcut(self): + '''removeEscapeShortcut() ... internal function - do not call.''' if self.escape: self.formPoint.removeAction(self.escape) self.escape = None def getPoint(self, whenDone, start=None): + '''getPoint(whenDone, start=None) ... invoke Snapper and call whenDone when a point is entered or the user cancels the operation. + whenDone(point, obj) is called either with a point and the object on which the point lies if the user set the point, + or None and None if the user cancelled the operation. + start is an optional Vector indicating from where to start Snapper. This is mostly used when editing existing points. Snapper also + creates a dotted line indicating from where the original point started from. + If start is specified the Snapper UI is closed on the first point the user enters. If start remains None, then Snapper is kept open + until the user explicitly closes Snapper. This lets the user enter multiple points in quick succession.''' def displayPoint(p): self.formPoint.ifValueX.setText(FreeCAD.Units.Quantity(p.x, FreeCAD.Units.Length).UserString) @@ -111,6 +136,7 @@ class TaskPanel: FreeCADGui.Snapper.forceGridOff=True def pointFinish(self, ok, cleanup = True): + '''pointFinish(ok, cleanup=True) ... internal function - do not call.''' obj = FreeCADGui.Snapper.lastSnappedObject if cleanup: @@ -129,18 +155,23 @@ class TaskPanel: self.pointWhenDone(None, None) def pointDone(self): + '''pointDone() ... internal function - do not call.''' self.pointFinish(False) def pointReject(self): + '''pointReject() ... internal function - do not call.''' self.pointFinish(False) def pointAccept(self): + '''pointAccept() ... internal function - do not call.''' self.pointFinish(True) def pointAcceptAndContinue(self): + '''pointAcceptAndContinue() ... internal function - do not call.''' self.pointFinish(True, False) def removeGlobalCallbacks(self): + '''removeGlobalCallbacks() ... internal function - do not call.''' if hasattr(self, 'view') and self.view: if self.pointCbClick: self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.pointCbClick) @@ -151,6 +182,7 @@ class TaskPanel: self.view = None def updatePoint(self): + '''updatePoint() ... internal function - do not call.''' x = FreeCAD.Units.Quantity(self.formPoint.ifValueX.text()).Value y = FreeCAD.Units.Quantity(self.formPoint.ifValueY.text()).Value z = FreeCAD.Units.Quantity(self.formPoint.ifValueZ.text()).Value diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index bd3979e935..5299dd2499 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -31,6 +31,8 @@ import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui +__doc__ = "Helix operation page controller and command implementation." + if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) @@ -39,11 +41,14 @@ else: class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): + '''Page controller class for Helix operations.''' def getForm(self): + '''getForm() ... return UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpHelixEdit.ui") def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' PathLog.track() if obj.Direction != str(self.form.direction.currentText()): obj.Direction = str(self.form.direction.currentText()) @@ -55,6 +60,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.updateToolController(obj, self.form.toolController) def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' PathLog.track() self.form.stepOverPercent.setValue(obj.StepOver) @@ -64,6 +70,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setupToolController(obj, self.form.toolController) def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.stepOverPercent.editingFinished) @@ -73,7 +80,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): return signals -PathOpGui.SetupOperation('Helix', +Command = PathOpGui.SetupOperation('Helix', PathHelix.Create, TaskPanelOpPage, 'Path-Helix', diff --git a/src/Mod/Path/PathScripts/PathMillFaceGui.py b/src/Mod/Path/PathScripts/PathMillFaceGui.py index 6ece838b15..5c23af73a7 100644 --- a/src/Mod/Path/PathScripts/PathMillFaceGui.py +++ b/src/Mod/Path/PathScripts/PathMillFaceGui.py @@ -30,12 +30,19 @@ import PathScripts.PathPocketBaseGui as PathPocketBaseGui from PySide import QtCore +__title__ = "Path Face Mill Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Face Mill operation page controller and command implementation." + class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage): + '''Page controller class for the face milling operation.''' def pocketFeatures(self): + '''pocketFeatures() ... return FeatureFacing (see PathPocketBaseGui)''' return PathPocketBaseGui.FeatureFacing -PathOpGui.SetupOperation('MillFace', +Command = PathOpGui.SetupOperation('MillFace', PathMillFace.Create, TaskPanelOpPage, 'Path-Face', diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 1b5e5d8332..d91479c3b4 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -33,6 +33,11 @@ import importlib from PathScripts import PathUtils from PySide import QtCore, QtGui +__title__ = "Path Operation UI base classes" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Base classes and framework for Path operation's UI" + # TaskPanelLayout # 0 ... existing toolbox layout # 1 ... reverse order @@ -52,6 +57,11 @@ def translate(context, text, disambig=None): 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): PathLog.track() @@ -69,10 +79,15 @@ class ViewProvider(object): 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.''' PathLog.track() return hasattr(self, 'deleteOnReject') and self.deleteOnReject def setEdit(self, vobj, mode=0): + '''setEdit(vobj, mode=0) ... initiate editing of receivers model.''' PathLog.track() page = self.getTaskPanelOpPage(vobj.Object) selection = self.getSelectionFactory() @@ -81,15 +96,19 @@ class ViewProvider(object): return True def setupTaskPanel(self, panel): + '''setupTaskPanel(panel) ... internal function to start the editor.''' self.panel = panel FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(panel) panel.setupUi() def clearTaskPanel(self): + '''clearTaskPanel() ... internal callback function when editing has finished.''' self.panel = None def __getstate__(self): + '''__getstate__() ... callback before receiver is saved to a file. + Returns a dictionary with the receiver's resources as strings.''' PathLog.track() state = {} state['OpName'] = self.OpName @@ -99,31 +118,41 @@ class ViewProvider(object): return state def __setstate__(self, state): + '''__setstate__(state) ... callback on restoring a saved instance, pendant to __getstate__() + state is the dictionary returned by __getstate__().''' 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''' return self.OpIcon def getTaskPanelOpPage(self, obj): + '''getTaskPanelOpPage(obj) ... use the stored information to instanciate 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.''' # PathLog.track(obj.Label, prop) # Creates a lot of noise if self.panel: self.panel.updateData(obj, prop) class TaskPanelPage(object): + '''Base class for all task panel pages.''' # task panel interaction framework def __init__(self, obj, features): + '''__init__(obj, features) ... framework initialisation. + Do not overwrite, implement initPage(obj) instead.''' self.obj = obj self.form = self.getForm() self.setDirty() @@ -131,50 +160,94 @@ class TaskPanelPage(object): self.features = features def setDirty(self): + '''setDirty() ... mark receiver as dirty, causing the model to be recalculated if OK or Apply is pressed.''' self.isdirty = True 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 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 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) instaed.''' 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 # subclass interface 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 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 # helpers def selectInComboBox(self, name, combo): + '''selectInComboBox(name, combo) ... helper function to select a specific value in a combo box.''' index = combo.findText(name, QtCore.Qt.MatchFixedString) if index >= 0: combo.blockSignals(True) @@ -182,6 +255,7 @@ class TaskPanelPage(object): combo.blockSignals(False) def setupToolController(self, obj, combo): + '''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) @@ -194,17 +268,20 @@ class TaskPanelPage(object): self.selectInComboBox(obj.ToolController.Label, combo) 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, combo.currentText()) if obj.ToolController != tc: obj.ToolController = tc def updateInputField(self, obj, prop, widget): + '''updateInputField(obj, prop, widget) ... helper function to update obj's property named prop with the value from widget, if it has changed.''' value = FreeCAD.Units.Quantity(widget.text()).Value if getattr(obj, prop) != value: PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, getattr(obj, prop), value)) setattr(obj, prop, value) class TaskPanelBaseGeometryPage(TaskPanelPage): + '''Page controller for the base geometry.''' DataObject = QtCore.Qt.ItemDataRole.UserRole DataObjectSub = QtCore.Qt.ItemDataRole.UserRole + 1 @@ -323,17 +400,14 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): class TaskPanelBaseLocationPage(TaskPanelPage): + '''Page controller for base locations. Uses PathGetPoint.''' DataLocation = QtCore.Qt.ItemDataRole.UserRole def getForm(self): self.formLoc = FreeCADGui.PySideUic.loadUi(":/panels/PageBaseLocationEdit.ui") - self.formPts = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui") self.formLoc.baseList.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) - self.formPts.setParent(self.formLoc.addRemoveEdit.parent()) - self.formLoc.addRemoveEdit.parent().layout().addWidget(self.formPts) - self.formPts.hide() - self.getPoint = PathGetPoint.TaskPanel(self.formLoc.addRemoveEdit, self.formPts) + self.getPoint = PathGetPoint.TaskPanel(self.formLoc.addRemoveEdit) return self.formLoc def modifyStandardButtons(self, buttonBox): @@ -430,10 +504,11 @@ class TaskPanelBaseLocationPage(TaskPanelPage): class TaskPanelHeightsPage(TaskPanelPage): + '''Page controller for heights.''' def getForm(self): return FreeCADGui.PySideUic.loadUi(":/panels/PageHeightsEdit.ui") def getTitle(self, obj): - return translate("Path_AreaOp", "Heights") + return translate("Path", "Heights") def getFields(self, obj): self.updateInputField(obj, 'SafeHeight', self.form.safeHeight) self.updateInputField(obj, 'ClearanceHeight', self.form.clearanceHeight) @@ -452,10 +527,9 @@ class TaskPanelHeightsPage(TaskPanelPage): class TaskPanelDepthsPage(TaskPanelPage): - + '''Page controller for depths.''' def getForm(self): return FreeCADGui.PySideUic.loadUi(":/panels/PageDepthsEdit.ui") - def initPage(self, obj): if not PathOp.FeatureStepDown & self.features: self.form.stepDown.hide() @@ -464,10 +538,8 @@ class TaskPanelDepthsPage(TaskPanelPage): if not PathOp.FeatureFinishDepth & self.features: self.form.finishDepth.hide() self.form.finishDepthLabel.hide() - def getTitle(self, obj): return translate("PathOp", "Depths") - def getFields(self, obj): self.updateInputField(obj, 'StartDepth', self.form.startDepth) self.updateInputField(obj, 'FinalDepth', self.form.finalDepth) @@ -491,16 +563,23 @@ class TaskPanelDepthsPage(TaskPanelPage): if PathOp.FeatureFinishDepth & self.features: signals.append(self.form.finishDepth.editingFinished) return signals - def pageUpdateData(self, obj, prop): if prop in ['StartDepth', 'FinalDepth', 'StepDown', 'FinishDepth']: 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 contollers 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): PathLog.track(obj.Label, deleteOnReject, opPage, selectionFactory) - FreeCAD.ActiveDocument.openTransaction(translate("Path_AreaOp", "AreaOp Operation")) + FreeCAD.ActiveDocument.openTransaction(translate("Path", "AreaOp Operation")) self.deleteOnReject = deleteOnReject self.featurePages = [] @@ -564,17 +643,20 @@ class TaskPanel(object): self.isdirty = 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 reciever and all its pages clean.''' self.isdirty = False for page in self.featurePages: page.setClean() def accept(self): + '''accept() ... callback invoked when user presses the task panel OK button.''' FreeCAD.ActiveDocument.commitTransaction() self.cleanup() if self.isDirty: @@ -582,49 +664,58 @@ class TaskPanel(object): FreeCAD.ActiveDocument.recompute() def reject(self): + '''reject() ... callback invoked when user presses the task panel Cancel button.''' FreeCAD.ActiveDocument.abortTransaction() self.cleanup() if self.deleteOnReject: - FreeCAD.ActiveDocument.openTransaction(translate("Path_AreaOp", "Uncreate AreaOp Operation")) + FreeCAD.ActiveDocument.openTransaction(translate("Path", "Uncreate AreaOp Operation")) FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() def cleanup(self): + '''cleanup() ... implements common cleanup tasks.''' self.obj.ViewObject.Proxy.clearTaskPanel() FreeCADGui.Control.closeDialog() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Selection.removeObserver(self.s) 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.''' for page in self.featurePages: page.modifyStandardButtons(buttonBox) def panelGetFields(self): + '''panelGetFields() ... invoked to trigger a complete transfer of UI data to the model.''' PathLog.track() for page in self.featurePages: page.pageGetFields() def panelSetFields(self): + '''panelSetFields() ... invoked to trigger a complete transfer of the model's propeties to the UI.''' PathLog.track() for page in self.featurePages: page.pageSetFields() def open(self): + '''open() ... callback invoked when the task panel is opened.''' self.s = SelObserver(self.selectionFactory) # install the function mode resident FreeCADGui.Selection.addObserver(self.s) def getStandardButtons(self): + '''getStandardButtons() ... returns the Buttons for the task panel.''' return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel) def setupUi(self): + '''setupUi() ... internal function to initialise all pages.''' PathLog.track(self.deleteOnReject) if self.deleteOnReject and PathOp.FeatureBaseGeometry & self.obj.Proxy.opFeatures(self.obj): @@ -639,6 +730,7 @@ class TaskPanel(object): page.pageRegisterSignalHandlers() def updateData(self, obj, prop): + '''updateDate(obj, prop) ... callback invoked whenever a model's property is assigned a value.''' # PathLog.track(obj.Label, prop) # creates a lot of noise for page in self.featurePages: page.pageUpdateData(obj, prop) @@ -647,7 +739,8 @@ class TaskPanel(object): return True class SelObserver: - + '''Implementation of the selection observer used by the task panel. + Its specific behaviour is determined by the factory function.''' def __init__(self, factory): factory() @@ -658,11 +751,12 @@ class SelObserver: FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') FreeCADGui.updateGui() -class _CommandSetStartPoint: +class CommandSetStartPoint: + '''Command to set the start point for an operation.''' def GetResources(self): return {'Pixmap': 'Path-StartPoint', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_AreaOp", "Pick Start Point"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_AreaOp", "Pick Start Point")} + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path", "Pick Start Point"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path", "Pick Start Point")} def IsActive(self): if FreeCAD.ActiveDocument is None: @@ -683,6 +777,10 @@ class _CommandSetStartPoint: 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 excpected 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) obj = res.objFactory(res.name) vobj = ViewProvider(obj.ViewObject, res) @@ -692,6 +790,10 @@ def Create(res): return obj 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 @@ -714,6 +816,7 @@ class CommandPathOp: 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 @@ -730,11 +833,21 @@ def SetupOperation(name, menuText, toolTip, accelKey = None): + '''SetupOperation(name, objFactory, opPageClass, pixmap, menuText, toolTip, accelKey=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), assign a view provider to it + (see ViewProvider in this module) and starts the editor specif for theis operation (driven by opPageClass). + This is an internal function that is automatically called by the intialisation code for each operation. + It is not expected to be called manually. + ''' + res = CommandResources(name, objFactory, opPageClass, pixmap, menuText, accelKey, toolTip) - FreeCADGui.addCommand("Path_%s" % name.replace(' ', '_'), CommandPathOp(res)) + command = CommandPathOp(res) + FreeCADGui.addCommand("Path_%s" % name.replace(' ', '_'), command) + return command -FreeCADGui.addCommand('Set_StartPoint', _CommandSetStartPoint()) +FreeCADGui.addCommand('Set_StartPoint', CommandSetStartPoint()) FreeCAD.Console.PrintLog("Loading PathOpGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathPocketBaseGui.py b/src/Mod/Path/PathScripts/PathPocketBaseGui.py index 67a1f198dd..7d19f99b33 100644 --- a/src/Mod/Path/PathScripts/PathPocketBaseGui.py +++ b/src/Mod/Path/PathScripts/PathPocketBaseGui.py @@ -31,6 +31,11 @@ import PathScripts.PathSelection as PathSelection from PySide import QtCore, QtGui +__title__ = "Path Pocket Base Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Base page controller and command implementation for path pocket operations." + def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -38,8 +43,21 @@ FeaturePocket = 0x01 FeatureFacing = 0x02 class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for pocket operations, supports two different features: + FeaturePocket ... used for pocketing operation + FeatureFacing ... used for face milling operation + ''' + + def pocketFeatures(self): + '''pocketFeatures() ... return which features of the UI are supported by the operation. + Typically one of the following is enabled: + FeaturePocket ... used for pocketing operation + FeatureFacing ... used for face milling operation + Must be overwritten by subclasses''' + pass def getForm(self): + '''getForm() ... returns UI, adapted to the resutls from pocketFeatures()''' form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpPocketFullEdit.ui") if not FeaturePocket & self.pocketFeatures(): @@ -51,6 +69,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return form def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' if obj.CutMode != str(self.form.cutMode.currentText()): obj.CutMode = str(self.form.cutMode.currentText()) if obj.StepOver != self.form.stepOverPercent.value(): @@ -72,6 +91,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): obj.BoundaryShape = str(self.form.boundaryShape.currentText()) def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' self.form.zigZagAngle.setText(FreeCAD.Units.Quantity(obj.ZigZagAngle, FreeCAD.Units.Angle).UserString) self.form.stepOverPercent.setValue(obj.StepOver) @@ -88,6 +108,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.BoundaryShape, self.form.boundaryShape) def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.cutMode.currentIndexChanged) diff --git a/src/Mod/Path/PathScripts/PathPocketGui.py b/src/Mod/Path/PathScripts/PathPocketGui.py index c84a6dcfee..be2b7273ef 100644 --- a/src/Mod/Path/PathScripts/PathPocketGui.py +++ b/src/Mod/Path/PathScripts/PathPocketGui.py @@ -29,12 +29,19 @@ import PathScripts.PathPocketBaseGui as PathPocketBaseGui from PySide import QtCore +__title__ = "Path Pocket Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Pocket operation page controller and command implementation." + class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage): + '''Page controller class for Pocket operation''' def pocketFeatures(self): + '''pocketFeatures() ... return FeaturePocket (see PathPocketBaseGui)''' return PathPocketBaseGui.FeaturePocket -PathOpGui.SetupOperation('Pocket', +Command = PathOpGui.SetupOperation('Pocket', PathPocket.Create, TaskPanelOpPage, 'Path-Pocket', diff --git a/src/Mod/Path/PathScripts/PathProfileBaseGui.py b/src/Mod/Path/PathScripts/PathProfileBaseGui.py index 96c4e34b44..92a01cbfa3 100644 --- a/src/Mod/Path/PathScripts/PathProfileBaseGui.py +++ b/src/Mod/Path/PathScripts/PathProfileBaseGui.py @@ -31,6 +31,11 @@ import PathScripts.PathSelection as PathSelection from PySide import QtCore +__title__ = "Path Profile Operation Base UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Base page controller for profile operations." + def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -38,8 +43,21 @@ FeatureSide = 0x01 FeatureProcessing = 0x02 class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Base class for profile operation page controllers. Two sub features are + support + FeatureSide ... Is the Side property exposed in the UI + FeatureProcessing ... Are the processing check boxes supported by the operation + ''' + + def profileFeatures(self): + '''profileFeatures() ... return which of the optional profile features are supported. + Currently two features are supported: + FeatureSide ... Is the Side property exposed in the UI + FeatureProcessing ... Are the processing check boxes supported by the operation + Must be overwritten by subclasses.''' def getForm(self): + '''getForm() ... returns UI customized according to profileFeatures()''' form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpProfileFullEdit.ui") if not FeatureSide & self.profileFeatures(): @@ -54,6 +72,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return form def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' self.updateInputField(obj, 'OffsetExtra', self.form.extraOffset) if obj.UseComp != self.form.useCompensation.isChecked(): obj.UseComp = self.form.useCompensation.isChecked() @@ -77,6 +96,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): obj.processCircles = self.form.processCircles.isChecked() def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' self.form.extraOffset.setText(FreeCAD.Units.Quantity(obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString) self.form.useCompensation.setChecked(obj.UseComp) self.form.useStartPoint.setChecked(obj.UseStartPoint) @@ -93,6 +113,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.processCircles.setChecked(obj.processCircles) def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.direction.currentIndexChanged) signals.append(self.form.useCompensation.clicked) diff --git a/src/Mod/Path/PathScripts/PathProfileContourGui.py b/src/Mod/Path/PathScripts/PathProfileContourGui.py index 1900e001ed..bb75be675a 100644 --- a/src/Mod/Path/PathScripts/PathProfileContourGui.py +++ b/src/Mod/Path/PathScripts/PathProfileContourGui.py @@ -29,13 +29,19 @@ import PathScripts.PathProfileContour as PathProfileContour from PySide import QtCore +__title__ = "Path Contour Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Contour operation page controller and command implementation." + class TaskPanelOpPage(PathProfileBaseGui.TaskPanelOpPage): + '''Page controller for the contour operation UI.''' def profileFeatures(self): - # I know this looks bad, contour is just the most basic profile op there is + '''profileFeatues() ... return 0 - profile doesn't support any of the optional UI features.''' return 0 -PathOpGui.SetupOperation('Contour', +Command = PathOpGui.SetupOperation('Contour', PathProfileContour.Create, TaskPanelOpPage, 'Path-Contour', diff --git a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py index b20ce684f5..d2ae49b2a8 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py +++ b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py @@ -29,12 +29,20 @@ import PathScripts.PathProfileEdges as PathProfileEdges from PySide import QtCore +__title__ = "Path Profile based on edges Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Profile based on edges operation page controller and command implementation." + class TaskPanelOpPage(PathProfileBaseGui.TaskPanelOpPage): + '''Page controller for profile based on edges operation.''' def profileFeatures(self): + '''profileFeatures() ... return FeatureSide + See PathProfileBaseGui.py for details.''' return PathProfileBaseGui.FeatureSide -PathOpGui.SetupOperation('Profile Edges', +Command = PathOpGui.SetupOperation('Profile Edges', PathProfileEdges.Create, TaskPanelOpPage, 'Path-Profile-Edges', diff --git a/src/Mod/Path/PathScripts/PathProfileFacesGui.py b/src/Mod/Path/PathScripts/PathProfileFacesGui.py index 7fe661bac5..8c725a8286 100644 --- a/src/Mod/Path/PathScripts/PathProfileFacesGui.py +++ b/src/Mod/Path/PathScripts/PathProfileFacesGui.py @@ -29,12 +29,20 @@ import PathScripts.PathProfileFaces as PathProfileFaces from PySide import QtCore +__title__ = "Path Profile based on faces Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Profile based on faces operation page controller and command implementation." + class TaskPanelOpPage(PathProfileBaseGui.TaskPanelOpPage): + '''Page controller for profile based on faces operation.''' def profileFeatures(self): + '''profileFeatures() ... return FeatureSide | FeatureProcessing. + See PathProfileBaseGui.py for details.''' return PathProfileBaseGui.FeatureSide | PathProfileBaseGui.FeatureProcessing -PathOpGui.SetupOperation('Profile Faces', +Command = PathOpGui.SetupOperation('Profile Faces', PathProfileFaces.Create, TaskPanelOpPage, 'Path-Profile-Face',