From 01ac1502124d8605126e6ec3e690361af58bda06 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 27 Jul 2020 10:26:22 -0500 Subject: [PATCH 1/4] Working html report with asciidoctor --- src/Mod/Path/PathScripts/PathJob.py | 14 +- src/Mod/Path/PathScripts/PathPost.py | 15 +- src/Mod/Path/PathScripts/PathSanity.py | 721 +++++++++++++++++++++---- 3 files changed, 642 insertions(+), 108 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 2f97fc628a..adc0adfee6 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -32,14 +32,14 @@ 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') -from PathScripts.PathPostProcessor import PostProcessor -from PySide import QtCore PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) @@ -103,6 +103,10 @@ class ObjectJob: 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")) @@ -226,7 +230,7 @@ class ObjectJob: # Tool controllers might refer to either legacy tool or toolbit PathLog.debug('taking down tool controller') for tc in obj.ToolController: - if hasattr(tc.Tool,"Proxy"): + if hasattr(tc.Tool, "Proxy"): PathUtil.clearExpressionEngine(tc.Tool) doc.removeObject(tc.Tool.Name) PathUtil.clearExpressionEngine(tc) @@ -378,7 +382,7 @@ class ObjectJob: 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: + except Exception: continue if opCycleTime > 0: @@ -457,7 +461,7 @@ def Instances(): 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]): + if isinstance(base[0], str): models = [] for baseName in base: models.append(FreeCAD.ActiveDocument.getObject(baseName)) diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 327a3eee8f..bfc0f35ea4 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -37,7 +37,7 @@ import os from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore, QtGui - +from datetime import datetime LOG_MODULE = PathLog.thisModule() @@ -210,9 +210,9 @@ class CommandPathPost: print("post: %s(%s, %s)" % (postname, filename, postArgs)) processor = PostProcessor.load(postname) gcode = processor.export(objs, filename, postArgs) - return (False, gcode) + return (False, gcode, filename) else: - return (True, '') + return (True, '', filename) def Activated(self): PathLog.track() @@ -391,17 +391,22 @@ class CommandPathPost: rc = '' # pylint: disable=unused-variable if split: for slist in postlist: - (fail, rc) = self.exportObjectsWith(slist, job) + (fail, rc, filename) = self.exportObjectsWith(slist, job) else: finalpostlist = [item for slist in postlist for item in slist] - (fail, rc) = self.exportObjectsWith(finalpostlist, job) + (fail, rc, filename) = self.exportObjectsWith(finalpostlist, job) self.subpart = 1 if fail: FreeCAD.ActiveDocument.abortTransaction() else: + if hasattr(job, "LastPostProcessDate"): + job.LastPostProcessDate = str(datetime.now()) + if hasattr(job, "LastPostProcessOutput"): + job.LastPostProcessOutput = filename FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 5c7e6235d0..4df987fec5 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -19,146 +19,671 @@ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * -# ***************************************************************************/ +# *************************************************************************** -'''This file has utilities for checking and catching common errors in FreeCAD +''' +This file has utilities for checking and catching common errors in FreeCAD Path projects. Ideally, the user could execute these utilities from an icon -to make sure tools are selected and configured and defaults have been revised''' +to make sure tools are selected and configured and defaults have been revised +''' from __future__ import print_function -from PySide import QtCore +from PySide import QtCore, QtGui import FreeCAD import FreeCADGui import PathScripts import PathScripts.PathLog as PathLog -# import PathScripts.PathCollision as PC +import PathScripts.PathUtil as PathUtil +import PathScripts.PathPreferences as PathPreferences +from collections import Counter +from datetime import datetime +import os +import webbrowser # Qt translation handling + + def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + LOG_MODULE = 'PathSanity' PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) -#PathLog.trackModule('PathSanity') +# PathLog.trackModule('PathSanity') class CommandPathSanity: - baseobj=None + baseobj = None + outputpath = PathPreferences.defaultOutputFile() + squawkData = {"items": []} def GetResources(self): - return {'Pixmap' : 'Path-Sanity', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Sanity","Check the Path project for common errors"), + return {'Pixmap': 'Path-Sanity', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Sanity", + "Check the path job for common errors"), 'Accel': "P, S", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Sanity","Check the Path Project for common errors")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Sanity", + "Check the path job for common errors")} def IsActive(self): obj = FreeCADGui.Selection.getSelectionEx()[0].Object - if hasattr(obj, 'Operations') and hasattr(obj, 'ToolController'): - return True - return False + return isinstance(obj.Proxy, PathScripts.PathJob.ObjectJob) def Activated(self): # if everything is ok, execute obj = FreeCADGui.Selection.getSelectionEx()[0].Object - self.baseobj = obj.Base - if self.baseobj is None: - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "The Job has no selected Base object.")+"\n") - return - self.__review(obj) + data = self.__summarize(obj) + html = self.__report(data) + if html is not None: + print(html) + webbrowser.open(html) - def __review(self, obj): - "checks the selected job for common errors" - clean = True + def __makePicture(self, obj, imageName): + """ + Makes an image of the target object. Returns filename + """ - # if obj.X_Max == obj.X_Min or obj.Y_Max == obj.Y_Min: - # FreeCAD.Console.PrintWarning(translate("Path_Sanity", "It appears the machine limits haven't been set. Not able to check path extents.")+"\n") + # remember vis state of document objects. Turn off all but target + visible = [o for o in obj.Document.Objects if o.Visibility] + for o in obj.Document.Objects: + o.Visibility = False + obj.Visibility = True - if obj.PostProcessor == '': - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Postprocessor has not been selected.")+"\n") - clean = False + aview = FreeCADGui.activeDocument().activeView() + aview.setAnimationEnabled(False) - if obj.PostProcessorOutputFile == '': - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "No output file is named. You'll be prompted during postprocessing.")+"\n") - clean = False + mw = FreeCADGui.getMainWindow() + mdi = mw.findChild(QtGui.QMdiArea) + view = mdi.activeSubWindow() + view.showNormal() + view.resize(320, 320) - for tc in obj.ToolController: - PathLog.info("Checking: {}.{}".format(obj.Label, tc.Label)) - clean &= self.__checkTC(tc) + imagepath = self.outputpath + '/{}'.format(imageName) - for op in obj.Operations.Group: - PathLog.info("Checking: {}.{}".format(obj.Label, op.Label)) + aview.viewIsometric() + FreeCADGui.Selection.clearSelection() + FreeCADGui.SendMsgToActiveView("PerspectiveCamera") + FreeCADGui.Selection.addSelection(obj) + FreeCADGui.SendMsgToActiveView("ViewSelection") + FreeCADGui.Selection.clearSelection() + aview.saveImage(imagepath + '.png', 320, 320, 'Current') + aview.saveImage(imagepath + '_t.png', 320, 320, 'Transparent') - if isinstance(op.Proxy, PathScripts.PathProfileContour.ObjectContour): - if op.Active: - # simobj = op.Proxy.execute(op, getsim=True) - # if simobj is not None: - # print ('collision detected') - # PC.getCollisionObject(self.baseobj, simobj) - # clean = False - pass + view.showMaximized() - if isinstance(op.Proxy, PathScripts.PathProfileFaces.ObjectProfile): - if op.Active: - # simobj = op.Proxy.execute(op, getsim=True) - # if simobj is not None: - # print ('collision detected') - # PC.getCollisionObject(self.baseobj, simobj) - # clean = False - pass + aview.setAnimationEnabled(True) - if isinstance(op.Proxy, PathScripts.PathProfileEdges.ObjectProfile): - if op.Active: - # simobj = op.Proxy.execute(op, getsim=True) - # if simobj is not None: - # print ('collision detected') - # PC.getCollisionObject(self.baseobj, simobj) - # clean = False - pass + # Restore visibility + obj.Visibility = False + for o in visible: + o.Visibility = True - if isinstance(op.Proxy, PathScripts.PathPocket.ObjectPocket): - if op.Active: - # simobj = op.Proxy.execute(op, getsim=True) - # if simobj is not None: - # print ('collision detected') - # PC.getCollisionObject(self.baseobj, simobj) - # clean = False - pass + # with open(imagepath, 'wb') as fd: + # fd.write(imagedata) + # fd.close() - if isinstance(op.Proxy, PathScripts.PathPocketShape.ObjectPocket): - if op.Active: - # simobj = op.Proxy.execute(op, getsim=True) - # if simobj is not None: - # print ('collision detected') - # PC.getCollisionObject(self.baseobj, simobj) - # clean = False - pass + return "{}_t.png".format(imagepath) - if not any(op.Active for op in obj.Operations.Group): #no active operations - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "No active operations was found. Post processing will not result in any tooling.")) - clean = False + def __report(self, data): + """ + generates an asciidoc file with the report information + """ - if len(obj.ToolController) == 0: #need at least one active TC - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Tool Controller was not found. Default values are used which is dangerous. Please add a Tool Controller.")+"\n") - clean = False + reportTemplate = """ += Setup Report for FreeCAD Job: {jobname} +:toc: +:icons: font +:imagesdir: "" +:data-uri: - if clean: - FreeCAD.Console.PrintMessage(translate("Path_Sanity", "No issues detected, {} has passed basic sanity check.").format(obj.Label)) +== Part Information + +|=== +{infoTable} +|=== + + +== Run Summary + +|=== +{runTable} +|=== + +== Rough Stock + +|=== +{stockTable} +|=== + +== Tool Data + +{toolTables} + +== Output (Gcode) + +|=== +{outTable} +|=== + +== Coolant + +|=== +{coolantTable} +|=== + +== Fixtures and Workholding + +|=== +{fixtureTable} +|=== + +== Squawks + +|=== +{squawkTable} +|=== + +""" + # Generate the markup for the Part Information Section + + infoTable = "" + + PartLabel = translate("Path_Sanity", "Base Object(s)") + SequenceLabel = translate("Path_Sanity", "Job Sequence") + JobTypeLabel = translate("Path_Sanity", "Job Type") + CADLabel = translate("Path_Sanity", "CAD File Name") + LastSaveLabel = translate("Path_Sanity", "Last Save Date") + CustomerLabel = translate("Path_Sanity", "Customer") + DesignerLabel = translate("Path_Sanity", "Designer") + + d = data['designData'] + b = data['baseData'] + + jobname = d['JobLabel'] + + basestable = "!===\n" + for key, val in b['bases'].items(): + basestable += "! " + key + " ! " + val + "\n" + + basestable += "!===" + + infoTable += "|*" + PartLabel + "* a| " + basestable + " .7+a|" + \ + "image::" + b['baseimage'] + "[" + PartLabel + "]\n" + infoTable += "|*" + SequenceLabel + "*|" + d['Sequence'] + infoTable += "|*" + JobTypeLabel + "*|" + d['JobType'] + infoTable += "|*" + CADLabel + "*|" + d['FileName'] + infoTable += "|*" + LastSaveLabel + "*|" + d['LastModifiedDate'] + infoTable += "|*" + CustomerLabel + "*|" + d['Customer'] + infoTable += "|*" + DesignerLabel + "*|" + d['Designer'] + + # Generate the markup for the Run Summary Section + + runTable = "" + opLabel = translate("Path_Sanity", "Operation") + zMinLabel = translate("Path_Sanity", "Minimum Z Height") + zMaxLabel = translate("Path_Sanity", "Maximum Z Height") + cycleTimeLabel = translate("Path_Sanity", "Cycle Time") + jobTotalLabel = translate("Path_Sanity", "TOTAL JOB") + + d = data['runData'] + + runTable += "|*" + opLabel + "*|*" + zMinLabel + "*|*" + zMaxLabel + \ + "*|*" + cycleTimeLabel + "*\n" + + for i in d['items']: + runTable += "|{}".format(i['opName']) + runTable += "|{}".format(i['minZ']) + runTable += "|{}".format(i['maxZ']) + runTable += "|{}".format(i['cycleTime']) + + runTable += "|*" + jobTotalLabel + "* |{} |{} |{}".format( + d['jobMinZ'], + d['jobMaxZ'], + d['cycletotal']) + + # Generate the markup for the Tool Data Section + toolTables = "" + + toolLabel = translate("Path_Sanity", "Tool Number") + descriptionLabel = translate("Path_Sanity", "Description") + manufLabel = translate("Path_Sanity", "Manufacturer") + partNumberLabel = translate("Path_Sanity", "Part Number") + urlLabel = translate("Path_Sanity", "URL") + inspectionNotesLabel = translate("Path_Sanity", "Inspection Notes") + opLabel = translate("Path_Sanity", "Operation") + tcLabel = translate("Path_Sanity", "Tool Controller") + feedLabel = translate("Path_Sanity", "Feed Rate") + speedLabel = translate("Path_Sanity", "Spindle Speed") + shapeLabel = translate("Path_Sanity", "Tool Shape") + diameterLabel = translate("Path_Sanity", "Tool Diameter") + + d = data['toolData'] + + for key, value in d.items(): + toolTables += "=== {}: T{}\n".format(toolLabel, key) + + toolTables += "|===\n" + + # toolTables += "|*" + toolLabel + "*| T" + key + " .2+a|" + "image::" + value['imagepath'] + "[" + key + "]|\n" + + toolTables += "|*" + descriptionLabel + "*|" + value['description'] + " a|" + "image::" + value['imagepath'] + "[" + key + "]\n" + toolTables += "|*" + manufLabel + "* 2+|" + value['manufacturer'] + "\n" + toolTables += "|*" + partNumberLabel + "* 2+|" + value['partNumber'] + "\n" + toolTables += "|*" + urlLabel + "* 2+|" + value['url'] + "\n" + toolTables += "|*" + inspectionNotesLabel + "* 2+|" + value['inspectionNotes'] + "\n" + toolTables += "|*" + shapeLabel + "* 2+|" + value['shape'] + "\n" + toolTables += "|*" + diameterLabel + "* 2+|" + value['diameter'] + "\n" + toolTables += "|===\n" + + toolTables += "|===\n" + toolTables += "|*" + opLabel + "*|*" + tcLabel + "*|*" + feedLabel + "*|*" + speedLabel + "*\n" + for o in value['ops']: + toolTables += "|" + o['Operation'] + "|" + o['ToolController'] + "|" + o['Feed'] + "|" + o['Speed'] + "\n" + toolTables += "|===\n" + + toolTables += "\n" + + # Generate the markup for the Rough Stock Section + stockTable = "" + xDimLabel = translate("Path_Sanity", "X Size") + yDimLabel = translate("Path_Sanity", "Y Size") + zDimLabel = translate("Path_Sanity", "Z Size") + materialLabel = translate("Path_Sanity", "Material") + + d = data['stockData'] + + stockTable += "|*" + materialLabel + "*|" + d['material'] + \ + " .4+a|" + "image::" + d['stockImage'] + "[stock]\n" + stockTable += "|*" + xDimLabel + "*|" + d['xLen'] + stockTable += "|*" + yDimLabel + "*|" + d['yLen'] + stockTable += "|*" + zDimLabel + "*|" + d['zLen'] + + # Generate the markup for the Fixture Section + + fixtureTable = "" + offsetsLabel = translate("Path_Sanity", "Work Offsets") + orderByLabel = translate("Path_Sanity", "Order By") + datumLabel = translate("Path_Sanity", "Part Datum") + + d = data['fixtureData'] + + fixtureTable += "|*" + offsetsLabel + "*|" + d['fixtures'] + "\n" + fixtureTable += "|*" + orderByLabel + "*|" + d['orderBy'] + fixtureTable += "|*" + datumLabel + "* a|image::" + d['datumImage'] + "[]" + + # Generate the markup for the Coolant Section + + coolantTable = "" + + opLabel = translate("Path_Sanity", "Operation") + coolantLabel = translate("Path_Sanity", "Coolant Mode") + + d = data['coolantData']['items'] + + coolantTable += "|*" + opLabel + "*|*" + coolantLabel + "*\n" + + for i in d: + coolantTable += "|" + i['opName'] + coolantTable += "|" + i['CoolantMode'] + + # Generate the markup for the Output Section + + outTable = "" + d = data['outputData'] + + gcodeFileLabel = translate("Path_Sanity", "Gcode File") + lastpostLabel = translate("Path_Sanity", "Last Post Process Date") + stopsLabel = translate("Path_Sanity", "Stops") + programmerLabel = translate("Path_Sanity", "Programmer") + machineLabel = translate("Path_Sanity", "Machine") + postLabel = translate("Path_Sanity", "Postprocessor") + flagsLabel = translate("Path_Sanity", "Post Processor Flags") + fileSizeLabel = translate("Path_Sanity", "File Size (kbs)") + lineCountLabel = translate("Path_Sanity", "Line Count") + + outTable += "|*" + gcodeFileLabel + "*|" + d['lastgcodefile'] + "\n" + outTable += "|*" + lastpostLabel + "*|" + d['lastpostprocess'] + "\n" + outTable += "|*" + stopsLabel + "*|" + d['optionalstops'] + "\n" + outTable += "|*" + programmerLabel + "*|" + d['programmer'] + "\n" + outTable += "|*" + machineLabel + "*|" + d['machine'] + "\n" + outTable += "|*" + postLabel + "*|" + d['postprocessor'] + "\n" + outTable += "|*" + flagsLabel + "*|" + d['postprocessorFlags'] + "\n" + outTable += "|*" + fileSizeLabel + "*|" + d['filesize'] + "\n" + outTable += "|*" + lineCountLabel + "*|" + d['linecount'] + "\n" + + # Generate the markup for the Squawk Section + + noteLabel = translate("Path_Sanity", "Note") + operatorLabel = translate("Path_Sanity", "Operator") + dateLabel = translate("Path_Sanity", "Date") + + squawkTable = "" + squawkTable += "|*" + noteLabel + "*|*" + operatorLabel + "*|*" + dateLabel + "*\n" + + d = data['squawkData'] + for i in d['items']: + squawkTable += "a|{}: {}".format(i['squawkType'], i['Note']) + squawkTable += "|{}".format(i['Operator']) + squawkTable += "|{}".format(i['Date']) + squawkTable += "\n" + + # merge template and custom markup + + report = reportTemplate.format( + jobname=jobname, + infoTable=infoTable, + runTable=runTable, + toolTables=toolTables, + stockTable=stockTable, + fixtureTable=fixtureTable, + outTable=outTable, + coolantTable=coolantTable, + squawkTable=squawkTable) + + # Save the report + + reportraw = self.outputpath + '/setupreport.asciidoc' + reporthtml = self.outputpath + '/setupreport.html' + with open(reportraw, 'w') as fd: + fd.write(report) + fd.close() + + try: + result = os.system('asciidoctor {} -o {}'.format(reportraw, reporthtml)) + if str(result) == "32512": + print('asciidoctor not found') + reporthtml = None + + except Exception as e: + print(e) + + return reporthtml + + def __summarize(self, obj): + """ + Top level function to summarize information for the report + Returns a dictionary of sections + """ + data = {} + data['baseData'] = self.__baseObjectData(obj) + data['designData'] = self.__designData(obj) + data['toolData'] = self.__toolData(obj) + data['runData'] = self.__runData(obj) + data['coolantData'] = self.__coolantData(obj) + data['outputData'] = self.__outputData(obj) + data['fixtureData'] = self.__fixtureData(obj) + data['stockData'] = self.__stockData(obj) + data['squawkData'] = self.squawkData + return data + + def squawk(self, operator, note, date=datetime.now(), squawkType="NOTE"): + squawkType = squawkType if squawkType in ["NOTE", "WARNING", "ERROR", "TIP"] else "NOTE" + + self.squawkData['items'].append({"Date": str(date), + "Operator": operator, + "Note": note, + "squawkType": squawkType}) + + def __baseObjectData(self, obj): + data = {} + try: + bases = {} + for name, count in \ + PathUtil.keyValueIter(Counter([obj.Proxy.baseObject(obj, + o).Label for o in obj.Model.Group])): + bases[name] = str(count) + + data['baseimage'] = self.__makePicture(obj.Model, "baseimage") + data['bases'] = bases + + except Exception as e: + data['errors'] = e + + return data + + def __designData(self, obj): + """ + Returns header information about the design document + Returns information about issues and concerns (squawks) + """ + + data = {} + try: + data['FileName'] = obj.Document.FileName + data['LastModifiedDate'] = str(obj.Document.LastModifiedDate) + data['Customer'] = obj.Document.Company + data['Designer'] = obj.Document.LastModifiedBy + data['JobNotes'] = obj.Description + data['JobLabel'] = obj.Label + + n = 0 + m = 0 + for i in obj.Document.Objects: + if hasattr(i, "Proxy"): + if isinstance(i.Proxy, PathScripts.PathJob.ObjectJob): + m += 1 + if i is obj: + n = m + data['Sequence'] = "{} of {}".format(n, m) + data['JobType'] = "2.5D Milling" # improve after job types added + + except Exception as e: + data['errors'] = e + + return data + + def __toolData(self, obj): + """ + Returns information about the tools used in the job, and associated + toolcontrollers + Returns information about issues and problems with the tools (squawks) + """ + + data = {} + + try: + for TC in obj.ToolController: + tooldata = data.setdefault(str(TC.ToolNumber), {}) + bitshape = tooldata.setdefault('BitShape', "") + if bitshape not in ["", TC.Tool.BitShape]: + self.squawk("PathSanity", + "Tool number {} used by multiple tools".format(TC.ToolNumber), + squawkType="ERROR") + tooldata['bitShape'] = TC.Tool.BitShape + tooldata['description'] = TC.Tool.Label + tooldata['manufacturer'] = "" + tooldata['url'] = "" + tooldata['inspectionNotes'] = "" + tooldata['diameter'] = str(TC.Tool.Diameter) + tooldata['shape'] = TC.Tool.ShapeName + + tooldata['partNumber'] = "" + imagedata = TC.Tool.Proxy.getBitThumbnail(TC.Tool) + imagepath = '{}/T{}.png'.format(self.outputpath, TC.ToolNumber) + tooldata['feedrate'] = str(TC.HorizFeed) + if TC.HorizFeed.Value == 0.0: + self.squawk("PathSanity", + "Tool Controller '{}' has no feedrate".format(TC.Label), + squawkType="WARNING") + + tooldata['spindlespeed'] = str(TC.SpindleSpeed) + if TC.SpindleSpeed == 0.0: + self.squawk("PathSanity", + "Tool Controller '{}' has no spindlespeed".format(TC.Label), + squawkType="WARNING") + + with open(imagepath, 'wb') as fd: + fd.write(imagedata) + fd.close() + tooldata['imagepath'] = imagepath + + used = False + for op in obj.Operations.Group: + if op.ToolController is TC: + used = True + tooldata.setdefault('ops', []).append( + {"Operation": op.Label, + "ToolController": TC.Name, + "Feed": str(TC.HorizFeed), + "Speed": str(TC.SpindleSpeed)}) + + if used is False: + tooldata.setdefault('ops', []) + self.squawk("PathSanity", + "Tool Controller '{}' is not used".format(TC.Label), + squawkType="WARNING") + + except Exception as e: + data['errors'] = e + + return data + + def __runData(self, obj): + data = {} + try: + data['cycletotal'] = str(obj.CycleTime) + data['jobMinZ'] = FreeCAD.Units.Quantity(obj.Path.BoundBox.ZMin, + FreeCAD.Units.Length).UserString + data['jobMaxZ'] = FreeCAD.Units.Quantity(obj.Path.BoundBox.ZMax, + FreeCAD.Units.Length).UserString + + data['items'] = [] + for op in obj.Operations.Group: + oplabel = op.Label if op.Active else op.Label + " (INACTIVE)" + opdata = {"opName": oplabel, + "minZ": FreeCAD.Units.Quantity(op.Path.BoundBox.ZMin, + FreeCAD.Units.Length).UserString, + "maxZ": FreeCAD.Units.Quantity(op.Path.BoundBox.ZMax, + FreeCAD.Units.Length).UserString, + #"maxZ": str(op.Path.BoundBox.ZMax), + "cycleTime": str(op.CycleTime)} + data['items'].append(opdata) + + except Exception as e: + data['errors'] = e + + return data + + def __stockData(self, obj): + data = {} + + try: + bb = obj.Stock.Shape.BoundBox + data['xLen'] = FreeCAD.Units.Quantity(bb.XLength, FreeCAD.Units.Length).UserString + data['yLen'] = FreeCAD.Units.Quantity(bb.YLength, FreeCAD.Units.Length).UserString + data['zLen'] = FreeCAD.Units.Quantity(bb.ZLength, FreeCAD.Units.Length).UserString + data['material'] = "Not Specified" # fix this + + if data['material'] == "Not Specified": + self.squawk("PathSanity", "Consider Specifying the Stock Material", squawkType="TIP") + + data['stockImage'] = self.__makePicture(obj.Stock, "stockImage") + except Exception as e: + data['errors'] = e + + return data + + def __coolantData(self, obj): + data = {"items": []} + + try: + for op in obj.Operations.Group: + opLabel = op.Label if op.Active else op.Label + " (INACTIVE)" + if hasattr(op, "CoolantMode"): + opdata = {"opName": opLabel, + "coolantMode": op.eCoolantMode} + else: + opdata = {"opName": opLabel, + "coolantMode": "N/A"} + data['items'].append(opdata) + + except Exception as e: + data['errors'] = e + + return data + + def __fixtureData(self, obj): + data = {} + try: + data['fixtures'] = str(obj.Fixtures) + data['orderBy'] = str(obj.OrderOutputBy) + + aview = FreeCADGui.activeDocument().activeView() + aview.setAnimationEnabled(False) + + obj.Visibility = False + obj.Operations.Visibility = False + + mw = FreeCADGui.getMainWindow() + mdi = mw.findChild(QtGui.QMdiArea) + view = mdi.activeSubWindow() + view.showNormal() + view.resize(320, 320) + + imagepath = '{}/origin'.format(self.outputpath) + + FreeCADGui.Selection.clearSelection() + FreeCADGui.SendMsgToActiveView("PerspectiveCamera") + aview.viewIsometric() + for i in obj.Model.Group: + FreeCADGui.Selection.addSelection(i) + FreeCADGui.SendMsgToActiveView("ViewSelection") + FreeCADGui.Selection.clearSelection() + obj.ViewObject.Proxy.editObject(obj) + aview.saveImage('{}.png'.format(imagepath), 320, 320, 'Current') + aview.saveImage('{}_t.png'.format(imagepath), 320, 320, 'Transparent') + obj.ViewObject.Proxy.uneditObject(obj) + obj.Visibility = True + obj.Operations.Visibility = True + + view.showMaximized() + + aview.setAnimationEnabled(True) + data['datumImage'] = '{}_t.png'.format(imagepath) + + except Exception as e: + data['errors'] = e + + return data + + def __outputData(self, obj): + data = {} + try: + data['lastpostprocess'] = str(obj.LastPostProcessDate) + data['lastgcodefile'] = str(obj.LastPostProcessOutput) + data['optionalstops'] = "False" + data['programmer'] = "" + data['machine'] = "" + data['postprocessor'] = str(obj.PostProcessor) + data['postprocessorFlags'] = str(obj.PostProcessorArgs) + + for op in obj.Operations.Group: + if isinstance(op.Proxy, PathScripts.PathStop.Stop) and op.Stop is True: + data['optionalstops'] = "True" + + if obj.LastPostProcessOutput == "": + data['filesize'] = str(0.0) + data['linecount'] = str(0) + self.squawk("PathSanity", "The Job has not been post-processed") + else: + data['filesize'] = str(os.path.getsize(obj.LastPostProcessOutput)) + data['linecount'] = str(sum(1 for line in open(obj.LastPostProcessOutput))) + + except Exception as e: + data['errors'] = e + + return data + + # def __inspectionData(self, obj): + # data = {} + # try: + # pass + + # except Exception as e: + # data['errors'] = e + + # return data - def __checkTC(self, tc): - clean = True - if tc.ToolNumber == 0: - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(tc.Label) + " is using ID 0 which the undefined default. Please set a real tool.")+"\n") - clean = False - if tc.HorizFeed == 0: - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(tc.Label) + " has a 0 value for the Horizontal feed rate")+"\n") - clean = False - if tc.VertFeed == 0: - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(tc.Label) + " has a 0 value for the Vertical feed rate")+"\n") - clean = False - if tc.SpindleSpeed == 0: - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(tc.Label) + " has a 0 value for the spindle speed")+"\n") - clean = False - return clean if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Sanity',CommandPathSanity()) + FreeCADGui.addCommand('Path_Sanity', CommandPathSanity()) From 3cfe849f6878fa6f0a4dc7cd868cf75e7592e759 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 26 Aug 2020 14:45:31 -0500 Subject: [PATCH 2/4] Allow adding a material to the stock object. Select the stock object and use the Arch Material button to add and assign the material to the stock. The only thing affected is the path-sanity report --- src/Mod/Path/PathScripts/PathSanity.py | 7 ++++++- src/Mod/Path/PathScripts/PathStock.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 4df987fec5..fdb27ec105 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -571,7 +571,11 @@ class CommandPathSanity: data['xLen'] = FreeCAD.Units.Quantity(bb.XLength, FreeCAD.Units.Length).UserString data['yLen'] = FreeCAD.Units.Quantity(bb.YLength, FreeCAD.Units.Length).UserString data['zLen'] = FreeCAD.Units.Quantity(bb.ZLength, FreeCAD.Units.Length).UserString - data['material'] = "Not Specified" # fix this + + data['material'] = "Not Specified" + if hasattr(obj.Stock, 'Material'): + if obj.Stock.Material is not None: + data['material'] = obj.Stock.Material.Material['Name'] if data['material'] == "Not Specified": self.squawk("PathSanity", "Consider Specifying the Stock Material", squawkType="TIP") @@ -579,6 +583,7 @@ class CommandPathSanity: data['stockImage'] = self.__makePicture(obj.Stock, "stockImage") except Exception as e: data['errors'] = e + print(e) return data diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py index e1ab825627..aa59109eb2 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -103,6 +103,7 @@ class StockFromBase(Stock): obj.addProperty("App::PropertyDistance", "ExtYpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Y direction")) obj.addProperty("App::PropertyDistance", "ExtZneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Z direction")) obj.addProperty("App::PropertyDistance", "ExtZpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Z direction")) + obj.addProperty("App::PropertyLink","Material","Component", QtCore.QT_TRANSLATE_NOOP("App::Property","A material for this object")) obj.Base = base obj.ExtXneg= 1.0 From e83eada9a5ffe59c24764a34506d6cfaef7884ef Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 26 Aug 2020 15:22:43 -0500 Subject: [PATCH 3/4] Ignore old-style tool controllers --- src/Mod/Path/PathScripts/PathSanity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index fdb27ec105..edbbbc5b44 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -482,6 +482,8 @@ class CommandPathSanity: try: for TC in obj.ToolController: + if not hasattr(TC.Tool, 'BitBody'): + continue # skip old-style tools tooldata = data.setdefault(str(TC.ToolNumber), {}) bitshape = tooldata.setdefault('BitShape', "") if bitshape not in ["", TC.Tool.BitShape]: From 82a4281adfb7ab0af2a5df65e76867e66f7f014a Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 29 Sep 2020 19:49:37 -0500 Subject: [PATCH 4/4] making it a bit more forgiving of old jobs/tools --- src/Mod/Path/PathScripts/PathSanity.py | 224 +++++++++++++------------ 1 file changed, 117 insertions(+), 107 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index edbbbc5b44..2212ee036f 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -70,11 +70,13 @@ class CommandPathSanity: def Activated(self): # if everything is ok, execute + self.squawkData["items"] = [] + obj = FreeCADGui.Selection.getSelectionEx()[0].Object data = self.__summarize(obj) html = self.__report(data) if html is not None: - print(html) + print("HTML report written to {}".format(html)) webbrowser.open(html) def __makePicture(self, obj, imageName): @@ -117,10 +119,6 @@ class CommandPathSanity: for o in visible: o.Visibility = True - # with open(imagepath, 'wb') as fd: - # fd.write(imagedata) - # fd.close() - return "{}_t.png".format(imagepath) def __report(self, data): @@ -164,12 +162,6 @@ class CommandPathSanity: {outTable} |=== -== Coolant - -|=== -{coolantTable} -|=== - == Fixtures and Workholding |=== @@ -189,6 +181,7 @@ class CommandPathSanity: PartLabel = translate("Path_Sanity", "Base Object(s)") SequenceLabel = translate("Path_Sanity", "Job Sequence") + DescriptionLabel = translate("Path_Sanity", "Job Description") JobTypeLabel = translate("Path_Sanity", "Job Type") CADLabel = translate("Path_Sanity", "CAD File Name") LastSaveLabel = translate("Path_Sanity", "Last Save Date") @@ -210,6 +203,7 @@ class CommandPathSanity: "image::" + b['baseimage'] + "[" + PartLabel + "]\n" infoTable += "|*" + SequenceLabel + "*|" + d['Sequence'] infoTable += "|*" + JobTypeLabel + "*|" + d['JobType'] + infoTable += "|*" + DescriptionLabel + "*|" + d['JobDescription'] infoTable += "|*" + CADLabel + "*|" + d['FileName'] infoTable += "|*" + LastSaveLabel + "*|" + d['LastModifiedDate'] infoTable += "|*" + CustomerLabel + "*|" + d['Customer'] @@ -222,17 +216,19 @@ class CommandPathSanity: zMinLabel = translate("Path_Sanity", "Minimum Z Height") zMaxLabel = translate("Path_Sanity", "Maximum Z Height") cycleTimeLabel = translate("Path_Sanity", "Cycle Time") + coolantLabel = translate("Path_Sanity", "Coolant") jobTotalLabel = translate("Path_Sanity", "TOTAL JOB") d = data['runData'] runTable += "|*" + opLabel + "*|*" + zMinLabel + "*|*" + zMaxLabel + \ - "*|*" + cycleTimeLabel + "*\n" + "*|*" + coolantLabel + "*|*" + cycleTimeLabel + "*\n" for i in d['items']: runTable += "|{}".format(i['opName']) runTable += "|{}".format(i['minZ']) runTable += "|{}".format(i['maxZ']) + runTable += "|{}".format(i['coolantMode']) runTable += "|{}".format(i['cycleTime']) runTable += "|*" + jobTotalLabel + "* |{} |{} |{}".format( @@ -263,15 +259,13 @@ class CommandPathSanity: toolTables += "|===\n" - # toolTables += "|*" + toolLabel + "*| T" + key + " .2+a|" + "image::" + value['imagepath'] + "[" + key + "]|\n" - - toolTables += "|*" + descriptionLabel + "*|" + value['description'] + " a|" + "image::" + value['imagepath'] + "[" + key + "]\n" - toolTables += "|*" + manufLabel + "* 2+|" + value['manufacturer'] + "\n" - toolTables += "|*" + partNumberLabel + "* 2+|" + value['partNumber'] + "\n" - toolTables += "|*" + urlLabel + "* 2+|" + value['url'] + "\n" - toolTables += "|*" + inspectionNotesLabel + "* 2+|" + value['inspectionNotes'] + "\n" - toolTables += "|*" + shapeLabel + "* 2+|" + value['shape'] + "\n" - toolTables += "|*" + diameterLabel + "* 2+|" + value['diameter'] + "\n" + toolTables += "|*{}*| {} a| image::{}[{}]\n".format(descriptionLabel, value['description'], value['imagepath'], key) + toolTables += "|*{}* 2+| {}\n".format(manufLabel, value['manufacturer']) + toolTables += "|*{}* 2+| {}\n".format(partNumberLabel, value['partNumber']) + toolTables += "|*{}* 2+| {}\n".format(urlLabel, value['url']) + toolTables += "|*{}* 2+| {}\n".format(inspectionNotesLabel, value['inspectionNotes']) + toolTables += "|*{}* 2+| {}\n".format(shapeLabel, value['shape']) + toolTables += "|*{}* 2+| {}\n".format(diameterLabel, value['diameter']) toolTables += "|===\n" toolTables += "|===\n" @@ -291,11 +285,13 @@ class CommandPathSanity: d = data['stockData'] - stockTable += "|*" + materialLabel + "*|" + d['material'] + \ - " .4+a|" + "image::" + d['stockImage'] + "[stock]\n" - stockTable += "|*" + xDimLabel + "*|" + d['xLen'] - stockTable += "|*" + yDimLabel + "*|" + d['yLen'] - stockTable += "|*" + zDimLabel + "*|" + d['zLen'] + stockTable += "|*{}*|{} .4+a|image::{}[stock]\n".format( + materialLabel, + d['material'], + d['stockImage']) + stockTable += "|*{}*|{}".format(xDimLabel, d['xLen']) + stockTable += "|*{}*|{}".format(yDimLabel, d['yLen']) + stockTable += "|*{}*|{}".format(zDimLabel, d['zLen']) # Generate the markup for the Fixture Section @@ -306,24 +302,9 @@ class CommandPathSanity: d = data['fixtureData'] - fixtureTable += "|*" + offsetsLabel + "*|" + d['fixtures'] + "\n" - fixtureTable += "|*" + orderByLabel + "*|" + d['orderBy'] - fixtureTable += "|*" + datumLabel + "* a|image::" + d['datumImage'] + "[]" - - # Generate the markup for the Coolant Section - - coolantTable = "" - - opLabel = translate("Path_Sanity", "Operation") - coolantLabel = translate("Path_Sanity", "Coolant Mode") - - d = data['coolantData']['items'] - - coolantTable += "|*" + opLabel + "*|*" + coolantLabel + "*\n" - - for i in d: - coolantTable += "|" + i['opName'] - coolantTable += "|" + i['CoolantMode'] + fixtureTable += "|*{}*|{}\n".format(offsetsLabel, d['fixtures']) + fixtureTable += "|*{}*|{}\n".format(orderByLabel, d['orderBy']) + fixtureTable += "|*{}* a| image::{}[]".format(datumLabel, d['datumImage']) # Generate the markup for the Output Section @@ -340,15 +321,15 @@ class CommandPathSanity: fileSizeLabel = translate("Path_Sanity", "File Size (kbs)") lineCountLabel = translate("Path_Sanity", "Line Count") - outTable += "|*" + gcodeFileLabel + "*|" + d['lastgcodefile'] + "\n" - outTable += "|*" + lastpostLabel + "*|" + d['lastpostprocess'] + "\n" - outTable += "|*" + stopsLabel + "*|" + d['optionalstops'] + "\n" - outTable += "|*" + programmerLabel + "*|" + d['programmer'] + "\n" - outTable += "|*" + machineLabel + "*|" + d['machine'] + "\n" - outTable += "|*" + postLabel + "*|" + d['postprocessor'] + "\n" - outTable += "|*" + flagsLabel + "*|" + d['postprocessorFlags'] + "\n" - outTable += "|*" + fileSizeLabel + "*|" + d['filesize'] + "\n" - outTable += "|*" + lineCountLabel + "*|" + d['linecount'] + "\n" + outTable += "|*{}*|{}\n".format(gcodeFileLabel, d['lastgcodefile']) + outTable += "|*{}*|{}\n".format(lastpostLabel, d['lastpostprocess']) + outTable += "|*{}*|{}\n".format(stopsLabel, d['optionalstops']) + outTable += "|*{}*|{}\n".format(programmerLabel, d['programmer']) + outTable += "|*{}*|{}\n".format(machineLabel, d['machine']) + outTable += "|*{}*|{}\n".format(postLabel, d['postprocessor']) + outTable += "|*{}*|{}\n".format(flagsLabel, d['postprocessorFlags']) + outTable += "|*{}*|{}\n".format(fileSizeLabel, d['filesize']) + outTable += "|*{}*|{}\n".format(lineCountLabel, d['linecount']) # Generate the markup for the Squawk Section @@ -356,8 +337,9 @@ class CommandPathSanity: operatorLabel = translate("Path_Sanity", "Operator") dateLabel = translate("Path_Sanity", "Date") - squawkTable = "" - squawkTable += "|*" + noteLabel + "*|*" + operatorLabel + "*|*" + dateLabel + "*\n" + squawkTable = "|*{}*|*{}*|*{}*\n".format(noteLabel, + operatorLabel, + dateLabel) d = data['squawkData'] for i in d['items']: @@ -376,7 +358,6 @@ class CommandPathSanity: stockTable=stockTable, fixtureTable=fixtureTable, outTable=outTable, - coolantTable=coolantTable, squawkTable=squawkTable) # Save the report @@ -388,7 +369,8 @@ class CommandPathSanity: fd.close() try: - result = os.system('asciidoctor {} -o {}'.format(reportraw, reporthtml)) + result = os.system('asciidoctor {} -o {}'.format(reportraw, + reporthtml)) if str(result) == "32512": print('asciidoctor not found') reporthtml = None @@ -408,7 +390,6 @@ class CommandPathSanity: data['designData'] = self.__designData(obj) data['toolData'] = self.__toolData(obj) data['runData'] = self.__runData(obj) - data['coolantData'] = self.__coolantData(obj) data['outputData'] = self.__outputData(obj) data['fixtureData'] = self.__fixtureData(obj) data['stockData'] = self.__stockData(obj) @@ -416,7 +397,8 @@ class CommandPathSanity: return data def squawk(self, operator, note, date=datetime.now(), squawkType="NOTE"): - squawkType = squawkType if squawkType in ["NOTE", "WARNING", "ERROR", "TIP"] else "NOTE" + squawkType = squawkType if squawkType in \ + ["NOTE", "WARNING", "CAUTION", "TIP"] else "NOTE" self.squawkData['items'].append({"Date": str(date), "Operator": operator, @@ -424,7 +406,8 @@ class CommandPathSanity: "squawkType": squawkType}) def __baseObjectData(self, obj): - data = {} + data = {'baseimage': '', + 'bases': ''} try: bases = {} for name, count in \ @@ -437,6 +420,7 @@ class CommandPathSanity: except Exception as e: data['errors'] = e + self.squawk("PathSanity(__baseObjectData)", e, squawkType="CAUTION") return data @@ -446,13 +430,20 @@ class CommandPathSanity: Returns information about issues and concerns (squawks) """ - data = {} + data = {'FileName': '', + 'LastModifiedDate': '', + 'Customer': '', + 'Designer': '', + 'JobDescription': '', + 'JobLabel': '', + 'Sequence': '', + 'JobType': ''} try: data['FileName'] = obj.Document.FileName data['LastModifiedDate'] = str(obj.Document.LastModifiedDate) data['Customer'] = obj.Document.Company data['Designer'] = obj.Document.LastModifiedBy - data['JobNotes'] = obj.Description + data['JobDescription'] = obj.Description data['JobLabel'] = obj.Label n = 0 @@ -468,6 +459,7 @@ class CommandPathSanity: except Exception as e: data['errors'] = e + self.squawk("PathSanity(__designData)", e, squawkType="CAUTION") return data @@ -477,19 +469,22 @@ class CommandPathSanity: toolcontrollers Returns information about issues and problems with the tools (squawks) """ - data = {} try: for TC in obj.ToolController: if not hasattr(TC.Tool, 'BitBody'): + self.squawk("PathSanity", + "Tool number {} is a legacy tool. Legacy tools not \ + supported by Path-Sanity".format(TC.ToolNumber), + squawkType="WARNING") continue # skip old-style tools tooldata = data.setdefault(str(TC.ToolNumber), {}) bitshape = tooldata.setdefault('BitShape', "") if bitshape not in ["", TC.Tool.BitShape]: self.squawk("PathSanity", "Tool number {} used by multiple tools".format(TC.ToolNumber), - squawkType="ERROR") + squawkType="CAUTION") tooldata['bitShape'] = TC.Tool.BitShape tooldata['description'] = TC.Tool.Label tooldata['manufacturer'] = "" @@ -520,11 +515,11 @@ class CommandPathSanity: used = False for op in obj.Operations.Group: - if op.ToolController is TC: + if hasattr(op, "ToolController") and op.ToolController is TC: used = True tooldata.setdefault('ops', []).append( {"Operation": op.Label, - "ToolController": TC.Name, + "ToolController": TC.Label, "Feed": str(TC.HorizFeed), "Speed": str(TC.SpindleSpeed)}) @@ -536,37 +531,71 @@ class CommandPathSanity: except Exception as e: data['errors'] = e + self.squawk("PathSanity(__toolData)", e, squawkType="CAUTION") return data def __runData(self, obj): - data = {} + data = {'cycletotal': '', + 'jobMinZ': '', + 'jobMaxZ': '', + 'jobDescription': '', + 'items': []} try: data['cycletotal'] = str(obj.CycleTime) data['jobMinZ'] = FreeCAD.Units.Quantity(obj.Path.BoundBox.ZMin, FreeCAD.Units.Length).UserString data['jobMaxZ'] = FreeCAD.Units.Quantity(obj.Path.BoundBox.ZMax, FreeCAD.Units.Length).UserString + data['jobDescription'] = obj.Description data['items'] = [] for op in obj.Operations.Group: - oplabel = op.Label if op.Active else op.Label + " (INACTIVE)" + + oplabel = op.Label + ctime = op.CycleTime if hasattr(op, "CycleTime") else 0.0 + cool = op.CoolantMode if hasattr(op, "CoolantMode") else "N/A" + + o = op + while len(o.ViewObject.claimChildren()) != 0: # dressup + oplabel = "{}:{}".format(oplabel, o.Base.Label) + o = o.Base + if hasattr(o, 'CycleTime'): + ctime = o.CycleTime + cool = o.CoolantMode if hasattr(o, "CoolantMode") else cool + + if hasattr(op, 'Active') and not op.Active: + oplabel = "{} (INACTIVE)".format(oplabel) + ctime = 0.0 + + if op.Path.BoundBox.isValid(): + zmin = FreeCAD.Units.Quantity(op.Path.BoundBox.ZMin, + FreeCAD.Units.Length).UserString + zmax = FreeCAD.Units.Quantity(op.Path.BoundBox.ZMax, + FreeCAD.Units.Length).UserString + else: + zmin = '' + zmax = '' + opdata = {"opName": oplabel, - "minZ": FreeCAD.Units.Quantity(op.Path.BoundBox.ZMin, - FreeCAD.Units.Length).UserString, - "maxZ": FreeCAD.Units.Quantity(op.Path.BoundBox.ZMax, - FreeCAD.Units.Length).UserString, - #"maxZ": str(op.Path.BoundBox.ZMax), - "cycleTime": str(op.CycleTime)} + "minZ": zmin, + "maxZ": zmax, + "cycleTime": ctime, + "coolantMode": cool} data['items'].append(opdata) except Exception as e: data['errors'] = e + self.squawk("PathSanity(__runData)", e, squawkType="CAUTION") return data def __stockData(self, obj): - data = {} + data = {'xLen': '', + 'yLen': '', + 'zLen': '', + 'material': '', + 'stockImage': ''} try: bb = obj.Stock.Shape.BoundBox @@ -585,31 +614,12 @@ class CommandPathSanity: data['stockImage'] = self.__makePicture(obj.Stock, "stockImage") except Exception as e: data['errors'] = e - print(e) - - return data - - def __coolantData(self, obj): - data = {"items": []} - - try: - for op in obj.Operations.Group: - opLabel = op.Label if op.Active else op.Label + " (INACTIVE)" - if hasattr(op, "CoolantMode"): - opdata = {"opName": opLabel, - "coolantMode": op.eCoolantMode} - else: - opdata = {"opName": opLabel, - "coolantMode": "N/A"} - data['items'].append(opdata) - - except Exception as e: - data['errors'] = e + self.squawk("PathSanity(__stockData)", e, squawkType="CAUTION") return data def __fixtureData(self, obj): - data = {} + data = {'fixtures': '', 'orderBy': '', 'datumImage': ''} try: data['fixtures'] = str(obj.Fixtures) data['orderBy'] = str(obj.OrderOutputBy) @@ -649,11 +659,20 @@ class CommandPathSanity: except Exception as e: data['errors'] = e + self.squawk("PathSanity(__fixtureData)", e, squawkType="CAUTION") return data def __outputData(self, obj): - data = {} + data = {'lastpostprocess': '', + 'lastgcodefile': '', + 'optionalstops': '', + 'programmer': '', + 'machine': '', + 'postprocessor': '', + 'postprocessorFlags': '', + 'filesize': '', + 'linecount': ''} try: data['lastpostprocess'] = str(obj.LastPostProcessDate) data['lastgcodefile'] = str(obj.LastPostProcessOutput) @@ -677,19 +696,10 @@ class CommandPathSanity: except Exception as e: data['errors'] = e + self.squawk("PathSanity(__outputData)", e, squawkType="CAUTION") return data - # def __inspectionData(self, obj): - # data = {} - # try: - # pass - - # except Exception as e: - # data['errors'] = e - - # return data - if FreeCAD.GuiUp: # register the FreeCAD command