def getCycleTime(self): seconds = 0 if len(self.obj.Operations.Group): for op in self.obj.Operations.Group: # Skip inactive operations if PathUtil.opProperty(op, "Active") is False: continue # Skip operations that don't have a cycletime attribute if PathUtil.opProperty(op, "CycleTime") is None: continue formattedCycleTime = PathUtil.opProperty(op, "CycleTime") opCycleTime = 0 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 Exception: continue if opCycleTime > 0: seconds = seconds + opCycleTime cycleTimeString = time.strftime("%H:%M:%S", time.gmtime(seconds)) self.obj.CycleTime = cycleTimeString
def getCycleTime(self): seconds = 0 for op in self.obj.Operations.Group: # Skip inactive operations if PathUtil.opProperty(op, 'Active') is False: continue # Skip operations that don't have a cycletime attribute if not PathUtil.opProperty(op, 'CycleTime') or PathUtil.opProperty(op, 'CycleTime') is None: continue formattedCycleTime = PathUtil.opProperty(op, 'CycleTime') 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: FreeCAD.Console.PrintWarning("Error converting the operations cycle time. Job Cycle time may be innacturate\n") continue if opCycleTime > 0: seconds = seconds + opCycleTime if seconds > 0: cycleTimeString = time.strftime("%H:%M:%S", time.gmtime(seconds)) else: cycleTimeString = translate('PathGui', 'Cycle Time Error') self.obj.CycleTime = cycleTimeString
def onJobChange(self): form = self.taskForm.form j = self.jobs[form.comboJobs.currentIndex()] self.job = j form.listOperations.clear() self.operations = [] for op in j.Operations.OutList: if PathUtil.opProperty(op, "Active"): listItem = QtGui.QListWidgetItem(op.ViewObject.Icon, op.Label) listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable) listItem.setCheckState(QtCore.Qt.CheckState.Checked) self.operations.append(op) form.listOperations.addItem(listItem) if self.initdone: self.SetupSimulation()
def Activated(self): PathLog.track() FreeCAD.ActiveDocument.openTransaction( translate("Path_Post", "Post Process the Selected path(s)")) FreeCADGui.addModule("PathScripts.PathPost") # Attempt to figure out what the user wants to post-process # If a job is selected, post that. # If there's only one job in a document, post it. # If a user has selected a subobject of a job, post the job. # If multiple jobs and can't guess, ask them. selected = FreeCADGui.Selection.getSelectionEx() if len(selected) > 1: FreeCAD.Console.PrintError( "Please select a single job or other path object\n") return elif len(selected) == 1: sel = selected[0].Object if sel.Name[:3] == "Job": job = sel elif hasattr(sel, "Path"): try: job = PathUtils.findParentJob(sel) except Exception: # pylint: disable=broad-except job = None else: job = None if job is None: targetlist = [] for o in FreeCAD.ActiveDocument.Objects: if hasattr(o, "Proxy"): if isinstance(o.Proxy, PathJob.ObjectJob): targetlist.append(o.Label) PathLog.debug("Possible post objects: {}".format(targetlist)) if len(targetlist) > 1: form = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobChooser.ui") form.cboProject.addItems(targetlist) r = form.exec_() if r is False: return else: jobname = form.cboProject.currentText() else: jobname = targetlist[0] job = FreeCAD.ActiveDocument.getObject(jobname) PathLog.debug("about to postprocess job: {}".format(job.Name)) # Build up an ordered list of operations and tool changes. # Then post-the ordered list if hasattr(job, "Fixtures"): wcslist = job.Fixtures else: wcslist = ['G54'] if hasattr(job, "OrderOutputBy"): orderby = job.OrderOutputBy else: orderby = "Operation" if hasattr(job, "SplitOutput"): split = job.SplitOutput else: split = False if hasattr(job, "OperatorSetupsheet"): ossheet = job.OperatorSetupsheet else: ossheet = False postlist = [] if orderby == 'Fixture': PathLog.debug("Ordering by Fixture") # Order by fixture means all operations and tool changes will be completed in one # fixture before moving to the next. currTool = None for index, f in enumerate(wcslist): # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if index != 0: c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist = [fobj] # Now generate the gcode for obj in job.Operations.Group: tc = PathUtil.toolControllerForOp(obj) if tc is not None and PathUtil.opProperty(obj, 'Active'): if tc.ToolNumber != currTool: sublist.append(tc) PathLog.debug("Appending TC: {}".format(tc.Name)) currTool = tc.ToolNumber sublist.append(obj) postlist.append(sublist) elif orderby == 'Tool': PathLog.debug("Ordering by Tool") # Order by tool means tool changes are minimized. # all operations with the current tool are processed in the current # fixture before moving to the next fixture. currTool = None fixturelist = [] for f in wcslist: # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path = Path.Path([c1, c2]) fobj.InList.append(job) fixturelist.append(fobj) # Now generate the gcode curlist = [] # list of ops for tool, will repeat for each fixture sublist = [] # list of ops for output splitting for idx, obj in enumerate(job.Operations.Group): # check if the operation is active active = PathUtil.opProperty(obj, 'Active') tc = PathUtil.toolControllerForOp(obj) if tc is None or tc.ToolNumber == currTool and active: curlist.append(obj) elif tc.ToolNumber != currTool and currTool is None and active: # first TC sublist.append(tc) curlist.append(obj) currTool = tc.ToolNumber elif tc.ToolNumber != currTool and currTool is not None and active: # TC for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) postlist.append(sublist) sublist = [tc] curlist = [obj] currTool = tc.ToolNumber if idx == len(job.Operations.Group) - 1: # Last operation. for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) postlist.append(sublist) elif orderby == 'Operation': PathLog.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. currTool = None firstFixture = True # Now generate the gcode for obj in job.Operations.Group: if PathUtil.opProperty(obj, 'Active'): sublist = [] PathLog.debug("obj: {}".format(obj.Name)) for f in wcslist: fobj = _TempObject() c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if not firstFixture: c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist.append(fobj) firstFixture = False tc = PathUtil.toolControllerForOp(obj) if tc is not None: if tc.ToolNumber != currTool: sublist.append(tc) currTool = tc.ToolNumber sublist.append(obj) postlist.append(sublist) fail = True rc = '' # pylint: disable=unused-variable if split: for slist in postlist: (fail, rc) = self.exportObjectsWith(slist, job) else: finalpostlist = [item for slist in postlist for item in slist] (fail, rc) = self.exportObjectsWith(finalpostlist, job) self.subpart = 1 if fail: FreeCAD.ActiveDocument.abortTransaction() else: FreeCAD.ActiveDocument.commitTransaction() if ossheet: FreeCAD.Console.PrintLog("Create OperatorSetupsheet\n") else: FreeCAD.Console.PrintLog("Do not create OperatorSetupsheet\n") FreeCAD.ActiveDocument.recompute()
def export(objectslist, filename, argstring): if not processArguments(argstring): return None global UNITS global UNIT_FORMAT global UNIT_SPEED_FORMAT global MOTION_MODE global SUPPRESS_COMMANDS print("Post Processor: " + __name__ + " postprocessing...") gcode = "" # write header if OUTPUT_HEADER: gcode += linenumber() + "(Exported by FreeCAD)\n" gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" gcode += linenumber() + "(Output Time:" + str(datetime.datetime.now()) + ")\n" # Check canned cycles for drilling if TRANSLATE_DRILL_CYCLES: if len(SUPPRESS_COMMANDS) == 0: SUPPRESS_COMMANDS = ['G99', 'G98', 'G80'] else: SUPPRESS_COMMANDS += ['G99', 'G98', 'G80'] # Write the preamble if OUTPUT_COMMENTS: gcode += linenumber() + "(Begin preamble)\n" for line in PREAMBLE.splitlines(True): gcode += linenumber() + line # verify if PREAMBLE have changed MOTION_MODE or UNITS if 'G90' in PREAMBLE: MOTION_MODE = 'G90' elif 'G91' in PREAMBLE: MOTION_MODE = 'G91' else: gcode += linenumber() + MOTION_MODE + "\n" if 'G21' in PREAMBLE: UNITS = 'G21' UNIT_FORMAT = 'mm' UNIT_SPEED_FORMAT = 'mm/min' elif 'G20' in PREAMBLE: UNITS = 'G20' UNIT_FORMAT = 'in' UNIT_SPEED_FORMAT = 'in/min' else: gcode += linenumber() + UNITS + "\n" for obj in objectslist: # Debug... # print("\n" + "*"*70) # dump(obj) # print("*"*70 + "\n") if not hasattr(obj, "Path"): print("The object " + obj.Name + " is not a path. Please select only path and Compounds.") return # Skip inactive operations if PathUtil.opProperty(obj, 'Active') is False: continue # do the pre_op if OUTPUT_BCNC: gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" gcode += linenumber() + "(Block-expand: 0)\n" gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line # get coolant mode coolantMode = 'None' if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"): if hasattr(obj, "CoolantMode"): coolantMode = obj.CoolantMode else: coolantMode = obj.Base.CoolantMode # turn coolant on if required if OUTPUT_COMMENTS: if not coolantMode == 'None': gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' if coolantMode == 'Flood': gcode += linenumber() + 'M8' + '\n' if coolantMode == 'Mist': gcode += linenumber() + 'M7' + '\n' # Parse the op gcode += parse(obj) # do the post_op if OUTPUT_COMMENTS: gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # turn coolant off if required if not coolantMode == 'None': if OUTPUT_COMMENTS: gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' gcode += linenumber() +'M9' + '\n' # do the post_amble if OUTPUT_BCNC: gcode += linenumber() + "(Block-name: post_amble)\n" gcode += linenumber() + "(Block-expand: 0)\n" gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: gcode += linenumber() + "(Begin postamble)\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line if RETURN_TO: gcode += linenumber() + "G0 X%s Y%s" % tuple(RETURN_TO) # show the gCode result dialog if FreeCAD.GuiUp and SHOW_EDITOR: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(gcode) result = dia.exec_() if result: final = dia.editor.toPlainText() else: final = gcode else: final = gcode print("Done postprocessing.") # write the file gfile = pythonopen(filename, "w") gfile.write(final) gfile.close()
def export(objectslist, filename, argstring): if not processArguments(argstring): return None global UNITS global UNIT_FORMAT global UNIT_FEED_FORMAT global MOTION_MODE global SUPPRESS_COMMANDS print('Post Processor: ' + __name__ + ' postprocessing...') gcode = '' # Write header: if OUTPUT_HEADER: gcode += linenumber() + '(Exported by FreeCAD)\n' gcode += linenumber() + '(Post Processor: ' + __name__ gcode += '.py, version: ' + Revised + ')\n' gcode += linenumber() + '(Output Time:' + str(datetime.now()) + ')\n' # Suppress drill-cycle commands: if TRANSLATE_DRILL_CYCLES: SUPPRESS_COMMANDS += ['G80', 'G98', 'G99'] # Write the preamble: if OUTPUT_COMMENTS: gcode += linenumber() + '(Begin preamble)\n' for line in PREAMBLE.splitlines(True): gcode += linenumber() + line # Write these settings AFTER the preamble, # to prevent the preamble from changing these: if OUTPUT_COMMENTS: gcode += linenumber() + '(Default Configuration)\n' gcode += linenumber() + MOTION_MODE + '\n' gcode += linenumber() + UNITS + '\n' gcode += linenumber() + WORK_PLANE + '\n' for obj in objectslist: # Debug... # print('\n' + '*'*70 + '\n') # dump(obj) # print('\n' + '*'*70 + '\n') if not hasattr(obj, 'Path'): print('The object ' + obj.Name + ' is not a path. Please select only path and Compounds.') return # Skip inactive operations: if PathUtil.opProperty(obj, 'Active') is False: continue # Do the pre_op: if OUTPUT_BCNC: gcode += linenumber() + '(Block-name: ' + obj.Label + ')\n' gcode += linenumber() + '(Block-expand: 0)\n' gcode += linenumber() + '(Block-enable: 1)\n' if OUTPUT_COMMENTS: gcode += linenumber() + '(Begin operation: ' + obj.Label + ')\n' for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line # Get coolant mode: coolantMode = 'None' # None is the word returned from the operation if hasattr(obj, 'CoolantMode') or hasattr(obj, 'Base') and \ hasattr(obj.Base, 'CoolantMode'): if hasattr(obj, 'CoolantMode'): coolantMode = obj.CoolantMode else: coolantMode = obj.Base.CoolantMode # Turn coolant on if required: if OUTPUT_COMMENTS: if not coolantMode == 'None': gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' if coolantMode == 'Flood': gcode += linenumber() + 'M8\n' if coolantMode == 'Mist': gcode += linenumber() + 'M7\n' # Parse the op: gcode += parse(obj) # Do the post_op: if OUTPUT_COMMENTS and OUTPUT_FINISH: gcode += linenumber() + '(Finish operation: ' + obj.Label + ')\n' for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # Turn coolant off if previously enabled: if not coolantMode == 'None': if OUTPUT_COMMENTS: gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' gcode += linenumber() + 'M9\n' # Do the post_amble: if OUTPUT_BCNC: gcode += linenumber() + '(Block-name: post_amble)\n' gcode += linenumber() + '(Block-expand: 0)\n' gcode += linenumber() + '(Block-enable: 1)\n' if OUTPUT_COMMENTS: gcode += linenumber() + '(Begin postamble)\n' for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line # Optionally add a final XYZ position to the end of the gcode: if RETURN_TO: first_comma = RETURN_TO.find(',') last_comma = RETURN_TO.rfind(',') # == first_comma if only one comma ref_X = ' X' + RETURN_TO[0:first_comma].strip() # Z is optional: if last_comma != first_comma: ref_Z = ' Z' + RETURN_TO[last_comma + 1:].strip() ref_Y = ' Y' + RETURN_TO[first_comma + 1:last_comma].strip() else: ref_Z = '' ref_Y = ' Y' + RETURN_TO[first_comma + 1:].strip() gcode += linenumber() + 'G0' + ref_X + ref_Y + ref_Z + '\n' # Optionally add recommended Marlin 2.x configuration to gcode file: if OUTPUT_MARLIN_CONFIG: gcode += linenumber() + '(Marlin 2.x Configuration)\n' gcode += linenumber() + '(The following should be enabled in)\n' gcode += linenumber() + '(the configuration files of Marlin 2.x)\n' gcode += linenumber() + '(#define ARC_SUPPORT)\n' gcode += linenumber() + '(#define CNC_COORDINATE_SYSTEMS)\n' gcode += linenumber() + '(#define PAREN_COMMENTS)\n' gcode += linenumber() + '(#define GCODE_MOTION_MODES)\n' gcode += linenumber() + '(#define G0_FEEDRATE)\n' gcode += linenumber() + '(define VARIABLE_G0_FEEDRATE)\n' # Show the gcode result dialog: if FreeCAD.GuiUp and SHOW_EDITOR: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(gcode) result = dia.exec_() if result: final = dia.editor.toPlainText() else: final = gcode else: final = gcode print('Done postprocessing.') # Write the file: with open(filename, 'w') as fp: fp.write(final)
def export(objectslist, filename, argstring): # pylint: disable=global-statement if not processArguments(argstring): return None global UNITS global UNIT_FORMAT global UNIT_SPEED_FORMAT for obj in objectslist: if not hasattr(obj, "Path"): print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") return None print("postprocessing...") gcode = "" # write header if OUTPUT_HEADER: gcode += linenumber() + "(Exported by FreeCAD)\n" gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" gcode += linenumber() + "(Output Time:" + str(now) + ")\n" # Write the preamble if OUTPUT_COMMENTS: gcode += linenumber() + "(begin preamble)\n" for line in PREAMBLE.splitlines(False): gcode += linenumber() + line + "\n" gcode += linenumber() + UNITS + "\n" for obj in objectslist: # Skip inactive operations if not PathUtil.opProperty(obj, 'Active'): continue # fetch machine details job = PathUtils.findParentJob(obj) myMachine = 'not set' if hasattr(job, "MachineName"): myMachine = job.MachineName if hasattr(job, "MachineUnits"): if job.MachineUnits == "Metric": UNITS = "G21" UNIT_FORMAT = 'mm' UNIT_SPEED_FORMAT = 'mm/min' else: UNITS = "G20" UNIT_FORMAT = 'in' UNIT_SPEED_FORMAT = 'in/min' # do the pre_op if OUTPUT_COMMENTS: gcode += linenumber() + "(begin operation: %s)\n" % obj.Label gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT) for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line # get coolant mode coolantMode = 'None' if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"): if hasattr(obj, "CoolantMode"): coolantMode = obj.CoolantMode else: coolantMode = obj.Base.CoolantMode # turn coolant on if required if OUTPUT_COMMENTS: if not coolantMode == 'None': gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' if coolantMode == 'Flood': gcode += linenumber() + 'M8' + '\n' if coolantMode == 'Mist': gcode += linenumber() + 'M7' + '\n' # process the operation gcode gcode += parse(obj) # do the post_op if OUTPUT_COMMENTS: gcode += linenumber() + "(finish operation: %s)\n" % obj.Label for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # turn coolant off if required if not coolantMode == 'None': if OUTPUT_COMMENTS: gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' gcode += linenumber() +'M9' + '\n' # do the post_amble if OUTPUT_COMMENTS: gcode += "(begin postamble)\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line if FreeCAD.GuiUp and SHOW_EDITOR: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(gcode) result = dia.exec_() if result: final = dia.editor.toPlainText() else: final = gcode else: final = gcode print("done postprocessing.") if not filename == '-': gfile = pythonopen(filename, "w") gfile.write(final) gfile.close() return final
def execute(self): if ( not self.baseOp or not self.baseOp.isDerivedFrom("Path::Feature") or not self.baseOp.Path ): return None if len(self.baseOp.Path.Commands) == 0: PathLog.warning("No Path Commands for %s" % self.baseOp.Label) return [] tc = PathDressup.toolController(self.baseOp) self.safeHeight = float(PathUtil.opProperty(self.baseOp, "SafeHeight")) self.clearanceHeight = float( PathUtil.opProperty(self.baseOp, "ClearanceHeight") ) self.strG0ZsafeHeight = Path.Command( # was a Feed rate with G1 "G0", {"Z": self.safeHeight, "F": tc.VertRapid.Value} ) self.strG0ZclearanceHeight = Path.Command("G0", {"Z": self.clearanceHeight}) cmd = self.baseOp.Path.Commands[0] pos = cmd.Placement.Base # bogus m/c position to create first edge bogusX = True bogusY = True commands = [cmd] lastExit = None for cmd in self.baseOp.Path.Commands[1:]: if cmd.Name in PathGeom.CmdMoveAll: if bogusX: bogusX = "X" not in cmd.Parameters if bogusY: bogusY = "Y" not in cmd.Parameters edge = PathGeom.edgeForCmd(cmd, pos) if edge: inside = edge.common(self.boundary).Edges outside = edge.cut(self.boundary).Edges if not self.inside: # UI "inside boundary" param tmp = inside inside = outside outside = tmp # it's really a shame that one cannot trust the sequence and/or # orientation of edges if 1 == len(inside) and 0 == len(outside): PathLog.track(_vstr(pos), _vstr(lastExit), " + ", cmd) # cmd fully included by boundary if lastExit: if not ( bogusX or bogusY ): # don't insert false paths based on bogus m/c position commands.extend( self.boundaryCommands( lastExit, pos, tc.VertFeed.Value ) ) lastExit = None commands.append(cmd) pos = PathGeom.commandEndPoint(cmd, pos) elif 0 == len(inside) and 1 == len(outside): PathLog.track(_vstr(pos), _vstr(lastExit), " - ", cmd) # cmd fully excluded by boundary if not lastExit: lastExit = pos pos = PathGeom.commandEndPoint(cmd, pos) else: PathLog.track( _vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd ) # cmd pierces boundary while inside or outside: ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] PathLog.track(ie) if ie: e = ie[0] LastPt = e.valueAt(e.LastParameter) flip = PathGeom.pointsCoincide(pos, LastPt) newPos = e.valueAt(e.FirstParameter) if flip else LastPt # inside edges are taken at this point (see swap of inside/outside # above - so we can just connect the dots ... if lastExit: if not (bogusX or bogusY): commands.extend( self.boundaryCommands( lastExit, pos, tc.VertFeed.Value ) ) lastExit = None PathLog.track(e, flip) if not ( bogusX or bogusY ): # don't insert false paths based on bogus m/c position commands.extend( PathGeom.cmdsForEdge( e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value, ) ) inside.remove(e) pos = newPos lastExit = newPos else: oe = [ e for e in outside if PathGeom.edgeConnectsTo(e, pos) ] PathLog.track(oe) if oe: e = oe[0] ptL = e.valueAt(e.LastParameter) flip = PathGeom.pointsCoincide(pos, ptL) newPos = ( e.valueAt(e.FirstParameter) if flip else ptL ) # outside edges are never taken at this point (see swap of # inside/outside above) - so just move along ... outside.remove(e) pos = newPos else: PathLog.error("huh?") import Part Part.show(Part.Vertex(pos), "pos") for e in inside: Part.show(e, "ei") for e in outside: Part.show(e, "eo") raise Exception("This is not supposed to happen") # Eif # Eif # Ewhile # Eif # pos = PathGeom.commandEndPoint(cmd, pos) # Eif else: PathLog.track("no-move", cmd) commands.append(cmd) if lastExit: commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value)) lastExit = None PathLog.track(commands) return Path.Path(commands)
def execute(self, obj): if not obj.Base or not obj.Base.isDerivedFrom('Path::Feature') or not obj.Base.Path: return tc = PathDressup.toolController(obj.Base) if len(obj.Base.Path.Commands) > 0: self.safeHeight = float(PathUtil.opProperty(obj.Base, 'SafeHeight')) self.clearanceHeight = float(PathUtil.opProperty(obj.Base, 'ClearanceHeight')) boundary = obj.Stock.Shape cmd = obj.Base.Path.Commands[0] pos = cmd.Placement.Base commands = [cmd] lastExit = None for cmd in obj.Base.Path.Commands[1:]: if cmd.Name in PathGeom.CmdMoveAll: edge = PathGeom.edgeForCmd(cmd, pos) inside = edge.common(boundary).Edges outside = edge.cut(boundary).Edges if not obj.Inside: t = inside inside = outside outside = t # it's really a shame that one cannot trust the sequence and/or # orientation of edges if 1 == len(inside) and 0 == len(outside): PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd) # cmd fully included by boundary if lastExit: commands.extend(self.boundaryCommands(obj, lastExit, pos, tc.VertFeed.Value)) lastExit = None commands.append(cmd) pos = PathGeom.commandEndPoint(cmd, pos) elif 0 == len(inside) and 1 == len(outside): PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd) # cmd fully excluded by boundary if not lastExit: lastExit = pos pos = PathGeom.commandEndPoint(cmd, pos) else: PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) # cmd pierces boundary while inside or outside: ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] PathLog.track(ie) if ie: e = ie[0] ptL = e.valueAt(e.LastParameter) flip = PathGeom.pointsCoincide(pos, ptL) newPos = e.valueAt(e.FirstParameter) if flip else ptL # inside edges are taken at this point (see swap of inside/outside # above - so we can just connect the dots ... if lastExit: commands.extend(self.boundaryCommands(obj, lastExit, pos, tc.VertFeed.Value)) lastExit = None PathLog.track(e, flip) commands.extend(PathGeom.cmdsForEdge(e, flip, False)) inside.remove(e) pos = newPos lastExit = newPos else: oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)] PathLog.track(oe) if oe: e = oe[0] ptL = e.valueAt(e.LastParameter) flip = PathGeom.pointsCoincide(pos, ptL) newPos = e.valueAt(e.FirstParameter) if flip else ptL # outside edges are never taken at this point (see swap of # inside/oustide above) - so just move along ... outside.remove(e) pos = newPos else: PathLog.error('huh?') import Part Part.show(Part.Vertex(pos), 'pos') for e in inside: Part.show(e, 'ei') for e in outside: Part.show(e, 'eo') raise Exception('This is not supposed to happen') #pos = PathGeom.commandEndPoint(cmd, pos) else: PathLog.track('no-move', cmd) commands.append(cmd) if lastExit: commands.extend(self.boundaryCommands(obj, lastExit, None, tc.VertFeed.Value)) lastExit = None else: PathLog.warning("No Path Commands for %s" % obj.Base.Label) commands = [] PathLog.track(commands) obj.Path = Path.Path(commands)
def buildPostList(job): """Takes the job and determines the specific objects and order to postprocess Returns a list of objects which can be passed to exportObjectsWith() for final posting.""" wcslist = job.Fixtures orderby = job.OrderOutputBy postlist = [] if orderby == "Fixture": PathLog.debug("Ordering by Fixture") # Order by fixture means all operations and tool changes will be # completed in one fixture before moving to the next. currTool = None for index, f in enumerate(wcslist): # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if index != 0: c2 = Path.Command( "G0 Z" + str( job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value ) ) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist = [fobj] # Now generate the gcode for obj in job.Operations.Group: tc = PathUtil.toolControllerForOp(obj) if tc is not None and PathUtil.opProperty(obj, "Active"): if tc.ToolNumber != currTool: sublist.append(tc) PathLog.debug("Appending TC: {}".format(tc.Name)) currTool = tc.ToolNumber sublist.append(obj) postlist.append((f, sublist)) elif orderby == "Tool": PathLog.debug("Ordering by Tool") # Order by tool means tool changes are minimized. # all operations with the current tool are processed in the current # fixture before moving to the next fixture. toolstring = "None" currTool = None # Build the fixture list fixturelist = [] for f in wcslist: # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) c2 = Path.Command( "G0 Z" + str( job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value ) ) fobj.Path = Path.Path([c1, c2]) fobj.InList.append(job) fixturelist.append(fobj) # Now generate the gcode curlist = [] # list of ops for tool, will repeat for each fixture sublist = [] # list of ops for output splitting PathLog.track(job.PostProcessorOutputFile) for idx, obj in enumerate(job.Operations.Group): PathLog.track(obj.Label) # check if the operation is active if not getattr(obj, "Active", True): PathLog.track() continue # Determine the proper string for the Op's TC tc = PathUtil.toolControllerForOp(obj) if tc is None: tcstring = "None" elif "%T" in job.PostProcessorOutputFile: tcstring = f"{tc.ToolNumber}" else: tcstring = re.sub(r"[^\w\d-]", "_", tc.Label) PathLog.track(toolstring) if tc is None or tc.ToolNumber == currTool: curlist.append(obj) elif tc.ToolNumber != currTool and currTool is None: # first TC sublist.append(tc) curlist.append(obj) currTool = tc.ToolNumber toolstring = tcstring elif tc.ToolNumber != currTool and currTool is not None: # TC for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) postlist.append((toolstring, sublist)) sublist = [tc] curlist = [obj] currTool = tc.ToolNumber toolstring = tcstring if idx == len(job.Operations.Group) - 1: # Last operation. for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) postlist.append((toolstring, sublist)) elif orderby == "Operation": PathLog.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. currTool = None firstFixture = True # Now generate the gcode for obj in job.Operations.Group: # check if the operation is active if not getattr(obj, "Active", True): continue sublist = [] PathLog.debug("obj: {}".format(obj.Name)) for f in wcslist: fobj = _TempObject() c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if not firstFixture: c2 = Path.Command( "G0 Z" + str( job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value ) ) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist.append(fobj) firstFixture = False tc = PathUtil.toolControllerForOp(obj) if tc is not None: if job.SplitOutput or (tc.ToolNumber != currTool): sublist.append(tc) currTool = tc.ToolNumber sublist.append(obj) postlist.append((obj.Label, sublist)) if job.SplitOutput: PathLog.track() return postlist else: PathLog.track() finalpostlist = [ ("allitems", [item for slist in postlist for item in slist[1]]) ] return finalpostlist
def Activated(self): PathLog.track() FreeCAD.ActiveDocument.openTransaction( "Post Process the Selected path(s)") FreeCADGui.addModule("PathScripts.PathPost") # Attempt to figure out what the user wants to post-process # If a job is selected, post that. # If there's only one job in a document, post it. # If a user has selected a subobject of a job, post the job. # If multiple jobs and can't guess, ask them. selected = FreeCADGui.Selection.getSelectionEx() if len(selected) > 1: FreeCAD.Console.PrintError( "Please select a single job or other path object\n") return elif len(selected) == 1: sel = selected[0].Object if sel.Name[:3] == "Job": job = sel elif hasattr(sel, "Path"): try: job = PathUtils.findParentJob(sel) except Exception: job = None else: job = None if job is None: targetlist = [] for o in FreeCAD.ActiveDocument.Objects: if hasattr(o, "Proxy"): if isinstance(o.Proxy, PathJob.ObjectJob): targetlist.append(o.Label) PathLog.debug("Possible post objects: {}".format(targetlist)) if len(targetlist) > 1: jobname, result = QtGui.QInputDialog.getItem( None, translate("Path", "Choose a Path Job"), None, targetlist) if result is False: return else: jobname = targetlist[0] job = FreeCAD.ActiveDocument.getObject(jobname) PathLog.debug("about to postprocess job: {}".format(job.Name)) wcslist = job.Fixtures orderby = job.OrderOutputBy split = job.SplitOutput postlist = [] if orderby == "Fixture": PathLog.debug("Ordering by Fixture") # Order by fixture means all operations and tool changes will be completed in one # fixture before moving to the next. currTool = None for index, f in enumerate(wcslist): # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if index != 0: c2 = Path.Command( "G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist = [fobj] # Now generate the gcode for obj in job.Operations.Group: tc = PathUtil.toolControllerForOp(obj) if tc is not None and PathUtil.opProperty(obj, "Active"): if tc.ToolNumber != currTool or split is True: sublist.append(tc) PathLog.debug("Appending TC: {}".format(tc.Name)) currTool = tc.ToolNumber sublist.append(obj) postlist.append(sublist) elif orderby == "Tool": PathLog.debug("Ordering by Tool") # Order by tool means tool changes are minimized. # all operations with the current tool are processed in the current # fixture before moving to the next fixture. currTool = None fixturelist = [] for f in wcslist: # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) c2 = Path.Command( "G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path = Path.Path([c1, c2]) fobj.InList.append(job) fixturelist.append(fobj) # Now generate the gcode curlist = [] # list of ops for tool, will repeat for each fixture sublist = [] # list of ops for output splitting for idx, obj in enumerate(job.Operations.Group): # check if the operation is active active = PathUtil.opProperty(obj, "Active") tc = PathUtil.toolControllerForOp(obj) if tc is None or tc.ToolNumber == currTool and active: curlist.append(obj) elif (tc.ToolNumber != currTool and currTool is None and active): # first TC sublist.append(tc) curlist.append(obj) currTool = tc.ToolNumber elif (tc.ToolNumber != currTool and currTool is not None and active): # TC for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) postlist.append(sublist) sublist = [tc] curlist = [obj] currTool = tc.ToolNumber if idx == len(job.Operations.Group) - 1: # Last operation. for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) postlist.append(sublist) elif orderby == "Operation": PathLog.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. currTool = None firstFixture = True # Now generate the gcode for obj in job.Operations.Group: if PathUtil.opProperty(obj, "Active"): sublist = [] PathLog.debug("obj: {}".format(obj.Name)) for f in wcslist: fobj = _TempObject() c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if not firstFixture: c2 = Path.Command("G0 Z" + str( job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist.append(fobj) firstFixture = False tc = PathUtil.toolControllerForOp(obj) if tc is not None: if job.SplitOutput or (tc.ToolNumber != currTool): sublist.append(tc) currTool = tc.ToolNumber sublist.append(obj) postlist.append(sublist) fail = True rc = "" if split: for slist in postlist: (fail, rc, filename) = self.exportObjectsWith(slist, job) if fail: break else: finalpostlist = [item for slist in postlist for item in slist] (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()
def buildPostList(self, job): """ Parses the job and returns the list(s) of objects to be written by the post Postlist is a list of lists. Each sublist is intended to be a separate file """ orderby = job.OrderOutputBy fixturelist = [] for f in job.Fixtures: # create an object to serve as the fixture path fobj = _TempObject() fobj.Label = f c1 = Path.Command(f) c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) fobj.Path = Path.Path([c1, c2]) # fobj.InList.append(job) fixturelist.append(fobj) postlist = [] if orderby == "Fixture": PathLog.debug("Ordering by Fixture") # Order by fixture means all operations and tool changes will be completed in one # fixture before moving to the next. for f in fixturelist: scratchpad = [(f, None)] # Now generate the gcode for obj in job.Operations.Group: if not PathUtil.opProperty(obj, "Active"): continue tc = PathUtil.toolControllerForOp(obj) scratchpad.append((obj, tc)) sublist = [] temptool = None for item in scratchpad: if item[1] in [temptool, None]: sublist.append(item[0]) else: sublist.append(item[1]) temptool = item[1] sublist.append(item[0]) postlist.append(sublist) elif orderby == "Tool": PathLog.debug("Ordering by Tool") # Order by tool means tool changes are minimized. # all operations with the current tool are processed in the current # fixture before moving to the next fixture. currTool = None # Now generate the gcode curlist = [] # list of ops for tool, will repeat for each fixture # sublist = [] # list of ops for output splitting for idx, obj in enumerate(job.Operations.Group): # check if the operation is active active = PathUtil.opProperty(obj, "Active") tc = PathUtil.toolControllerForOp(obj) if not active: # pass on any inactive ops continue if tc is None: curlist.append((obj, None)) continue if tc == currTool: curlist.append((obj, tc)) continue if tc != currTool and currTool is None: # first TC currTool = tc curlist.append((obj, tc)) continue if tc != currTool and currTool is not None: # TC changed if tc.ToolNumber == currTool.ToolNumber: # Same tool /diff params curlist.append((obj, tc)) currTool = tc else: # Actual Toolchange # dump current state to postlist sublist = [] t = None for fixture in fixturelist: sublist.append(fixture) for item in curlist: if item[1] == t: sublist.append(item[0]) else: sublist.append(item[1]) t = item[1] sublist.append(item[0]) postlist.append(sublist) # set up for next tool group currTool = tc curlist = [(obj, tc)] # flush remaining curlist to output sublist = [] t = None for fixture in fixturelist: sublist.append(fixture) for item in curlist: if item[1] == t: sublist.append(item[0]) else: sublist.append(item[1]) t = item[1] sublist.append(item[0]) postlist.append(sublist) elif orderby == "Operation": PathLog.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. currTool = None # firstFixture = True # Now generate the gcode for obj in job.Operations.Group: scratchpad = [] tc = PathUtil.toolControllerForOp(obj) if not PathUtil.opProperty(obj, "Active"): continue PathLog.debug("obj: {}".format(obj.Name)) for f in fixturelist: scratchpad.append((f, None)) scratchpad.append((obj, tc)) sublist = [] temptool = None for item in scratchpad: if item[1] in [temptool, None]: sublist.append(item[0]) else: sublist.append(item[1]) temptool = item[1] sublist.append(item[0]) postlist.append(sublist) if job.SplitOutput: return postlist else: finalpostlist = [item for slist in postlist for item in slist] return [finalpostlist]
def export(objectslist, filename, argstring): if not processArguments(argstring): return None global UNITS global UNIT_FORMAT global UNIT_FEED_FORMAT global MOTION_MODE global SUPPRESS_COMMANDS print("Post Processor: " + __name__ + " postprocessing...") gcode = "" # Write header: if OUTPUT_HEADER: gcode += linenumber() + "(Exported by FreeCAD)\n" gcode += linenumber() + "(Post Processor: " + __name__ gcode += ".py, version: " + Revised + ")\n" gcode += linenumber() + "(Output Time:" + str(datetime.now()) + ")\n" # Suppress drill-cycle commands: if TRANSLATE_DRILL_CYCLES: SUPPRESS_COMMANDS += ["G80", "G98", "G99"] # Write the preamble: if OUTPUT_COMMENTS: gcode += linenumber() + "(Begin preamble)\n" for line in PREAMBLE.splitlines(True): gcode += linenumber() + line # Write these settings AFTER the preamble, # to prevent the preamble from changing these: if OUTPUT_COMMENTS: gcode += linenumber() + "(Default Configuration)\n" gcode += linenumber() + MOTION_MODE + "\n" gcode += linenumber() + UNITS + "\n" gcode += linenumber() + WORK_PLANE + "\n" for obj in objectslist: # Debug... # print('\n' + '*'*70 + '\n') # dump(obj) # print('\n' + '*'*70 + '\n') if not hasattr(obj, "Path"): print( "The object " + obj.Name + " is not a path. Please select only path and Compounds." ) return # Skip inactive operations: if PathUtil.opProperty(obj, "Active") is False: continue # Do the pre_op: if OUTPUT_BCNC: gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" gcode += linenumber() + "(Block-expand: 0)\n" gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line # Get coolant mode: coolantMode = "None" # None is the word returned from the operation if ( hasattr(obj, "CoolantMode") or hasattr(obj, "Base") and hasattr(obj.Base, "CoolantMode") ): if hasattr(obj, "CoolantMode"): coolantMode = obj.CoolantMode else: coolantMode = obj.Base.CoolantMode # Turn coolant on if required: if OUTPUT_COMMENTS: if not coolantMode == "None": gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n" if coolantMode == "Flood": gcode += linenumber() + "M8\n" if coolantMode == "Mist": gcode += linenumber() + "M7\n" # Parse the op: gcode += parse(obj) # Do the post_op: if OUTPUT_COMMENTS and OUTPUT_FINISH: gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # Turn coolant off if previously enabled: if not coolantMode == "None": if OUTPUT_COMMENTS: gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n" gcode += linenumber() + "M9\n" # Do the post_amble: if OUTPUT_BCNC: gcode += linenumber() + "(Block-name: post_amble)\n" gcode += linenumber() + "(Block-expand: 0)\n" gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: gcode += linenumber() + "(Begin postamble)\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line # Optionally add a final XYZ position to the end of the gcode: if RETURN_TO: first_comma = RETURN_TO.find(",") last_comma = RETURN_TO.rfind(",") # == first_comma if only one comma ref_X = " X" + RETURN_TO[0:first_comma].strip() # Z is optional: if last_comma != first_comma: ref_Z = " Z" + RETURN_TO[last_comma + 1 :].strip() ref_Y = " Y" + RETURN_TO[first_comma + 1 : last_comma].strip() else: ref_Z = "" ref_Y = " Y" + RETURN_TO[first_comma + 1 :].strip() gcode += linenumber() + "G0" + ref_X + ref_Y + ref_Z + "\n" # Optionally add recommended Marlin 2.x configuration to gcode file: if OUTPUT_MARLIN_CONFIG: gcode += linenumber() + "(Marlin 2.x Configuration)\n" gcode += linenumber() + "(The following should be enabled in)\n" gcode += linenumber() + "(the configuration files of Marlin 2.x)\n" gcode += linenumber() + "(#define ARC_SUPPORT)\n" gcode += linenumber() + "(#define CNC_COORDINATE_SYSTEMS)\n" gcode += linenumber() + "(#define PAREN_COMMENTS)\n" gcode += linenumber() + "(#define GCODE_MOTION_MODES)\n" gcode += linenumber() + "(#define G0_FEEDRATE)\n" gcode += linenumber() + "(define VARIABLE_G0_FEEDRATE)\n" # Show the gcode result dialog: if FreeCAD.GuiUp and SHOW_EDITOR: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(gcode) result = dia.exec_() if result: final = dia.editor.toPlainText() else: final = gcode else: final = gcode print("Done postprocessing.") # Write the file: with open(filename, "w") as fp: fp.write(final)