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())