Added doc strings to operations.

This commit is contained in:
Markus Lampert
2017-08-14 16:47:04 -07:00
committed by Yorik van Havre
parent d57fb7bd33
commit 684b4f6e7e
12 changed files with 300 additions and 59 deletions

View File

@@ -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

View File

@@ -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:
<holeId>.<wireId>.<edgeId>
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 = []

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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