diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index 57d8c2c709..496b3e43d1 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -28,14 +28,13 @@ import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils -from PathScripts.PathUtils import depth_params -from PathScripts.PathUtils import makeWorkplane from PathScripts.PathUtils import waiting_effects from PySide import QtCore __title__ = "Base class for PathArea based operations." __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" +__doc__ = "Base class and properties for Path.Area based operations." if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -48,11 +47,22 @@ def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectOp(PathOp.ObjectOp): + '''Base class for all Path.Area based operations. + Provides standard features including debugging properties AreaParams, + PathParams and removalshape, all hidding. + The main reason for existance is to implement the standard interface + to Path.Area so subclasses only have to provide the shapes for the + operations.''' def opFeatures(self, obj): + '''opFeatures(obj) ... returns the base features supported by all Path.Area based operations. + The standard feature list is OR'ed with the return value of areaOpFeatures(). + Do not overwrite, implement areaOpFeatures(obj) instead.''' return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) def initOperation(self, obj): + '''initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp(). + Do not overwrite, overwrite initAreaOp(obj) instead.''' PathLog.track() # Debugging @@ -66,6 +76,8 @@ class ObjectOp(PathOp.ObjectOp): self.initAreaOp(obj) def areaOpShapeForDepths(self, obj): + '''areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used. + The default implementation retuns the job's Base.Shape''' job = PathUtils.findParentJob(obj) if job and job.Base: PathLog.debug("job=%s base=%s shape=%s" % (job, job.Base, job.Base.Shape)) @@ -77,9 +89,15 @@ class ObjectOp(PathOp.ObjectOp): return None def areaOpOnChanged(self, obj, prop): + '''areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties. + Can safely be overwritten by subclasses.''' pass def opOnChanged(self, obj, prop): + '''opOnChanged(obj, prop) ... base implemenation of the notification framework - do not overwrite. + The base implementation takes a stab at determining Heights and Depths if the operations's Base + changes. + Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.''' #PathLog.track(obj.Label, prop) if prop in ['AreaParams', 'PathParams', 'removalshape']: obj.setEditorMode(prop, 2) @@ -124,6 +142,10 @@ class ObjectOp(PathOp.ObjectOp): self.areaOpOnChanged(obj, prop) def opSetDefaultValues(self, obj): + '''opSetDefaultValues(obj) ... base implementation, do not overwrite. + The base implementation sets the depths and heights based on the + areaOpShapeForDepths() return value. + Do not overwrite, overwrite areaOpSetDefaultValues(obj) instead.''' PathLog.info("opSetDefaultValues(%s)" % (obj.Label)) if PathOp.FeatureDepths & self.opFeatures(obj): try: @@ -159,10 +181,16 @@ class ObjectOp(PathOp.ObjectOp): self.areaOpSetDefaultValues(obj) + def areaOpSetDefaultValues(self, obj): + '''areaOpSetDefaultValues(obj) ... overwrite to set initial values of operation specific properties. + Can safely be overwritten by subclasses.''' + pass + def _buildPathArea(self, obj, baseobject, isHole, start, getsim): + '''_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.''' PathLog.track() area = Path.Area() - area.setPlane(makeWorkplane(baseobject)) + area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) areaParams = self.areaOpAreaParams(obj, isHole) @@ -211,10 +239,18 @@ class ObjectOp(PathOp.ObjectOp): return pp, simobj def opExecute(self, obj, getsim=False): + '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. + determines the parameters for _buildPathArea(). + Do not overwrite, implement + areaOpAreaParams(obj, isHole) ... op specific area param dictionary + areaOpPathParams(obj, isHole) ... op specific path param dictionary + areaOpShapes(obj) ... the shape for path area to process + areaOpUseProjection(obj) ... return true if operation can use projection + instead.''' PathLog.track() self.endVector = None - self.depthparams = depth_params( + self.depthparams = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.StartDepth.Value, @@ -241,3 +277,22 @@ class ObjectOp(PathOp.ObjectOp): FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") return sims + + def areaOpAreaParams(self, obj, isHole): + '''areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary. + Note that the resulting parameters are stored in the property AreaParams. + Must be overwritten by subclasses.''' + pass + def areaOpPathParams(self, obj, isHole): + '''areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary. + Note that the resulting parameters are stored in the property PathParams. + Must be overwritten by subclasses.''' + pass + def areaOpShapes(self, obj): + '''areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op. + Must be overwritten by subclasses.''' + pass + def areaOpUseProjection(self, obj): + '''areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False. + Can safely be overwritten by subclasses.''' + return False diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index a026ab8096..4b3b075e82 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -33,6 +33,11 @@ import sys from PySide import QtCore +__title__ = "Path Circular Holes Base Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Base class an implementation for operations on circular holes." + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -42,18 +47,32 @@ if True: PathLog.trackModule(PathLog.thisModule()) class ObjectOp(PathOp.ObjectOp): + '''Base class for proxy objects of all operations on circular holes.''' def opFeatures(self, obj): + '''opFeatures(obj) ... calls circularHoleFeatures(obj) and ORs in the standard features required for processing circular holes. + Do not overwrite, implement circularHoleFeatures(obj) instead''' return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureBaseFaces | self.circularHoleFeatures(obj) def initOperation(self, obj): + '''initOperation(obj) ... adds Disabled properties and calls initCircularHoleOperation(obj). + Do not overwrite, implement initCircularHoleOperation(obj) instead.''' obj.addProperty("App::PropertyStringList", "Disabled", "Base", QtCore.QT_TRANSLATE_NOOP("Path", "List of disabled features")) self.initCircularHoleOperation(obj) def baseIsArchPanel(self, obj, base): + '''baseIsArchPanel(obj, base) ... return true if op deals with an Arch.Panel.''' return hasattr(base, "Proxy") and isinstance(base.Proxy, ArchPanel.PanelSheet) def getArchPanelEdge(self, obj, base, sub): + '''getArchPanelEdge(obj, base, sub) ... helper function to identify a specific edge of an Arch.Panel. + Edges are identified by 3 numbers: + .. + Let's say the edge is specified as "3.2.7", then the 7th edge of the 2nd wire in the 3rd hole returned + by the panel sheet is the edge returned. + Obviously this is as fragile as can be, but currently the best we can do while the panel sheets + hide the actual features from Path and they can't be referenced directly. + ''' ids = string.split(sub, '.') holeId = int(ids[0]) wireId = int(ids[1]) @@ -68,6 +87,7 @@ class ObjectOp(PathOp.ObjectOp): return edge def holeDiameter(self, obj, base, sub): + '''holeDiameter(obj, base, sub) ... returns the diameter of the specified hole.''' if self.baseIsArchPanel(obj, base): edge = self.getArchPanelEdge(obj, base, sub) return edge.BoundBox.XLength @@ -80,6 +100,8 @@ class ObjectOp(PathOp.ObjectOp): return shape.BoundBox.XLength def holePosition(self, obj, base, sub): + '''holePosition(obj, base, sub) ... returns a Vector for the position defined by the given features. + Note that the value for Z is set to 0.''' if self.baseIsArchPanel(obj, base): edge = self.getArchPanelEdge(obj, base, sub) center = edge.Curve.Center @@ -98,10 +120,17 @@ class ObjectOp(PathOp.ObjectOp): PathLog.error('This is bad') def isHoleEnabled(self, obj, base, sub): + '''isHoleEnabled(obj, base, sub) ... return true if hole is enabled.''' name = "%s.%s" % (base.Name, sub) return not name in obj.Disabled def opExecute(self, obj): + '''opExecute(obj) ... processes all Base features and Locations and collects + them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes). + If no Base geometries and no Locations are present, the job's Base is inspected and all + drillable features are added to Base. In this case appropriate values for depths are also + calculated and assigned. + Do not overwrite, implement circularHoleExecute(obj, holes) instead.''' PathLog.track() def haveLocations(self, obj): @@ -145,7 +174,16 @@ class ObjectOp(PathOp.ObjectOp): if len(holes) > 0: self.circularHoleExecute(obj, holes) + def circularHoleExecute(self, obj, holes): + '''circularHoleExecute(obj, holes) ... implement processing of holes. + holes is a list of dictionaries with 'x', 'y' and 'r' specified for each hole. + Note that for Vertexes, non-circular Edges and Locations r=0. + Must be overwritten by subclasses.''' + pass + def opOnChanged(self, obj, prop): + '''opOnChange(obj, prop) ... implements depth calculation if Base changes. + Do not overwrite.''' if 'Base' == prop and not 'Restore' in obj.State and obj.Base: features = [] for base, subs in obj.Base: @@ -159,6 +197,12 @@ class ObjectOp(PathOp.ObjectOp): self.setupDepthsFrom(obj, features, job.Base) def setupDepthsFrom(self, obj, features, baseobject): + '''setupDepthsFrom(obj, features, baseobject) ... determins the min and max Z values necessary. + Note that the algorithm calculates "safe" values, + it determines the highest top Z value of all features + and it also determines the highest bottom Z value of all features. + The result is that all features can be drilled safely and the operation + does not drill deeper than any of the features requires.''' zmax = None zmin = None if not self.baseIsArchPanel(obj, baseobject): @@ -174,6 +218,7 @@ class ObjectOp(PathOp.ObjectOp): self.setDepths(obj, zmax, zmin, baseobject.Shape.BoundBox) def setDepths(self, obj, zmax, zmin, bb): + '''setDepths(obj, zmax, zmin, bb) ... set properties according to the provided values.''' PathLog.track(obj.Label, zmax, zmin, bb) if zmax is None: zmax = bb.ZMax @@ -191,6 +236,7 @@ class ObjectOp(PathOp.ObjectOp): obj.FinalDepth = zmin def findHoles(self, obj, baseobject): + '''findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole.''' shape = baseobject.Shape PathLog.track('obj: {} shape: {}'.format(obj, shape)) holelist = [] diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index 1a98c92d80..4d085fb93b 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -35,13 +35,17 @@ import PathScripts.PathUtils as PathUtils from PathScripts.PathUtils import fmt, waiting_effects from PySide import QtCore +__title__ = "Path Drilling Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Path Drilling operation." + if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -"""Path Drilling object and FreeCAD command""" # Qt tanslation handling def translate(context, text, disambig=None): @@ -49,13 +53,14 @@ def translate(context, text, disambig=None): class ObjectDrilling(PathCircularHoleBase.ObjectOp): + '''Proxy object for Drilling operation.''' def circularHoleFeatures(self, obj): - # drilling works on anything + '''circularHoleFeatures(obj) ... drilling works on anything, turn on all Base geometries and Locations.''' return PathOp.FeatureBaseGeometry | PathOp.FeatureLocations def initCircularHoleOperation(self, obj): - + '''initCircularHoleOperation(obj) ... add drilling specific properties to obj.''' obj.addProperty("App::PropertyLength", "PeckDepth", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Incremental Drill depth before retracting to clear chips")) obj.addProperty("App::PropertyBool", "PeckEnabled", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable pecking")) obj.addProperty("App::PropertyFloat", "DwellTime", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "The time to dwell between peck cycles")) @@ -67,6 +72,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): obj.addProperty("App::PropertyDistance", "RetractHeight", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height where feed starts and height during retract tool when path is finished")) def circularHoleExecute(self, obj, holes): + '''circularHoleExecute(obj, holes) ... generate drill operation for each hole in holes.''' PathLog.track() self.commandlist.append(Path.Command("(Begin Drilling)")) @@ -111,6 +117,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): self.commandlist.append(Path.Command('G80')) def setDepths(self, obj, zmax, zmin, bb): + '''setDepths(obj, zmax, zmin, bb) ... call base implementation and set RetractHeight accordingly.''' super(self.__class__, self).setDepths(obj, zmax, zmin, bb) if zmax is not None: obj.RetractHeight = zmax + 1.0 @@ -118,9 +125,11 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): obj.RetractHeight = 6.0 def opSetDefaultValues(self, obj): + '''opSetDefaultValues(obj) ... set default value for RetractHeight''' obj.RetractHeight = 10 def opOnChanged(self, obj, prop): + '''opOnChanged(obj, prop) ... if Locations changed, check if depths should be calculated.''' super(self.__class__, self).opOnChanged(obj, prop) if prop == 'Locations' and not 'Restore' in obj.State and obj.Locations and not obj.Base: if not hasattr(self, 'baseobject'): @@ -129,6 +138,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): self.setupDepthsFrom(obj, [], job.Base) def Create(name): + '''Create(name) ... Creates and returns a Drilling operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectDrilling(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathEngrave.py b/src/Mod/Path/PathScripts/PathEngrave.py index eb37ec4356..2e8e227318 100644 --- a/src/Mod/Path/PathScripts/PathEngrave.py +++ b/src/Mod/Path/PathScripts/PathEngrave.py @@ -33,7 +33,7 @@ import PathScripts.PathUtils as PathUtils from PySide import QtCore -"""Path Engrave object and FreeCAD command""" +__doc__ = "Class and implementation of Path Engrave operation" if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -47,14 +47,18 @@ def translate(context, text, disambig=None): class ObjectEngrave(PathOp.ObjectOp): + '''Proxy class for Engrave operation.''' def opFeatures(self, obj): + '''opFeatures(obj) ... return all standard features and edges based geomtries''' return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseEdges; def initOperation(self, obj): + '''initOperation(obj) ... create engraving specific properties.''' obj.addProperty("App::PropertyInteger", "StartVertex", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The vertex index to start the path from")) def opExecute(self, obj): + '''opExecute(obj) ... process engraving operation''' PathLog.track() zValues = [] @@ -107,6 +111,7 @@ class ObjectEngrave(PathOp.ObjectOp): PathLog.error(translate("Path", "The Job Base Object has no engraveable element. Engraving operation will produce no output.")) def buildpathocc(self, obj, wires, zValues): + '''buildpathocc(obj, wires, zValues) ... internal helper function to generate engraving commands.''' PathLog.track() output = "" @@ -175,6 +180,7 @@ class ObjectEngrave(PathOp.ObjectOp): return output def opSetDefaultValues(self, obj): + '''opSetDefaultValues(obj) ... set depths for engraving''' job = PathUtils.findParentJob(obj) if job and job.Base: bb = job.Base.Shape.BoundBox @@ -189,6 +195,7 @@ class ObjectEngrave(PathOp.ObjectOp): obj.StepDown = obj.StartDepth.Value - obj.FinalDepth.Value def Create(name): + '''Create(name) ... Creates and returns a Engrave operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectEngrave(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 9913fc8d7e..a04b104928 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -34,19 +34,21 @@ import PathScripts.PathUtils as PathUtils from PathUtils import fmt from PySide import QtCore -"""Helix Drill object and FreeCAD command""" +__doc__ = "Class and implementation of Helix Drill operation" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectHelix(PathCircularHoleBase.ObjectOp): + '''Proxy class for Helix operations.''' def circularHoleFeatures(self, obj): + '''circularHoleFeatures(obj) ... enable features supported by Helix.''' return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels def initCircularHoleOperation(self, obj): - # Basic + '''initCircularHoleOperation(obj) ... create helix specific properties.''' obj.addProperty("App::PropertyEnumeration", "Direction", "Helix Drill", translate("PathHelix", "The direction of the circular cuts, clockwise (CW), or counter clockwise (CCW)")) obj.Direction = ['CW', 'CCW'] @@ -56,6 +58,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): obj.addProperty("App::PropertyLength", "StepOver", "Helix Drill", translate("PathHelix", "Radius increment (must be smaller than tool diameter)")) def circularHoleExecute(self, obj, holes): + '''circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes''' PathLog.track() self.commandlist.append(Path.Command('(helix cut operation)')) @@ -70,18 +73,10 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): PathLog.debug(output) def helix_cut(self, obj, x0, y0, r_out, r_in, dr): - """ - x0, y0: - coordinates of center - r_out, r_in: floats - radial range, cut from outer radius r_out in layers of dr to inner radius r_in - zmax, zmin: floats - z-range, cut from zmax in layers of dz down to zmin - safe_z: float - safety layer height - tool_diameter: float - Width of tool - """ + '''helix_cut(obj, x0, y0, r_out, r_in, dr) ... generate helix commands for specified hole. + x0, y0: coordinates of center + r_out, r_in: outer and inner radius of the hole + dr: step over radius value''' from numpy import ceil, linspace if (obj.StartDepth.Value <= obj.FinalDepth.Value): @@ -199,6 +194,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): obj.StepOver = 100 def Create(name): + '''Create(name) ... Creates and returns a Helix operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectHelix(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 46d14c0bc5..c761691eed 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -50,17 +50,18 @@ def translate(context, text, disambig=None): __title__ = "Path Mill Face Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" - -"""Path Face object and FreeCAD command""" +__doc__ = "Class and implementation of Mill Facing operation." class ObjectFace(PathAreaOp.ObjectOp): + '''Proxy object for Mill Facing operation.''' def areaOpFeatures(self, obj): + '''areaOpFeatures(obj) ... mill facing uses FinishDepth and is based on faces.''' return PathOp.FeatureBaseFaces | PathOp.FeatureFinishDepth def initAreaOp(self, obj): - + '''initAreaOp(obj) ... create operation specific properties''' # Face Properties obj.addProperty("App::PropertyEnumeration", "CutMode", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW")) obj.CutMode = ['Climb', 'Conventional'] @@ -79,6 +80,7 @@ class ObjectFace(PathAreaOp.ObjectOp): def areaOpOnChanged(self, obj, prop): + '''areaOpOnChanged(obj, prop) ... facing specific depths calculation.''' PathLog.track(prop) if prop == "StepOver" and obj.StepOver == 0: obj.StepOver = 1 @@ -94,9 +96,11 @@ class ObjectFace(PathAreaOp.ObjectOp): obj.FinalDepth = d.start_depth def areaOpUseProjection(self, obj): + '''areaOpUseProjection(obj) ... return False''' return False def areaOpAreaParams(self, obj, isHole): + '''areaOpAreaPrams(obj, isHole) ... return dictionary with mill facing area parameters''' params = {} params['Fill'] = 0 params['Coplanar'] = 0 @@ -115,6 +119,7 @@ class ObjectFace(PathAreaOp.ObjectOp): return params def areaOpPathParams(self, obj, isHole): + '''areaOpPathPrams(obj, isHole) ... return dictionary with mill facing path parameters''' params = {} params['feedrate'] = self.horizFeed params['feedrate_v'] = self.vertFeed @@ -128,6 +133,7 @@ class ObjectFace(PathAreaOp.ObjectOp): return params def areaOpShapes(self, obj): + '''areaOpShapes(obj) ... return top face''' # Facing is done either against base objects if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) @@ -160,6 +166,7 @@ class ObjectFace(PathAreaOp.ObjectOp): return [(env, False)] def areaOpSetDefaultValues(self, obj): + '''areaOpSetDefaultValues(obj) ... initialize mill facing properties''' obj.StepOver = 50 obj.ZigZagAngle = 45.0 @@ -173,6 +180,7 @@ class ObjectFace(PathAreaOp.ObjectOp): obj.FinalDepth = d.start_depth def Create(name): + '''Create(name) ... Creates and returns a Mill Facing operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectFace(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index db293e659b..5235807fe4 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -33,6 +33,7 @@ from PySide import QtCore __title__ = "Base class for all operations." __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" +__doc__ = "Base class and properties implemenation for all Path operations." if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -44,21 +45,48 @@ else: def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -FeatureTool = 0x0001 -FeatureDepths = 0x0002 -FeatureHeights = 0x0004 -FeatureStartPoint = 0x0008 -FeatureFinishDepth = 0x0010 -FeatureStepDown = 0x0020 -FeatureBaseVertexes = 0x0100 -FeatureBaseEdges = 0x0200 -FeatureBaseFaces = 0x0400 -FeatureBasePanels = 0x0800 -FeatureLocations = 0x1000 +FeatureTool = 0x0001 # ToolController +FeatureDepths = 0x0002 # FinalDepth, StartDepth +FeatureHeights = 0x0004 # ClearanceHeight, SafeHeight +FeatureStartPoint = 0x0008 # StartPoint +FeatureFinishDepth = 0x0010 # FinishDepth +FeatureStepDown = 0x0020 # StepDown +FeatureBaseVertexes = 0x0100 # Base +FeatureBaseEdges = 0x0200 # Base +FeatureBaseFaces = 0x0400 # Base +FeatureBasePanels = 0x0800 # Base +FeatureLocations = 0x1000 # Locations FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels class ObjectOp(object): + ''' + Base class for proxy objects of all Path operations. + + Use this class as a base class for new operations. It provides properties + and some functionality for the standard properties each operation supports. + By OR'ing features from the feature list an operation can select which ones + of the standard features it requires and/or supports. + + The currently supported features are: + FeatureTool ... Use of a ToolController + FeatureDepths ... Depths, for start, final + FeatureHeights ... Heights, safe and clearance + FeatureStartPoint ... Supports setting a start point + FeatureFinishDepth ... Operation supports a finish depth + FeatureStepDown ... Support for step down + FeatureBaseVertexes ... Base geometry support for vertexes + FeatureBaseEdges ... Base geometry support for edges + FeatureBaseFaces ... Base geometry support for faces + FeatureBasePanels ... Base geometry support for Arch.Panels + FeatureLocations ... Base location support + + The base class handles all base API and forwards calls to subclasses with + an op prefix. For instance, an op is not expected to overwrite onChanged(), + but implement the function opOnChanged(). + If a base class overwrites a base API function it should call the super's + implementation - otherwise the base functionality might be broken. + ''' def __init__(self, obj): PathLog.track() @@ -102,22 +130,49 @@ class ObjectOp(object): self.setDefaultValues(obj) def __getstate__(self): + '''__getstat__(self) ... called when receiver is saved. + Can safely be overwritten by subclasses.''' return None def __setstate__(self, state): + '''__getstat__(self) ... called when receiver is restored. + Can safely be overwritten by subclasses.''' return None def opFeatures(self, obj): + '''opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation. + The default implementation returns "FeatureTool | FeatureDeptsh | FeatureHeights | FeatureStartPoint" + Should be overwritten by subclasses.''' return FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint | FeatureBaseGeometry | FeatureFinishDepth - def opOnChanged(self, obj, prop): + + def initOperation(self, obj): + '''initOperation(obj) ... implement to create additional properties. + Should be overwritten by subclasses.''' pass + + def opOnChanged(self, obj, prop): + '''opOnChanged(obj, prop) ... overwrite to process property changes. + This is a callback function that is invoked each time a property of the + receiver is assigned a value. Note that the FC framework does not + distinguish between assigning a different value and assigning the same + value again. + Can safely be overwritten by subclasses.''' + pass + def opSetDefaultValues(self, obj): + '''opSetDefaultValues(obj) ... overwrite to set initial default values. + Called after the reciever has been fully created with all properties. + Can safely be overwritten by subclasses.''' pass def onChanged(self, obj, prop): + '''onChanged(obj, prop) ... base implementation of the FC notification framework. + Do not overwrite, overwrite opOnChanged() instead.''' self.opOnChanged(obj, prop) - def setDefaultValues(self, obj, callOp = True): + def setDefaultValues(self, obj): + '''setDefaultValues(obj) ... base implementation. + Do not overwrite, overwrite opSetDefaultValues() instead.''' PathUtils.addToJob(obj) obj.Active = True @@ -145,6 +200,24 @@ class ObjectOp(object): @waiting_effects def execute(self, obj): + '''execute(obj) ... base implementation - do not overwrite! + Verifies that the operation is assigned to a job and that the job also has a valid Base. + It also sets the following instance variables that can and should be safely be used by + implementation of opExecute(): + self.baseobject ... Base object of the Job itself + self.vertFeed ... vertical feed rate of assigned tool + self.vertRapid ... vertical rapid rate of assigned tool + self.horizFeed ... horizontal feed rate of assigned tool + self.horizRapid ... norizontal rapid rate of assigned tool + self.tool ... the actual tool being used + self.radius ... the main radius of the tool being used + self.commandlist ... a list for collecting all commands produced by the operation + + Once everything is validated and above variables are set the implementation calls + opExectue(obj) - which is expected to add the generated commands to self.commandlist + Finally the base implementation adds a rapid move to clearance height and assigns + the receiver's Path property from the command list. + ''' PathLog.track() if not obj.Active: @@ -188,8 +261,9 @@ class ObjectOp(object): result = self.opExecute(obj) - # Let's finish by rapid to clearance...just for safety - self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + if FeatureHeights & self.opFeatures(obj): + # Let's finish by rapid to clearance...just for safety + self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) path = Path.Path(self.commandlist) obj.Path = path diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 612327bd02..08cb153e48 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -31,7 +31,7 @@ import PathScripts.PathOp as PathOp from PathScripts import PathUtils from PySide import QtCore -"""Path Pocket object and FreeCAD command""" +__doc__ = "Class and implementation of the Pocket operation." if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -45,11 +45,14 @@ def translate(context, text, disambig=None): class ObjectPocket(PathAreaOp.ObjectOp): + '''Proxy object for Pocket operation.''' def areaOpFeatures(self, obj): + '''areaOpFeatures(obj) ... Pockets have a FinishDepth and work on Faces''' return PathOp.FeatureBaseFaces | PathOp.FeatureFinishDepth def initAreaOp(self, obj): + '''initAreaOp(obj) ... create pocket specific properties.''' PathLog.track() # Pocket Properties @@ -65,9 +68,11 @@ class ObjectPocket(PathAreaOp.ObjectOp): obj.addProperty("App::PropertyBool", "MinTravel", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path")) def areaOpUseProjection(self, obj): + '''areaOpUseProjection(obj) ... return False''' return False def areaOpAreaParams(self, obj, isHole): + '''areaOpAreaParams(obj, isHole) ... return dictionary with pocket's area parameters''' params = {} params['Fill'] = 0 params['Coplanar'] = 0 @@ -84,6 +89,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): return params def areaOpPathParams(self, obj, isHole): + '''areaOpAreaParams(obj, isHole) ... return dictionary with pocket's path parameters''' params = {} # if MinTravel is turned on, set path sorting to 3DSort @@ -94,6 +100,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): return params def areaOpShapes(self, obj): + '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: @@ -120,11 +127,13 @@ class ObjectPocket(PathAreaOp.ObjectOp): return removalshapes def areaOpSetDefaultValues(self, obj): + '''areaOpSetDefaultValues(obj) ... set default values''' obj.StepOver = 100 obj.ZigZagAngle = 45 def Create(name): + '''Create(name) ... Creates and returns a Pocket operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectPocket(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathProfileBase.py b/src/Mod/Path/PathScripts/PathProfileBase.py index 70de5030ee..6c61877013 100644 --- a/src/Mod/Path/PathScripts/PathProfileBase.py +++ b/src/Mod/Path/PathScripts/PathProfileBase.py @@ -29,7 +29,10 @@ import PathScripts.PathOp as PathOp from PySide import QtCore -"""Path Profile from base features""" +__title__ = "Path Contour Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Base class and implementation for Path profile operations." if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -49,13 +52,17 @@ def translate(context, text, disambig=None): __title__ = "Path Profile Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" +__doc__ = "Base class and properties for all profile operations." """Path Profile object and FreeCAD command for operating on sets of features""" class ObjectProfile(PathAreaOp.ObjectOp): + '''Base class for proxy objects of all profile operations.''' def initAreaOp(self, obj): + '''initAreaOp(obj) ... creates all profile specific properties. + Do not overwrite.''' # Profile Properties obj.addProperty("App::PropertyEnumeration", "Side", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Side of edge that tool should cut")) obj.Side = ['Outside', 'Inside'] # side of profile that cutter is on in relation to direction of profile @@ -70,6 +77,8 @@ class ObjectProfile(PathAreaOp.ObjectOp): obj.setEditorMode('MiterLimit', 2) def areaOpOnChanged(self, obj, prop): + '''areaOpOnChanged(obj, prop) ... updates Side and MiterLimit visibility depending on changed properties. + Do not overwrite.''' if prop == "UseComp": if not obj.UseComp: obj.setEditorMode('Side', 2) @@ -83,6 +92,8 @@ class ObjectProfile(PathAreaOp.ObjectOp): obj.setEditorMode('MiterLimit', 2) def areaOpAreaParams(self, obj, isHole): + '''areaOpAreaParams(obj, isHole) ... returns dictionary with area parameters. + Do not overwrite.''' params = {} params['Fill'] = 0 params['Coplanar'] = 0 @@ -106,6 +117,8 @@ class ObjectProfile(PathAreaOp.ObjectOp): return params def areaOpPathParams(self, obj, isHole): + '''areaOpPathParams(obj, isHole) ... returns dictionary with path parameters. + Do not overwrite.''' params = {} # Reverse the direction for holes @@ -122,9 +135,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): return params def areaOpUseProjection(self, obj): + '''areaOpUseProjection(obj) ... returns True''' return True def areaOpSetDefaultValues(self, obj): + '''areaOpSetDefaultValues(obj) ... sets default values. + Do not overwrite.''' obj.Side = "Outside" obj.OffsetExtra = 0.0 obj.Direction = "CW" diff --git a/src/Mod/Path/PathScripts/PathProfileContour.py b/src/Mod/Path/PathScripts/PathProfileContour.py index 10084d60cf..9e9e842ba5 100644 --- a/src/Mod/Path/PathScripts/PathProfileContour.py +++ b/src/Mod/Path/PathScripts/PathProfileContour.py @@ -50,27 +50,33 @@ def translate(context, text, disambig=None): __title__ = "Path Contour Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" - -"""Path Contour object and FreeCAD command""" +__doc__ = "Implementation of the Contour operation." class ObjectContour(PathProfileBase.ObjectProfile): + '''Proxy object for Contour operations.''' def baseObject(self): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' return super(self.__class__, self) def areaOpFeatures(self, obj): + '''areaOpFeatures(obj) ... returns 0, Contour only requires the base profile features.''' return 0 def initAreaOp(self, obj): + '''initAreaOp(obj) ... call super's implementation and hide Side property.''' self.baseObject().initAreaOp(obj) obj.setEditorMode('Side', 2) # it's always outside def areaOpSetDefaultValues(self, obj): + '''areaOpSetDefaultValues(obj) ... call super's implementation and set Side="Outside".''' self.baseObject().areaOpSetDefaultValues(obj) obj.Side = 'Outside' def areaOpShapes(self, obj): + '''areaOpShapes(obj) ... return envelope over the job's Base.Shape or all Arch.Panel shapes.''' if obj.UseComp: self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: @@ -96,6 +102,7 @@ class ObjectContour(PathProfileBase.ObjectProfile): return params def Create(name): + '''Create(name) ... Creates and returns a Contour operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectContour(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index aca05c4817..51289f0bdc 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -54,19 +54,23 @@ def translate(context, text, disambig=None): __title__ = "Path Profile Edges Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" - -"""Path Profile object and FreeCAD command for operating on sets of edges""" +__doc__ = "Path Profile operation based on edges." class ObjectProfile(PathProfileBase.ObjectProfile): + '''Proxy object for Profile operations based on edges.''' def baseObject(self): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' return super(self.__class__, self) def areaOpFeatures(self, obj): + '''areaOpFeatures(obj) ... add support for edge base geometry.''' return PathOp.FeatureBaseEdges def areaOpShapes(self, obj): + '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' PathLog.track() if obj.UseComp: @@ -97,6 +101,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): return shapes def Create(name): + '''Create(name) ... Creates and returns a Profile based on edges operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectProfile(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 44446849b1..6fa8d24493 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -43,19 +43,27 @@ PathLog.trackModule(PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -__title__ = "Path Profile Operation" +__title__ = "Path Profile Faces Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" - -"""Path Profile object and FreeCAD command""" +__doc__ = "Path Profile operation based on faces." class ObjectProfile(PathProfileBase.ObjectProfile): + '''Proxy object for Profile operations based on faces.''' def baseObject(self): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' return super(self.__class__, self) + def areaOpFeatures(self, obj): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' + return PathOp.FeatureBaseFaces + def initAreaOp(self, obj): + '''initAreaOp(obj) ... adds properties for hole, circle and perimeter processing.''' # Face specific Properties obj.addProperty("App::PropertyBool", "processHoles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile holes as well as the outline")) obj.addProperty("App::PropertyBool", "processPerimeter", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline")) @@ -63,17 +71,8 @@ class ObjectProfile(PathProfileBase.ObjectProfile): self.baseObject().initAreaOp(obj) - def areaOpSetDefaultValues(self, obj): - self.baseObject().areaOpSetDefaultValues(obj) - - obj.processHoles = False - obj.processCircles = False - obj.processPerimeter = True - - def areaOpFeatures(self, obj): - return PathOp.FeatureBaseFaces - def areaOpShapes(self, obj): + '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.''' if obj.UseComp: self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: @@ -130,7 +129,16 @@ class ObjectProfile(PathProfileBase.ObjectProfile): return shapes + def areaOpSetDefaultValues(self, obj): + '''areaOpSetDefaultValues(obj) ... sets default values for hole, circle and perimeter processing.''' + self.baseObject().areaOpSetDefaultValues(obj) + + obj.processHoles = False + obj.processCircles = False + obj.processPerimeter = True + def Create(name): + '''Create(name) ... Creates and returns a Profile based on faces operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectProfile(obj) return obj