Output fillename for the report is now the same as the postprocessor job (job.PostProcessorOutputFile), this way the setup file will be autoloaded in LinuxCNC qtDraggon ui and others based on probe-basic. There is some error checking missing, the check to see if the job.PostProcessorOutputFile is not empty is not there for example. But since the report can only be generated when there is a postprocessor file this is not a big problem.
752 lines
28 KiB
Python
752 lines
28 KiB
Python
# -*- coding: utf-8 -*-
|
|
# ***************************************************************************
|
|
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com> *
|
|
# * *
|
|
# * This file is part of the FreeCAD CAx development system. *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify *
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * FreeCAD is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Lesser General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with FreeCAD; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
# * USA *
|
|
# * *
|
|
# ***************************************************************************
|
|
|
|
'''
|
|
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
|
|
'''
|
|
|
|
from __future__ import print_function
|
|
from PySide import QtCore, QtGui
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
import PathScripts
|
|
import PathScripts.PathLog as PathLog
|
|
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')
|
|
|
|
|
|
class CommandPathSanity:
|
|
|
|
def resolveOutputPath(self, job):
|
|
if job.PostProcessorOutputFile != "":
|
|
filepath = job.PostProcessorOutputFile
|
|
elif PathPreferences.defaultOutputFile() != "":
|
|
filepath = PathPreferences.defaultOutputFile()
|
|
else:
|
|
filepath = PathPreferences.macroFilePath()
|
|
|
|
if '%D' in filepath:
|
|
D = FreeCAD.ActiveDocument.FileName
|
|
if D:
|
|
D = os.path.dirname(D)
|
|
# in case the document is in the current working directory
|
|
if not D:
|
|
D = '.'
|
|
else:
|
|
FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n")
|
|
return None
|
|
filepath = filepath.replace('%D', D)
|
|
|
|
if '%d' in filepath:
|
|
d = FreeCAD.ActiveDocument.Label
|
|
filepath = filepath.replace('%d', d)
|
|
|
|
if '%j' in filepath:
|
|
j = job.Label
|
|
filepath = filepath.replace('%j', j)
|
|
|
|
if '%M' in filepath:
|
|
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
|
|
M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir())
|
|
filepath = filepath.replace('%M', M)
|
|
|
|
PathLog.debug('filepath: {}'.format(filepath))
|
|
|
|
# starting at the derived filename, iterate up until we have a valid
|
|
# directory to write to
|
|
while not os.path.isdir(filepath):
|
|
filepath = os.path.dirname(filepath)
|
|
|
|
PathLog.debug('filepath: {}'.format(filepath))
|
|
return filepath + os.sep
|
|
|
|
def GetResources(self):
|
|
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 job for common errors")}
|
|
|
|
def IsActive(self):
|
|
obj = FreeCADGui.Selection.getSelectionEx()[0].Object
|
|
return isinstance(obj.Proxy, PathScripts.PathJob.ObjectJob)
|
|
|
|
def Activated(self):
|
|
# if everything is ok, execute
|
|
self.squawkData = {"items": []}
|
|
|
|
obj = FreeCADGui.Selection.getSelectionEx()[0].Object
|
|
self.outputpath = self.resolveOutputPath(obj)
|
|
data = self.__summarize(obj)
|
|
html = self.__report(data)
|
|
if html is not None:
|
|
FreeCAD.Console.PrintMessage("HTML report written to {}".format(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
|
|
|
|
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}
|
|
|===
|
|
|
|
== 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")
|
|
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")
|
|
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 += "|*" + DescriptionLabel + "*|" + d['JobDescription']
|
|
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")
|
|
coolantLabel = translate("Path_Sanity", "Coolant")
|
|
jobTotalLabel = translate("Path_Sanity", "TOTAL JOB")
|
|
|
|
d = data['runData']
|
|
|
|
runTable += "|*" + opLabel + "*|*" + zMinLabel + "*|*" + zMaxLabel + \
|
|
"*|*" + 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(
|
|
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 += "|*{}*| {} 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"
|
|
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 += "|*{}*|{} .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
|
|
|
|
fixtureTable = ""
|
|
offsetsLabel = translate("Path_Sanity", "Work Offsets")
|
|
orderByLabel = translate("Path_Sanity", "Order By")
|
|
datumLabel = translate("Path_Sanity", "Part Datum")
|
|
|
|
d = data['fixtureData']
|
|
|
|
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
|
|
|
|
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 += "|*{}*|{}\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
|
|
|
|
noteLabel = translate("Path_Sanity", "Note")
|
|
operatorLabel = translate("Path_Sanity", "Operator")
|
|
dateLabel = translate("Path_Sanity", "Date")
|
|
|
|
squawkTable = "|*{}*|*{}*|*{}*\n".format(noteLabel,
|
|
operatorLabel,
|
|
dateLabel)
|
|
|
|
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,
|
|
squawkTable=squawkTable)
|
|
|
|
# Save the report
|
|
|
|
reportraw = self.outputpath + job.PostProcessorOutputFile + '.asciidoc'
|
|
reporthtml = self.outputpath + job.PostProcessorOutputFile + '.html'
|
|
with open(reportraw, 'w') as fd:
|
|
fd.write(report)
|
|
fd.close()
|
|
FreeCAD.Console.PrintMessage('asciidoc file written to {}\n'.format(reportraw))
|
|
|
|
try:
|
|
result = os.system('asciidoctor {} -o {}'.format(reportraw,
|
|
reporthtml))
|
|
if str(result) == "32512":
|
|
msg = "asciidoctor not found. html cannot be generated."
|
|
QtGui.QMessageBox.information(None, "Path Sanity", msg)
|
|
FreeCAD.Console.PrintMessage(msg)
|
|
reporthtml = None
|
|
|
|
except Exception as e:
|
|
FreeCAD.Console.PrintError(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['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", "CAUTION", "TIP"] else "NOTE"
|
|
|
|
self.squawkData['items'].append({"Date": str(date),
|
|
"Operator": operator,
|
|
"Note": note,
|
|
"squawkType": squawkType})
|
|
|
|
def __baseObjectData(self, obj):
|
|
data = {'baseimage': '',
|
|
'bases': ''}
|
|
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
|
|
self.squawk("PathSanity(__baseObjectData)", e, squawkType="CAUTION")
|
|
|
|
return data
|
|
|
|
def __designData(self, obj):
|
|
"""
|
|
Returns header information about the design document
|
|
Returns information about issues and concerns (squawks)
|
|
"""
|
|
|
|
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['JobDescription'] = 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
|
|
self.squawk("PathSanity(__designData)", e, squawkType="CAUTION")
|
|
|
|
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.Tools.Group:
|
|
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="CAUTION")
|
|
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 hasattr(op, "ToolController") and op.ToolController is TC:
|
|
used = True
|
|
tooldata.setdefault('ops', []).append(
|
|
{"Operation": op.Label,
|
|
"ToolController": TC.Label,
|
|
"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
|
|
self.squawk("PathSanity(__toolData)", e, squawkType="CAUTION")
|
|
|
|
return data
|
|
|
|
def __runData(self, obj):
|
|
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
|
|
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": 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 = {'xLen': '',
|
|
'yLen': '',
|
|
'zLen': '',
|
|
'material': '',
|
|
'stockImage': ''}
|
|
|
|
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"
|
|
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")
|
|
|
|
data['stockImage'] = self.__makePicture(obj.Stock, "stockImage")
|
|
except Exception as e:
|
|
data['errors'] = e
|
|
self.squawk("PathSanity(__stockData)", e, squawkType="CAUTION")
|
|
|
|
return data
|
|
|
|
def __fixtureData(self, obj):
|
|
data = {'fixtures': '', 'orderBy': '', 'datumImage': ''}
|
|
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
|
|
self.squawk("PathSanity(__fixtureData)", e, squawkType="CAUTION")
|
|
|
|
return data
|
|
|
|
def __outputData(self, obj):
|
|
data = {'lastpostprocess': '',
|
|
'lastgcodefile': '',
|
|
'optionalstops': '',
|
|
'programmer': '',
|
|
'machine': '',
|
|
'postprocessor': '',
|
|
'postprocessorFlags': '',
|
|
'filesize': '',
|
|
'linecount': ''}
|
|
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
|
|
self.squawk("PathSanity(__outputData)", e, squawkType="CAUTION")
|
|
|
|
return data
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
# register the FreeCAD command
|
|
FreeCADGui.addCommand('Path_Sanity', CommandPathSanity())
|