def loadSettings(self): self.form.leDefaultFilePath.setText(PathPreferences.defaultFilePath()) self.form.leDefaultJobTemplate.setText(PathPreferences.defaultJobTemplate()) blacklist = PathPreferences.postProcessorBlacklist() for processor in PathPreferences.allAvailablePostProcessors(): item = QtGui.QListWidgetItem(processor) if processor in blacklist: item.setCheckState(QtCore.Qt.CheckState.Unchecked) else: item.setCheckState(QtCore.Qt.CheckState.Checked) item.setFlags( QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable) self.form.postProcessorList.addItem(item) self.verifyAndUpdateDefaultPostProcessorWith(PathPreferences.defaultPostProcessor()) self.form.defaultPostProcessorArgs.setText(PathPreferences.defaultPostProcessorArgs()) geomTol = Units.Quantity(PathPreferences.defaultGeometryTolerance(), Units.Length) self.form.geometryTolerance.setText(geomTol.UserString) self.form.curveAccuracy.setText(Units.Quantity(PathPreferences.defaultLibAreaCurveAccuracy(), Units.Length).UserString) self.form.leOutputFile.setText(PathPreferences.defaultOutputFile()) self.selectComboEntry(self.form.cboOutputPolicy, PathPreferences.defaultOutputPolicy()) self.form.tbDefaultFilePath.clicked.connect(self.browseDefaultFilePath) self.form.tbDefaultJobTemplate.clicked.connect(self.browseDefaultJobTemplate) self.form.postProcessorList.itemEntered.connect(self.setProcessorListTooltip) self.form.postProcessorList.itemChanged.connect(self.verifyAndUpdateDefaultPostProcessor) self.form.defaultPostProcessor.currentIndexChanged.connect(self.updateDefaultPostProcessorToolTip) self.form.tbOutputFile.clicked.connect(self.browseOutputFile) self.loadStockSettings()
def __init__(self, obj, models, templateFile = None): self.obj = obj 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", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("PathJob","An optional description for this job")) obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) obj.setEditorMode('CycleTime', 1) # read-only obj.addProperty("App::PropertyDistance", "GeometryTolerance", "Geometry", QtCore.QT_TRANSLATE_NOOP("PathJob", "For computing Paths; smaller increases accuracy, but slows down computation")) obj.addProperty("App::PropertyLink", "Stock", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock.")) obj.addProperty("App::PropertyLink", "Operations", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Compound path of all operations in the order they are processed.")) obj.addProperty("App::PropertyLinkList", "ToolController", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Collection of tool controllers available for this job.")) obj.addProperty("App::PropertyBool", "SplitOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob","Split output into multiple gcode files")) obj.addProperty("App::PropertyEnumeration", "OrderOutputBy", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "If multiple WCS, order the output this way")) obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job")) obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation'] obj.Fixtures = ['G54'] obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() #obj.setEditorMode("PostProcessorOutputFile", 0) # set to default mode obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors() defaultPostProcessor = PathPreferences.defaultPostProcessor() # Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed) if defaultPostProcessor in postProcessors: obj.PostProcessor = defaultPostProcessor else: obj.PostProcessor = postProcessors[0] obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations") if ops.ViewObject: ops.ViewObject.Proxy = 0 ops.ViewObject.Visibility = False obj.Operations = ops obj.setEditorMode('Operations', 2) # hide obj.setEditorMode('Placement', 2) self.setupSetupSheet(obj) self.setupBaseModel(obj, models) self.tooltip = None self.tooltipArgs = None obj.Proxy = self self.setFromTemplateFile(obj, templateFile) if not obj.Stock: stockTemplate = PathPreferences.defaultStockTemplate() if stockTemplate: obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate)) if not obj.Stock: obj.Stock = PathStock.CreateFromBase(obj) if obj.Stock.ViewObject: obj.Stock.ViewObject.Visibility = False
def resolveFileName(self, job): path = PathPreferences.defaultOutputFile() if job.PostProcessorOutputFile: path = job.PostProcessorOutputFile filename = path if '%D' in filename: 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 filename = filename.replace('%D', D) if '%d' in filename: d = FreeCAD.ActiveDocument.Label filename = filename.replace('%d', d) if '%j' in filename: j = job.Label filename = filename.replace('%j', j) if '%M' in filename: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) filename = filename.replace('%M', M) policy = PathPreferences.defaultOutputPolicy() openDialog = policy == 'Open File Dialog' if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): # Either the entire filename resolves into a directory or the parent directory doesn't exist. # Either way I don't know what to do - ask for help openDialog = True if os.path.isfile(filename) and not openDialog: if policy == 'Open File Dialog on conflict': openDialog = True elif policy == 'Append Unique ID on conflict': fn, ext = os.path.splitext(filename) nr = fn[-3:] n = 1 if nr.isdigit(): n = int(nr) while os.path.isfile("%s%03d%s" % (fn, n, ext)): n = n + 1 filename = "%s%03d%s" % (fn, n, ext) if openDialog: foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename) if foo: filename = foo[0] else: filename = None return filename
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 __init__(self, obj, models, templateFile = None): self.obj = obj 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", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("PathJob","An optional description for this job")) obj.addProperty("App::PropertyDistance", "GeometryTolerance", "Geometry", QtCore.QT_TRANSLATE_NOOP("PathJob", "For computing Paths; smaller increases accuracy, but slows down computation")) obj.addProperty("App::PropertyLink", "Stock", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock.")) obj.addProperty("App::PropertyLink", "Operations", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Compound path of all operations in the order they are processed.")) obj.addProperty("App::PropertyLinkList", "ToolController", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Collection of tool controllers available for this job.")) obj.addProperty("App::PropertyBool", "SplitOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob","Split output into multiple gcode files")) obj.addProperty("App::PropertyEnumeration", "OrderOutputBy", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "If multiple WCS, order the output this way")) obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job")) obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation'] obj.Fixtures = ['G54'] obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() #obj.setEditorMode("PostProcessorOutputFile", 0) # set to default mode obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors() defaultPostProcessor = PathPreferences.defaultPostProcessor() # Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed) if defaultPostProcessor in postProcessors: obj.PostProcessor = defaultPostProcessor else: obj.PostProcessor = postProcessors[0] obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations") if ops.ViewObject: ops.ViewObject.Proxy = 0 ops.ViewObject.Visibility = False obj.Operations = ops obj.setEditorMode('Operations', 2) # hide obj.setEditorMode('Placement', 2) self.setupSetupSheet(obj) self.setupBaseModel(obj, models) obj.Proxy = self self.setFromTemplateFile(obj, templateFile) if not obj.Stock: stockTemplate = PathPreferences.defaultStockTemplate() if stockTemplate: obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate)) if not obj.Stock: obj.Stock = PathStock.CreateFromBase(obj) if obj.Stock.ViewObject: obj.Stock.ViewObject.Visibility = False
def __init__(self, obj, models, templateFile=None): self.obj = obj self.tooltip = None self.tooltipArgs = None obj.Proxy = self obj.addProperty( "App::PropertyFile", "PostProcessorOutputFile", "Output", QT_TRANSLATE_NOOP("App::Property", "The NC output file for this project"), ) obj.addProperty( "App::PropertyEnumeration", "PostProcessor", "Output", QT_TRANSLATE_NOOP("App::Property", "Select the Post Processor"), ) obj.addProperty( "App::PropertyString", "PostProcessorArgs", "Output", QT_TRANSLATE_NOOP( "App::Property", "Arguments for the Post Processor (specific to the script)", ), ) obj.addProperty( "App::PropertyString", "LastPostProcessDate", "Output", QT_TRANSLATE_NOOP("App::Property", "Last Time the Job was post-processed"), ) obj.setEditorMode("LastPostProcessDate", 2) # Hide obj.addProperty( "App::PropertyString", "LastPostProcessOutput", "Output", QT_TRANSLATE_NOOP("App::Property", "Last Time the Job was post-processed"), ) obj.setEditorMode("LastPostProcessOutput", 2) # Hide obj.addProperty( "App::PropertyString", "Description", "Path", QT_TRANSLATE_NOOP("App::Property", "An optional description for this job"), ) obj.addProperty( "App::PropertyString", "CycleTime", "Path", QT_TRANSLATE_NOOP("App::Property", "Job Cycle Time Estimation"), ) obj.setEditorMode("CycleTime", 1) # read-only obj.addProperty( "App::PropertyDistance", "GeometryTolerance", "Geometry", QT_TRANSLATE_NOOP( "App::Property", "For computing Paths; smaller increases accuracy, but slows down computation", ), ) obj.addProperty( "App::PropertyLink", "Stock", "Base", QT_TRANSLATE_NOOP("App::Property", "Solid object to be used as stock."), ) obj.addProperty( "App::PropertyLink", "Operations", "Base", QT_TRANSLATE_NOOP( "App::Property", "Compound path of all operations in the order they are processed.", ), ) obj.addProperty( "App::PropertyEnumeration", "JobType", "Base", QT_TRANSLATE_NOOP("App::Property", "Select the Type of Job"), ) obj.setEditorMode("JobType", 2) # Hide obj.addProperty( "App::PropertyBool", "SplitOutput", "Output", QT_TRANSLATE_NOOP("App::Property", "Split output into multiple gcode files"), ) obj.addProperty( "App::PropertyEnumeration", "OrderOutputBy", "WCS", QT_TRANSLATE_NOOP("App::Property", "If multiple WCS, order the output this way"), ) obj.addProperty( "App::PropertyStringList", "Fixtures", "WCS", QT_TRANSLATE_NOOP("App::Property", "The Work Coordinate Systems for the Job"), ) obj.Fixtures = ["G54"] for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors( ) defaultPostProcessor = PathPreferences.defaultPostProcessor() # Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed) if defaultPostProcessor in postProcessors: obj.PostProcessor = defaultPostProcessor else: obj.PostProcessor = postProcessors[0] obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() self.setupOperations(obj) self.setupSetupSheet(obj) self.setupBaseModel(obj, models) self.setupToolTable(obj) self.setFromTemplateFile(obj, templateFile) self.setupStock(obj)
def __init__(self, obj, base, templateFile=None): self.obj = obj obj.addProperty( "App::PropertyFile", "PostProcessorOutputFile", "Output", QtCore.QT_TRANSLATE_NOOP("App::Property", "The NC output file for this project")) obj.addProperty( "App::PropertyEnumeration", "PostProcessor", "Output", QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the Post Processor")) obj.addProperty( "App::PropertyString", "PostProcessorArgs", "Output", QtCore.QT_TRANSLATE_NOOP( "App::Property", "Arguments for the Post Processor (specific to the script)")) obj.addProperty( "App::PropertyString", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "An optional description for this job")) obj.addProperty( "App::PropertyDistance", "GeometryTolerance", "Geometry", QtCore.QT_TRANSLATE_NOOP( "App::Property", "For computing Paths; smaller increases accuracy, but slows down computation" )) obj.addProperty( "App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "The base object for all operations")) obj.addProperty( "App::PropertyLink", "Stock", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock.")) obj.addProperty( "App::PropertyLink", "Operations", "Base", QtCore.QT_TRANSLATE_NOOP( "PathJob", "Compound path of all operations in the order they are processed." )) obj.addProperty( "App::PropertyLinkList", "ToolController", "Base", QtCore.QT_TRANSLATE_NOOP( "PathJob", "Collection of tool controllers available for this job.")) obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() #obj.setEditorMode("PostProcessorOutputFile", 0) # set to default mode obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors( ) defaultPostProcessor = PathPreferences.defaultPostProcessor() # Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed) if defaultPostProcessor in postProcessors: obj.PostProcessor = defaultPostProcessor else: obj.PostProcessor = postProcessors[0] obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations") if ops.ViewObject: ops.ViewObject.Proxy = 0 ops.ViewObject.Visibility = False obj.Operations = ops obj.setEditorMode('Operations', 2) # hide obj.setEditorMode('Placement', 2) self.setupSetupSheet(obj) obj.Base = createResourceClone(obj, base, 'Base', 'BaseGeometry') obj.Proxy = self self.setFromTemplateFile(obj, templateFile) if not obj.Stock: stockTemplate = PathPreferences.defaultStockTemplate() if stockTemplate: obj.Stock = PathStock.CreateFromTemplate( obj, json.loads(stockTemplate)) if not obj.Stock: obj.Stock = PathStock.CreateFromBase(obj) if obj.Stock.ViewObject: obj.Stock.ViewObject.Visibility = False
class CommandPathSanity: baseobj = None outputpath = PathPreferences.defaultOutputFile() squawkData = {"items": []} 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 data = self.__summarize(obj) html = self.__report(data) if html is not None: print("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 + '/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['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.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="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
def resolveFileName(job, subpartname, sequencenumber): PathLog.track(subpartname, sequencenumber) validPathSubstitutions = ["D", "d", "M", "j"] validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"] # Look for preference default outputpath, filename = os.path.split(PathPreferences.defaultOutputFile()) filename, ext = os.path.splitext(filename) # Override with document default if it exists if job.PostProcessorOutputFile: matchstring = job.PostProcessorOutputFile candidateOutputPath, candidateFilename = os.path.split(matchstring) if candidateOutputPath: outputpath = candidateOutputPath if candidateFilename: filename, ext = os.path.splitext(candidateFilename) # Strip any invalid substitutions from the ouputpath for match in re.findall("%(.)", outputpath): if match not in validPathSubstitutions: outputpath = outputpath.replace(f"%{match}", "") # if nothing else, use current directory if not outputpath: outputpath = "." # Strip any invalid substitutions from the filename for match in re.findall("%(.)", filename): if match not in validFilenameSubstitutions: filename = filename.replace(f"%{match}", "") # if no filename, use the active document label if not filename: filename = FreeCAD.ActiveDocument.Label # if no extension, use something sensible if not ext: ext = ".nc" # By now we should have a sanitized path, filename and extension to work with PathLog.track(f"path: {outputpath} name: {filename} ext: {ext}") fullPath = processFileNameSubstitutions( job, subpartname, sequencenumber, outputpath, filename, ext, ) # This section determines whether user interaction is necessary policy = PathPreferences.defaultOutputPolicy() openDialog = policy == "Open File Dialog" # if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): # # Either the entire filename resolves into a directory or the parent # # directory doesn't exist. # # Either way I don't know what to do - ask for help # openDialog = True if not FreeCAD.GuiUp: # if testing, or scripting, never open dialog. policy = "Append Unique ID on conflict" openDialog = False if os.path.isfile(fullPath) and not openDialog: if policy == "Open File Dialog on conflict": openDialog = True elif policy == "Append Unique ID on conflict": fn, ext = os.path.splitext(fullPath) nr = fn[-3:] n = 1 if nr.isdigit(): n = int(nr) while os.path.isfile("%s%03d%s" % (fn, n, ext)): n = n + 1 fullPath = "%s%03d%s" % (fn, n, ext) if openDialog: foo = QtGui.QFileDialog.getSaveFileName( QtGui.QApplication.activeWindow(), "Output File", filename ) if foo[0]: fullPath = foo[0] else: fullPath = None # remove any unused substitution strings: for s in validPathSubstitutions + validFilenameSubstitutions: fullPath = fullPath.replace(f"%{s}", "") fullPath = os.path.normpath(fullPath) PathLog.track(fullPath) return fullPath
def resolveFileName(job, subpartname, sequencenumber): PathLog.track(subpartname, sequencenumber) validPathSubstitutions = ["D", "d", "M", "j"] validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"] # Look for preference default outputpath, filename = os.path.split(PathPreferences.defaultOutputFile()) filename, ext = os.path.splitext(filename) # Override with document default if it exists if job.PostProcessorOutputFile: matchstring = job.PostProcessorOutputFile candidateOutputPath, candidateFilename = os.path.split(matchstring) if candidateOutputPath: outputpath = candidateOutputPath if candidateFilename: filename, ext = os.path.splitext(candidateFilename) # Strip any invalid substitutions from the ouputpath for match in re.findall("%(.)", outputpath): if match not in validPathSubstitutions: outputpath = outputpath.replace(f"%{match}", "") # if nothing else, use current directory if not outputpath: outputpath = "." # Strip any invalid substitutions from the filename for match in re.findall("%(.)", filename): if match not in validFilenameSubstitutions: filename = filename.replace(f"%{match}", "") # if no filename, use the active document label if not filename: filename = FreeCAD.ActiveDocument.Label # if no extension, use something sensible if not ext: ext = ".nc" # By now we should have a sanitized path, filename and extension to work with PathLog.track(f"path: {outputpath} name: {filename} ext: {ext}") # The following section allows substitution within the path part PathLog.track(f"path before substitution: {outputpath}") if "%D" in outputpath: # Directory of active document 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 outputpath = outputpath.replace("%D", D) if "%M" in outputpath: M = FreeCAD.getUserMacroDir() outputpath = outputpath.replace("%M", M) # Use the file label if "%d" in outputpath: d = FreeCAD.ActiveDocument.Label outputpath = outputpath.replace("%d", d) # Use the name of the active job object if "%j" in outputpath: j = job.Label outputpath = outputpath.replace("%j", j) PathLog.track(f"path after substitution: {outputpath}") # The following section allows substitution within the filename part PathLog.track(f"filename before substitution: {filename}") # Use the file label if "%d" in filename: d = FreeCAD.ActiveDocument.Label filename = filename.replace("%d", d) # Use the name of the active job object if "%j" in filename: j = job.Label filename = filename.replace("%j", j) # Use the sequnce number if explicitly called if "%S" in filename: j = job.Label filename = filename.replace("%S", str(sequencenumber)) # This section handles unique names for splitting output if job.SplitOutput: PathLog.track() if "%T" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%T", subpartname) if "%t" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%t", subpartname) if "%W" in filename and job.OrderOutputBy == "Fixture": filename = filename.replace("%W", subpartname) if "%O" in filename and job.OrderOutputBy == "Operation": filename = filename.replace("%O", subpartname) if ( "%S" in filename ): # We always add a sequence number but the user can say where filename = filename.replace("%S", str(sequencenumber)) else: filename = f"{filename}-{sequencenumber}" PathLog.track(f"filename after substitution: {filename}") if not ext: ext = ".nc" PathLog.track(f"file extension: {ext}") fullPath = f"{outputpath}{os.path.sep}{filename}{ext}" PathLog.track(f"full filepath: {fullPath}") # This section determines whether user interaction is necessary policy = PathPreferences.defaultOutputPolicy() openDialog = policy == "Open File Dialog" # if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): # # Either the entire filename resolves into a directory or the parent directory doesn't exist. # # Either way I don't know what to do - ask for help # openDialog = True if not FreeCAD.GuiUp: # if testing, or scripting, never open dialog. policy = "Append Unique ID on conflict" openDialog = False if os.path.isfile(fullPath) and not openDialog: if policy == "Open File Dialog on conflict": openDialog = True elif policy == "Append Unique ID on conflict": fn, ext = os.path.splitext(fullPath) nr = fn[-3:] n = 1 if nr.isdigit(): n = int(nr) while os.path.isfile("%s%03d%s" % (fn, n, ext)): n = n + 1 fullPath = "%s%03d%s" % (fn, n, ext) if openDialog: foo = QtGui.QFileDialog.getSaveFileName( QtGui.QApplication.activeWindow(), "Output File", filename ) if foo[0]: fullPath = foo[0] else: fullPath = None # remove any unused substitution strings: for s in validPathSubstitutions + validFilenameSubstitutions: fullPath = fullPath.replace(f"%{s}", "") fullPath = os.path.normpath(fullPath) PathLog.track(fullPath) return fullPath