purge archpanel support

This commit is contained in:
sliptonic
2021-08-12 10:45:13 -05:00
parent c03c771839
commit 13779a6db9
7 changed files with 1611 additions and 834 deletions

View File

@@ -29,10 +29,10 @@ from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel')
Draft = LazyLoader('Draft', globals(), 'Draft')
Part = LazyLoader('Part', globals(), 'Part')
DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils')
Draft = LazyLoader("Draft", globals(), "Draft")
Part = LazyLoader("Part", globals(), "Part")
DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
__title__ = "Path Circular Holes Base Operation"
@@ -51,81 +51,70 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class ObjectOp(PathOp.ObjectOp):
'''Base class for proxy objects of all operations on circular holes.'''
"""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) \
"""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)
| PathOp.FeatureCoolant
)
def circularHoleFeatures(self, obj):
'''circularHoleFeatures(obj) ... overwrite to add operations specific features.
Can safely be overwritten by subclasses.'''
"""circularHoleFeatures(obj) ... overwrite to add operations specific features.
Can safely be overwritten by subclasses."""
return 0
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"))
"""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 initCircularHoleOperation(self, obj):
'''initCircularHoleOperation(obj) ... overwrite if the subclass needs initialisation.
Can safely be overwritten by subclasses.'''
"""initCircularHoleOperation(obj) ... overwrite if the subclass needs initialisation.
Can safely be overwritten by subclasses."""
pass
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 = sub.split(".")
holeId = int(ids[0])
wireId = int(ids[1])
edgeId = int(ids[2])
for holeNr, hole in enumerate(base.Proxy.getHoles(base, transform=True)):
if holeNr == holeId:
for wireNr, wire in enumerate(hole.Wires):
if wireNr == wireId:
for edgeNr, edge in enumerate(wire.Edges):
if edgeNr == edgeId:
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
"""holeDiameter(obj, base, sub) ... returns the diameter of the specified hole."""
try:
shape = base.Shape.getElement(sub)
if shape.ShapeType == 'Vertex':
if shape.ShapeType == "Vertex":
return 0
if shape.ShapeType == 'Edge' and type(shape.Curve) == Part.Circle:
if shape.ShapeType == "Edge" and type(shape.Curve) == Part.Circle:
return shape.Curve.Radius * 2
if shape.ShapeType == 'Face':
if shape.ShapeType == "Face":
for i in range(len(shape.Edges)):
if (type(shape.Edges[i].Curve) == Part.Circle and
shape.Edges[i].Curve.Radius * 2 < shape.BoundBox.XLength*1.1 and
shape.Edges[i].Curve.Radius * 2 > shape.BoundBox.XLength*0.9):
if (
type(shape.Edges[i].Curve) == Part.Circle
and shape.Edges[i].Curve.Radius * 2
< shape.BoundBox.XLength * 1.1
and shape.Edges[i].Curve.Radius * 2
> shape.BoundBox.XLength * 0.9
):
return shape.Edges[i].Curve.Radius * 2
# for all other shapes the diameter is just the dimension in X.
# This may be inaccurate as the BoundBox is calculated on the tessellated geometry
PathLog.warning(translate("Path", "Hole diameter may be inaccurate due to tessellation on face. Consider selecting hole edge."))
PathLog.warning(
translate(
"Path",
"Hole diameter may be inaccurate due to tessellation on face. Consider selecting hole edge.",
)
)
return shape.BoundBox.XLength
except Part.OCCError as e:
PathLog.error(e)
@@ -133,44 +122,48 @@ class ObjectOp(PathOp.ObjectOp):
return 0
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
return FreeCAD.Vector(center.x, center.y, 0)
"""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."""
try:
shape = base.Shape.getElement(sub)
if shape.ShapeType == 'Vertex':
if shape.ShapeType == "Vertex":
return FreeCAD.Vector(shape.X, shape.Y, 0)
if shape.ShapeType == 'Edge' and hasattr(shape.Curve, 'Center'):
if shape.ShapeType == "Edge" and hasattr(shape.Curve, "Center"):
return FreeCAD.Vector(shape.Curve.Center.x, shape.Curve.Center.y, 0)
if shape.ShapeType == 'Face':
if hasattr(shape.Surface, 'Center'):
return FreeCAD.Vector(shape.Surface.Center.x, shape.Surface.Center.y, 0)
if shape.ShapeType == "Face":
if hasattr(shape.Surface, "Center"):
return FreeCAD.Vector(
shape.Surface.Center.x, shape.Surface.Center.y, 0
)
if len(shape.Edges) == 1 and type(shape.Edges[0].Curve) == Part.Circle:
return shape.Edges[0].Curve.Center
except Part.OCCError as e:
PathLog.error(e)
PathLog.error(translate("Path", "Feature %s.%s cannot be processed as a circular hole - please remove from Base geometry list.") % (base.Label, sub))
PathLog.error(
translate(
"Path",
"Feature %s.%s cannot be processed as a circular hole - please remove from Base geometry list.",
)
% (base.Label, sub)
)
return None
def isHoleEnabled(self, obj, base, sub):
'''isHoleEnabled(obj, base, sub) ... return true if hole is enabled.'''
"""isHoleEnabled(obj, base, sub) ... return true if hole is enabled."""
name = "%s.%s" % (base.Name, sub)
return not name in obj.Disabled
return name not in obj.Disabled
def opExecute(self, obj):
'''opExecute(obj) ... processes all Base features and Locations and collects
"""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.'''
Do not overwrite, implement circularHoleExecute(obj, holes) instead."""
PathLog.track()
def haveLocations(self, obj):
@@ -182,80 +175,91 @@ class ObjectOp(PathOp.ObjectOp):
for base, subs in obj.Base:
for sub in subs:
PathLog.debug('processing {} in {}'.format(sub, base.Name))
PathLog.debug("processing {} in {}".format(sub, base.Name))
if self.isHoleEnabled(obj, base, sub):
pos = self.holePosition(obj, base, sub)
if pos:
holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub)})
holes.append(
{
"x": pos.x,
"y": pos.y,
"r": self.holeDiameter(obj, base, sub),
}
)
if haveLocations(self, obj):
for location in obj.Locations:
holes.append({'x': location.x, 'y': location.y, 'r': 0})
holes.append({"x": location.x, "y": location.y, "r": 0})
if len(holes) > 0:
self.circularHoleExecute(obj, holes)
def circularHoleExecute(self, obj, holes):
'''circularHoleExecute(obj, holes) ... implement processing of 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.'''
Must be overwritten by subclasses."""
pass
def findAllHoles(self, obj):
'''findAllHoles(obj) ... find all holes of all base models and assign as features.'''
"""findAllHoles(obj) ... find all holes of all base models and assign as features."""
PathLog.track()
if not self.getJob(obj):
return
features = []
if 1 == len(self.model) and self.baseIsArchPanel(obj, self.model[0]):
panel = self.model[0]
holeshapes = panel.Proxy.getHoles(panel, transform=True)
tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter)
for holeNr, hole in enumerate(holeshapes):
PathLog.debug('Entering new HoleShape')
for wireNr, wire in enumerate(hole.Wires):
PathLog.debug('Entering new Wire')
for edgeNr, edge in enumerate(wire.Edges):
if PathUtils.isDrillable(panel, edge, tooldiameter):
PathLog.debug('Found drillable hole edges: {}'.format(edge))
features.append((panel, "%d.%d.%d" % (holeNr, wireNr, edgeNr)))
else:
for base in self.model:
features.extend(self.findHoles(obj, base))
for base in self.model:
features.extend(self.findHoles(obj, base))
obj.Base = features
obj.Disabled = []
def findHoles(self, obj, baseobject):
'''findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole.'''
"""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))
PathLog.track("obj: {} shape: {}".format(obj, shape))
holelist = []
features = []
# tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter)
tooldiameter = None
PathLog.debug('search for holes larger than tooldiameter: {}: '.format(tooldiameter))
PathLog.debug(
"search for holes larger than tooldiameter: {}: ".format(tooldiameter)
)
if DraftGeomUtils.isPlanar(shape):
PathLog.debug("shape is planar")
for i in range(len(shape.Edges)):
candidateEdgeName = "Edge" + str(i + 1)
e = shape.getElement(candidateEdgeName)
if PathUtils.isDrillable(shape, e, tooldiameter):
PathLog.debug('edge candidate: {} (hash {})is drillable '.format(e, e.hashCode()))
PathLog.debug(
"edge candidate: {} (hash {})is drillable ".format(
e, e.hashCode()
)
)
x = e.Curve.Center.x
y = e.Curve.Center.y
diameter = e.BoundBox.XLength
holelist.append({'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True})
holelist.append(
{
"featureName": candidateEdgeName,
"feature": e,
"x": x,
"y": y,
"d": diameter,
"enabled": True,
}
)
features.append((baseobject, candidateEdgeName))
PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateEdgeName))
PathLog.debug(
"Found hole feature %s.%s"
% (baseobject.Label, candidateEdgeName)
)
else:
PathLog.debug("shape is not planar")
for i in range(len(shape.Faces)):
candidateFaceName = "Face" + str(i + 1)
f = shape.getElement(candidateFaceName)
if PathUtils.isDrillable(shape, f, tooldiameter):
PathLog.debug('face candidate: {} is drillable '.format(f))
if hasattr(f.Surface, 'Center'):
PathLog.debug("face candidate: {} is drillable ".format(f))
if hasattr(f.Surface, "Center"):
x = f.Surface.Center.x
y = f.Surface.Center.y
diameter = f.BoundBox.XLength
@@ -264,9 +268,21 @@ class ObjectOp(PathOp.ObjectOp):
x = center.x
y = center.y
diameter = f.Edges[0].Curve.Radius * 2
holelist.append({'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True})
holelist.append(
{
"featureName": candidateFaceName,
"feature": f,
"x": x,
"y": y,
"d": diameter,
"enabled": True,
}
)
features.append((baseobject, candidateFaceName))
PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateFaceName))
PathLog.debug(
"Found hole feature %s.%s"
% (baseobject.Label, candidateFaceName)
)
PathLog.debug("holes found: {}".format(holelist))
return features
return features

View File

@@ -31,13 +31,13 @@ from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel')
Part = LazyLoader('Part', globals(), 'Part')
Part = LazyLoader("Part", globals(), "Part")
__doc__ = "Class and implementation of Path Engrave operation"
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
# Qt translation handling
@@ -46,27 +46,55 @@ def translate(context, text, disambig=None):
class ObjectEngrave(PathEngraveBase.ObjectOp):
'''Proxy class for Engrave operation.'''
"""Proxy class for Engrave operation."""
def __init__(self, obj, name, parentJob):
super(ObjectEngrave, self).__init__(obj, name, parentJob)
self.wires = []
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 | PathOp.FeatureCoolant
"""opFeatures(obj) ... return all standard features and edges based geomtries"""
return (
PathOp.FeatureTool
| PathOp.FeatureDepths
| PathOp.FeatureHeights
| PathOp.FeatureStepDown
| PathOp.FeatureBaseEdges
| PathOp.FeatureCoolant
)
def setupAdditionalProperties(self, obj):
if not hasattr(obj, 'BaseShapes'):
obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Additional base objects to be engraved"))
obj.setEditorMode('BaseShapes', 2) # hide
if not hasattr(obj, 'BaseObject'):
obj.addProperty("App::PropertyLink", "BaseObject", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Additional base objects to be engraved"))
obj.setEditorMode('BaseObject', 2) # hide
if not hasattr(obj, "BaseShapes"):
obj.addProperty(
"App::PropertyLinkList",
"BaseShapes",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathEngrave", "Additional base objects to be engraved"
),
)
obj.setEditorMode("BaseShapes", 2) # hide
if not hasattr(obj, "BaseObject"):
obj.addProperty(
"App::PropertyLink",
"BaseObject",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathEngrave", "Additional base objects to be engraved"
),
)
obj.setEditorMode("BaseObject", 2) # hide
def initOperation(self, obj):
'''initOperation(obj) ... create engraving specific properties.'''
obj.addProperty("App::PropertyInteger", "StartVertex", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "The vertex index to start the path from"))
"""initOperation(obj) ... create engraving specific properties."""
obj.addProperty(
"App::PropertyInteger",
"StartVertex",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathEngrave", "The vertex index to start the path from"
),
)
self.setupAdditionalProperties(obj)
def opOnDocumentRestored(self, obj):
@@ -74,7 +102,7 @@ class ObjectEngrave(PathEngraveBase.ObjectOp):
self.setupAdditionalProperties(obj)
def opExecute(self, obj):
'''opExecute(obj) ... process engraving operation'''
"""opExecute(obj) ... process engraving operation"""
PathLog.track()
jobshapes = []
@@ -106,36 +134,36 @@ class ObjectEngrave(PathEngraveBase.ObjectOp):
PathLog.track(self.model)
for base in self.model:
PathLog.track(base.Label)
if base.isDerivedFrom('Part::Part2DObject'):
if base.isDerivedFrom("Part::Part2DObject"):
jobshapes.append(base.Shape)
elif base.isDerivedFrom('Sketcher::SketchObject'):
elif base.isDerivedFrom("Sketcher::SketchObject"):
jobshapes.append(base.Shape)
elif hasattr(base, 'ArrayType'):
elif hasattr(base, "ArrayType"):
jobshapes.append(base.Shape)
elif isinstance(base.Proxy, ArchPanel.PanelSheet):
for tag in self.model[0].Proxy.getTags(self.model[0], transform=True):
tagWires = []
for w in tag.Wires:
tagWires.append(Part.Wire(w.Edges))
jobshapes.append(Part.makeCompound(tagWires))
if len(jobshapes) > 0:
PathLog.debug('processing {} jobshapes'.format(len(jobshapes)))
PathLog.debug("processing {} jobshapes".format(len(jobshapes)))
wires = []
for shape in jobshapes:
shapeWires = shape.Wires
PathLog.debug('jobshape has {} edges'.format(len(shape.Edges)))
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
PathLog.debug("jobshape has {} edges".format(len(shape.Edges)))
self.commandlist.append(
Path.Command(
"G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}
)
)
self.buildpathocc(obj, shapeWires, self.getZValues(obj))
wires.extend(shapeWires)
self.wires = wires
PathLog.debug('processing {} jobshapes -> {} wires'.format(len(jobshapes), len(wires)))
PathLog.debug(
"processing {} jobshapes -> {} wires".format(len(jobshapes), len(wires))
)
# the last command is a move to clearance, which is automatically added by PathOp
if self.commandlist:
self.commandlist.pop()
def opUpdateDepths(self, obj):
'''updateDepths(obj) ... engraving is always done at the top most z-value'''
"""updateDepths(obj) ... engraving is always done at the top most z-value"""
job = PathUtils.findParentJob(obj)
self.opSetDefaultValues(obj, job)

View File

@@ -51,7 +51,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp):
def circularHoleFeatures(self, obj):
'''circularHoleFeatures(obj) ... enable features supported by Helix.'''
return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels
return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces
def initCircularHoleOperation(self, obj):
'''initCircularHoleOperation(obj) ... create helix specific properties.'''

View File

@@ -20,6 +20,8 @@
# * *
# ***************************************************************************
from PathScripts.PathPostProcessor import PostProcessor
from PySide import QtCore
import FreeCAD
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
@@ -29,13 +31,11 @@ import PathScripts.PathToolController as PathToolController
import PathScripts.PathUtil as PathUtil
import json
import time
from PathScripts.PathPostProcessor import PostProcessor
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel')
Draft = LazyLoader('Draft', globals(), 'Draft')
Draft = LazyLoader("Draft", globals(), "Draft")
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
@@ -48,45 +48,41 @@ def translate(context, text, disambig=None):
class JobTemplate:
# pylint: disable=no-init
'''Attribute and sub element strings for template export/import.'''
Description = 'Desc'
GeometryTolerance = 'Tolerance'
Job = 'Job'
PostProcessor = 'Post'
PostProcessorArgs = 'PostArgs'
PostProcessorOutputFile = 'Output'
Fixtures = 'Fixtures'
OrderOutputBy = 'OrderOutputBy'
SplitOutput = 'SplitOutput'
SetupSheet = 'SetupSheet'
Stock = 'Stock'
"""Attribute and sub element strings for template export/import."""
Description = "Desc"
GeometryTolerance = "Tolerance"
Job = "Job"
PostProcessor = "Post"
PostProcessorArgs = "PostArgs"
PostProcessorOutputFile = "Output"
Fixtures = "Fixtures"
OrderOutputBy = "OrderOutputBy"
SplitOutput = "SplitOutput"
SetupSheet = "SetupSheet"
Stock = "Stock"
# TCs are grouped under Tools in a job, the template refers to them directly though
ToolController = 'ToolController'
Version = 'Version'
def isArchPanelSheet(obj):
return hasattr(obj, 'Proxy') and isinstance(obj.Proxy, ArchPanel.PanelSheet)
ToolController = "ToolController"
Version = "Version"
def isResourceClone(obj, propLink, resourceName):
# pylint: disable=unused-argument
if hasattr(propLink, 'PathResource') and (resourceName is None or resourceName == propLink.PathResource):
if hasattr(propLink, "PathResource") and (
resourceName is None or resourceName == propLink.PathResource
):
return True
return False
def createResourceClone(obj, orig, name, icon):
if isArchPanelSheet(orig):
# can't clone panel sheets - they have to be panel sheets
return orig
clone = Draft.clone(orig)
clone.Label = "%s-%s" % (name, orig.Label)
clone.addProperty('App::PropertyString', 'PathResource')
clone.addProperty("App::PropertyString", "PathResource")
clone.PathResource = name
if clone.ViewObject:
import PathScripts.PathIconViewProvider
PathScripts.PathIconViewProvider.Attach(clone.ViewObject, icon)
clone.ViewObject.Visibility = False
clone.ViewObject.Transparency = 80
@@ -95,7 +91,7 @@ def createResourceClone(obj, orig, name, icon):
def createModelResourceClone(obj, orig):
return createResourceClone(obj, orig, 'Model', 'BaseGeometry')
return createResourceClone(obj, orig, "Model", "BaseGeometry")
class NotificationClass(QtCore.QObject):
@@ -106,30 +102,108 @@ Notification = NotificationClass()
class ObjectJob:
def __init__(self, obj, models, templateFile=None):
self.obj = obj
obj.addProperty("App::PropertyFile", "PostProcessorOutputFile", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project"))
obj.addProperty("App::PropertyEnumeration", "PostProcessor", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Post Processor"))
obj.addProperty("App::PropertyString", "PostProcessorArgs", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Arguments for the Post Processor (specific to the script)"))
obj.addProperty("App::PropertyString", "LastPostProcessDate", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"))
obj.setEditorMode('LastPostProcessDate', 2) # Hide
obj.addProperty("App::PropertyString", "LastPostProcessOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"))
obj.setEditorMode('LastPostProcessOutput', 2) # Hide
obj.addProperty(
"App::PropertyFile",
"PostProcessorOutputFile",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project"),
)
obj.addProperty(
"App::PropertyEnumeration",
"PostProcessor",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Post Processor"),
)
obj.addProperty(
"App::PropertyString",
"PostProcessorArgs",
"Output",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Arguments for the Post Processor (specific to the script)"
),
)
obj.addProperty(
"App::PropertyString",
"LastPostProcessDate",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"),
)
obj.setEditorMode("LastPostProcessDate", 2) # Hide
obj.addProperty(
"App::PropertyString",
"LastPostProcessOutput",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"),
)
obj.setEditorMode("LastPostProcessOutput", 2) # Hide
obj.addProperty("App::PropertyString", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("PathJob", "An optional description for this job"))
obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Job Cycle Time Estimation"))
obj.setEditorMode('CycleTime', 1) # read-only
obj.addProperty("App::PropertyDistance", "GeometryTolerance", "Geometry", QtCore.QT_TRANSLATE_NOOP("PathJob", "For computing Paths; smaller increases accuracy, but slows down computation"))
obj.addProperty(
"App::PropertyString",
"Description",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathJob", "An optional description for this job"),
)
obj.addProperty(
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Job Cycle Time Estimation"),
)
obj.setEditorMode("CycleTime", 1) # read-only
obj.addProperty(
"App::PropertyDistance",
"GeometryTolerance",
"Geometry",
QtCore.QT_TRANSLATE_NOOP(
"PathJob",
"For computing Paths; smaller increases accuracy, but slows down computation",
),
)
obj.addProperty("App::PropertyLink", "Stock", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock."))
obj.addProperty("App::PropertyLink", "Operations", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Compound path of all operations in the order they are processed."))
obj.addProperty(
"App::PropertyLink",
"Stock",
"Base",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock."),
)
obj.addProperty(
"App::PropertyLink",
"Operations",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob",
"Compound path of all operations in the order they are processed.",
),
)
obj.addProperty("App::PropertyBool", "SplitOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Split output into multiple gcode files"))
obj.addProperty("App::PropertyEnumeration", "OrderOutputBy", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "If multiple WCS, order the output this way"))
obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job"))
obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation']
obj.Fixtures = ['G54']
obj.addProperty(
"App::PropertyBool",
"SplitOutput",
"Output",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Split output into multiple gcode files"
),
)
obj.addProperty(
"App::PropertyEnumeration",
"OrderOutputBy",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "If multiple WCS, order the output this way"
),
)
obj.addProperty(
"App::PropertyStringList",
"Fixtures",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "The Work Coordinate Systems for the Job"
),
)
obj.OrderOutputBy = ["Fixture", "Tool", "Operation"]
obj.Fixtures = ["G54"]
obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile()
obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors()
@@ -142,14 +216,16 @@ class ObjectJob:
obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs()
obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance()
ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations")
ops = FreeCAD.ActiveDocument.addObject(
"Path::FeatureCompoundPython", "Operations"
)
if ops.ViewObject:
ops.ViewObject.Proxy = 0
ops.ViewObject.Visibility = False
obj.Operations = ops
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Placement', 2)
obj.setEditorMode("Operations", 2) # hide
obj.setEditorMode("Placement", 2)
self.setupSetupSheet(obj)
self.setupBaseModel(obj, models)
@@ -171,41 +247,73 @@ class ObjectJob:
obj.Stock.ViewObject.Visibility = False
def setupSetupSheet(self, obj):
if not getattr(obj, 'SetupSheet', None):
obj.addProperty('App::PropertyLink', 'SetupSheet', 'Base', QtCore.QT_TRANSLATE_NOOP('PathJob', 'SetupSheet holding the settings for this job'))
if not getattr(obj, "SetupSheet", None):
obj.addProperty(
"App::PropertyLink",
"SetupSheet",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "SetupSheet holding the settings for this job"
),
)
obj.SetupSheet = PathSetupSheet.Create()
if obj.SetupSheet.ViewObject:
import PathScripts.PathIconViewProvider
PathScripts.PathIconViewProvider.Attach(obj.SetupSheet.ViewObject, 'SetupSheet')
PathScripts.PathIconViewProvider.Attach(
obj.SetupSheet.ViewObject, "SetupSheet"
)
self.setupSheet = obj.SetupSheet.Proxy
def setupBaseModel(self, obj, models=None):
PathLog.track(obj.Label, models)
if not hasattr(obj, 'Model'):
obj.addProperty("App::PropertyLink", "Model", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "The base objects for all operations"))
model = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Model")
if not hasattr(obj, "Model"):
obj.addProperty(
"App::PropertyLink",
"Model",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "The base objects for all operations"
),
)
model = FreeCAD.ActiveDocument.addObject(
"App::DocumentObjectGroup", "Model"
)
if model.ViewObject:
model.ViewObject.Visibility = False
if models:
model.addObjects([createModelResourceClone(obj, base) for base in models])
model.addObjects(
[createModelResourceClone(obj, base) for base in models]
)
obj.Model = model
if hasattr(obj, 'Base'):
PathLog.info("Converting Job.Base to new Job.Model for {}".format(obj.Label))
if hasattr(obj, "Base"):
PathLog.info(
"Converting Job.Base to new Job.Model for {}".format(obj.Label)
)
obj.Model.addObject(obj.Base)
obj.Base = None
obj.removeProperty('Base')
obj.removeProperty("Base")
def setupToolTable(self, obj):
if not hasattr(obj, 'Tools'):
obj.addProperty("App::PropertyLink", "Tools", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Collection of all tool controllers for the job"))
toolTable = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Tools")
toolTable.Label = 'Tools'
if not hasattr(obj, "Tools"):
obj.addProperty(
"App::PropertyLink",
"Tools",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Collection of all tool controllers for the job"
),
)
toolTable = FreeCAD.ActiveDocument.addObject(
"App::DocumentObjectGroup", "Tools"
)
toolTable.Label = "Tools"
if toolTable.ViewObject:
toolTable.ViewObject.Visibility = False
if hasattr(obj, 'ToolController'):
if hasattr(obj, "ToolController"):
toolTable.addObjects(obj.ToolController)
obj.removeProperty('ToolController')
obj.removeProperty("ToolController")
obj.Tools = toolTable
def removeBase(self, obj, base, removeFromModel):
@@ -219,16 +327,22 @@ class ObjectJob:
return PathStock.shapeBoundBox(obj.Model.Group)
def onDelete(self, obj, arg2=None):
'''Called by the view provider, there doesn't seem to be a callback on the obj itself.'''
"""Called by the view provider, there doesn't seem to be a callback on the obj itself."""
PathLog.track(obj.Label, arg2)
doc = obj.Document
if getattr(obj, 'Operations', None):
if getattr(obj, "Operations", None):
# the first to tear down are the ops, they depend on other resources
PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()])
PathLog.debug(
"taking down ops: %s" % [o.Name for o in self.allOperations()]
)
while obj.Operations.Group:
op = obj.Operations.Group[0]
if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()):
if (
not op.ViewObject
or not hasattr(op.ViewObject.Proxy, "onDelete")
or op.ViewObject.Proxy.onDelete(op.ViewObject, ())
):
PathUtil.clearExpressionEngine(op)
doc.removeObject(op.Name)
obj.Operations.Group = []
@@ -236,14 +350,14 @@ class ObjectJob:
obj.Operations = None
# stock could depend on Model, so delete it first
if getattr(obj, 'Stock', None):
PathLog.debug('taking down stock')
if getattr(obj, "Stock", None):
PathLog.debug("taking down stock")
PathUtil.clearExpressionEngine(obj.Stock)
doc.removeObject(obj.Stock.Name)
obj.Stock = None
# base doesn't depend on anything inside job
if getattr(obj, 'Model', None):
if getattr(obj, "Model", None):
for base in obj.Model.Group:
PathLog.debug("taking down base %s" % base.Label)
self.removeBase(obj, base, False)
@@ -252,8 +366,8 @@ class ObjectJob:
obj.Model = None
# Tool controllers might refer to either legacy tool or toolbit
if getattr(obj, 'Tools', None):
PathLog.debug('taking down tool controller')
if getattr(obj, "Tools", None):
PathLog.debug("taking down tool controller")
for tc in obj.Tools.Group:
if hasattr(tc.Tool, "Proxy"):
PathUtil.clearExpressionEngine(tc.Tool)
@@ -266,7 +380,7 @@ class ObjectJob:
obj.Tools = None
# SetupSheet
if getattr(obj, 'SetupSheet', None):
if getattr(obj, "SetupSheet", None):
PathUtil.clearExpressionEngine(obj.SetupSheet)
doc.removeObject(obj.SetupSheet.Name)
obj.SetupSheet = None
@@ -274,13 +388,15 @@ class ObjectJob:
return True
def fixupOperations(self, obj):
if getattr(obj.Operations, 'ViewObject', None):
if getattr(obj.Operations, "ViewObject", None):
try:
obj.Operations.ViewObject.DisplayMode
except Exception: # pylint: disable=broad-except
name = obj.Operations.Name
label = obj.Operations.Label
ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations")
ops = FreeCAD.ActiveDocument.addObject(
"Path::FeatureCompoundPython", "Operations"
)
ops.ViewObject.Proxy = 0
ops.Group = obj.Operations.Group
obj.Operations.Group = []
@@ -294,23 +410,49 @@ class ObjectJob:
self.setupSetupSheet(obj)
self.setupToolTable(obj)
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Placement', 2)
obj.setEditorMode("Operations", 2) # hide
obj.setEditorMode("Placement", 2)
if not hasattr(obj, 'CycleTime'):
obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"))
obj.setEditorMode('CycleTime', 1) # read-only
if not hasattr(obj, "CycleTime"):
obj.addProperty(
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"),
)
obj.setEditorMode("CycleTime", 1) # read-only
if not hasattr(obj, "Fixtures"):
obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job"))
obj.Fixtures = ['G54']
obj.addProperty(
"App::PropertyStringList",
"Fixtures",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "The Work Coordinate Systems for the Job"
),
)
obj.Fixtures = ["G54"]
if not hasattr(obj, "OrderOutputBy"):
obj.addProperty("App::PropertyEnumeration", "OrderOutputBy", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "If multiple WCS, order the output this way"))
obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation']
obj.addProperty(
"App::PropertyEnumeration",
"OrderOutputBy",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "If multiple WCS, order the output this way"
),
)
obj.OrderOutputBy = ["Fixture", "Tool", "Operation"]
if not hasattr(obj, "SplitOutput"):
obj.addProperty("App::PropertyBool", "SplitOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Split output into multiple gcode files"))
obj.addProperty(
"App::PropertyBool",
"SplitOutput",
"Output",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Split output into multiple gcode files"
),
)
obj.SplitOutput = False
def onChanged(self, obj, prop):
@@ -320,17 +462,17 @@ class ObjectJob:
self.tooltipArgs = processor.tooltipArgs
def baseObject(self, obj, base):
'''Return the base object, not its clone.'''
if isResourceClone(obj, base, 'Model') or isResourceClone(obj, base, 'Base'):
"""Return the base object, not its clone."""
if isResourceClone(obj, base, "Model") or isResourceClone(obj, base, "Base"):
return base.Objects[0]
return base
def baseObjects(self, obj):
'''Return the base objects, not their clones.'''
"""Return the base objects, not their clones."""
return [self.baseObject(obj, base) for base in obj.Model.Group]
def resourceClone(self, obj, base):
'''resourceClone(obj, base) ... Return the resource clone for base if it exists.'''
"""resourceClone(obj, base) ... Return the resource clone for base if it exists."""
if isResourceClone(obj, base, None):
return base
for b in obj.Model.Group:
@@ -339,11 +481,11 @@ class ObjectJob:
return None
def setFromTemplateFile(self, obj, template):
'''setFromTemplateFile(obj, template) ... extract the properties from the given template file and assign to receiver.
This will also create any TCs stored in the template.'''
"""setFromTemplateFile(obj, template) ... extract the properties from the given template file and assign to receiver.
This will also create any TCs stored in the template."""
tcs = []
if template:
with open(PathUtil.toUnicode(template), 'rb') as fp:
with open(PathUtil.toUnicode(template), "rb") as fp:
attrs = json.load(fp)
if attrs.get(JobTemplate.Version) and 1 == int(attrs[JobTemplate.Version]):
@@ -352,15 +494,19 @@ class ObjectJob:
self.setupSheet.setFromTemplate(attrs[JobTemplate.SetupSheet])
if attrs.get(JobTemplate.GeometryTolerance):
obj.GeometryTolerance = float(attrs.get(JobTemplate.GeometryTolerance))
obj.GeometryTolerance = float(
attrs.get(JobTemplate.GeometryTolerance)
)
if attrs.get(JobTemplate.PostProcessor):
obj.PostProcessor = attrs.get(JobTemplate.PostProcessor)
if attrs.get(JobTemplate.PostProcessorArgs):
obj.PostProcessorArgs = attrs.get(JobTemplate.PostProcessorArgs)
else:
obj.PostProcessorArgs = ''
obj.PostProcessorArgs = ""
if attrs.get(JobTemplate.PostProcessorOutputFile):
obj.PostProcessorOutputFile = attrs.get(JobTemplate.PostProcessorOutputFile)
obj.PostProcessorOutputFile = attrs.get(
JobTemplate.PostProcessorOutputFile
)
if attrs.get(JobTemplate.Description):
obj.Description = attrs.get(JobTemplate.Description)
@@ -368,10 +514,14 @@ class ObjectJob:
for tc in attrs.get(JobTemplate.ToolController):
tcs.append(PathToolController.FromTemplate(tc))
if attrs.get(JobTemplate.Stock):
obj.Stock = PathStock.CreateFromTemplate(obj, attrs.get(JobTemplate.Stock))
obj.Stock = PathStock.CreateFromTemplate(
obj, attrs.get(JobTemplate.Stock)
)
if attrs.get(JobTemplate.Fixtures):
obj.Fixtures = [x for y in attrs.get(JobTemplate.Fixtures) for x in y]
obj.Fixtures = [
x for y in attrs.get(JobTemplate.Fixtures) for x in y
]
if attrs.get(JobTemplate.OrderOutputBy):
obj.OrderOutputBy = attrs.get(JobTemplate.OrderOutputBy)
@@ -382,12 +532,15 @@ class ObjectJob:
PathLog.debug("setting tool controllers (%d)" % len(tcs))
obj.Tools.Group = tcs
else:
PathLog.error(translate('PathJob', "Unsupported PathJob template version %s") % attrs.get(JobTemplate.Version))
PathLog.error(
translate("PathJob", "Unsupported PathJob template version %s")
% attrs.get(JobTemplate.Version)
)
if not tcs:
self.addToolController(PathToolController.Create())
def templateAttrs(self, obj):
'''templateAttrs(obj) ... answer a dictionary with all properties of the receiver that should be stored in a template file.'''
"""templateAttrs(obj) ... answer a dictionary with all properties of the receiver that should be stored in a template file."""
attrs = {}
attrs[JobTemplate.Version] = 1
if obj.PostProcessor:
@@ -408,13 +561,13 @@ class ObjectJob:
def __setstate__(self, state):
for obj in FreeCAD.ActiveDocument.Objects:
if hasattr(obj, 'Proxy') and obj.Proxy == self:
if hasattr(obj, "Proxy") and obj.Proxy == self:
self.obj = obj
break
return None
def execute(self, obj):
if getattr(obj, 'Operations', None):
if getattr(obj, "Operations", None):
obj.Path = obj.Operations.Path
self.getCycleTime()
@@ -425,18 +578,23 @@ class ObjectJob:
for op in self.obj.Operations.Group:
# Skip inactive operations
if PathUtil.opProperty(op, 'Active') is False:
if PathUtil.opProperty(op, "Active") is False:
continue
# Skip operations that don't have a cycletime attribute
if PathUtil.opProperty(op, 'CycleTime') is None:
if PathUtil.opProperty(op, "CycleTime") is None:
continue
formattedCycleTime = PathUtil.opProperty(op, 'CycleTime')
formattedCycleTime = PathUtil.opProperty(op, "CycleTime")
opCycleTime = 0
try:
# Convert the formatted time from HH:MM:SS to just seconds
opCycleTime = sum(x * int(t) for x, t in zip([1, 60, 3600], reversed(formattedCycleTime.split(":"))))
opCycleTime = sum(
x * int(t)
for x, t in zip(
[1, 60, 3600], reversed(formattedCycleTime.split(":"))
)
)
except Exception:
continue
@@ -469,10 +627,26 @@ class ObjectJob:
def addToolController(self, tc):
group = self.obj.Tools.Group
PathLog.debug("addToolController(%s): %s" % (tc.Label, [t.Label for t in group]))
PathLog.debug(
"addToolController(%s): %s" % (tc.Label, [t.Label for t in group])
)
if tc.Name not in [str(t.Name) for t in group]:
tc.setExpression('VertRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.VertRapid))
tc.setExpression('HorizRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.HorizRapid))
tc.setExpression(
"VertRapid",
"%s.%s"
% (
self.setupSheet.expressionReference(),
PathSetupSheet.Template.VertRapid,
),
)
tc.setExpression(
"HorizRapid",
"%s.%s"
% (
self.setupSheet.expressionReference(),
PathSetupSheet.Template.HorizRapid,
),
)
self.obj.Tools.addObject(tc)
Notification.updateTC.emit(self.obj, tc)
@@ -480,17 +654,19 @@ class ObjectJob:
ops = []
def collectBaseOps(op):
if hasattr(op, 'TypeId'):
if op.TypeId == 'Path::FeaturePython':
if hasattr(op, "TypeId"):
if op.TypeId == "Path::FeaturePython":
ops.append(op)
if hasattr(op, 'Base'):
if hasattr(op, "Base"):
collectBaseOps(op.Base)
if op.TypeId == 'Path::FeatureCompoundPython':
if op.TypeId == "Path::FeatureCompoundPython":
ops.append(op)
for sub in op.Group:
collectBaseOps(sub)
if getattr(self.obj, 'Operations', None) and getattr(self.obj.Operations, 'Group', None):
if getattr(self.obj, "Operations", None) and getattr(
self.obj.Operations, "Group", None
):
for op in self.obj.Operations.Group:
collectBaseOps(op)
@@ -505,25 +681,32 @@ class ObjectJob:
@classmethod
def baseCandidates(cls):
'''Answer all objects in the current document which could serve as a Base for a job.'''
return sorted([obj for obj in FreeCAD.ActiveDocument.Objects if cls.isBaseCandidate(obj)], key=lambda o: o.Label)
"""Answer all objects in the current document which could serve as a Base for a job."""
return sorted(
[obj for obj in FreeCAD.ActiveDocument.Objects if cls.isBaseCandidate(obj)],
key=lambda o: o.Label,
)
@classmethod
def isBaseCandidate(cls, obj):
'''Answer true if the given object can be used as a Base for a job.'''
return PathUtil.isValidBaseObject(obj) or isArchPanelSheet(obj)
"""Answer true if the given object can be used as a Base for a job."""
return PathUtil.isValidBaseObject(obj)
def Instances():
'''Instances() ... Return all Jobs in the current active document.'''
"""Instances() ... Return all Jobs in the current active document."""
if FreeCAD.ActiveDocument:
return [job for job in FreeCAD.ActiveDocument.Objects if hasattr(job, 'Proxy') and isinstance(job.Proxy, ObjectJob)]
return [
job
for job in FreeCAD.ActiveDocument.Objects
if hasattr(job, "Proxy") and isinstance(job.Proxy, ObjectJob)
]
return []
def Create(name, base, templateFile=None):
'''Create(name, base, templateFile=None) ... creates a new job and all it's resources.
If a template file is specified the new job is initialized with the values from the template.'''
"""Create(name, base, templateFile=None) ... creates a new job and all it's resources.
If a template file is specified the new job is initialized with the values from the template."""
if isinstance(base[0], str):
models = []
for baseName in base:

File diff suppressed because it is too large Load Diff

View File

@@ -24,17 +24,18 @@ import time
from PySide import QtCore
from PathScripts.PathUtils import waiting_effects
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
from PathScripts.PathUtils import waiting_effects
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader('Part', globals(), 'Part')
Part = LazyLoader("Part", globals(), "Part")
__title__ = "Base class for all operations."
__author__ = "sliptonic (Brad Collette)"
@@ -65,11 +66,11 @@ FeatureLocations = 0x1000 # Locations
FeatureCoolant = 0x2000 # Coolant
FeatureDiameters = 0x4000 # Turning Diameters
FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels
FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges
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
@@ -88,7 +89,6 @@ class ObjectOp(object):
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
FeatureCoolant ... Support for operation coolant
FeatureDiameters ... Support for turning operation diameters
@@ -98,35 +98,93 @@ class ObjectOp(object):
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 addBaseProperty(self, obj):
obj.addProperty("App::PropertyLinkSubListGlobal", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "The base geometry for this operation"))
obj.addProperty(
"App::PropertyLinkSubListGlobal",
"Base",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "The base geometry for this operation"),
)
def addOpValues(self, obj, values):
if 'start' in values:
obj.addProperty("App::PropertyDistance", "OpStartDepth", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the calculated value for the StartDepth"))
obj.setEditorMode('OpStartDepth', 1) # read-only
if 'final' in values:
obj.addProperty("App::PropertyDistance", "OpFinalDepth", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the calculated value for the FinalDepth"))
obj.setEditorMode('OpFinalDepth', 1) # read-only
if 'tooldia' in values:
obj.addProperty("App::PropertyDistance", "OpToolDiameter", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the diameter of the tool"))
obj.setEditorMode('OpToolDiameter', 1) # read-only
if 'stockz' in values:
obj.addProperty("App::PropertyDistance", "OpStockZMax", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the max Z value of Stock"))
obj.setEditorMode('OpStockZMax', 1) # read-only
obj.addProperty("App::PropertyDistance", "OpStockZMin", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the min Z value of Stock"))
obj.setEditorMode('OpStockZMin', 1) # read-only
if "start" in values:
obj.addProperty(
"App::PropertyDistance",
"OpStartDepth",
"Op Values",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Holds the calculated value for the StartDepth"
),
)
obj.setEditorMode("OpStartDepth", 1) # read-only
if "final" in values:
obj.addProperty(
"App::PropertyDistance",
"OpFinalDepth",
"Op Values",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Holds the calculated value for the FinalDepth"
),
)
obj.setEditorMode("OpFinalDepth", 1) # read-only
if "tooldia" in values:
obj.addProperty(
"App::PropertyDistance",
"OpToolDiameter",
"Op Values",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the diameter of the tool"),
)
obj.setEditorMode("OpToolDiameter", 1) # read-only
if "stockz" in values:
obj.addProperty(
"App::PropertyDistance",
"OpStockZMax",
"Op Values",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the max Z value of Stock"),
)
obj.setEditorMode("OpStockZMax", 1) # read-only
obj.addProperty(
"App::PropertyDistance",
"OpStockZMin",
"Op Values",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the min Z value of Stock"),
)
obj.setEditorMode("OpStockZMin", 1) # read-only
def __init__(self, obj, name, parentJob=None):
PathLog.track()
obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "An optional comment for this Operation"))
obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label"))
obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"))
obj.setEditorMode('CycleTime', 1) # read-only
obj.addProperty(
"App::PropertyBool",
"Active",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Make False, to prevent operation from generating code"
),
)
obj.addProperty(
"App::PropertyString",
"Comment",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "An optional comment for this Operation"
),
)
obj.addProperty(
"App::PropertyString",
"UserLabel",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label"),
)
obj.addProperty(
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"),
)
obj.setEditorMode("CycleTime", 1) # read-only
features = self.opFeatures(obj)
@@ -134,45 +192,136 @@ class ObjectOp(object):
self.addBaseProperty(obj)
if FeatureLocations & features:
obj.addProperty("App::PropertyVectorList", "Locations", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation"))
obj.addProperty(
"App::PropertyVectorList",
"Locations",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation"),
)
if FeatureTool & features:
obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "The tool controller that will be used to calculate the path"))
self.addOpValues(obj, ['tooldia'])
obj.addProperty(
"App::PropertyLink",
"ToolController",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathOp",
"The tool controller that will be used to calculate the path",
),
)
self.addOpValues(obj, ["tooldia"])
if FeatureCoolant & features:
obj.addProperty("App::PropertyString", "CoolantMode", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant mode for this operation"))
obj.addProperty(
"App::PropertyString",
"CoolantMode",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant mode for this operation"),
)
if FeatureDepths & features:
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Final Depth of Tool- lowest value in Z"))
obj.addProperty(
"App::PropertyDistance",
"StartDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Starting Depth of Tool- first cut depth in Z"
),
)
obj.addProperty(
"App::PropertyDistance",
"FinalDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Final Depth of Tool- lowest value in Z"
),
)
if FeatureNoFinalDepth & features:
obj.setEditorMode('FinalDepth', 2) # hide
self.addOpValues(obj, ['start', 'final'])
obj.setEditorMode("FinalDepth", 2) # hide
self.addOpValues(obj, ["start", "final"])
else:
# StartDepth has become necessary for expressions on other properties
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Starting Depth internal use only for derived values"))
obj.setEditorMode('StartDepth', 1) # read-only
obj.addProperty(
"App::PropertyDistance",
"StartDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Starting Depth internal use only for derived values"
),
)
obj.setEditorMode("StartDepth", 1) # read-only
self.addOpValues(obj, ['stockz'])
self.addOpValues(obj, ["stockz"])
if FeatureStepDown & features:
obj.addProperty("App::PropertyDistance", "StepDown", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool"))
obj.addProperty(
"App::PropertyDistance",
"StepDown",
"Depth",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool"),
)
if FeatureFinishDepth & features:
obj.addProperty("App::PropertyDistance", "FinishDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Maximum material removed on final pass."))
obj.addProperty(
"App::PropertyDistance",
"FinishDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Maximum material removed on final pass."
),
)
if FeatureHeights & features:
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Rapid Safety Height between locations."))
obj.addProperty(
"App::PropertyDistance",
"ClearanceHeight",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "The height needed to clear clamps and obstructions"
),
)
obj.addProperty(
"App::PropertyDistance",
"SafeHeight",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Rapid Safety Height between locations."
),
)
if FeatureStartPoint & features:
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"))
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"
),
)
if FeatureDiameters & features:
obj.addProperty("App::PropertyDistance", "MinDiameter", "Diameter", QtCore.QT_TRANSLATE_NOOP("PathOp", "Lower limit of the turning diameter"))
obj.addProperty("App::PropertyDistance", "MaxDiameter", "Diameter", QtCore.QT_TRANSLATE_NOOP("PathOp", "Upper limit of the turning diameter."))
obj.addProperty(
"App::PropertyDistance",
"MinDiameter",
"Diameter",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Lower limit of the turning diameter"
),
)
obj.addProperty(
"App::PropertyDistance",
"MaxDiameter",
"Diameter",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Upper limit of the turning diameter."
),
)
# members being set later
self.commandlist = None
@@ -199,135 +348,153 @@ class ObjectOp(object):
obj.Proxy = self
def setEditorModes(self, obj, features):
'''Editor modes are not preserved during document store/restore, set editor modes for all properties'''
"""Editor modes are not preserved during document store/restore, set editor modes for all properties"""
for op in ['OpStartDepth', 'OpFinalDepth', 'OpToolDiameter', 'CycleTime']:
for op in ["OpStartDepth", "OpFinalDepth", "OpToolDiameter", "CycleTime"]:
if hasattr(obj, op):
obj.setEditorMode(op, 1) # read-only
if FeatureDepths & features:
if FeatureNoFinalDepth & features:
obj.setEditorMode('OpFinalDepth', 2)
obj.setEditorMode("OpFinalDepth", 2)
def onDocumentRestored(self, obj):
features = self.opFeatures(obj)
if FeatureBaseGeometry & features and 'App::PropertyLinkSubList' == obj.getTypeIdOfProperty('Base'):
if (
FeatureBaseGeometry & features
and "App::PropertyLinkSubList" == obj.getTypeIdOfProperty("Base")
):
PathLog.info("Replacing link property with global link (%s)." % obj.State)
base = obj.Base
obj.removeProperty('Base')
obj.removeProperty("Base")
self.addBaseProperty(obj)
obj.Base = base
obj.touch()
obj.Document.recompute()
if FeatureTool & features and not hasattr(obj, 'OpToolDiameter'):
self.addOpValues(obj, ['tooldia'])
if FeatureTool & features and not hasattr(obj, "OpToolDiameter"):
self.addOpValues(obj, ["tooldia"])
if FeatureCoolant & features and not hasattr(obj, 'CoolantMode'):
obj.addProperty("App::PropertyString", "CoolantMode", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant option for this operation"))
if FeatureCoolant & features and not hasattr(obj, "CoolantMode"):
obj.addProperty(
"App::PropertyString",
"CoolantMode",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant option for this operation"),
)
if FeatureDepths & features and not hasattr(obj, 'OpStartDepth'):
self.addOpValues(obj, ['start', 'final'])
if FeatureDepths & features and not hasattr(obj, "OpStartDepth"):
self.addOpValues(obj, ["start", "final"])
if FeatureNoFinalDepth & features:
obj.setEditorMode('OpFinalDepth', 2)
obj.setEditorMode("OpFinalDepth", 2)
if not hasattr(obj, 'OpStockZMax'):
self.addOpValues(obj, ['stockz'])
if not hasattr(obj, "OpStockZMax"):
self.addOpValues(obj, ["stockz"])
if not hasattr(obj, 'CycleTime'):
obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"))
if not hasattr(obj, "CycleTime"):
obj.addProperty(
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"),
)
self.setEditorModes(obj, features)
self.opOnDocumentRestored(obj)
def __getstate__(self):
'''__getstat__(self) ... called when receiver is saved.
Can safely be overwritten by subclasses.'''
"""__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.'''
"""__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.
"""opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation.
The default implementation returns "FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint"
Should be overwritten by subclasses.'''
Should be overwritten by subclasses."""
# pylint: disable=unused-argument
return FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint | FeatureBaseGeometry | FeatureFinishDepth | FeatureCoolant
return (
FeatureTool
| FeatureDepths
| FeatureHeights
| FeatureStartPoint
| FeatureBaseGeometry
| FeatureFinishDepth
| FeatureCoolant
)
def initOperation(self, obj):
'''initOperation(obj) ... implement to create additional properties.
Should be overwritten by subclasses.'''
"""initOperation(obj) ... implement to create additional properties.
Should be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def opOnDocumentRestored(self, obj):
'''opOnDocumentRestored(obj) ... implement if an op needs special handling like migrating the data model.
Should be overwritten by subclasses.'''
"""opOnDocumentRestored(obj) ... implement if an op needs special handling like migrating the data model.
Should be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def opOnChanged(self, obj, prop):
'''opOnChanged(obj, prop) ... overwrite to process property changes.
"""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.'''
Can safely be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def opSetDefaultValues(self, obj, job):
'''opSetDefaultValues(obj, job) ... overwrite to set initial default values.
"""opSetDefaultValues(obj, job) ... overwrite to set initial default values.
Called after the receiver has been fully created with all properties.
Can safely be overwritten by subclasses.'''
Can safely be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def opUpdateDepths(self, obj):
'''opUpdateDepths(obj) ... overwrite to implement special depths calculation.
Can safely be overwritten by subclass.'''
"""opUpdateDepths(obj) ... overwrite to implement special depths calculation.
Can safely be overwritten by subclass."""
pass # pylint: disable=unnecessary-pass
def opExecute(self, obj):
'''opExecute(obj) ... called whenever the receiver needs to be recalculated.
"""opExecute(obj) ... called whenever the receiver needs to be recalculated.
See documentation of execute() for a list of base functionality provided.
Should be overwritten by subclasses.'''
Should be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def opRejectAddBase(self, obj, base, sub):
'''opRejectAddBase(base, sub) ... if op returns True the addition of the feature is prevented.
Should be overwritten by subclasses.'''
"""opRejectAddBase(base, sub) ... if op returns True the addition of the feature is prevented.
Should be overwritten by subclasses."""
# pylint: disable=unused-argument
return False
def onChanged(self, obj, prop):
'''onChanged(obj, prop) ... base implementation of the FC notification framework.
Do not overwrite, overwrite opOnChanged() instead.'''
"""onChanged(obj, prop) ... base implementation of the FC notification framework.
Do not overwrite, overwrite opOnChanged() instead."""
# there's a bit of cycle going on here, if sanitizeBase causes the transaction to
# be cancelled we end right here again with the unsainitized Base - if that is the
# case, stop the cycle and return immediately
if prop == 'Base' and self.sanitizeBase(obj):
if prop == "Base" and self.sanitizeBase(obj):
return
if 'Restore' not in obj.State and prop in ['Base', 'StartDepth', 'FinalDepth']:
if "Restore" not in obj.State and prop in ["Base", "StartDepth", "FinalDepth"]:
self.updateDepths(obj, True)
self.opOnChanged(obj, prop)
def applyExpression(self, obj, prop, expr):
'''applyExpression(obj, prop, expr) ... set expression expr on obj.prop if expr is set'''
"""applyExpression(obj, prop, expr) ... set expression expr on obj.prop if expr is set"""
if expr:
obj.setExpression(prop, expr)
return True
return False
def setDefaultValues(self, obj):
'''setDefaultValues(obj) ... base implementation.
Do not overwrite, overwrite opSetDefaultValues() instead.'''
if self.job:
job = self.job
else:
job = PathUtils.addToJob(obj)
"""setDefaultValues(obj) ... base implementation.
Do not overwrite, overwrite opSetDefaultValues() instead."""
job = PathUtils.addToJob(obj)
obj.Active = True
@@ -335,7 +502,9 @@ class ObjectOp(object):
if FeatureTool & features:
if 1 < len(job.Operations.Group):
obj.ToolController = PathUtil.toolControllerForOp(job.Operations.Group[-2])
obj.ToolController = PathUtil.toolControllerForOp(
job.Operations.Group[-2]
)
else:
obj.ToolController = PathUtils.findToolController(obj, self)
if not obj.ToolController:
@@ -346,11 +515,15 @@ class ObjectOp(object):
obj.CoolantMode = job.SetupSheet.CoolantMode
if FeatureDepths & features:
if self.applyExpression(obj, 'StartDepth', job.SetupSheet.StartDepthExpression):
if self.applyExpression(
obj, "StartDepth", job.SetupSheet.StartDepthExpression
):
obj.OpStartDepth = 1.0
else:
obj.StartDepth = 1.0
if self.applyExpression(obj, 'FinalDepth', job.SetupSheet.FinalDepthExpression):
if self.applyExpression(
obj, "FinalDepth", job.SetupSheet.FinalDepthExpression
):
obj.OpFinalDepth = 0.0
else:
obj.FinalDepth = 0.0
@@ -358,20 +531,26 @@ class ObjectOp(object):
obj.StartDepth = 1.0
if FeatureStepDown & features:
if not self.applyExpression(obj, 'StepDown', job.SetupSheet.StepDownExpression):
obj.StepDown = '1 mm'
if not self.applyExpression(
obj, "StepDown", job.SetupSheet.StepDownExpression
):
obj.StepDown = "1 mm"
if FeatureHeights & features:
if job.SetupSheet.SafeHeightExpression:
if not self.applyExpression(obj, 'SafeHeight', job.SetupSheet.SafeHeightExpression):
obj.SafeHeight = '3 mm'
if not self.applyExpression(
obj, "SafeHeight", job.SetupSheet.SafeHeightExpression
):
obj.SafeHeight = "3 mm"
if job.SetupSheet.ClearanceHeightExpression:
if not self.applyExpression(obj, 'ClearanceHeight', job.SetupSheet.ClearanceHeightExpression):
obj.ClearanceHeight = '5 mm'
if not self.applyExpression(
obj, "ClearanceHeight", job.SetupSheet.ClearanceHeightExpression
):
obj.ClearanceHeight = "5 mm"
if FeatureDiameters & features:
obj.MinDiameter = '0 mm'
obj.MaxDiameter = '0 mm'
obj.MinDiameter = "0 mm"
obj.MaxDiameter = "0 mm"
if job.Stock:
obj.MaxDiameter = job.Stock.Shape.BoundBox.XLength
@@ -389,7 +568,10 @@ class ObjectOp(object):
return False
if not job.Model.Group:
if not ignoreErrors:
PathLog.error(translate("Path", "Parent job %s doesn't have a base object") % job.Label)
PathLog.error(
translate("Path", "Parent job %s doesn't have a base object")
% job.Label
)
return False
self.job = job
self.model = job.Model.Group
@@ -397,15 +579,15 @@ class ObjectOp(object):
return True
def getJob(self, obj):
'''getJob(obj) ... return the job this operation is part of.'''
if not hasattr(self, 'job') or self.job is None:
"""getJob(obj) ... return the job this operation is part of."""
if not hasattr(self, "job") or self.job is None:
if not self._setBaseAndStock(obj):
return None
return self.job
def updateDepths(self, obj, ignoreErrors=False):
'''updateDepths(obj) ... base implementation calculating depths depending on base geometry.
Should not be overwritten.'''
"""updateDepths(obj) ... base implementation calculating depths depending on base geometry.
Should not be overwritten."""
def faceZmin(bb, fbb):
if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face
@@ -428,7 +610,7 @@ class ObjectOp(object):
obj.OpStockZMin = zmin
obj.OpStockZMax = zmax
if hasattr(obj, 'Base') and obj.Base:
if hasattr(obj, "Base") and obj.Base:
for base, sublist in obj.Base:
bb = base.Shape.BoundBox
zmax = max(zmax, bb.ZMax)
@@ -456,7 +638,9 @@ class ObjectOp(object):
zmin = obj.OpFinalDepth.Value
def minZmax(z):
if hasattr(obj, 'StepDown') and not PathGeom.isRoughly(obj.StepDown.Value, 0):
if hasattr(obj, "StepDown") and not PathGeom.isRoughly(
obj.StepDown.Value, 0
):
return z + obj.StepDown.Value
else:
return z + 1
@@ -476,21 +660,23 @@ class ObjectOp(object):
self.opUpdateDepths(obj)
def sanitizeBase(self, obj):
'''sanitizeBase(obj) ... check if Base is valid and clear on errors.'''
if hasattr(obj, 'Base'):
"""sanitizeBase(obj) ... check if Base is valid and clear on errors."""
if hasattr(obj, "Base"):
try:
for (o, sublist) in obj.Base:
for sub in sublist:
e = o.Shape.getElement(sub)
except Part.OCCError as e:
PathLog.error("{} - stale base geometry detected - clearing.".format(obj.Label))
o.Shape.getElement(sub)
except Part.OCCError:
PathLog.error(
"{} - stale base geometry detected - clearing.".format(obj.Label)
)
obj.Base = []
return True
return False
@waiting_effects
def execute(self, obj):
'''execute(obj) ... base implementation - do not overwrite!
"""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():
@@ -508,7 +694,7 @@ class ObjectOp(object):
opExecute(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:
@@ -523,13 +709,22 @@ class ObjectOp(object):
self.sanitizeBase(obj)
if FeatureCoolant & self.opFeatures(obj):
if not hasattr(obj, 'CoolantMode'):
PathLog.error(translate("Path", "No coolant property found. Please recreate operation."))
if not hasattr(obj, "CoolantMode"):
PathLog.error(
translate(
"Path", "No coolant property found. Please recreate operation."
)
)
if FeatureTool & self.opFeatures(obj):
tc = obj.ToolController
if tc is None or tc.ToolNumber == 0:
PathLog.error(translate("Path", "No Tool Controller is selected. We need a tool to build a Path."))
PathLog.error(
translate(
"Path",
"No Tool Controller is selected. We need a tool to build a Path.",
)
)
return
else:
self.vertFeed = tc.VertFeed.Value
@@ -538,7 +733,12 @@ class ObjectOp(object):
self.horizRapid = tc.HorizRapid.Value
tool = tc.Proxy.getTool(tc)
if not tool or float(tool.Diameter) == 0:
PathLog.error(translate("Path", "No Tool found or diameter is zero. We need a tool to build a Path."))
PathLog.error(
translate(
"Path",
"No Tool found or diameter is zero. We need a tool to build a Path.",
)
)
return
self.radius = float(tool.Diameter) / 2.0
self.tool = tool
@@ -558,7 +758,9 @@ class ObjectOp(object):
if self.commandlist and (FeatureHeights & self.opFeatures(obj)):
# Let's finish by rapid to clearance...just for safety
self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
self.commandlist.append(
Path.Command("G0", {"Z": obj.ClearanceHeight.Value})
)
path = Path.Path(self.commandlist)
obj.Path = path
@@ -572,25 +774,39 @@ class ObjectOp(object):
if tc is None or tc.ToolNumber == 0:
PathLog.error(translate("Path", "No Tool Controller selected."))
return translate('Path', 'Tool Error')
return translate("Path", "Tool Error")
hFeedrate = tc.HorizFeed.Value
vFeedrate = tc.VertFeed.Value
hRapidrate = tc.HorizRapid.Value
vRapidrate = tc.VertRapid.Value
if (hFeedrate == 0 or vFeedrate == 0) and not PathPreferences.suppressAllSpeedsWarning():
PathLog.warning(translate("Path", "Tool Controller feedrates required to calculate the cycle time."))
return translate('Path', 'Feedrate Error')
if (
hFeedrate == 0 or vFeedrate == 0
) and not PathPreferences.suppressAllSpeedsWarning():
PathLog.warning(
translate(
"Path",
"Tool Controller feedrates required to calculate the cycle time.",
)
)
return translate("Path", "Feedrate Error")
if (hRapidrate == 0 or vRapidrate == 0) and not PathPreferences.suppressRapidSpeedsWarning():
PathLog.warning(translate("Path", "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times."))
if (
hRapidrate == 0 or vRapidrate == 0
) and not PathPreferences.suppressRapidSpeedsWarning():
PathLog.warning(
translate(
"Path",
"Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.",
)
)
# Get the cycle time in seconds
seconds = obj.Path.getCycleTime(hFeedrate, vFeedrate, hRapidrate, vRapidrate)
if not seconds:
return translate('Path', 'Cycletime Error')
return translate("Path", "Cycletime Error")
# Convert the cycle time to a HH:MM:SS format
cycleTime = time.strftime("%H:%M:%S", time.gmtime(seconds))
@@ -613,17 +829,29 @@ class ObjectOp(object):
for p, el in baselist:
if p == base and sub in el:
PathLog.notice((translate("Path", "Base object %s.%s already in the list") + "\n") % (base.Label, sub))
PathLog.notice(
(
translate("Path", "Base object %s.%s already in the list")
+ "\n"
)
% (base.Label, sub)
)
return
if not self.opRejectAddBase(obj, base, sub):
baselist.append((base, sub))
obj.Base = baselist
else:
PathLog.notice((translate("Path", "Base object %s.%s rejected by operation") + "\n") % (base.Label, sub))
PathLog.notice(
(
translate("Path", "Base object %s.%s rejected by operation")
+ "\n"
)
% (base.Label, sub)
)
def isToolSupported(self, obj, tool):
'''toolSupported(obj, tool) ... Returns true if the op supports the given tool.
This function can safely be overwritten by subclasses.'''
"""toolSupported(obj, tool) ... Returns true if the op supports the given tool.
This function can safely be overwritten by subclasses."""
return True

File diff suppressed because it is too large Load Diff