Merge pull request #3450 from dubstar-04/fixes/cycletime
[Path] - fixes/cycletime
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user