Path: Restructure initOperation() for backward compatibility

Added initOpProperties() to handle independent creation of properties for backward compatibility and to allow for future storage of enumeration property lists within the operation.
Path: Change from PathSurface code to `Waterline`


Path: Updates and fixes


Path: Simplify code

Remove `Algorithm` and `AreaParams` property usage.
Remove other unused properties.
Simplify setupEditorProperties().
Path: Remove unused methods


Path: Update property initialization and handling

Make properties backward compatible with previous versions of the operation.
Remove references to unused properties due to 3D Surface and Waterline separation.
Path: Insert placeholder for `Experimental` algorithm


Path: Fix missing property and fix syntax

Missing `Algrorithm
This commit is contained in:
Russell Johnson
2020-03-26 18:04:04 -05:00
parent df253cdcdc
commit 4109f0c7df
2 changed files with 326 additions and 853 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@
# ***************************************************************************
# * *
# * Additional modifications and contributions beginning 2019 *
# * by Russell Johnson <russ4262@gmail.com> 2020-03-15 10:55 CST *
# * by Russell Johnson <russ4262@gmail.com> 2020-03-23 16:15 CST *
# * *
# ***************************************************************************
@@ -45,7 +45,7 @@ import Draft
if FreeCAD.GuiUp:
import FreeCADGui
__title__ = "Path Surface Operation"
__title__ = "Path Waterline Operation"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Class and implementation of Mill Facing operation."
@@ -64,12 +64,12 @@ try:
import ocl
except ImportError:
FreeCAD.Console.PrintError(
translate("Path_Surface", "This operation requires OpenCamLib to be installed.") + "\n")
translate("Path_Waterline", "This operation requires OpenCamLib to be installed.") + "\n")
import sys
sys.exit(translate("Path_Surface", "This operation requires OpenCamLib to be installed."))
sys.exit(translate("Path_Waterline", "This operation requires OpenCamLib to be installed."))
class ObjectSurface(PathOp.ObjectOp):
class ObjectWaterline(PathOp.ObjectOp):
'''Proxy object for Surfacing operation.'''
def baseObject(self):
@@ -82,122 +82,148 @@ class ObjectSurface(PathOp.ObjectOp):
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces
def initOperation(self, obj):
'''initPocketOp(obj) ... create facing specific properties'''
obj.addProperty("App::PropertyEnumeration", "BoundBox", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object"))
obj.addProperty("App::PropertyEnumeration", "LayerMode", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass"))
obj.addProperty("App::PropertyEnumeration", "ScanType", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan."))
obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan"))
obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis."))
obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan"))
obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan"))
obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces."))
obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces."))
obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary."))
obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s)."))
obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object"))
obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features."))
obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature."))
obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face."))
obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection."))
obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times"))
obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path"))
obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path"))
obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom."))
obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)"))
obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use"))
obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns"))
obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc."))
obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output."))
obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path."))
obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns."))
obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path."))
obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry."))
obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth."))
obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore."))
obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model."))
obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path"))
obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point"))
'''initPocketOp(obj) ...
Initialize the operation - property creation and property editor status.'''
self.initOpProperties(obj)
# For debugging
obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging')
obj.setEditorMode('AreaParams', 2) # hide
obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown."))
if PathLog.getLevel(PathLog.thisModule()) != 4:
obj.setEditorMode('ShowTempObjects', 2) # hide
obj.BoundBox = ['BaseBoundBox', 'Stock']
obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom']
obj.CutMode = ['Conventional', 'Climb']
obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle']
obj.HandleMultipleFeatures = ['Collectively', 'Individually']
obj.LayerMode = ['Single-pass', 'Multi-pass']
obj.ProfileEdges = ['None', 'Only', 'First', 'Last']
obj.RotationAxis = ['X', 'Y']
obj.ScanType = ['Planar', 'Rotational']
if not hasattr(obj, 'DoNotSetDefaultValues'):
self.setEditorProperties(obj)
def initOpProperties(self, obj):
'''initOpProperties(obj) ... create operation specific properties'''
PROPS = [
("App::PropertyBool", "ShowTempObjects", "Debug",
QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")),
("App::PropertyDistance", "AngularDeflection", "Mesh Conversion",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")),
("App::PropertyDistance", "LinearDeflection", "Mesh Conversion",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")),
("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")),
("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")),
("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")),
("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")),
("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")),
("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")),
("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")),
("App::PropertyEnumeration", "Algorithm", "Waterline",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental.")),
("App::PropertyEnumeration", "BoundBox", "Waterline",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")),
("App::PropertyDistance", "SampleInterval", "Waterline",
QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")),
("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")),
("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")),
("App::PropertyEnumeration", "CutMode", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")),
("App::PropertyEnumeration", "CutPattern", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")),
("App::PropertyFloat", "CutPatternAngle", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")),
("App::PropertyBool", "CutPatternReversed", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")),
("App::PropertyDistance", "DepthOffset", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")),
("App::PropertyEnumeration", "LayerMode", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")),
("App::PropertyEnumeration", "ProfileEdges", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")),
("App::PropertyPercent", "StepOver", "Clearing Options",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")),
("App::PropertyBool", "OptimizeLinearPaths", "Waterline Optimization",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")),
("App::PropertyBool", "OptimizeStepOverTransitions", "Waterline Optimization",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")),
("App::PropertyBool", "CircularUseG2G3", "Waterline Optimization",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")),
("App::PropertyDistance", "GapThreshold", "Waterline Optimization",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")),
("App::PropertyString", "GapSizes", "Waterline Optimization",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")),
("App::PropertyVectorDistance", "StartPoint", "Start Point",
QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")),
("App::PropertyBool", "UseStartPoint", "Start Point",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point"))
]
missing = list()
for (prtyp, nm, grp, tt) in PROPS:
if not hasattr(obj, nm):
obj.addProperty(prtyp, nm, grp, tt)
missing.append(nm)
# Set enumeration lists for enumeration properties
if len(missing) > 0:
ENUMS = self._propertyEnumerations()
for n in ENUMS:
if n in missing:
cmdStr = 'obj.{}={}'.format(n, ENUMS[n])
exec(cmdStr)
self.addedAllProperties = True
def _propertyEnumerations(self):
# Enumeration lists for App::PropertyEnumeration properties
return {
'Algorithm': ['OCL Dropcutter', 'Experimental'],
'BoundBox': ['BaseBoundBox', 'Stock'],
'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'],
'CutMode': ['Conventional', 'Climb'],
'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle']
'HandleMultipleFeatures': ['Collectively', 'Individually'],
'LayerMode': ['Single-pass', 'Multi-pass'],
'ProfileEdges': ['None', 'Only', 'First', 'Last'],
}
def setEditorProperties(self, obj):
# Used to hide inputs in properties list
'''
obj.setEditorMode('CutPattern', 0)
obj.setEditorMode('HandleMultipleFeatures', 0)
obj.setEditorMode('CircularCenterAt', 0)
obj.setEditorMode('CircularCenterCustom', 0)
obj.setEditorMode('CutPatternAngle', 0)
# obj.setEditorMode('BoundaryEnforcement', 0)
if obj.ScanType == 'Planar':
obj.setEditorMode('RotationAxis', 2) # 2=hidden
obj.setEditorMode('StartIndex', 2)
obj.setEditorMode('StopIndex', 2)
obj.setEditorMode('CutterTilt', 2)
if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag':
obj.setEditorMode('CutPatternAngle', 2)
else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag':
obj.setEditorMode('CircularCenterAt', 2)
obj.setEditorMode('CircularCenterCustom', 2)
elif obj.ScanType == 'Rotational':
obj.setEditorMode('RotationAxis', 0) # 0=show & editable
obj.setEditorMode('StartIndex', 0)
obj.setEditorMode('StopIndex', 0)
obj.setEditorMode('CutterTilt', 0)
'''
obj.setEditorMode('HandleMultipleFeatures', 2)
obj.setEditorMode('CutPattern', 2)
obj.setEditorMode('CutPatternAngle', 2)
# obj.setEditorMode('BoundaryEnforcement', 2)
# Disable IgnoreWaste feature
obj.setEditorMode('IgnoreWaste', 2)
obj.setEditorMode('IgnoreWasteDepth', 2)
obj.setEditorMode('ReleaseFromWaste', 2)
show = 0
hide = 2
obj.setEditorMode('BoundaryEnforcement', hide)
obj.setEditorMode('ProfileEdges', hide)
obj.setEditorMode('InternalFeaturesAdjustment', hide)
obj.setEditorMode('InternalFeaturesCut', hide)
# if obj.CutPattern in ['Line', 'ZigZag']:
if obj.CutPattern in ['Circular', 'CircularZigZag']:
show = 2 # hide
hide = 0 # show
obj.setEditorMode('CutPatternAngle', show)
obj.setEditorMode('CircularCenterAt', hide)
obj.setEditorMode('CircularCenterCustom', hide)
def onChanged(self, obj, prop):
if hasattr(self, 'addedAllProperties'):
if self.addedAllProperties is True:
if prop == 'ScanType':
self.setEditorProperties(obj)
if prop == 'CutPattern':
self.setEditorProperties(obj)
def opOnDocumentRestored(self, obj):
self.initOpProperties(obj)
if PathLog.getLevel(PathLog.thisModule()) != 4:
obj.setEditorMode('ShowTempObjects', 2) # hide
else:
obj.setEditorMode('ShowTempObjects', 0) # show
self.addedAllProperties = True
self.setEditorProperties(obj)
def opSetDefaultValues(self, obj, job):
@@ -205,8 +231,6 @@ class ObjectSurface(PathOp.ObjectOp):
job = PathUtils.findParentJob(obj)
obj.OptimizeLinearPaths = True
obj.IgnoreWaste = False
obj.ReleaseFromWaste = False
obj.InternalFeaturesCut = True
obj.OptimizeStepOverTransitions = False
obj.CircularUseG2G3 = False
@@ -217,21 +241,16 @@ class ObjectSurface(PathOp.ObjectOp):
obj.StartPoint.x = 0.0
obj.StartPoint.y = 0.0
obj.StartPoint.z = obj.ClearanceHeight.Value
obj.Algorithm = 'OCL Dropcutter'
obj.ProfileEdges = 'None'
obj.LayerMode = 'Single-pass'
obj.ScanType = 'Planar'
obj.RotationAxis = 'X'
obj.CutMode = 'Conventional'
obj.CutPattern = 'Line'
obj.HandleMultipleFeatures = 'Collectively' # 'Individually'
obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom'
obj.AreaParams = ''
obj.GapSizes = 'No gaps identified.'
obj.StepOver = 100
obj.CutPatternAngle = 0.0
obj.CutterTilt = 0.0
obj.StartIndex = 0.0
obj.StopIndex = 360.0
obj.SampleInterval.Value = 1.0
obj.BoundaryAdjustment.Value = 0.0
obj.InternalFeaturesAdjustment.Value = 0.0
@@ -266,39 +285,21 @@ class ObjectSurface(PathOp.ObjectOp):
def opApplyPropertyLimits(self, obj):
'''opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.'''
# Limit start index
if obj.StartIndex < 0.0:
obj.StartIndex = 0.0
if obj.StartIndex > 360.0:
obj.StartIndex = 360.0
# Limit stop index
if obj.StopIndex > 360.0:
obj.StopIndex = 360.0
if obj.StopIndex < 0.0:
obj.StopIndex = 0.0
# Limit cutter tilt
if obj.CutterTilt < -90.0:
obj.CutterTilt = -90.0
if obj.CutterTilt > 90.0:
obj.CutterTilt = 90.0
# Limit sample interval
if obj.SampleInterval.Value < 0.001:
obj.SampleInterval.Value = 0.001
PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.'))
PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.'))
if obj.SampleInterval.Value > 25.4:
obj.SampleInterval.Value = 25.4
PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.'))
PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.'))
# Limit cut pattern angle
if obj.CutPatternAngle < -360.0:
obj.CutPatternAngle = 0.0
PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +-360 degrees.'))
PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +-360 degrees.'))
if obj.CutPatternAngle >= 360.0:
obj.CutPatternAngle = 0.0
PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +- 360 degrees.'))
PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +- 360 degrees.'))
# Limit StepOver to natural number percentage
if obj.StepOver > 100:
@@ -309,10 +310,10 @@ class ObjectSurface(PathOp.ObjectOp):
# Limit AvoidLastX_Faces to zero and positive values
if obj.AvoidLastX_Faces < 0:
obj.AvoidLastX_Faces = 0
PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Only zero or positive values permitted.'))
PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Only zero or positive values permitted.'))
if obj.AvoidLastX_Faces > 100:
obj.AvoidLastX_Faces = 100
PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.'))
PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.'))
def opExecute(self, obj):
'''opExecute(obj) ... process surface operation'''
@@ -345,16 +346,13 @@ class ObjectSurface(PathOp.ObjectOp):
self.showDebugObjects = False
# mark beginning of operation and identify parent Job
PathLog.info('\nBegin 3D Surface operation...')
PathLog.info('\nBegin Waterline operation...')
startTime = time.time()
# Disable(ignore) ReleaseFromWaste option(input)
obj.ReleaseFromWaste = False
# Identify parent Job
JOB = PathUtils.findParentJob(obj)
if JOB is None:
PathLog.error(translate('PathSurface', "No JOB"))
PathLog.error(translate('PathWaterline', "No JOB"))
return
self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin
@@ -390,7 +388,7 @@ class ObjectSurface(PathOp.ObjectOp):
# Create temporary group for temporary objects, removing existing
# if self.showDebugObjects is True:
tempGroupName = 'tempPathSurfaceGroup'
tempGroupName = 'tempPathWaterlineGroup'
if FCAD.getObject(tempGroupName):
for to in FCAD.getObject(tempGroupName).Group:
FCAD.removeObject(to.Name)
@@ -410,7 +408,7 @@ class ObjectSurface(PathOp.ObjectOp):
self.cutter = self.setOclCutter(obj)
self.safeCutter = self.setOclCutter(obj, safe=True)
if self.cutter is False or self.safeCutter is False:
PathLog.error(translate('PathSurface', "Canceling 3D Surface operation. Error creating OCL cutter."))
PathLog.error(translate('PathWaterline', "Canceling Waterline operation. Error creating OCL cutter."))
return
toolDiam = self.cutter.getDiameter()
self.cutOut = (toolDiam * (float(obj.StepOver) / 100.0))
@@ -463,18 +461,6 @@ class ObjectSurface(PathOp.ObjectOp):
# ###### MAIN COMMANDS FOR OPERATION ######
# If algorithm is `Waterline`, force certain property values
'''
# Save initial value for restoration later.
if obj.Algorithm == 'OCL Waterline':
preCP = obj.CutPattern
preCPA = obj.CutPatternAngle
preRB = obj.BoundaryEnforcement
obj.CutPattern = 'Line'
obj.CutPatternAngle = 0.0
obj.BoundaryEnforcement = False
'''
# Begin processing obj.Base data and creating GCode
# Process selected faces, if available
pPM = self._preProcessModel(JOB, obj)
@@ -505,14 +491,6 @@ class ObjectSurface(PathOp.ObjectOp):
# Save gcode produced
self.commandlist.extend(CMDS)
# If algorithm is `Waterline`, restore initial property values
'''
if obj.Algorithm == 'OCL Waterline':
obj.CutPattern = preCP
obj.CutPatternAngle = preCPA
obj.BoundaryEnforcement = preRB
'''
# ###### CLOSING COMMANDS FOR OPERATION ######
# Delete temporary objects
@@ -593,8 +571,8 @@ class ObjectSurface(PathOp.ObjectOp):
VOIDS = list()
fShapes = list()
vShapes = list()
preProcEr = translate('PathSurface', 'Error pre-processing Face')
warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face')
preProcEr = translate('PathWaterline', 'Error pre-processing Face')
warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face')
GRP = JOB.Model.Group
lenGRP = len(GRP)
@@ -936,8 +914,8 @@ class ObjectSurface(PathOp.ObjectOp):
outFace = False
INTFCS = list()
fNum = fcIdx + 1
# preProcEr = translate('PathSurface', 'Error pre-processing Face')
warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face')
# preProcEr = translate('PathWaterline', 'Error pre-processing Face')
warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face')
PathLog.debug('_getFaceWires() from Face{}'.format(fNum))
WIRES = self._extractWiresFromFace(base, fcshp)
@@ -1140,10 +1118,6 @@ class ObjectSurface(PathOp.ObjectOp):
area.add(fcShape)
area.setParams(**areaParams) # set parameters
# Save parameters for debugging
# obj.AreaParams = str(area.getParams())
# PathLog.debug("Area with params: {}".format(area.getParams()))
offsetShape = area.getShape()
wCnt = len(offsetShape.Wires)
if wCnt == 0:
@@ -1514,7 +1488,7 @@ class ObjectSurface(PathOp.ObjectOp):
def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS):
'''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)...
This method applies any avoided faces or regions to the selected faces.
It then calls the correct scan method depending on the ScanType property.'''
It then calls the correct method.'''
PathLog.debug('_processCutAreas()')
final = list()
@@ -1533,7 +1507,10 @@ class ObjectSurface(PathOp.ObjectOp):
COMP = ADD
final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline
if obj.Algorithm == 'OCL Dropcutter':
final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline
else:
final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline
elif obj.HandleMultipleFeatures == 'Individually':
for fsi in range(0, len(FCS)):
@@ -1552,7 +1529,10 @@ class ObjectSurface(PathOp.ObjectOp):
COMP = ADD
final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline
if obj.Algorithm == 'OCL Dropcutter':
final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline
else:
final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline
COMP = None
# Eif
@@ -1570,8 +1550,8 @@ class ObjectSurface(PathOp.ObjectOp):
return pdc
# Main waterline functions
def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None):
'''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.'''
def _oclWaterlineOp(self, JOB, obj, mdlIdx, subShp=None):
'''_oclWaterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.'''
commands = []
t_begin = time.time()
@@ -1955,6 +1935,10 @@ class ObjectSurface(PathOp.ObjectOp):
return output
def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None):
PathLog.error('The `Experimental` algorithm is not available at this time.')
return []
# Support functions for both dropcutter and waterline operations
def isPointOnLine(self, strtPnt, endPnt, pointP):
'''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.'''
@@ -1987,54 +1971,6 @@ class ObjectSurface(PathOp.ObjectOp):
cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed
return cmds
def holdStopEndCmds(self, obj, p2, txt):
'''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.'''
cmds = []
msg = 'N (' + txt + ')'
cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel
cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel
# cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate
return cmds
def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax):
'''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ...
This function returns a subsection of the CLP scan, limited to the min/max values supplied.'''
section = list()
lenCLP = len(CLP)
for i in range(0, lenCLP):
if CLP[i].x < xmax:
if CLP[i].y < ymax:
if CLP[i].x > xmin:
if CLP[i].y > ymin:
section.append(CLP[i])
return section
def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP):
''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ...
This function connects two HOLD points with line.
Each point within the subsection point list is tested to determinie if it is under cutter.
Points determined to be under the cutter on line are tested for z height.
The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra.
'''
dx = (p2.x - p1.x)
if dx == 0.0:
dx = 0.00001 # Need to employ a global tolerance here
m = (p2.y - p1.y) / dx
b = p1.y - (m * p1.x)
avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance
zMax = finalDepth
lenCLP = len(CLP)
for i in range(0, lenCLP):
mSqrd = m**2
if mSqrd < 0.0000001: # Need to employ a global tolerance here
mSqrd = 0.0000001
perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd)))
if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed
if CLP[i].z > zMax:
zMax = CLP[i].z
return zMax + 2.0
def resetOpVariables(self, all=True):
'''resetOpVariables() ... Reset class variables used for instance of operation.'''
self.holdPoint = None
@@ -2147,31 +2083,6 @@ class ObjectSurface(PathOp.ObjectOp):
PathLog.error('Unable to set OCL cutter.')
return False
def determineVectDirect(self, pnt, nxt, travVect):
if nxt.x == pnt.x:
travVect.x = 0
elif nxt.x < pnt.x:
travVect.x = -1
else:
travVect.x = 1
if nxt.y == pnt.y:
travVect.y = 0
elif nxt.y < pnt.y:
travVect.y = -1
else:
travVect.y = 1
return travVect
def determineLineOfTravel(self, travVect):
if travVect.x == 0 and travVect.y != 0:
lineOfTravel = "Y"
elif travVect.y == 0 and travVect.x != 0:
lineOfTravel = "X"
else:
lineOfTravel = "O" # used for turns
return lineOfTravel
def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None):
A = (p1.x, p1.y)
B = (p2.x, p2.y)
@@ -2189,6 +2100,7 @@ class ObjectSurface(PathOp.ObjectOp):
def SetupProperties():
''' SetupProperties() ... Return list of properties required for operation.'''
setup = []
setup.append('Algorithm')
setup.append('AvoidLastX_Faces')
setup.append('AvoidLastX_InternalFeatures')
setup.append('BoundBox')
@@ -2202,7 +2114,6 @@ def SetupProperties():
setup.append('CutPattern')
setup.append('CutPatternAngle')
setup.append('CutPatternReversed')
setup.append('CutterTilt')
setup.append('DepthOffset')
setup.append('GapSizes')
setup.append('GapThreshold')
@@ -2211,27 +2122,18 @@ def SetupProperties():
setup.append('OptimizeStepOverTransitions')
setup.append('ProfileEdges')
setup.append('BoundaryEnforcement')
setup.append('RotationAxis')
setup.append('SampleInterval')
setup.append('ScanType')
setup.append('StartIndex')
setup.append('StartPoint')
setup.append('StepOver')
setup.append('StopIndex')
setup.append('UseStartPoint')
# For debugging
setup.append('AreaParams')
setup.append('ShowTempObjects')
# Targeted for possible removal
setup.append('IgnoreWaste')
setup.append('IgnoreWasteDepth')
setup.append('ReleaseFromWaste')
return setup
def Create(name, obj=None):
'''Create(name) ... Creates and returns a Surface operation.'''
'''Create(name) ... Creates and returns a Waterline operation.'''
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectSurface(obj, name)
obj.Proxy = ObjectWaterline(obj, name)
return obj