Download src/Mod/CAM/Path/Main/Job.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 31.3 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Main/Job.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/CAM/Path/Main/Job.py
-
curl -L -o Job.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Main/Job.py
31.3 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> * | |
| # * * | |
| # * This program is free software; you can redistribute it and/or modify * | |
| # * it under the terms of the GNU Lesser General Public License (LGPL) * | |
| # * as published by the Free Software Foundation; either version 2 of * | |
| # * the License, or (at your option) any later version. * | |
| # * for detail see the LICENCE text file. * | |
| # * * | |
| # * This program is distributed in the hope that it will be useful, * | |
| # * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| # * GNU Library General Public License for more details. * | |
| # * * | |
| # * You should have received a copy of the GNU Library General Public * | |
| # * License along with this program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| from Path.Post.Processor import PostProcessorFactory # PostProcessor, | |
| from PySide import QtCore | |
| from PySide.QtCore import QT_TRANSLATE_NOOP | |
| import FreeCAD | |
| import Path | |
| import Path.Base.SetupSheet as PathSetupSheet | |
| import Path.Base.Util as PathUtil | |
| import Path.Main.Stock as PathStock | |
| import Path.Tool.Controller as PathToolController | |
| import json | |
| import time | |
| # lazily loaded modules | |
| from lazy_loader.lazy_loader import LazyLoader | |
| Draft = LazyLoader("Draft", globals(), "Draft") | |
| if False: | |
| Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) | |
| Path.Log.trackModule(Path.Log.thisModule()) | |
| else: | |
| Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) | |
| translate = FreeCAD.Qt.translate | |
| class JobTemplate: | |
| """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 isResourceClone(obj, propLink, resourceName): | |
| if hasattr(propLink, "PathResource") and ( | |
| resourceName is None or resourceName == propLink.PathResource | |
| ): | |
| return True | |
| return False | |
| def createResourceClone(obj, orig, name, icon): | |
| clone = Draft.clone(orig) | |
| clone.Label = "%s-%s" % (name, orig.Label) | |
| clone.addProperty("App::PropertyString", "PathResource") | |
| clone.PathResource = name | |
| if clone.ViewObject: | |
| import Path.Base.Gui.IconViewProvider | |
| Path.Base.Gui.IconViewProvider.Attach(clone.ViewObject, icon) | |
| clone.ViewObject.Visibility = False | |
| clone.ViewObject.Transparency = 80 | |
| obj.Document.recompute() # necessary to create the clone shape | |
| return clone | |
| def createModelResourceClone(obj, orig): | |
| return createResourceClone(obj, orig, "Model", "BaseGeometry") | |
| class NotificationClass(QtCore.QObject): | |
| updateTC = QtCore.Signal(object, object) | |
| Notification = NotificationClass() | |
| class ObjectJob: | |
| def __init__(self, obj, models, templateFile=None): | |
| self.obj = obj | |
| self.tooltip = None | |
| self.tooltipArgs = None | |
| obj.Proxy = self | |
| obj.addProperty( | |
| "App::PropertyFile", | |
| "PostProcessorOutputFile", | |
| "Output", | |
| QT_TRANSLATE_NOOP("App::Property", "The G-code output file for this project"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "PostProcessor", | |
| "Output", | |
| QT_TRANSLATE_NOOP("App::Property", "Select the Post Processor"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyString", | |
| "PostProcessorArgs", | |
| "Output", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Arguments for the Post Processor (specific to the script)", | |
| ), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyString", | |
| "LastPostProcessDate", | |
| "Output", | |
| QT_TRANSLATE_NOOP("App::Property", "Last Time the Job was post processed"), | |
| ) | |
| obj.setEditorMode("LastPostProcessDate", 2) # Hide | |
| obj.addProperty( | |
| "App::PropertyString", | |
| "LastPostProcessOutput", | |
| "Output", | |
| QT_TRANSLATE_NOOP("App::Property", "Last Time the Job was post processed"), | |
| ) | |
| obj.setEditorMode("LastPostProcessOutput", 2) # Hide | |
| obj.addProperty( | |
| "App::PropertyString", | |
| "Description", | |
| "Path", | |
| QT_TRANSLATE_NOOP("App::Property", "An optional description for this job"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyString", | |
| "CycleTime", | |
| "Path", | |
| QT_TRANSLATE_NOOP("App::Property", "Job Cycle Time Estimation"), | |
| ) | |
| obj.setEditorMode("CycleTime", 1) # read-only | |
| obj.addProperty( | |
| "App::PropertyDistance", | |
| "GeometryTolerance", | |
| "Geometry", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "For computing Paths; smaller increases accuracy, but slows down computation", | |
| ), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyLink", | |
| "Stock", | |
| "Base", | |
| QT_TRANSLATE_NOOP("App::Property", "Solid object to be used as stock."), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyLink", | |
| "Operations", | |
| "Base", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Compound path of all operations in the order they are processed.", | |
| ), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "JobType", | |
| "Base", | |
| QT_TRANSLATE_NOOP("App::Property", "Select the Type of Job"), | |
| ) | |
| obj.setEditorMode("JobType", 2) # Hide | |
| obj.addProperty( | |
| "App::PropertyBool", | |
| "SplitOutput", | |
| "Output", | |
| QT_TRANSLATE_NOOP("App::Property", "Split output into multiple G-code files"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "OrderOutputBy", | |
| "WCS", | |
| QT_TRANSLATE_NOOP("App::Property", "If multiple WCS, order the output this way"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyStringList", | |
| "Fixtures", | |
| "WCS", | |
| QT_TRANSLATE_NOOP("App::Property", "The Work Coordinate Systems for the Job"), | |
| ) | |
| obj.Fixtures = ["G54"] | |
| for n in self.propertyEnumerations(): | |
| setattr(obj, n[0], n[1]) | |
| obj.PostProcessorOutputFile = Path.Preferences.defaultOutputFile() | |
| postProcessors = Path.Preferences.allEnabledPostProcessors() | |
| # Add empty string as a valid enumeration option | |
| if "" not in postProcessors: | |
| postProcessors = [""] + postProcessors | |
| obj.PostProcessor = postProcessors | |
| defaultPostProcessor = Path.Preferences.defaultPostProcessor() | |
| # Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed) | |
| if defaultPostProcessor in postProcessors: | |
| obj.PostProcessor = defaultPostProcessor | |
| else: | |
| obj.PostProcessor = "" | |
| obj.PostProcessorArgs = Path.Preferences.defaultPostProcessorArgs() | |
| obj.GeometryTolerance = Path.Preferences.defaultGeometryTolerance() | |
| self.setupOperations(obj) | |
| self.setupSetupSheet(obj) | |
| self.setupBaseModel(obj, models) | |
| self.setupToolTable(obj) | |
| self.setFromTemplateFile(obj, templateFile) | |
| self.setupStock(obj) | |
| def propertyEnumerations(self, dataType="data"): | |
| """propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. | |
| Args: | |
| dataType = 'data', 'raw', 'translated' | |
| Notes: | |
| 'data' is list of internal string literals used in code | |
| 'raw' is list of (translated_text, data_string) tuples | |
| 'translated' is list of translated string literals | |
| """ | |
| enums = { | |
| "OrderOutputBy": [ | |
| (translate("CAM_Job", "Fixture"), "Fixture"), | |
| (translate("CAM_Job", "Tool"), "Tool"), | |
| (translate("CAM_Job", "Operation"), "Operation"), | |
| ], | |
| "JobType": [ | |
| (translate("CAM_Job", "2D"), "2D"), | |
| (translate("CAM_Job", "2.5D"), "2.5D"), | |
| (translate("CAM_Job", "Lathe"), "Lathe"), | |
| (translate("CAM_Job", "Multiaxis"), "Multiaxis"), | |
| ], | |
| } | |
| if dataType == "raw": | |
| return enums | |
| data = list() | |
| idx = 0 if dataType == "translated" else 1 | |
| Path.Log.debug(enums) | |
| for k, v in enumerate(enums): | |
| data.append((v, [tup[idx] for tup in enums[v]])) | |
| Path.Log.debug(data) | |
| return data | |
| def setupOperations(self, obj): | |
| """setupOperations(obj)... setup the Operations group for the Job object.""" | |
| # ops = FreeCAD.ActiveDocument.addObject( | |
| # "Path::FeatureCompoundPython", "Operations" | |
| # ) | |
| ops = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Operations") | |
| if ops.ViewObject: | |
| # ops.ViewObject.Proxy = 0 | |
| ops.ViewObject.Visibility = True | |
| obj.Operations = ops | |
| obj.setEditorMode("Operations", 2) # hide | |
| obj.setEditorMode("Placement", 2) | |
| def setupSetupSheet(self, obj): | |
| if not getattr(obj, "SetupSheet", None): | |
| if not hasattr(obj, "SetupSheet"): | |
| obj.addProperty( | |
| "App::PropertyLink", | |
| "SetupSheet", | |
| "Base", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", "SetupSheet holding the settings for this job" | |
| ), | |
| ) | |
| obj.SetupSheet = PathSetupSheet.Create() | |
| if obj.SetupSheet.ViewObject: | |
| import Path.Base.Gui.IconViewProvider | |
| Path.Base.Gui.IconViewProvider.Attach(obj.SetupSheet.ViewObject, "SetupSheet") | |
| obj.SetupSheet.Label = "SetupSheet" | |
| self.setupSheet = obj.SetupSheet.Proxy | |
| def setupBaseModel(self, obj, models=None): | |
| Path.Log.track(obj.Label, models) | |
| addModels = False | |
| if not hasattr(obj, "Model"): | |
| obj.addProperty( | |
| "App::PropertyLink", | |
| "Model", | |
| "Base", | |
| QT_TRANSLATE_NOOP("App::Property", "The base objects for all operations"), | |
| ) | |
| addModels = True | |
| elif obj.Model is None: | |
| addModels = True | |
| if addModels: | |
| 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]) | |
| obj.Model = model | |
| obj.Model.Label = "Model" | |
| if hasattr(obj, "Base"): | |
| Path.Log.info("Converting Job.Base to new Job.Model for {}".format(obj.Label)) | |
| obj.Model.addObject(obj.Base) | |
| obj.Base = None | |
| obj.removeProperty("Base") | |
| def setupToolTable(self, obj): | |
| addTable = False | |
| if not hasattr(obj, "Tools"): | |
| obj.addProperty( | |
| "App::PropertyLink", | |
| "Tools", | |
| "Base", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", "Collection of all tool controllers for the job" | |
| ), | |
| ) | |
| addTable = True | |
| elif obj.Tools is None: | |
| addTable = True | |
| if addTable: | |
| toolTable = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Tools") | |
| toolTable.Label = "Tools" | |
| if toolTable.ViewObject: | |
| toolTable.ViewObject.Visibility = False | |
| if hasattr(obj, "ToolController"): | |
| toolTable.addObjects(obj.ToolController) | |
| obj.removeProperty("ToolController") | |
| obj.Tools = toolTable | |
| def setupStock(self, obj): | |
| """setupStock(obj)... setup the Stock for the Job object.""" | |
| if not obj.Stock: | |
| stockTemplate = Path.Preferences.defaultStockTemplate() | |
| if stockTemplate: | |
| obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate)) | |
| if not obj.Stock: | |
| obj.Stock = PathStock.CreateFromBase(obj) | |
| if obj.Stock.ViewObject: | |
| obj.Stock.ViewObject.Visibility = False | |
| def removeBase(self, obj, base, removeFromModel): | |
| if isResourceClone(obj, base, None): | |
| PathUtil.clearExpressionEngine(base) | |
| if removeFromModel: | |
| obj.Model.removeObject(base) | |
| obj.Document.removeObject(base.Name) | |
| def modelBoundBox(self, obj): | |
| 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.""" | |
| Path.Log.track(obj.Label, arg2) | |
| doc = obj.Document | |
| if getattr(obj, "Operations", None): | |
| # the first to tear down are the ops, they depend on other resources | |
| Path.Log.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, ()) | |
| ): | |
| PathUtil.clearExpressionEngine(op) | |
| doc.removeObject(op.Name) | |
| obj.Operations.Group = [] | |
| doc.removeObject(obj.Operations.Name) | |
| obj.Operations = None | |
| # stock could depend on Model, so delete it first | |
| if getattr(obj, "Stock", None): | |
| Path.Log.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): | |
| for base in obj.Model.Group: | |
| Path.Log.debug("taking down base %s" % base.Label) | |
| self.removeBase(obj, base, False) | |
| obj.Model.Group = [] | |
| doc.removeObject(obj.Model.Name) | |
| obj.Model = None | |
| # Tool controllers might refer to either legacy tool or toolbit | |
| if getattr(obj, "Tools", None): | |
| Path.Log.debug("taking down tool controller") | |
| for tc in obj.Tools.Group: | |
| if hasattr(tc.Tool, "BitBody") and tc.Tool.BitBody: | |
| tc.Tool.BitBody.removeObjectsFromDocument() | |
| doc.removeObject(tc.Tool.BitBody.Name) | |
| if hasattr(tc.Tool, "Proxy"): | |
| PathUtil.clearExpressionEngine(tc.Tool) | |
| doc.removeObject(tc.Tool.Name) | |
| PathUtil.clearExpressionEngine(tc) | |
| tc.Proxy.onDelete(tc) | |
| doc.removeObject(tc.Name) | |
| obj.Tools.Group = [] | |
| doc.removeObject(obj.Tools.Name) | |
| obj.Tools = None | |
| # SetupSheet | |
| if getattr(obj, "SetupSheet", None): | |
| PathUtil.clearExpressionEngine(obj.SetupSheet) | |
| doc.removeObject(obj.SetupSheet.Name) | |
| obj.SetupSheet = None | |
| return True | |
| def fixupOperations(self, obj): | |
| if getattr(obj.Operations, "ViewObject", None): | |
| try: | |
| obj.Operations.ViewObject.DisplayMode | |
| except Exception: | |
| name = obj.Operations.Name | |
| label = obj.Operations.Label | |
| ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations") | |
| ops.ViewObject.Proxy = 0 | |
| ops.Group = obj.Operations.Group | |
| obj.Operations.Group = [] | |
| obj.Operations = ops | |
| FreeCAD.ActiveDocument.removeObject(name) | |
| if label == "Unnamed": | |
| ops.Label = "Operations" | |
| else: | |
| ops.Label = label | |
| def onDocumentRestored(self, obj): | |
| self.setupBaseModel(obj) | |
| self.fixupOperations(obj) | |
| self.setupSetupSheet(obj) | |
| self.setupToolTable(obj) | |
| self.integrityCheck(obj) | |
| obj.setEditorMode("Operations", 2) # hide | |
| obj.setEditorMode("Placement", 2) | |
| if hasattr(obj, "Path"): | |
| obj.Path = Path.Path() | |
| if not hasattr(obj, "CycleTime"): | |
| obj.addProperty( | |
| "App::PropertyString", | |
| "CycleTime", | |
| "Path", | |
| QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"), | |
| ) | |
| obj.setEditorMode("CycleTime", 1) # read-only | |
| if not hasattr(obj, "Fixtures"): | |
| obj.addProperty( | |
| "App::PropertyStringList", | |
| "Fixtures", | |
| "WCS", | |
| QT_TRANSLATE_NOOP("App::Property", "The Work Coordinate Systems for the Job"), | |
| ) | |
| obj.Fixtures = ["G54"] | |
| if not hasattr(obj, "OrderOutputBy"): | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "OrderOutputBy", | |
| "WCS", | |
| QT_TRANSLATE_NOOP("App::Property", "If multiple WCS, order the output this way"), | |
| ) | |
| obj.OrderOutputBy = ["Fixture", "Tool", "Operation"] | |
| if not hasattr(obj, "SplitOutput"): | |
| obj.addProperty( | |
| "App::PropertyBool", | |
| "SplitOutput", | |
| "Output", | |
| QT_TRANSLATE_NOOP("App::Property", "Split output into multiple G-code files"), | |
| ) | |
| obj.SplitOutput = False | |
| if not hasattr(obj, "JobType"): | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "JobType", | |
| "Base", | |
| QT_TRANSLATE_NOOP("App::Property", "Select the type of Job"), | |
| ) | |
| obj.setEditorMode("JobType", 2) # Hide | |
| for n in self.propertyEnumerations(): | |
| setattr(obj, n[0], n[1]) | |
| def onChanged(self, obj, prop): | |
| if prop == "PostProcessor" and obj.PostProcessor: | |
| processor = PostProcessorFactory.get_post_processor(obj, obj.PostProcessor) | |
| self.tooltip = processor.tooltip | |
| 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"): | |
| if hasattr(base, "Objects") and base.Objects: | |
| return base.Objects[0] | |
| return base | |
| def baseObjects(self, obj): | |
| """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.""" | |
| if isResourceClone(obj, base, None): | |
| return base | |
| for b in obj.Model.Group: | |
| if base == b.Objects[0]: | |
| return b | |
| 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.""" | |
| tcs = [] | |
| if template: | |
| with open(str(template), "rb") as fp: | |
| attrs = json.load(fp) | |
| if attrs.get(JobTemplate.Version) and 1 == int(attrs[JobTemplate.Version]): | |
| attrs = self.setupSheet.decodeTemplateAttributes(attrs) | |
| if attrs.get(JobTemplate.SetupSheet): | |
| self.setupSheet.setFromTemplate(attrs[JobTemplate.SetupSheet]) | |
| if 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 = "" | |
| if attrs.get(JobTemplate.PostProcessorOutputFile): | |
| obj.PostProcessorOutputFile = attrs.get(JobTemplate.PostProcessorOutputFile) | |
| if attrs.get(JobTemplate.Description): | |
| obj.Description = attrs.get(JobTemplate.Description) | |
| if attrs.get(JobTemplate.ToolController): | |
| for tc in attrs.get(JobTemplate.ToolController): | |
| ctrl = PathToolController.FromTemplate(tc) | |
| if ctrl: | |
| tcs.append(ctrl) | |
| else: | |
| Path.Log.debug(f"skipping TC {tc['name']}") | |
| if 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] | |
| if attrs.get(JobTemplate.OrderOutputBy): | |
| obj.OrderOutputBy = attrs.get(JobTemplate.OrderOutputBy) | |
| if attrs.get(JobTemplate.SplitOutput): | |
| obj.SplitOutput = attrs.get(JobTemplate.SplitOutput) | |
| Path.Log.debug("setting tool controllers (%d)" % len(tcs)) | |
| if tcs: | |
| obj.Tools.Group = tcs | |
| else: | |
| Path.Log.error( | |
| "Unsupported PathJob template version {}".format(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.""" | |
| attrs = {} | |
| attrs[JobTemplate.Version] = 1 | |
| if obj.PostProcessor: | |
| attrs[JobTemplate.PostProcessor] = obj.PostProcessor | |
| attrs[JobTemplate.PostProcessorArgs] = obj.PostProcessorArgs | |
| attrs[JobTemplate.Fixtures] = [{f: True} for f in obj.Fixtures] | |
| attrs[JobTemplate.OrderOutputBy] = obj.OrderOutputBy | |
| attrs[JobTemplate.SplitOutput] = obj.SplitOutput | |
| if obj.PostProcessorOutputFile: | |
| attrs[JobTemplate.PostProcessorOutputFile] = obj.PostProcessorOutputFile | |
| attrs[JobTemplate.GeometryTolerance] = str(obj.GeometryTolerance.Value) | |
| if obj.Description: | |
| attrs[JobTemplate.Description] = obj.Description | |
| return attrs | |
| def dumps(self): | |
| return None | |
| def loads(self, state): | |
| for obj in FreeCAD.ActiveDocument.Objects: | |
| if hasattr(obj, "Proxy") and obj.Proxy == self: | |
| self.obj = obj | |
| break | |
| return None | |
| def execute(self, obj): | |
| if getattr(obj, "Operations", None): | |
| # obj.Path = obj.Operations.Path | |
| self.getCycleTime() | |
| if hasattr(obj, "PathChanged"): | |
| obj.PathChanged = True | |
| def getCycleTime(self): | |
| seconds = 0 | |
| if len(self.obj.Operations.Group): | |
| for op in self.obj.Operations.Group: | |
| # Skip inactive operations | |
| if PathUtil.opProperty(op, "Active") is False: | |
| continue | |
| # Skip operations that don't have a cycletime attribute | |
| if PathUtil.opProperty(op, "CycleTime") is None: | |
| continue | |
| 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(":"))) | |
| ) | |
| except Exception: | |
| continue | |
| if opCycleTime > 0: | |
| seconds = seconds + opCycleTime | |
| cycleTimeString = time.strftime("%H:%M:%S", time.gmtime(seconds)) | |
| self.obj.CycleTime = cycleTimeString | |
| def addOperation(self, op, before=None, removeBefore=False): | |
| group = self.obj.Operations.Group | |
| if op not in group: | |
| if before: | |
| try: | |
| group.insert(group.index(before), op) | |
| if removeBefore: | |
| group.remove(before) | |
| except Exception as e: | |
| Path.Log.error(e) | |
| group.append(op) | |
| else: | |
| group.append(op) | |
| self.obj.Operations.Group = group | |
| # op.Path.Center = self.obj.Operations.Path.Center | |
| def nextToolNumber(self): | |
| # returns the next available toolnumber in the job | |
| group = self.obj.Tools.Group | |
| if len(group) > 0: | |
| return sorted([t.ToolNumber for t in group])[-1] + 1 | |
| else: | |
| return 1 | |
| def addToolController(self, tc): | |
| group = self.obj.Tools.Group | |
| Path.Log.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.obj.SetupSheet.Proxy.expressionReference(), | |
| PathSetupSheet.Template.VertRapid, | |
| ), | |
| ) | |
| tc.setExpression( | |
| "HorizRapid", | |
| "%s.%s" | |
| % ( | |
| self.obj.SetupSheet.Proxy.expressionReference(), | |
| PathSetupSheet.Template.HorizRapid, | |
| ), | |
| ) | |
| self.obj.Tools.addObject(tc) | |
| Notification.updateTC.emit(self.obj, tc) | |
| def allOperations(self): | |
| ops = [] | |
| def collectBaseOps(op): | |
| if hasattr(op, "TypeId"): | |
| if op.TypeId == "Path::FeaturePython": | |
| ops.append(op) | |
| if hasattr(op, "Base"): | |
| collectBaseOps(op.Base) | |
| 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): | |
| for op in self.obj.Operations.Group: | |
| collectBaseOps(op) | |
| return ops | |
| def setCenterOfRotation(self, center): | |
| if center != self.obj.Path.Center: | |
| self.obj.Path.Center = center | |
| for op in self.allOperations(): | |
| op.Path.Center = center | |
| def integrityCheck(self, job): | |
| """integrityCheck(job)... Return True if job has all expected children objects. Attempts to restore any missing children.""" | |
| suffix = "" | |
| if len(job.Name) > 3: | |
| suffix = job.Name[3:] | |
| def errorMessage(grp, job): | |
| Path.Log.error("{} corrupt in {} job.".format(grp, job.Name)) | |
| if not job.Operations: | |
| self.setupOperations(job) | |
| job.Operations.Label = "Operations" + suffix | |
| if not job.Operations: | |
| errorMessage("Operations", job) | |
| return False | |
| if not job.SetupSheet: | |
| self.setupSetupSheet(job) | |
| job.SetupSheet.Label = "SetupSheet" + suffix | |
| if not job.SetupSheet: | |
| errorMessage("SetupSheet", job) | |
| return False | |
| if not job.Model: | |
| self.setupBaseModel(job) | |
| job.Model.Label = "Model" + suffix | |
| if not job.Model: | |
| errorMessage("Model", job) | |
| return False | |
| if not job.Stock: | |
| self.setupStock(job) | |
| job.Stock.Label = "Stock" + suffix | |
| if not job.Stock: | |
| errorMessage("Stock", job) | |
| return False | |
| if not job.Tools: | |
| self.setupToolTable(job) | |
| job.Tools.Label = "Tools" + suffix | |
| if not job.Tools: | |
| errorMessage("Tools", job) | |
| return False | |
| return True | |
| 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, | |
| ) | |
| def isBaseCandidate(cls, 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.""" | |
| 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): | |
| """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: | |
| models.append(FreeCAD.ActiveDocument.getObject(baseName)) | |
| else: | |
| models = base | |
| obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) | |
| obj.addExtension("App::GroupExtensionPython") | |
| obj.Proxy = ObjectJob(obj, models, templateFile) | |
| return obj | |