From dfc4e53f67785841b9bf106a79ccf5a6f7b0d524 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 | 751 +++++++++++++++++++++---- 3 files changed, 657 insertions(+), 123 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 2f97fc628a29..adc0adfee694 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -32,14 +32,14 @@ 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 @@ def __init__(self, obj, models, templateFile=None): 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 @@ def onDelete(self, obj, arg2=None): # 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 @@ def getCycleTime(self): 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 327a3eee8fbe..bfc0f35ea45e 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -37,7 +37,7 @@ from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore, QtGui - +from datetime import datetime LOG_MODULE = PathLog.thisModule() @@ -210,9 +210,9 @@ def exportObjectsWith(self, objs, job, needFilename=True): 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 @@ def Activated(self): 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 5c7e6235d017..4df987fec55c 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) - - def __review(self, obj): - "checks the selected job for common errors" - clean = True - - # 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") - - if obj.PostProcessor == '': - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Postprocessor has not been selected.")+"\n") - clean = False - - if obj.PostProcessorOutputFile == '': - FreeCAD.Console.PrintWarning(translate("Path_Sanity", "No output file is named. You'll be prompted during postprocessing.")+"\n") - clean = False - - for tc in obj.ToolController: - PathLog.info("Checking: {}.{}".format(obj.Label, tc.Label)) - clean &= self.__checkTC(tc) - - for op in obj.Operations.Group: - PathLog.info("Checking: {}.{}".format(obj.Label, op.Label)) - - 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 - - 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 - - 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 - - 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 - - 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 - - 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 - - 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 - - if clean: - FreeCAD.Console.PrintMessage(translate("Path_Sanity", "No issues detected, {} has passed basic sanity check.").format(obj.Label)) - - 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 + data = self.__summarize(obj) + html = self.__report(data) + if html is not None: + print(html) + webbrowser.open(html) + + def __makePicture(self, obj, imageName): + """ + Makes an image of the target object. Returns filename + """ + + # 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 + + aview = FreeCADGui.activeDocument().activeView() + aview.setAnimationEnabled(False) + + mw = FreeCADGui.getMainWindow() + mdi = mw.findChild(QtGui.QMdiArea) + view = mdi.activeSubWindow() + view.showNormal() + view.resize(320, 320) + + imagepath = self.outputpath + '/{}'.format(imageName) + + 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') + + view.showMaximized() + + aview.setAnimationEnabled(True) + + # Restore visibility + obj.Visibility = False + 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): + """ + generates an asciidoc file with the report information + """ + + reportTemplate = """ += Setup Report for FreeCAD Job: {jobname} +:toc: +:icons: font +:imagesdir: "" +:data-uri: + +== 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 + if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Sanity',CommandPathSanity()) + FreeCADGui.addCommand('Path_Sanity', CommandPathSanity()) From a8a76deb87dfbe8d0451edcb9ab25b039051999a 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 4df987fec55c..fdb27ec105c9 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -571,7 +571,11 @@ def __stockData(self, obj): 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 @@ def __stockData(self, obj): 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 e1ab825627cb..aa59109eb24c 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -103,6 +103,7 @@ def __init__(self, obj, base): 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 cfa38c26a40439304461330bb897e53cb681f352 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 fdb27ec105c9..edbbbc5b4427 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -482,6 +482,8 @@ def __toolData(self, obj): 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 c42149b0d45ce488b474f75150f1b16815fef53a 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 edbbbc5b4427..2212ee036fb0 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -70,11 +70,13 @@ def IsActive(self): 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 @@ def __makePicture(self, obj, imageName): 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 @@ def __report(self, data): {outTable} |=== -== Coolant - -|=== -{coolantTable} -|=== - == Fixtures and Workholding |=== @@ -189,6 +181,7 @@ def __report(self, data): 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 @@ def __report(self, data): "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 @@ def __report(self, data): 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 @@ def __report(self, data): 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 @@ def __report(self, data): 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 @@ def __report(self, data): 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 @@ def __report(self, data): 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 @@ def __report(self, data): 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 @@ def __report(self, data): stockTable=stockTable, fixtureTable=fixtureTable, outTable=outTable, - coolantTable=coolantTable, squawkTable=squawkTable) # Save the report @@ -388,7 +369,8 @@ def __report(self, data): 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 @@ def __summarize(self, 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) @@ -416,7 +397,8 @@ def __summarize(self, obj): 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 @@ def squawk(self, operator, note, date=datetime.now(), squawkType="NOTE"): "squawkType": squawkType}) def __baseObjectData(self, obj): - data = {} + data = {'baseimage': '', + 'bases': ''} try: bases = {} for name, count in \ @@ -437,6 +420,7 @@ def __baseObjectData(self, obj): except Exception as e: data['errors'] = e + self.squawk("PathSanity(__baseObjectData)", e, squawkType="CAUTION") return data @@ -446,13 +430,20 @@ def __designData(self, obj): 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 @@ def __designData(self, obj): except Exception as e: data['errors'] = e + self.squawk("PathSanity(__designData)", e, squawkType="CAUTION") return data @@ -477,19 +469,22 @@ def __toolData(self, obj): 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 @@ def __toolData(self, obj): 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 @@ def __toolData(self, obj): 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 @@ def __stockData(self, obj): 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 @@ def __fixtureData(self, obj): 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 @@ def __outputData(self, obj): 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