Example #1
0
    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
Example #2
0
    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
Example #3
0
 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()
Example #4
0
	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()
Example #5
0
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()
Example #6
0
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)
Example #7
0
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
Example #8
0
    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)
Example #10
0
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
Example #11
0
    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()
Example #12
0
    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]
Example #13
0
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)