Merge pull request #3450 from dubstar-04/fixes/cycletime

[Path] - fixes/cycletime
This commit is contained in:
sliptonic
2020-05-09 22:39:25 -05:00
committed by GitHub
2 changed files with 61 additions and 58 deletions

View File

@@ -30,7 +30,8 @@ import PathScripts.PathSetupSheet as PathSetupSheet
import PathScripts.PathStock as PathStock
import PathScripts.PathToolController as PathToolController
import PathScripts.PathUtil as PathUtil
import json, time
import json
import time
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -41,13 +42,13 @@ from PathScripts.PathPostProcessor import PostProcessor
from PySide import QtCore
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class JobTemplate:
# pylint: disable=no-init
'''Attribute and sub element strings for template export/import.'''
@@ -62,15 +63,18 @@ class JobTemplate:
ToolController = 'ToolController'
Version = 'Version'
def isArchPanelSheet(obj):
return hasattr(obj, 'Proxy') and isinstance(obj.Proxy, ArchPanel.PanelSheet)
def isResourceClone(obj, propLink, resourceName):
# pylint: disable=unused-argument
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
@@ -84,22 +88,24 @@ def createResourceClone(obj, orig, name, icon):
PathIconViewProvider.Attach(clone.ViewObject, icon)
clone.ViewObject.Visibility = False
clone.ViewObject.Transparency = 80
obj.Document.recompute() # necessary to create the clone shape
obj.Document.recompute() # necessary to create the clone shape
return clone
def createModelResourceClone(obj, orig):
return createResourceClone(obj, orig, 'Model', 'BaseGeometry')
class ObjectJob:
def __init__(self, obj, models, templateFile = None):
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::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", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("PathJob","An optional description for this job"))
obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"))
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"))
@@ -107,14 +113,13 @@ class ObjectJob:
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::PropertyLinkList", "ToolController", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Collection of tool controllers available for this job."))
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.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.setEditorMode("PostProcessorOutputFile", 0) # set to default mode
obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors()
defaultPostProcessor = PathPreferences.defaultPostProcessor()
# Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed)
@@ -131,7 +136,7 @@ class ObjectJob:
ops.ViewObject.Visibility = False
obj.Operations = ops
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Placement', 2)
self.setupSetupSheet(obj)
@@ -235,7 +240,7 @@ class ObjectJob:
if obj.Operations.ViewObject:
try:
obj.Operations.ViewObject.DisplayMode
except Exception: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
name = obj.Operations.Name
label = obj.Operations.Label
ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations")
@@ -246,12 +251,11 @@ class ObjectJob:
FreeCAD.ActiveDocument.removeObject(name)
ops.Label = label
def onDocumentRestored(self, obj):
self.setupBaseModel(obj)
self.fixupOperations(obj)
self.setupSetupSheet(obj)
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Placement', 2)
if not hasattr(obj, 'CycleTime'):
@@ -327,13 +331,13 @@ class ObjectJob:
attrs = {}
attrs[JobTemplate.Version] = 1
if obj.PostProcessor:
attrs[JobTemplate.PostProcessor] = obj.PostProcessor
attrs[JobTemplate.PostProcessorArgs] = obj.PostProcessorArgs
attrs[JobTemplate.PostProcessor] = obj.PostProcessor
attrs[JobTemplate.PostProcessorArgs] = obj.PostProcessorArgs
if obj.PostProcessorOutputFile:
attrs[JobTemplate.PostProcessorOutputFile] = obj.PostProcessorOutputFile
attrs[JobTemplate.GeometryTolerance] = str(obj.GeometryTolerance.Value)
attrs[JobTemplate.GeometryTolerance] = str(obj.GeometryTolerance.Value)
if obj.Description:
attrs[JobTemplate.Description] = obj.Description
attrs[JobTemplate.Description] = obj.Description
return attrs
def __getstate__(self):
@@ -367,19 +371,18 @@ class ObjectJob:
formattedCycleTime = PathUtil.opProperty(op, 'CycleTime')
opCycleTime = 0
try:
## convert the formatted time from HH:MM:SS to just seconds
# 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(":"))))
except:
FreeCAD.Console.PrintWarning("Error converting the operations cycle time. Job Cycle time may be innacturate\n")
continue
if opCycleTime > 0:
seconds = seconds + opCycleTime
cycleTimeString = time.strftime("%H:%M:%S", time.gmtime(seconds))
self.obj.CycleTime = cycleTimeString
cycleTimeString = time.strftime("%H:%M:%S", time.gmtime(seconds))
self.obj.CycleTime = cycleTimeString
def addOperation(self, op, before = None, removeBefore = False):
def addOperation(self, op, before=None, removeBefore=False):
group = self.obj.Operations.Group
if op not in group:
if before:
@@ -387,7 +390,7 @@ class ObjectJob:
group.insert(group.index(before), op)
if removeBefore:
group.remove(before)
except Exception as e: # pylint: disable=broad-except
except Exception as e: # pylint: disable=broad-except
PathLog.error(e)
group.append(op)
else:
@@ -399,13 +402,14 @@ class ObjectJob:
group = self.obj.ToolController
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('VertRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.VertRapid))
tc.setExpression('HorizRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.HorizRapid))
group.append(tc)
self.obj.ToolController = group
def allOperations(self):
ops = []
def collectBaseOps(op):
if hasattr(op, 'TypeId'):
if op.TypeId == 'Path::FeaturePython':
@@ -437,13 +441,15 @@ class ObjectJob:
'''Answer true if the given object can be used as a Base for a job.'''
return PathUtil.isValidBaseObject(obj) or isArchPanelSheet(obj)
def Instances():
'''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 []
def Create(name, base, templateFile = None):
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.'''
if str == type(base[0]):
@@ -455,4 +461,3 @@ def Create(name, base, templateFile = None):
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectJob(obj, models, templateFile)
return obj

View File

@@ -43,13 +43,13 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Base class and properties implementation for all Path operations."
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule()
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
FeatureTool = 0x0001 # ToolController
FeatureDepths = 0x0002 # FinalDepth, StartDepth
FeatureHeights = 0x0004 # ClearanceHeight, SafeHeight
@@ -89,7 +89,7 @@ class ObjectOp(object):
FeatureBaseFaces ... Base geometry support for faces
FeatureBasePanels ... Base geometry support for Arch.Panels
FeatureLocations ... Base location support
FeatureCoolant ... Support for operation coolant
FeatureCoolant ... Support for operation coolant
The base class handles all base API and forwards calls to subclasses with
an op prefix. For instance, an op is not expected to overwrite onChanged(),
@@ -140,7 +140,7 @@ class ObjectOp(object):
if FeatureCoolant & features:
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"))
@@ -194,7 +194,7 @@ class ObjectOp(object):
for op in ['OpStartDepth', 'OpFinalDepth', 'OpToolDiameter', 'CycleTime']:
if hasattr(obj, op):
obj.setEditorMode(op, 1) # read-only
obj.setEditorMode(op, 1) # read-only
if FeatureDepths & features:
if FeatureNoFinalDepth & features:
@@ -255,12 +255,12 @@ class ObjectOp(object):
def initOperation(self, obj):
'''initOperation(obj) ... implement to create additional properties.
Should be overwritten by subclasses.'''
pass # pylint: disable=unnecessary-pass
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.'''
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def opOnChanged(self, obj, prop):
'''opOnChanged(obj, prop) ... overwrite to process property changes.
@@ -269,24 +269,24 @@ class ObjectOp(object):
distinguish between assigning a different value and assigning the same
value again.
Can safely be overwritten by subclasses.'''
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def opSetDefaultValues(self, obj, job):
'''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.'''
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def opUpdateDepths(self, obj):
'''opUpdateDepths(obj) ... overwrite to implement special depths calculation.
Can safely be overwritten by subclass.'''
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def opExecute(self, obj):
'''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.'''
pass # pylint: disable=unnecessary-pass
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.
@@ -297,12 +297,11 @@ class ObjectOp(object):
def onChanged(self, obj, prop):
'''onChanged(obj, prop) ... base implementation of the FC notification framework.
Do not overwrite, overwrite opOnChanged() instead.'''
if not 'Restore' 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'''
if expr:
@@ -483,18 +482,17 @@ class ObjectOp(object):
obj.Path = path
return
if not self._setBaseAndStock(obj):
return
if FeatureCoolant & self.opFeatures(obj):
if not hasattr(obj, 'CoolantMode'):
FreeCAD.Console.PrintError("No coolant property found. Please recreate operation.")
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:
FreeCAD.Console.PrintError("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
@@ -503,9 +501,9 @@ class ObjectOp(object):
self.horizRapid = tc.HorizRapid.Value
tool = tc.Proxy.getTool(tc)
if not tool or float(tool.Diameter) == 0:
FreeCAD.Console.PrintError("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
self.radius = float(tool.Diameter) / 2
self.tool = tool
obj.OpToolDiameter = tool.Diameter
@@ -519,7 +517,7 @@ class ObjectOp(object):
if obj.Comment:
self.commandlist.append(Path.Command("(%s)" % obj.Comment))
result = self.opExecute(obj) # pylint: disable=assignment-from-no-return
result = self.opExecute(obj) # pylint: disable=assignment-from-no-return
if FeatureHeights & self.opFeatures(obj):
# Let's finish by rapid to clearance...just for safety
@@ -536,8 +534,8 @@ class ObjectOp(object):
tc = obj.ToolController
if tc is None or tc.ToolNumber == 0:
FreeCAD.Console.PrintError("No Tool Controller is selected. Tool feed rates required to calculate the cycle time.\n")
return translate('PathGui', 'Tool Error')
PathLog.error(translate("Path", "No Tool Controller selected."))
return translate('Path', 'Tool Error')
hFeedrate = tc.HorizFeed.Value
vFeedrate = tc.VertFeed.Value
@@ -545,19 +543,19 @@ class ObjectOp(object):
vRapidrate = tc.VertRapid.Value
if hFeedrate == 0 or vFeedrate == 0:
FreeCAD.Console.PrintError("Tool Controller requires feed rates. Tool feed rates required to calculate the cycle time.\n")
return translate('PathGui', 'Feedrate Error')
PathLog.warning(translate("Path", "Tool Controller feedrates required to calculate the cycle time."))
return translate('Path', 'Feedrate Error')
if hRapidrate == 0 or vRapidrate == 0:
FreeCAD.Console.PrintWarning("Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.\n")
PathLog.warning(translate("Path", "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times."))
## get the cycle time in seconds
# Get the cycle time in seconds
seconds = obj.Path.getCycleTime(hFeedrate, vFeedrate, hRapidrate, vRapidrate)
if not seconds:
return translate('PathGui', 'Cycletime Error')
## convert the cycle time to a HH:MM:SS format
return translate('Path', 'Cycletime Error')
# Convert the cycle time to a HH:MM:SS format
cycleTime = time.strftime("%H:%M:%S", time.gmtime(seconds))
return cycleTime
@@ -578,11 +576,11 @@ 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))