Moved post processing files into new Path python module
This commit is contained in:
@@ -28,7 +28,7 @@ import FreeCADGui
|
||||
import Mesh
|
||||
import Path
|
||||
import PathScripts
|
||||
import PathScripts.PathPost as PathPost
|
||||
import Path.Post.Command as PathPost
|
||||
import camotics
|
||||
import io
|
||||
import json
|
||||
|
||||
@@ -60,7 +60,7 @@ def Startup():
|
||||
from PathScripts import PathMillFaceGui
|
||||
from PathScripts import PathPocketGui
|
||||
from PathScripts import PathPocketShapeGui
|
||||
from PathScripts import PathPost
|
||||
from Path.Post import Command
|
||||
from PathScripts import PathProbeGui
|
||||
from PathScripts import PathProfileGui
|
||||
from PathScripts import PathPropertyBagGui
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from PathScripts.PathPostProcessor import PostProcessor
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import FreeCAD
|
||||
import Path
|
||||
import Path.Post.Processor as PostProcessor
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathSetupSheet as PathSetupSheet
|
||||
import PathScripts.PathStock as PathStock
|
||||
|
||||
@@ -1,630 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
""" Post Process command that will make use of the Output File and Post Processor entries in PathJob """
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
import PathScripts.PathJob as PathJob
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import os
|
||||
import re
|
||||
|
||||
from PathScripts.PathPostProcessor import PostProcessor
|
||||
from PySide import QtCore, QtGui
|
||||
from datetime import datetime
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
LOG_MODULE = PathLog.thisModule()
|
||||
|
||||
if True:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class _TempObject:
|
||||
Path = None
|
||||
Name = "Fixture"
|
||||
InList = []
|
||||
Label = "Fixture"
|
||||
|
||||
|
||||
def processFileNameSubstitutions(
|
||||
job,
|
||||
subpartname,
|
||||
sequencenumber,
|
||||
outputpath,
|
||||
filename,
|
||||
ext,
|
||||
):
|
||||
"""Process any substitutions in the outputpath or filename."""
|
||||
|
||||
# The following section allows substitution within the path part
|
||||
Path.Log.track(f"path before substitution: {outputpath}")
|
||||
|
||||
if "%D" in outputpath: # Directory of active document
|
||||
D = FreeCAD.ActiveDocument.FileName
|
||||
if D:
|
||||
D = os.path.dirname(D)
|
||||
# in case the document is in the current working directory
|
||||
if not D:
|
||||
D = "."
|
||||
else:
|
||||
FreeCAD.Console.PrintError(
|
||||
"Please save document in order to resolve output path!\n"
|
||||
)
|
||||
return None
|
||||
outputpath = outputpath.replace("%D", D)
|
||||
|
||||
if "%M" in outputpath:
|
||||
M = FreeCAD.getUserMacroDir()
|
||||
outputpath = outputpath.replace("%M", M)
|
||||
|
||||
# Use the file label
|
||||
if "%d" in outputpath:
|
||||
d = FreeCAD.ActiveDocument.Label
|
||||
outputpath = outputpath.replace("%d", d)
|
||||
|
||||
# Use the name of the active job object
|
||||
if "%j" in outputpath:
|
||||
j = job.Label
|
||||
outputpath = outputpath.replace("%j", j)
|
||||
|
||||
Path.Log.track(f"path after substitution: {outputpath}")
|
||||
|
||||
# The following section allows substitution within the filename part
|
||||
Path.Log.track(f"filename before substitution: {filename}")
|
||||
|
||||
# Use the file label
|
||||
if "%d" in filename:
|
||||
d = FreeCAD.ActiveDocument.Label
|
||||
filename = filename.replace("%d", d)
|
||||
|
||||
# Use the name of the active job object
|
||||
if "%j" in filename:
|
||||
j = job.Label
|
||||
filename = filename.replace("%j", j)
|
||||
|
||||
# Use the sequence number if explicitly called
|
||||
if "%S" in filename:
|
||||
j = job.Label
|
||||
filename = filename.replace("%S", str(sequencenumber))
|
||||
|
||||
# This section handles unique names for splitting output
|
||||
if job.SplitOutput:
|
||||
Path.Log.track()
|
||||
if "%T" in filename and job.OrderOutputBy == "Tool":
|
||||
filename = filename.replace("%T", subpartname)
|
||||
|
||||
if "%t" in filename and job.OrderOutputBy == "Tool":
|
||||
filename = filename.replace("%t", subpartname)
|
||||
|
||||
if "%W" in filename and job.OrderOutputBy == "Fixture":
|
||||
filename = filename.replace("%W", subpartname)
|
||||
|
||||
if "%O" in filename and job.OrderOutputBy == "Operation":
|
||||
filename = filename.replace("%O", subpartname)
|
||||
|
||||
if (
|
||||
"%S" in filename
|
||||
): # We always add a sequence number but the user can say where
|
||||
filename = filename.replace("%S", str(sequencenumber))
|
||||
else:
|
||||
filename = f"{filename}-{sequencenumber}"
|
||||
|
||||
Path.Log.track(f"filename after substitution: {filename}")
|
||||
|
||||
if not ext:
|
||||
ext = ".nc"
|
||||
Path.Log.track(f"file extension: {ext}")
|
||||
|
||||
fullPath = f"{outputpath}{os.path.sep}{filename}{ext}"
|
||||
|
||||
Path.Log.track(f"full filepath: {fullPath}")
|
||||
return fullPath
|
||||
|
||||
|
||||
def resolveFileName(job, subpartname, sequencenumber):
|
||||
Path.Log.track(subpartname, sequencenumber)
|
||||
|
||||
validPathSubstitutions = ["D", "d", "M", "j"]
|
||||
validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"]
|
||||
|
||||
# Look for preference default
|
||||
outputpath, filename = os.path.split(PathPreferences.defaultOutputFile())
|
||||
filename, ext = os.path.splitext(filename)
|
||||
|
||||
# Override with document default if it exists
|
||||
if job.PostProcessorOutputFile:
|
||||
matchstring = job.PostProcessorOutputFile
|
||||
candidateOutputPath, candidateFilename = os.path.split(matchstring)
|
||||
|
||||
if candidateOutputPath:
|
||||
outputpath = candidateOutputPath
|
||||
|
||||
if candidateFilename:
|
||||
filename, ext = os.path.splitext(candidateFilename)
|
||||
|
||||
# Strip any invalid substitutions from the ouputpath
|
||||
for match in re.findall("%(.)", outputpath):
|
||||
if match not in validPathSubstitutions:
|
||||
outputpath = outputpath.replace(f"%{match}", "")
|
||||
|
||||
# if nothing else, use current directory
|
||||
if not outputpath:
|
||||
outputpath = "."
|
||||
|
||||
# Strip any invalid substitutions from the filename
|
||||
for match in re.findall("%(.)", filename):
|
||||
if match not in validFilenameSubstitutions:
|
||||
filename = filename.replace(f"%{match}", "")
|
||||
|
||||
# if no filename, use the active document label
|
||||
if not filename:
|
||||
filename = FreeCAD.ActiveDocument.Label
|
||||
|
||||
# if no extension, use something sensible
|
||||
if not ext:
|
||||
ext = ".nc"
|
||||
|
||||
# By now we should have a sanitized path, filename and extension to work with
|
||||
Path.Log.track(f"path: {outputpath} name: {filename} ext: {ext}")
|
||||
|
||||
fullPath = processFileNameSubstitutions(
|
||||
job,
|
||||
subpartname,
|
||||
sequencenumber,
|
||||
outputpath,
|
||||
filename,
|
||||
ext,
|
||||
)
|
||||
|
||||
# This section determines whether user interaction is necessary
|
||||
policy = PathPreferences.defaultOutputPolicy()
|
||||
|
||||
openDialog = policy == "Open File Dialog"
|
||||
# if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
|
||||
# # Either the entire filename resolves into a directory or the parent
|
||||
# # directory doesn't exist.
|
||||
# # Either way I don't know what to do - ask for help
|
||||
# openDialog = True
|
||||
|
||||
if not FreeCAD.GuiUp: # if testing, or scripting, never open dialog.
|
||||
policy = "Append Unique ID on conflict"
|
||||
openDialog = False
|
||||
|
||||
if os.path.isfile(fullPath) and not openDialog:
|
||||
if policy == "Open File Dialog on conflict":
|
||||
openDialog = True
|
||||
elif policy == "Append Unique ID on conflict":
|
||||
fn, ext = os.path.splitext(fullPath)
|
||||
nr = fn[-3:]
|
||||
n = 1
|
||||
if nr.isdigit():
|
||||
n = int(nr)
|
||||
while os.path.isfile("%s%03d%s" % (fn, n, ext)):
|
||||
n = n + 1
|
||||
fullPath = "%s%03d%s" % (fn, n, ext)
|
||||
|
||||
if openDialog:
|
||||
foo = QtGui.QFileDialog.getSaveFileName(
|
||||
QtGui.QApplication.activeWindow(), "Output File", filename
|
||||
)
|
||||
if foo[0]:
|
||||
fullPath = foo[0]
|
||||
else:
|
||||
fullPath = None
|
||||
|
||||
if fullPath:
|
||||
# remove any unused substitution strings:
|
||||
for s in validPathSubstitutions + validFilenameSubstitutions:
|
||||
fullPath = fullPath.replace(f"%{s}", "")
|
||||
|
||||
fullPath = os.path.normpath(fullPath)
|
||||
Path.Log.track(fullPath)
|
||||
return fullPath
|
||||
|
||||
|
||||
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":
|
||||
Path.Log.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)
|
||||
Path.Log.debug("Appending TC: {}".format(tc.Name))
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append((f, sublist))
|
||||
|
||||
elif orderby == "Tool":
|
||||
Path.Log.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
|
||||
|
||||
Path.Log.track(job.PostProcessorOutputFile)
|
||||
for idx, obj in enumerate(job.Operations.Group):
|
||||
Path.Log.track(obj.Label)
|
||||
|
||||
# check if the operation is active
|
||||
if not getattr(obj, "Active", True):
|
||||
Path.Log.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)
|
||||
Path.Log.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":
|
||||
Path.Log.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 = []
|
||||
Path.Log.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:
|
||||
Path.Log.track()
|
||||
return postlist
|
||||
else:
|
||||
Path.Log.track()
|
||||
finalpostlist = [
|
||||
("allitems", [item for slist in postlist for item in slist[1]])
|
||||
]
|
||||
return finalpostlist
|
||||
|
||||
|
||||
class DlgSelectPostProcessor:
|
||||
def __init__(self, parent=None):
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgSelectPostProcessor.ui")
|
||||
firstItem = None
|
||||
for post in PathPreferences.allEnabledPostProcessors():
|
||||
item = QtGui.QListWidgetItem(post)
|
||||
item.setFlags(
|
||||
QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
)
|
||||
self.dialog.lwPostProcessor.addItem(item)
|
||||
if not firstItem:
|
||||
firstItem = item
|
||||
if firstItem:
|
||||
self.dialog.lwPostProcessor.setCurrentItem(firstItem)
|
||||
else:
|
||||
self.dialog.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)
|
||||
self.tooltips = {}
|
||||
self.dialog.lwPostProcessor.itemDoubleClicked.connect(self.dialog.accept)
|
||||
self.dialog.lwPostProcessor.setMouseTracking(True)
|
||||
self.dialog.lwPostProcessor.itemEntered.connect(self.updateTooltip)
|
||||
|
||||
def updateTooltip(self, item):
|
||||
if item.text() in self.tooltips.keys():
|
||||
tooltip = self.tooltips[item.text()]
|
||||
else:
|
||||
processor = PostProcessor.load(item.text())
|
||||
self.tooltips[item.text()] = processor.tooltip
|
||||
tooltip = processor.tooltip
|
||||
self.dialog.lwPostProcessor.setToolTip(tooltip)
|
||||
|
||||
def exec_(self):
|
||||
if self.dialog.exec_() == 1:
|
||||
posts = self.dialog.lwPostProcessor.selectedItems()
|
||||
return posts[0].text()
|
||||
return None
|
||||
|
||||
|
||||
class CommandPathPost:
|
||||
subpart = 1
|
||||
|
||||
def resolvePostProcessor(self, job):
|
||||
if hasattr(job, "PostProcessor"):
|
||||
post = PathPreferences.defaultPostProcessor()
|
||||
if job.PostProcessor:
|
||||
post = job.PostProcessor
|
||||
if post and PostProcessor.exists(post):
|
||||
return post
|
||||
dlg = DlgSelectPostProcessor()
|
||||
return dlg.exec_()
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": "Path_Post",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_Post", "Post Process"),
|
||||
"Accel": "P, P",
|
||||
"ToolTip": QT_TRANSLATE_NOOP("Path_Post", "Post Process the selected Job"),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
if FreeCADGui.Selection.getCompleteSelection():
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def exportObjectsWith(self, objs, partname, job, sequence, extraargs=None):
|
||||
Path.Log.track(extraargs)
|
||||
# check if the user has a project and has set the default post and
|
||||
# output filename
|
||||
# extraargs can be passed in at this time
|
||||
Path.Log.track(partname, sequence)
|
||||
Path.Log.track(objs)
|
||||
|
||||
# partname = objs[0]
|
||||
# slist = objs[1]
|
||||
Path.Log.track(objs, partname)
|
||||
|
||||
postArgs = PathPreferences.defaultPostProcessorArgs()
|
||||
if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs:
|
||||
postArgs = job.PostProcessorArgs
|
||||
elif hasattr(job, "PostProcessor") and job.PostProcessor:
|
||||
postArgs = ""
|
||||
|
||||
if extraargs is not None:
|
||||
postArgs += " {}".format(extraargs)
|
||||
|
||||
Path.Log.track(postArgs)
|
||||
|
||||
postname = self.resolvePostProcessor(job)
|
||||
# filename = "-"
|
||||
filename = resolveFileName(job, partname, sequence)
|
||||
# if postname and needFilename:
|
||||
# filename = resolveFileName(job)
|
||||
|
||||
if postname and filename:
|
||||
print("post: %s(%s, %s)" % (postname, filename, postArgs))
|
||||
processor = PostProcessor.load(postname)
|
||||
gcode = processor.export(objs, filename, postArgs)
|
||||
return (False, gcode, filename)
|
||||
else:
|
||||
return (True, "", filename)
|
||||
|
||||
def Activated(self):
|
||||
Path.Log.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)
|
||||
Path.Log.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)
|
||||
|
||||
Path.Log.debug("about to postprocess job: {}".format(job.Name))
|
||||
|
||||
postlist = buildPostList(job)
|
||||
# filename = resolveFileName(job, "allitems", 0)
|
||||
|
||||
filenames = []
|
||||
|
||||
success = True
|
||||
finalgcode = ""
|
||||
for idx, section in enumerate(postlist):
|
||||
partname = section[0]
|
||||
sublist = section[1]
|
||||
|
||||
result, gcode, name = self.exportObjectsWith(sublist, partname, job, idx)
|
||||
filenames.append(name)
|
||||
Path.Log.track(result, gcode, name)
|
||||
|
||||
if name is None:
|
||||
success = False
|
||||
else:
|
||||
finalgcode += gcode
|
||||
|
||||
# if job.SplitOutput:
|
||||
# for idx, sublist in enumerate(postlist): # name, slist in postlist:
|
||||
# result = self.exportObjectsWith(sublist[1], sublist[0], job, idx)
|
||||
|
||||
# if result is None:
|
||||
# success = False
|
||||
# else:
|
||||
# gcode += result
|
||||
|
||||
# else:
|
||||
# finalpostlist = [item for (_, slist) in postlist for item in slist]
|
||||
# gcode = self.exportObjectsWith(finalpostlist, "allitems", job, 1)
|
||||
# success = gcode is not None
|
||||
|
||||
Path.Log.track(success)
|
||||
if success:
|
||||
if hasattr(job, "LastPostProcessDate"):
|
||||
job.LastPostProcessDate = str(datetime.now())
|
||||
if hasattr(job, "LastPostProcessOutput"):
|
||||
job.LastPostProcessOutput = " \n".join(filenames)
|
||||
Path.Log.track(job.LastPostProcessOutput)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
else:
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand("Path_Post", CommandPathPost())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathPost... done\n")
|
||||
@@ -1,86 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import Path
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import sys
|
||||
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
class PostProcessor:
|
||||
@classmethod
|
||||
def exists(cls, processor):
|
||||
return processor in PathPreferences.allAvailablePostProcessors()
|
||||
|
||||
@classmethod
|
||||
def load(cls, processor):
|
||||
Path.Log.track(processor)
|
||||
syspath = sys.path
|
||||
paths = PathPreferences.searchPathsPost()
|
||||
paths.extend(sys.path)
|
||||
sys.path = paths
|
||||
|
||||
postname = processor + "_post"
|
||||
namespace = {}
|
||||
|
||||
# can't modify function local scope with exec in python3
|
||||
exec("import %s as current_post" % postname, namespace)
|
||||
current_post = namespace["current_post"]
|
||||
|
||||
# make sure the script is reloaded if it was previously loaded
|
||||
# should the script have been imported for the first time above
|
||||
# then the initialization code of the script gets executed twice
|
||||
# resulting in 2 load messages if the script outputs one of those.
|
||||
try:
|
||||
# Python 2.7
|
||||
exec("reload(%s)" % "current_post")
|
||||
except NameError:
|
||||
# Python 3.4+
|
||||
from importlib import reload
|
||||
|
||||
exec("reload(%s)" % "current_post")
|
||||
|
||||
sys.path = syspath
|
||||
|
||||
instance = PostProcessor(current_post)
|
||||
if hasattr(current_post, "UNITS"):
|
||||
if current_post.UNITS == "G21":
|
||||
instance.units = "Metric"
|
||||
else:
|
||||
instance.units = "Inch"
|
||||
|
||||
if hasattr(current_post, "TOOLTIP"):
|
||||
instance.tooltip = current_post.TOOLTIP
|
||||
if hasattr(current_post, "TOOLTIP_ARGS"):
|
||||
instance.tooltipArgs = current_post.TOOLTIP_ARGS
|
||||
return instance
|
||||
|
||||
def __init__(self, script):
|
||||
self.script = script
|
||||
self.tooltip = None
|
||||
self.tooltipArgs = None
|
||||
self.units = None
|
||||
self.machineName = None
|
||||
|
||||
def export(self, obj, filename, args):
|
||||
return self.script.export(obj, filename, args)
|
||||
@@ -75,8 +75,8 @@ def preferences():
|
||||
return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
|
||||
|
||||
|
||||
def pathScriptsSourcePath():
|
||||
return os.path.join(FreeCAD.getHomePath(), "Mod/Path/PathScripts/")
|
||||
def pathPostSourcePath():
|
||||
return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Path/Post/")
|
||||
|
||||
|
||||
def pathDefaultToolsPath(sub=None):
|
||||
@@ -160,8 +160,8 @@ def searchPathsPost():
|
||||
if p:
|
||||
paths.append(p)
|
||||
paths.append(macroFilePath())
|
||||
paths.append(os.path.join(pathScriptsSourcePath(), "post/"))
|
||||
paths.append(pathScriptsSourcePath())
|
||||
paths.append(os.path.join(pathPostSourcePath(), "scripts/"))
|
||||
paths.append(pathPostSourcePath())
|
||||
return paths
|
||||
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import Path.Post.Processor as PostProcessor
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathStock as PathStock
|
||||
import json
|
||||
|
||||
from FreeCAD import Units
|
||||
from PySide import QtCore, QtGui
|
||||
from PathScripts.PathPostProcessor import PostProcessor
|
||||
|
||||
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
"""
|
||||
These are common functions and classes for creating custom post processors.
|
||||
"""
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
import FreeCAD
|
||||
|
||||
import Path
|
||||
import Part
|
||||
|
||||
from PathMachineState import MachineState
|
||||
from PathScripts.PathGeom import CmdMoveArc, edgeForCmd, cmdsForEdge
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
FreeCADGui = None
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
|
||||
class GCodeHighlighter(QtGui.QSyntaxHighlighter):
|
||||
def __init__(self, parent=None):
|
||||
super(GCodeHighlighter, self).__init__(parent)
|
||||
|
||||
keywordFormat = QtGui.QTextCharFormat()
|
||||
keywordFormat.setForeground(QtCore.Qt.cyan)
|
||||
keywordFormat.setFontWeight(QtGui.QFont.Bold)
|
||||
keywordPatterns = ["\\bG[0-9]+\\b", "\\bM[0-9]+\\b"]
|
||||
|
||||
self.highlightingRules = [
|
||||
(QtCore.QRegularExpression(pattern), keywordFormat) for pattern in keywordPatterns
|
||||
]
|
||||
|
||||
speedFormat = QtGui.QTextCharFormat()
|
||||
speedFormat.setFontWeight(QtGui.QFont.Bold)
|
||||
speedFormat.setForeground(QtCore.Qt.green)
|
||||
self.highlightingRules.append((QtCore.QRegularExpression("\\bF[0-9\\.]+\\b"), speedFormat))
|
||||
|
||||
def highlightBlock(self, text):
|
||||
for pattern, hlFormat in self.highlightingRules:
|
||||
expression = QtCore.QRegularExpression(pattern)
|
||||
index = expression.match(text)
|
||||
while index.hasMatch():
|
||||
length = index.capturedLength()
|
||||
self.setFormat(index.capturedStart(), length, hlFormat)
|
||||
index = expression.match(text, index.capturedStart() + length)
|
||||
|
||||
|
||||
class GCodeEditorDialog(QtGui.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
if parent is None:
|
||||
parent = FreeCADGui.getMainWindow()
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
|
||||
layout = QtGui.QVBoxLayout(self)
|
||||
|
||||
# nice text editor widget for editing the gcode
|
||||
self.editor = QtGui.QTextEdit()
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Courier")
|
||||
font.setFixedPitch(True)
|
||||
font.setPointSize(10)
|
||||
self.editor.setFont(font)
|
||||
self.editor.setText("G01 X55 Y4.5 F300.0")
|
||||
layout.addWidget(self.editor)
|
||||
|
||||
# OK and Cancel buttons
|
||||
self.buttons = QtGui.QDialogButtonBox(
|
||||
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel,
|
||||
QtCore.Qt.Horizontal,
|
||||
self,
|
||||
)
|
||||
layout.addWidget(self.buttons)
|
||||
|
||||
# restore placement and size
|
||||
self.paramKey = "User parameter:BaseApp/Values/Mod/Path/GCodeEditor/"
|
||||
params = FreeCAD.ParamGet(self.paramKey)
|
||||
posX = params.GetInt("posX")
|
||||
posY = params.GetInt("posY")
|
||||
if posX > 0 and posY > 0:
|
||||
self.move(posX, posY)
|
||||
width = params.GetInt("width")
|
||||
height = params.GetInt("height")
|
||||
if width > 0 and height > 0:
|
||||
self.resize(width, height)
|
||||
|
||||
self.buttons.accepted.connect(self.accept)
|
||||
self.buttons.rejected.connect(self.reject)
|
||||
|
||||
def done(self, *args, **kwargs):
|
||||
params = FreeCAD.ParamGet(self.paramKey)
|
||||
params.SetInt("posX", self.x())
|
||||
params.SetInt("posY", self.y())
|
||||
params.SetInt("width", self.size().width())
|
||||
params.SetInt("height", self.size().height())
|
||||
return QtGui.QDialog.done(self, *args, **kwargs)
|
||||
|
||||
|
||||
def stringsplit(commandline):
|
||||
returndict = {
|
||||
"command": None,
|
||||
"X": None,
|
||||
"Y": None,
|
||||
"Z": None,
|
||||
"A": None,
|
||||
"B": None,
|
||||
"F": None,
|
||||
"T": None,
|
||||
"S": None,
|
||||
"I": None,
|
||||
"J": None,
|
||||
"K": None,
|
||||
"txt": None,
|
||||
}
|
||||
wordlist = [a.strip() for a in commandline.split(" ")]
|
||||
if wordlist[0][0] == "(":
|
||||
returndict["command"] = "message"
|
||||
returndict["txt"] = wordlist[0]
|
||||
else:
|
||||
returndict["command"] = wordlist[0]
|
||||
for word in wordlist[1:]:
|
||||
returndict[word[0]] = word[1:]
|
||||
|
||||
return returndict
|
||||
|
||||
|
||||
def fmt(num, dec, units):
|
||||
"""Use to format axis moves, feedrate, etc for decimal places and units."""
|
||||
if units == "G21": # metric
|
||||
fnum = "%.*f" % (dec, num)
|
||||
else: # inch
|
||||
fnum = "%.*f" % (dec, num / 25.4) # since FreeCAD uses metric units internally
|
||||
return fnum
|
||||
|
||||
|
||||
def editor(gcode):
|
||||
"""Pops up a handy little editor to look at the code output."""
|
||||
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
|
||||
# default Max Highlighter Size = 512 Ko
|
||||
defaultMHS = 512 * 1024
|
||||
mhs = prefs.GetUnsigned("inspecteditorMaxHighlighterSize", defaultMHS)
|
||||
|
||||
dia = GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
gcodeSize = len(dia.editor.toPlainText())
|
||||
if gcodeSize <= mhs:
|
||||
# because of poor performance, syntax highlighting is
|
||||
# limited to mhs octets (default 512 KB).
|
||||
# It seems than the response time curve has an inflexion near 500 KB
|
||||
# beyond 500 KB, the response time increases exponentially.
|
||||
dia.highlighter = GCodeHighlighter(dia.editor.document())
|
||||
else:
|
||||
FreeCAD.Console.PrintMessage(
|
||||
translate(
|
||||
"Path",
|
||||
"GCode size too big ({} o), disabling syntax highlighter.".format(
|
||||
gcodeSize
|
||||
),
|
||||
)
|
||||
)
|
||||
result = dia.exec_()
|
||||
if result: # If user selected 'OK' get modified G Code
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = gcode
|
||||
return final
|
||||
|
||||
|
||||
def fcoms(string, commentsym):
|
||||
"""Filter and rebuild comments with user preferred comment symbol."""
|
||||
if len(commentsym) == 1:
|
||||
s1 = string.replace("(", commentsym)
|
||||
comment = s1.replace(")", "")
|
||||
else:
|
||||
return string
|
||||
return comment
|
||||
|
||||
|
||||
def splitArcs(path):
|
||||
"""Filter a path object and replace all G2/G3 moves with discrete G1 moves.
|
||||
|
||||
Returns a Path object.
|
||||
"""
|
||||
prefGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
|
||||
deflection = prefGrp.GetFloat("LibAreaCurveAccuarcy", 0.01)
|
||||
|
||||
results = []
|
||||
if not isinstance(path, Path.Path):
|
||||
raise TypeError("path must be a Path object")
|
||||
|
||||
machine = MachineState()
|
||||
for command in path.Commands:
|
||||
|
||||
if command.Name not in CmdMoveArc:
|
||||
machine.addCommand(command)
|
||||
results.append(command)
|
||||
continue
|
||||
|
||||
edge = edgeForCmd(command, machine.getPosition())
|
||||
pts = edge.discretize(Deflection=deflection)
|
||||
edges = [Part.makeLine(v1, v2) for v1, v2 in zip(pts, pts[1:])]
|
||||
for edge in edges:
|
||||
results.extend(cmdsForEdge(edge))
|
||||
|
||||
machine.addCommand(command)
|
||||
|
||||
return Path.Path(results)
|
||||
@@ -1,784 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * Copyright (c) 2018, 2019 Gauthier Briere *
|
||||
# * Copyright (c) 2019, 2020 Schildkroet *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
"""
|
||||
These are functions related to arguments and values for creating custom post processors.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import shlex
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from FreeCAD import Units
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def add_flag_type_arguments(
|
||||
argument_group,
|
||||
default_flag,
|
||||
true_argument,
|
||||
false_argument,
|
||||
true_help,
|
||||
false_help,
|
||||
visible=True,
|
||||
):
|
||||
if visible:
|
||||
if default_flag:
|
||||
true_help += " (default)"
|
||||
else:
|
||||
false_help += " (default)"
|
||||
else:
|
||||
true_help = false_help = argparse.SUPPRESS
|
||||
argument_group.add_argument(true_argument, action="store_true", help=true_help)
|
||||
argument_group.add_argument(false_argument, action="store_true", help=false_help)
|
||||
|
||||
|
||||
def init_argument_defaults(argument_defaults):
|
||||
"""Initialize which argument to show as the default in flag-type arguments."""
|
||||
argument_defaults["axis-modal"] = False
|
||||
argument_defaults["bcnc"] = False
|
||||
argument_defaults["comments"] = True
|
||||
argument_defaults["header"] = True
|
||||
argument_defaults["line-numbers"] = False
|
||||
argument_defaults["metric_inches"] = True
|
||||
argument_defaults["modal"] = False
|
||||
argument_defaults["output_all_arguments"] = False
|
||||
argument_defaults["output_visible_arguments"] = False
|
||||
argument_defaults["show-editor"] = True
|
||||
argument_defaults["tlo"] = True
|
||||
argument_defaults["tool_change"] = True
|
||||
argument_defaults["translate_drill"] = False
|
||||
|
||||
|
||||
def init_arguments_visible(arguments_visible):
|
||||
"""Initialize the flags for which arguments are visible in the arguments tooltip."""
|
||||
arguments_visible["bcnc"] = False
|
||||
arguments_visible["axis-modal"] = True
|
||||
arguments_visible["axis-precision"] = True
|
||||
arguments_visible["comments"] = True
|
||||
arguments_visible["feed-precision"] = True
|
||||
arguments_visible["header"] = True
|
||||
arguments_visible["line-numbers"] = True
|
||||
arguments_visible["metric_inches"] = True
|
||||
arguments_visible["modal"] = True
|
||||
arguments_visible["output_all_arguments"] = True
|
||||
arguments_visible["output_visible_arguments"] = True
|
||||
arguments_visible["postamble"] = True
|
||||
arguments_visible["preamble"] = True
|
||||
arguments_visible["precision"] = True
|
||||
arguments_visible["return-to"] = False
|
||||
arguments_visible["show-editor"] = True
|
||||
arguments_visible["tlo"] = True
|
||||
arguments_visible["tool_change"] = False
|
||||
arguments_visible["translate_drill"] = False
|
||||
arguments_visible["wait-for-spindle"] = False
|
||||
|
||||
|
||||
def init_shared_arguments(values, argument_defaults, arguments_visible):
|
||||
"""Initialize the shared arguments for postprocessors."""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=values["MACHINE_NAME"], usage=argparse.SUPPRESS, add_help=False
|
||||
)
|
||||
shared = parser.add_argument_group(
|
||||
"Arguments that are shared with all postprocessors"
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["metric_inches"],
|
||||
"--metric",
|
||||
"--inches",
|
||||
"Convert output for Metric mode (G21)",
|
||||
"Convert output for US imperial mode (G20)",
|
||||
arguments_visible["metric_inches"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["axis-modal"],
|
||||
"--axis-modal",
|
||||
"--no-axis-modal",
|
||||
"Don't output axis values if they are the same as the previous line",
|
||||
"Output axis values even if they are the same as the previous line",
|
||||
arguments_visible["axis-modal"],
|
||||
)
|
||||
if arguments_visible["axis-precision"]:
|
||||
help_message = (
|
||||
"Number of digits of precision for axis moves, default is "
|
||||
+ str(values["DEFAULT_AXIS_PRECISION"])
|
||||
)
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument(
|
||||
"--axis-precision",
|
||||
default=-1,
|
||||
type=int,
|
||||
help=help_message,
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["bcnc"],
|
||||
"--bcnc",
|
||||
"--no-bcnc",
|
||||
"Add Job operations as bCNC block headers. Consider suppressing comments by adding --no-comments",
|
||||
"Suppress bCNC block header output",
|
||||
arguments_visible["bcnc"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["comments"],
|
||||
"--comments",
|
||||
"--no-comments",
|
||||
"Output comments",
|
||||
"Suppress comment output",
|
||||
arguments_visible["comments"],
|
||||
)
|
||||
if arguments_visible["feed-precision"]:
|
||||
help_message = "Number of digits of precision for feed rate, default is " + str(
|
||||
values["DEFAULT_FEED_PRECISION"]
|
||||
)
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument(
|
||||
"--feed-precision",
|
||||
default=-1,
|
||||
type=int,
|
||||
help=help_message,
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["header"],
|
||||
"--header",
|
||||
"--no-header",
|
||||
"Output headers",
|
||||
"Suppress header output",
|
||||
arguments_visible["header"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["line-numbers"],
|
||||
"--line-numbers",
|
||||
"--no-line-numbers",
|
||||
"Prefix with line numbers",
|
||||
"Don't prefix with line numbers",
|
||||
arguments_visible["line-numbers"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["modal"],
|
||||
"--modal",
|
||||
"--no-modal",
|
||||
"Don't output the G-command name if it is the same as the previous line",
|
||||
"Output the G-command name even if it is the same as the previous line",
|
||||
arguments_visible["modal"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["output_all_arguments"],
|
||||
"--output_all_arguments",
|
||||
"--no-output_all_arguments",
|
||||
"Output all of the available arguments",
|
||||
"Don't output all of the available arguments",
|
||||
arguments_visible["output_all_arguments"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["output_visible_arguments"],
|
||||
"--output_visible_arguments",
|
||||
"--no-output_visible_arguments",
|
||||
"Output all of the visible arguments",
|
||||
"Don't output the visible arguments",
|
||||
arguments_visible["output_visible_arguments"],
|
||||
)
|
||||
if arguments_visible["postamble"]:
|
||||
help_message = (
|
||||
'Set commands to be issued after the last command, default is "'
|
||||
+ values["POSTAMBLE"]
|
||||
+ '"'
|
||||
)
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument("--postamble", help=help_message)
|
||||
if arguments_visible["preamble"]:
|
||||
help_message = (
|
||||
'Set commands to be issued before the first command, default is "'
|
||||
+ values["PREAMBLE"]
|
||||
+ '"'
|
||||
)
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument("--preamble", help=help_message)
|
||||
# The --precision argument is included for backwards compatibility with
|
||||
# some postprocessors. If both --axis-precision and --precision are provided,
|
||||
# the --axis-precision value "wins". If both --feed-precision and --precision
|
||||
# are provided, the --feed-precision value "wins".
|
||||
if arguments_visible["precision"]:
|
||||
help_message = (
|
||||
"Number of digits of precision for both feed rate and axis moves, default is "
|
||||
+ str(values["DEFAULT_AXIS_PRECISION"])
|
||||
+ " for metric or "
|
||||
+ str(values["DEFAULT_INCH_AXIS_PRECISION"])
|
||||
+ " for inches"
|
||||
)
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument(
|
||||
"--precision",
|
||||
default=-1,
|
||||
type=int,
|
||||
help=help_message,
|
||||
)
|
||||
if arguments_visible["return-to"]:
|
||||
help_message = "Move to the specified x,y,z coordinates at the end, e.g. --return-to=0,0,0 (default is do not move)"
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument("--return-to", default="", help=help_message)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["show-editor"],
|
||||
"--show-editor",
|
||||
"--no-show-editor",
|
||||
"Pop up editor before writing output",
|
||||
"Don't pop up editor before writing output",
|
||||
arguments_visible["show-editor"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["tlo"],
|
||||
"--tlo",
|
||||
"--no-tlo",
|
||||
"Output tool length offset (G43) following tool changes",
|
||||
"Suppress tool length offset (G43) following tool changes",
|
||||
arguments_visible["tlo"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["tool_change"],
|
||||
"--tool_change",
|
||||
"--no-tool_change",
|
||||
"Insert M6 and any other tool change G-code for all tool changes",
|
||||
"Convert M6 to a comment for all tool changes",
|
||||
arguments_visible["tool_change"],
|
||||
)
|
||||
add_flag_type_arguments(
|
||||
shared,
|
||||
argument_defaults["translate_drill"],
|
||||
"--translate_drill",
|
||||
"--no-translate_drill",
|
||||
"Translate drill cycles G81, G82 & G83 into G0/G1 movements",
|
||||
"Don't translate drill cycles G81, G82 & G83 into G0/G1 movements",
|
||||
arguments_visible["translate_drill"],
|
||||
)
|
||||
if arguments_visible["wait-for-spindle"]:
|
||||
help_message = "Time to wait (in seconds) after M3, M4 (default = 0.0)"
|
||||
else:
|
||||
help_message = argparse.SUPPRESS
|
||||
shared.add_argument(
|
||||
"--wait-for-spindle", type=float, default=0.0, help=help_message
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def init_shared_values(values):
|
||||
"""Initialize the default values in postprocessors."""
|
||||
#
|
||||
# The starting axis precision is 3 digits after the decimal point.
|
||||
#
|
||||
values["AXIS_PRECISION"] = 3
|
||||
#
|
||||
# How far to move up (in millimeters) in the Z axis when chipbreaking with a G73 command.
|
||||
#
|
||||
values["CHIPBREAKING_AMOUNT"] = Units.Quantity(0.25, FreeCAD.Units.Length)
|
||||
|
||||
#
|
||||
# If this is set to "", all spaces are removed from between commands and parameters.
|
||||
#
|
||||
values["COMMAND_SPACE"] = " "
|
||||
#
|
||||
# The character that indicates a comment. While "(" is the most common,
|
||||
# ";" is also used.
|
||||
#
|
||||
values["COMMENT_SYMBOL"] = "("
|
||||
#
|
||||
# Variables storing the current position for the drill_translate routine.
|
||||
#
|
||||
values["CURRENT_X"] = 0
|
||||
values["CURRENT_Y"] = 0
|
||||
values["CURRENT_Z"] = 0
|
||||
#
|
||||
# Default axis precision for metric is 3 digits after the decimal point.
|
||||
# (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices)
|
||||
#
|
||||
values["DEFAULT_AXIS_PRECISION"] = 3
|
||||
#
|
||||
# The default precision for feed is also set to 3 for metric.
|
||||
#
|
||||
values["DEFAULT_FEED_PRECISION"] = 3
|
||||
#
|
||||
# Default axis precision for inch/imperial is 4 digits after the decimal point.
|
||||
#
|
||||
values["DEFAULT_INCH_AXIS_PRECISION"] = 4
|
||||
#
|
||||
# The default precision for feed is also set to 4 for inch/imperial.
|
||||
#
|
||||
values["DEFAULT_INCH_FEED_PRECISION"] = 4
|
||||
#
|
||||
# If TRANSLATE_DRILL_CYCLES is True, these are the drill cycles
|
||||
# that get translated to G0 and G1 commands.
|
||||
#
|
||||
values["DRILL_CYCLES_TO_TRANSLATE"] = ("G73", "G81", "G82", "G83")
|
||||
#
|
||||
# The default value of drill retractations (CURRENT_Z).
|
||||
# The other possible value is G99.
|
||||
#
|
||||
values["DRILL_RETRACT_MODE"] = "G98"
|
||||
#
|
||||
# If this is set to True, then M7, M8, and M9 commands
|
||||
# to enable/disable coolant will be output.
|
||||
#
|
||||
values["ENABLE_COOLANT"] = False
|
||||
#
|
||||
# If this is set to True, then commands that are placed in
|
||||
# comments that look like (MC_RUN_COMMAND: blah) will be output.
|
||||
#
|
||||
values["ENABLE_MACHINE_SPECIFIC_COMMANDS"] = False
|
||||
#
|
||||
# By default the line ending characters of the output file(s)
|
||||
# are written to match the system that the postprocessor runs on.
|
||||
# If you need to force the line ending characters to a specific
|
||||
# value, set this variable to "\n" or "\r\n" instead.
|
||||
#
|
||||
values["END_OF_LINE_CHARACTERS"] = os.linesep
|
||||
#
|
||||
# The starting precision for feed is also set to 3 digits after the decimal point.
|
||||
#
|
||||
values["FEED_PRECISION"] = 3
|
||||
#
|
||||
# This value shows up in the post_op comment as "Finish operation:".
|
||||
# At least one postprocessor changes it to "End" to produce "End operation:".
|
||||
#
|
||||
values["FINISH_LABEL"] = "Finish"
|
||||
#
|
||||
# The line number increment value
|
||||
#
|
||||
values["LINE_INCREMENT"] = 10
|
||||
#
|
||||
# The line number starting value
|
||||
#
|
||||
values["line_number"] = 100
|
||||
#
|
||||
# If this value is True, then a list of tool numbers
|
||||
# with their labels are output just before the preamble.
|
||||
#
|
||||
values["LIST_TOOLS_IN_PREAMBLE"] = False
|
||||
#
|
||||
# The name of the machine the postprocessor is for
|
||||
#
|
||||
values["MACHINE_NAME"] = "unknown machine"
|
||||
#
|
||||
# If this value is true G-code commands are suppressed if they are
|
||||
# the same as the previous line.
|
||||
#
|
||||
values["MODAL"] = False
|
||||
#
|
||||
# This defines the motion commands that might change the X, Y, and Z position.
|
||||
#
|
||||
values["MOTION_COMMANDS"] = [
|
||||
"G0",
|
||||
"G00",
|
||||
"G1",
|
||||
"G01",
|
||||
"G2",
|
||||
"G02",
|
||||
"G3",
|
||||
"G03",
|
||||
]
|
||||
#
|
||||
# Keeps track of the motion mode currently in use.
|
||||
# G90 for absolute moves, G91 for relative
|
||||
#
|
||||
values["MOTION_MODE"] = "G90"
|
||||
#
|
||||
# If True enables special processing for operations with "Adaptive" in the name
|
||||
#
|
||||
values["OUTPUT_ADAPTIVE"] = False
|
||||
#
|
||||
# If True adds bCNC operation block headers to the output G-code file.
|
||||
#
|
||||
values["OUTPUT_BCNC"] = False
|
||||
#
|
||||
# If True output comments. If False comments are suppressed.
|
||||
#
|
||||
values["OUTPUT_COMMENTS"] = True
|
||||
#
|
||||
# if False duplicate axis values are suppressed if they are the same as
|
||||
# the previous line.
|
||||
#
|
||||
values["OUTPUT_DOUBLES"] = True
|
||||
#
|
||||
# If True output the machine name in the pre_op
|
||||
#
|
||||
values["OUTPUT_MACHINE_NAME"] = False
|
||||
#
|
||||
# If True output a header at the front of the G-code file.
|
||||
# The header contains comments similar to:
|
||||
# (Exported by FreeCAD)
|
||||
# (Post Processor: centroid_post)
|
||||
# (Cam File: box.fcstd)
|
||||
# (Output Time:2020-01-01 01:02:03.123456)
|
||||
#
|
||||
values["OUTPUT_HEADER"] = True
|
||||
#
|
||||
# If True output line numbers at the front of each line.
|
||||
# If False do not output line numbers.
|
||||
#
|
||||
values["OUTPUT_LINE_NUMBERS"] = False
|
||||
#
|
||||
# If True output Path labels at the beginning of each Path.
|
||||
#
|
||||
values["OUTPUT_PATH_LABELS"] = False
|
||||
#
|
||||
# If True output tool change G-code for M6 commands followed
|
||||
# by any commands in the "TOOL_CHANGE" value.
|
||||
# If False output the M6 command as a comment and do not output
|
||||
# any commands in the "TOOL_CHANGE" value.
|
||||
#
|
||||
values["OUTPUT_TOOL_CHANGE"] = True
|
||||
#
|
||||
# This list controls the order of parameters in a line during output.
|
||||
#
|
||||
values["PARAMETER_ORDER"] = [
|
||||
"D",
|
||||
"H",
|
||||
"L",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"R",
|
||||
"P",
|
||||
"E",
|
||||
"Q",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
]
|
||||
#
|
||||
# Any commands in this value will be output as the last commands
|
||||
# in the G-code file.
|
||||
#
|
||||
values["POSTAMBLE"] = """"""
|
||||
#
|
||||
# Any commands in this value will be output after the operation(s).
|
||||
#
|
||||
values["POST_OPERATION"] = """"""
|
||||
#
|
||||
# Any commands in this value will be output after the header and
|
||||
# safety block at the beginning of the G-code file.
|
||||
#
|
||||
values["PREAMBLE"] = """"""
|
||||
#
|
||||
# Any commands in this value will be output before the operation(s).
|
||||
#
|
||||
values["PRE_OPERATION"] = """"""
|
||||
#
|
||||
# Defines which G-code commands are considered "rapid" moves.
|
||||
#
|
||||
values["RAPID_MOVES"] = ["G0", "G00"]
|
||||
#
|
||||
# If True suppress any messages.
|
||||
#
|
||||
values["REMOVE_MESSAGES"] = True
|
||||
#
|
||||
# Any commands in this value are output after the operation(s)
|
||||
# and post_operation commands are output but before the
|
||||
# TOOLRETURN, SAFETYBLOCK, and POSTAMBLE.
|
||||
#
|
||||
values["RETURN_TO"] = None
|
||||
#
|
||||
# Any commands in this value are output after the header but before the preamble,
|
||||
# then again after the TOOLRETURN but before the POSTAMBLE.
|
||||
#
|
||||
values["SAFETYBLOCK"] = """"""
|
||||
#
|
||||
# If True then the G-code editor widget is shown before writing
|
||||
# the G-code to the file.
|
||||
#
|
||||
values["SHOW_EDITOR"] = True
|
||||
#
|
||||
# If True then the current machine units are output just before the PRE_OPERATION.
|
||||
#
|
||||
values["SHOW_MACHINE_UNITS"] = True
|
||||
#
|
||||
# If True then the current operation label is output just before the PRE_OPERATION.
|
||||
#
|
||||
values["SHOW_OPERATION_LABELS"] = True
|
||||
#
|
||||
# The number of decimal places to use when outputting the speed (S) parameter.
|
||||
#
|
||||
values["SPINDLE_DECIMALS"] = 0
|
||||
#
|
||||
# The amount of time (in seconds) to wait after turning on the spindle
|
||||
# using an M3 or M4 command (a floating point number).
|
||||
#
|
||||
values["SPINDLE_WAIT"] = 0.0
|
||||
#
|
||||
# If true then then an M5 command to stop the spindle is output
|
||||
# after the M6 tool change command and before the TOOL_CHANGE commands.
|
||||
#
|
||||
values["STOP_SPINDLE_FOR_TOOL_CHANGE"] = True
|
||||
#
|
||||
# These commands are ignored by commenting them out.
|
||||
# Used when replacing the drill commands by G0 and G1 commands, for example.
|
||||
#
|
||||
values["SUPPRESS_COMMANDS"] = []
|
||||
#
|
||||
# Any commands in this value are output after the M6 command
|
||||
# when changing at tool (if OUTPUT_TOOL_CHANGE is True).
|
||||
#
|
||||
values["TOOL_CHANGE"] = """"""
|
||||
#
|
||||
# Any commands in this value are output after the POST_OPERATION,
|
||||
# RETURN_TO, and OUTPUT_BCNC and before the SAFETYBLOCK and POSTAMBLE.
|
||||
#
|
||||
values["TOOLRETURN"] = """"""
|
||||
#
|
||||
# If true, G81, G82 & G83 drill moves are translated into G0/G1 moves.
|
||||
#
|
||||
values["TRANSLATE_DRILL_CYCLES"] = False
|
||||
#
|
||||
# These values keep track of whether we are in Metric mode (G21)
|
||||
# or inches/imperial mode (G20).
|
||||
#
|
||||
values["UNITS"] = "G21"
|
||||
values["UNIT_FORMAT"] = "mm"
|
||||
values["UNIT_SPEED_FORMAT"] = "mm/min"
|
||||
#
|
||||
# If true a tool length command (G43) will be output following tool changes.
|
||||
#
|
||||
values["USE_TLO"] = True
|
||||
|
||||
|
||||
def process_shared_arguments(values, parser, argstring, all_visible, filename):
|
||||
"""Process the arguments to the postprocessor."""
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.output_all_arguments:
|
||||
argument_text = all_visible.format_help()
|
||||
if not filename == "-":
|
||||
gfile = pythonopen(filename, "w", newline=values["END_OF_LINE_CHARACTERS"])
|
||||
gfile.write(argument_text)
|
||||
gfile.close()
|
||||
return (False, argument_text)
|
||||
if args.output_visible_arguments:
|
||||
argument_text = parser.format_help()
|
||||
if not filename == "-":
|
||||
gfile = pythonopen(filename, "w", newline=values["END_OF_LINE_CHARACTERS"])
|
||||
gfile.write(argument_text)
|
||||
gfile.close()
|
||||
return (False, argument_text)
|
||||
# Default to metric unless an argument overrides it
|
||||
values["UNITS"] = "G21"
|
||||
if args.metric:
|
||||
values["UNITS"] = "G21"
|
||||
if args.inches:
|
||||
values["UNITS"] = "G20"
|
||||
if values["UNITS"] == "G21":
|
||||
values["UNIT_FORMAT"] = "mm"
|
||||
values["UNIT_SPEED_FORMAT"] = "mm/min"
|
||||
if values["UNITS"] == "G20":
|
||||
values["UNIT_FORMAT"] = "in"
|
||||
values["UNIT_SPEED_FORMAT"] = "in/min"
|
||||
# The precision-related arguments need to be processed
|
||||
# after the metric/inches arguments are processed.
|
||||
# If both --axis-precision and --precision are given,
|
||||
# the --axis-precision argument "wins".
|
||||
if args.axis_precision != -1:
|
||||
values["AXIS_PRECISION"] = args.axis_precision
|
||||
elif args.precision != -1:
|
||||
values["AXIS_PRECISION"] = args.precision
|
||||
else:
|
||||
if values["UNITS"] == "G21":
|
||||
values["AXIS_PRECISION"] = values["DEFAULT_AXIS_PRECISION"]
|
||||
if values["UNITS"] == "G20":
|
||||
values["AXIS_PRECISION"] = values["DEFAULT_INCH_AXIS_PRECISION"]
|
||||
# If both --feed-precision and --precision are given,
|
||||
# the --feed-precision argument "wins".
|
||||
if args.feed_precision != -1:
|
||||
values["FEED_PRECISION"] = args.feed_precision
|
||||
elif args.precision != -1:
|
||||
values["FEED_PRECISION"] = args.precision
|
||||
else:
|
||||
if values["UNITS"] == "G21":
|
||||
values["FEED_PRECISION"] = values["DEFAULT_FEED_PRECISION"]
|
||||
if values["UNITS"] == "G20":
|
||||
values["FEED_PRECISION"] = values["DEFAULT_INCH_FEED_PRECISION"]
|
||||
if args.axis_modal:
|
||||
values["OUTPUT_DOUBLES"] = False
|
||||
if args.no_axis_modal:
|
||||
values["OUTPUT_DOUBLES"] = True
|
||||
if args.bcnc:
|
||||
values["OUTPUT_BCNC"] = True
|
||||
if args.no_bcnc:
|
||||
values["OUTPUT_BCNC"] = False
|
||||
if args.comments:
|
||||
values["OUTPUT_COMMENTS"] = True
|
||||
if args.no_comments:
|
||||
values["OUTPUT_COMMENTS"] = False
|
||||
if args.header:
|
||||
values["OUTPUT_HEADER"] = True
|
||||
if args.no_header:
|
||||
values["OUTPUT_HEADER"] = False
|
||||
if args.line_numbers:
|
||||
values["OUTPUT_LINE_NUMBERS"] = True
|
||||
if args.no_line_numbers:
|
||||
values["OUTPUT_LINE_NUMBERS"] = False
|
||||
if args.modal:
|
||||
values["MODAL"] = True
|
||||
if args.no_modal:
|
||||
values["MODAL"] = False
|
||||
if args.postamble is not None:
|
||||
values["POSTAMBLE"] = args.postamble
|
||||
if args.preamble is not None:
|
||||
values["PREAMBLE"] = args.preamble
|
||||
if args.return_to != "":
|
||||
values["RETURN_TO"] = [int(v) for v in args.return_to.split(",")]
|
||||
if len(values["RETURN_TO"]) != 3:
|
||||
values["RETURN_TO"] = None
|
||||
print(
|
||||
"--return-to coordinates must be specified as <x>,<y>,<z>, ignoring"
|
||||
)
|
||||
if args.show_editor:
|
||||
values["SHOW_EDITOR"] = True
|
||||
if args.no_show_editor:
|
||||
values["SHOW_EDITOR"] = False
|
||||
if args.tlo:
|
||||
values["USE_TLO"] = True
|
||||
if args.no_tlo:
|
||||
values["USE_TLO"] = False
|
||||
if args.tool_change:
|
||||
values["OUTPUT_TOOL_CHANGE"] = True
|
||||
if args.no_tool_change:
|
||||
values["OUTPUT_TOOL_CHANGE"] = False
|
||||
if args.translate_drill:
|
||||
values["TRANSLATE_DRILL_CYCLES"] = True
|
||||
if args.no_translate_drill:
|
||||
values["TRANSLATE_DRILL_CYCLES"] = False
|
||||
if args.wait_for_spindle > 0.0:
|
||||
values["SPINDLE_WAIT"] = args.wait_for_spindle
|
||||
|
||||
except Exception:
|
||||
return (False, "")
|
||||
|
||||
return (True, args)
|
||||
|
||||
#
|
||||
# LinuxCNC (and GRBL) G-Code Parameter/word Patterns
|
||||
# __________________________________________________
|
||||
#
|
||||
# LinuxCNC words (called parameters in this code in many places) may be
|
||||
# reordered in any way without changing the meaning of the line.
|
||||
# However, the documentation shows the examples with the parameters/words
|
||||
# in a certain order that people might be used to and want to see.
|
||||
#
|
||||
# axes one or more of "X Y Z A B C U V W", usually in that order
|
||||
#
|
||||
# default parameter order D H L axes I J K R P E Q F S T $
|
||||
#
|
||||
# G10 L P axes R I J Q
|
||||
#
|
||||
# G33.1 X Y Z K I $
|
||||
#
|
||||
# G53 G00|G01 or G00|G01 G53 X Y Z
|
||||
#
|
||||
# G73, G74, G81 to G86, G89 (X Y Z) or (U V W) R Q L P F K $
|
||||
#
|
||||
# G76 P Z I J R K Q H E L $
|
||||
#
|
||||
# M19 R Q P $
|
||||
#
|
||||
# M66 P|E L Q
|
||||
#
|
||||
# M98 P Q L
|
||||
#
|
||||
# N and O are associated with line numbers
|
||||
#
|
||||
#
|
||||
#
|
||||
# Tormach PathPilot (based on LinuxCNC, mostly) G-Code Patterns
|
||||
# _____________________________________________________________
|
||||
#
|
||||
# (Just the exceptions to the LinuxCNC patterns shown above)
|
||||
#
|
||||
# G47 Z R X Y P Q D
|
||||
#
|
||||
#
|
||||
# Mach4 G-Code Patterns
|
||||
# _____________________
|
||||
#
|
||||
# (Just the exceptions to the LinuxCNC patterns shown above)
|
||||
#
|
||||
# G10 L1 P Z W D R X U Y V Q
|
||||
#
|
||||
# G30 P axes
|
||||
#
|
||||
# G41, G42 D|P X Y F
|
||||
#
|
||||
# G65, G66 P A B C
|
||||
#
|
||||
# G73, G74, G76, G81-9 X Y Z Q R I J P L F
|
||||
#
|
||||
# G84.2, G84.3 X Y Z R P L F J
|
||||
#
|
||||
#
|
||||
#
|
||||
# Centroid G-Code Patterns
|
||||
# ________________________
|
||||
#
|
||||
# (Just the exceptions to the LinuxCNC patterns shown above)
|
||||
#
|
||||
# E1-E6 are equivalent to G54-G59
|
||||
#
|
||||
# G10 P D H R
|
||||
#
|
||||
# G65 P L arguments (arguments are A-Z excluding G, L, N, O, and P)
|
||||
# G65 "program.cnc" L arguments
|
||||
#
|
||||
# G117, G118, G119 P X Y Z I J K P Q
|
||||
#
|
||||
@@ -1,313 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * Copyright (c) 2018, 2019 Gauthier Briere *
|
||||
# * Copyright (c) 2019, 2020 Schildkroet *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from PathScripts import PathToolController
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PostUtilsParse
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
#
|
||||
# This routine processes things in the following order:
|
||||
#
|
||||
# OUTPUT_HEADER
|
||||
# SAFETYBLOCK
|
||||
# LIST_TOOLS_IN_PREAMBLE
|
||||
# PREAMBLE
|
||||
# OUTPUT_BCNC
|
||||
# SHOW_OPERATION_LABELS
|
||||
# SHOW_MACHINE_UNITS
|
||||
# PRE_OPERATION
|
||||
# ENABLE_COOLANT (coolant on)
|
||||
# operation(s)
|
||||
# POST_OPERATION
|
||||
# ENABLE_COOLANT (coolant off)
|
||||
# RETURN_TO
|
||||
# OUTPUT_BCNC
|
||||
# TOOLRETURN
|
||||
# SAFETYBLOCK
|
||||
# POSTAMBLE
|
||||
# SHOW_EDITOR
|
||||
#
|
||||
# The names in all caps may be enabled/disabled/modified by setting
|
||||
# the corresponding value in the postprocessor.
|
||||
#
|
||||
|
||||
|
||||
def export_common(values, objectslist, filename):
|
||||
"""Do the common parts of postprocessing the objects in objectslist to filename."""
|
||||
#
|
||||
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
|
||||
|
||||
# for obj in objectslist:
|
||||
# print(obj.Name)
|
||||
|
||||
print("PostProcessor: " + values["POSTPROCESSOR_FILE_NAME"] + " postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
# write header
|
||||
if values["OUTPUT_HEADER"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Exported by FreeCAD", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Post Processor: " + values["POSTPROCESSOR_FILE_NAME"],
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
if FreeCAD.ActiveDocument:
|
||||
cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName)
|
||||
else:
|
||||
cam_file = "<None>"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Cam File: " + cam_file, values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Output Time: " + str(datetime.datetime.now()), values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
|
||||
# Check canned cycles for drilling
|
||||
if values["TRANSLATE_DRILL_CYCLES"]:
|
||||
if len(values["SUPPRESS_COMMANDS"]) == 0:
|
||||
values["SUPPRESS_COMMANDS"] = ["G99", "G98", "G80"]
|
||||
else:
|
||||
values["SUPPRESS_COMMANDS"] += ["G99", "G98", "G80"]
|
||||
|
||||
for line in values["SAFETYBLOCK"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
|
||||
# Write the preamble
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
if values["LIST_TOOLS_IN_PREAMBLE"]:
|
||||
for item in objectslist:
|
||||
if hasattr(item, "Proxy") and isinstance(
|
||||
item.Proxy, PathToolController.ToolController
|
||||
):
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"T{}={}".format(item.ToolNumber, item.Name),
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Begin preamble", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
for line in values["PREAMBLE"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
# verify if PREAMBLE or SAFETYBLOCK have changed MOTION_MODE or UNITS
|
||||
if "G90" in values["PREAMBLE"] or "G90" in values["SAFETYBLOCK"]:
|
||||
values["MOTION_MODE"] = "G90"
|
||||
elif "G91" in values["PREAMBLE"] or "G91" in values["SAFETYBLOCK"]:
|
||||
values["MOTION_MODE"] = "G91"
|
||||
else:
|
||||
gcode += PostUtilsParse.linenumber(values) + values["MOTION_MODE"] + "\n"
|
||||
if "G21" in values["PREAMBLE"] or "G21" in values["SAFETYBLOCK"]:
|
||||
values["UNITS"] = "G21"
|
||||
values["UNIT_FORMAT"] = "mm"
|
||||
values["UNIT_SPEED_FORMAT"] = "mm/min"
|
||||
elif "G20" in values["PREAMBLE"] or "G20" in values["SAFETYBLOCK"]:
|
||||
values["UNITS"] = "G20"
|
||||
values["UNIT_FORMAT"] = "in"
|
||||
values["UNIT_SPEED_FORMAT"] = "in/min"
|
||||
else:
|
||||
gcode += PostUtilsParse.linenumber(values) + values["UNITS"] + "\n"
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# Debug...
|
||||
# print("\n" + "*"*70)
|
||||
# dump(obj)
|
||||
# print("*"*70 + "\n")
|
||||
|
||||
# Skip inactive operations
|
||||
if hasattr(obj, "Active"):
|
||||
if not obj.Active:
|
||||
continue
|
||||
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# do the pre_op
|
||||
if values["OUTPUT_BCNC"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Block-name: " + obj.Label, values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Block-expand: 0", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Block-enable: 1", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
if values["SHOW_OPERATION_LABELS"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Begin operation: %s" % obj.Label, values["COMMENT_SYMBOL"]
|
||||
)
|
||||
else:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Begin operation", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
if values["SHOW_MACHINE_UNITS"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Machine units: %s" % values["UNIT_SPEED_FORMAT"],
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
if values["OUTPUT_MACHINE_NAME"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Machine: %s, %s"
|
||||
% (values["MACHINE_NAME"], values["UNIT_SPEED_FORMAT"]),
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
for line in values["PRE_OPERATION"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
|
||||
# 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 values["ENABLE_COOLANT"]:
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
if not coolantMode == "None":
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Coolant On:" + coolantMode, values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
if coolantMode == "Flood":
|
||||
gcode += PostUtilsParse.linenumber(values) + "M8" + "\n"
|
||||
if coolantMode == "Mist":
|
||||
gcode += PostUtilsParse.linenumber(values) + "M7" + "\n"
|
||||
|
||||
# process the operation gcode
|
||||
gcode += PostUtilsParse.parse(values, obj)
|
||||
|
||||
# do the post_op
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"%s operation: %s" % (values["FINISH_LABEL"], obj.Label),
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
for line in values["POST_OPERATION"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
|
||||
# turn coolant off if required
|
||||
if values["ENABLE_COOLANT"]:
|
||||
if not coolantMode == "None":
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Coolant Off:" + coolantMode, values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
gcode += PostUtilsParse.linenumber(values) + "M9" + "\n"
|
||||
|
||||
if values["RETURN_TO"]:
|
||||
gcode += PostUtilsParse.linenumber(values) + "G0 X%s Y%s Z%s\n" % tuple(
|
||||
values["RETURN_TO"]
|
||||
)
|
||||
|
||||
# do the post_amble
|
||||
if values["OUTPUT_BCNC"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Block-name: post_amble", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Block-expand: 0", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Block-enable: 1", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
comment = PostUtilsParse.create_comment(
|
||||
"Begin postamble", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
|
||||
for line in values["TOOLRETURN"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
for line in values["SAFETYBLOCK"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
for line in values["POSTAMBLE"].splitlines(False):
|
||||
gcode += PostUtilsParse.linenumber(values) + line + "\n"
|
||||
|
||||
if FreeCAD.GuiUp and values["SHOW_EDITOR"]:
|
||||
final = gcode
|
||||
if len(gcode) > 100000:
|
||||
print("Skipping editor since output is greater than 100kb")
|
||||
else:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = gcode
|
||||
|
||||
print("done postprocessing.")
|
||||
|
||||
if not filename == "-":
|
||||
gfile = pythonopen(filename, "w", newline=values["END_OF_LINE_CHARACTERS"])
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
@@ -1,595 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * Copyright (c) 2018, 2019 Gauthier Briere *
|
||||
# * Copyright (c) 2019, 2020 Schildkroet *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import re
|
||||
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
|
||||
import Path
|
||||
|
||||
from PathScripts import PostUtils
|
||||
|
||||
|
||||
def create_comment(comment_string, comment_symbol):
|
||||
"""Create a comment from a string using the correct comment symbol."""
|
||||
if comment_symbol == "(":
|
||||
comment_string = "(" + comment_string + ")"
|
||||
else:
|
||||
comment_string = comment_symbol + comment_string
|
||||
return comment_string
|
||||
|
||||
|
||||
def drill_translate(values, outstring, cmd, params):
|
||||
"""Translate drill cycles."""
|
||||
axis_precision_string = "." + str(values["AXIS_PRECISION"]) + "f"
|
||||
feed_precision_string = "." + str(values["FEED_PRECISION"]) + "f"
|
||||
|
||||
trBuff = ""
|
||||
|
||||
if values["OUTPUT_COMMENTS"]: # Comment the original command
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ create_comment(
|
||||
values["COMMAND_SPACE"]
|
||||
+ format_outstring(values, outstring)
|
||||
+ values["COMMAND_SPACE"],
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
# cycle conversion
|
||||
# currently only cycles in XY are provided (G17)
|
||||
# other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only.
|
||||
drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length)
|
||||
drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length)
|
||||
drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length)
|
||||
RETRACT_Z = Units.Quantity(params["R"], FreeCAD.Units.Length)
|
||||
# R less than Z is error
|
||||
if RETRACT_Z < drill_Z:
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ create_comment(
|
||||
"Drill cycle error: R less than Z", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
return trBuff
|
||||
|
||||
if values["MOTION_MODE"] == "G91": # G91 relative movements
|
||||
drill_X += values["CURRENT_X"]
|
||||
drill_Y += values["CURRENT_Y"]
|
||||
drill_Z += values["CURRENT_Z"]
|
||||
RETRACT_Z += values["CURRENT_Z"]
|
||||
|
||||
if values["DRILL_RETRACT_MODE"] == "G98" and values["CURRENT_Z"] >= RETRACT_Z:
|
||||
RETRACT_Z = values["CURRENT_Z"]
|
||||
|
||||
# get the other parameters
|
||||
drill_feedrate = Units.Quantity(params["F"], FreeCAD.Units.Velocity)
|
||||
if cmd in ("G73", "G83"):
|
||||
drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length)
|
||||
# NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit."
|
||||
a_bit = drill_Step * 0.05
|
||||
elif cmd == "G82":
|
||||
drill_DwellTime = params["P"]
|
||||
|
||||
# wrap this block to ensure machine's values["MOTION_MODE"] is restored in case of error
|
||||
# try:
|
||||
if values["MOTION_MODE"] == "G91":
|
||||
# force absolute coordinates during cycles
|
||||
trBuff += linenumber(values) + "G90\n"
|
||||
|
||||
strG0_RETRACT_Z = (
|
||||
"G0 Z"
|
||||
+ format(float(RETRACT_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
|
||||
+ "\n"
|
||||
)
|
||||
strF_Feedrate = (
|
||||
" F"
|
||||
+ format(
|
||||
float(drill_feedrate.getValueAs(values["UNIT_SPEED_FORMAT"])), feed_precision_string
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
# print(strF_Feedrate)
|
||||
|
||||
# preliminary movement(s)
|
||||
if values["CURRENT_Z"] < RETRACT_Z:
|
||||
trBuff += linenumber(values) + strG0_RETRACT_Z
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G0 X"
|
||||
+ format(float(drill_X.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
|
||||
+ " Y"
|
||||
+ format(float(drill_Y.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
|
||||
+ "\n"
|
||||
)
|
||||
if values["CURRENT_Z"] > RETRACT_Z:
|
||||
# NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z
|
||||
# Here use G1 since retract height may be below surface !
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G1 Z"
|
||||
+ format(float(RETRACT_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
last_Stop_Z = RETRACT_Z
|
||||
|
||||
# drill moves
|
||||
if cmd in ("G81", "G82"):
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G1 Z"
|
||||
+ format(float(drill_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
# pause where applicable
|
||||
if cmd == "G82":
|
||||
trBuff += linenumber(values) + "G4 P" + str(drill_DwellTime) + "\n"
|
||||
trBuff += linenumber(values) + strG0_RETRACT_Z
|
||||
else: # "G73" or "G83"
|
||||
if params["Q"] != 0:
|
||||
while 1:
|
||||
if last_Stop_Z != RETRACT_Z:
|
||||
# rapid move to just short of last drilling depth
|
||||
clearance_depth = last_Stop_Z + a_bit
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G0 Z"
|
||||
+ format(
|
||||
float(clearance_depth.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
next_Stop_Z = last_Stop_Z - drill_Step
|
||||
if next_Stop_Z > drill_Z:
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G1 Z"
|
||||
+ format(
|
||||
float(next_Stop_Z.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
if cmd == "G73":
|
||||
# Rapid up "a small amount".
|
||||
chip_breaker_height = next_Stop_Z + values["CHIPBREAKING_AMOUNT"]
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G0 Z"
|
||||
+ format(
|
||||
float(chip_breaker_height.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
else: # "G83"
|
||||
# Rapid up to the retract height
|
||||
trBuff += linenumber(values) + strG0_RETRACT_Z
|
||||
last_Stop_Z = next_Stop_Z
|
||||
else:
|
||||
trBuff += (
|
||||
linenumber(values)
|
||||
+ "G1 Z"
|
||||
+ format(
|
||||
float(drill_Z.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
trBuff += linenumber(values) + strG0_RETRACT_Z
|
||||
break
|
||||
|
||||
# except Exception:
|
||||
# print("exception occurred")
|
||||
# pass
|
||||
|
||||
if values["MOTION_MODE"] == "G91":
|
||||
trBuff += linenumber(values) + "G91\n" # Restore if changed
|
||||
|
||||
return trBuff
|
||||
|
||||
|
||||
def dump(obj):
|
||||
"""For debug..."""
|
||||
for attr in dir(obj):
|
||||
print("obj.%s = %s" % (attr, getattr(obj, attr)))
|
||||
|
||||
|
||||
def format_outstring(values, strTable):
|
||||
"""Construct the line for the final output."""
|
||||
s = ""
|
||||
for w in strTable:
|
||||
s += w + values["COMMAND_SPACE"]
|
||||
s = s.strip()
|
||||
return s
|
||||
|
||||
|
||||
def linenumber(values, space=None):
|
||||
"""Output the next line number if appropriate."""
|
||||
if values["OUTPUT_LINE_NUMBERS"]:
|
||||
if space is None:
|
||||
space = values["COMMAND_SPACE"]
|
||||
line_num = str(values["line_number"])
|
||||
values["line_number"] += values["LINE_INCREMENT"]
|
||||
return "N" + line_num + space
|
||||
return ""
|
||||
|
||||
|
||||
def parse(values, pathobj):
|
||||
"""Parse a Path."""
|
||||
out = ""
|
||||
lastcommand = None
|
||||
axis_precision_string = "." + str(values["AXIS_PRECISION"]) + "f"
|
||||
feed_precision_string = "." + str(values["FEED_PRECISION"]) + "f"
|
||||
|
||||
currLocation = {} # keep track for no doubles
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
comment = create_comment(
|
||||
"Compound: " + pathobj.Label, values["COMMENT_SYMBOL"]
|
||||
)
|
||||
out += linenumber(values) + comment + "\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(values, p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
if values["OUTPUT_PATH_LABELS"] and values["OUTPUT_COMMENTS"]:
|
||||
comment = create_comment("Path: " + pathobj.Label, values["COMMENT_SYMBOL"])
|
||||
out += linenumber(values) + comment + "\n"
|
||||
|
||||
if values["OUTPUT_ADAPTIVE"]:
|
||||
adaptiveOp = False
|
||||
opHorizRapid = 0
|
||||
opVertRapid = 0
|
||||
if "Adaptive" in pathobj.Name:
|
||||
adaptiveOp = True
|
||||
if hasattr(pathobj, "ToolController"):
|
||||
if (
|
||||
hasattr(pathobj.ToolController, "HorizRapid")
|
||||
and pathobj.ToolController.HorizRapid > 0
|
||||
):
|
||||
opHorizRapid = Units.Quantity(
|
||||
pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Tool Controller Horizontal Rapid Values are unset" + "\n"
|
||||
)
|
||||
if (
|
||||
hasattr(pathobj.ToolController, "VertRapid")
|
||||
and pathobj.ToolController.VertRapid > 0
|
||||
):
|
||||
opVertRapid = Units.Quantity(
|
||||
pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Tool Controller Vertical Rapid Values are unset" + "\n"
|
||||
)
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
|
||||
# List of elements in the command, code, and params.
|
||||
outstring = []
|
||||
# command M or G code or comment string
|
||||
command = c.Name
|
||||
if command[0] == "(":
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
if values["COMMENT_SYMBOL"] != "(":
|
||||
command = PostUtils.fcoms(command, values["COMMENT_SYMBOL"])
|
||||
else:
|
||||
continue
|
||||
if values["OUTPUT_ADAPTIVE"]:
|
||||
if adaptiveOp and command in values["RAPID_MOVES"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
command = "G1"
|
||||
else:
|
||||
outstring.append(
|
||||
"(Tool Controller Rapid Values are unset)" + "\n"
|
||||
)
|
||||
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if values["MODAL"]:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in values["PARAMETER_ORDER"]:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param]
|
||||
or values["OUTPUT_DOUBLES"]
|
||||
):
|
||||
# centroid and linuxcnc don't use rapid speeds
|
||||
if command not in values["RAPID_MOVES"]:
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(values["UNIT_SPEED_FORMAT"]) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(
|
||||
speed.getValueAs(
|
||||
values["UNIT_SPEED_FORMAT"]
|
||||
)
|
||||
),
|
||||
feed_precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param in ("H", "L", "T"):
|
||||
outstring.append(param + str(int(c.Parameters[param])))
|
||||
elif param == "D":
|
||||
if command in ("G41", "G42"):
|
||||
outstring.append(param + str(int(c.Parameters[param])))
|
||||
elif command in ("G41.1", "G42.1"):
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
elif command in ("G96", "G97"):
|
||||
outstring.append(
|
||||
param
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters[param],
|
||||
values["SPINDLE_DECIMALS"],
|
||||
"G21",
|
||||
)
|
||||
)
|
||||
else: # anything else that is supported
|
||||
outstring.append(param + str(float(c.Parameters[param])))
|
||||
elif param == "P":
|
||||
if command in (
|
||||
"G2",
|
||||
"G02",
|
||||
"G3",
|
||||
"G03",
|
||||
"G5.2",
|
||||
"G5.3",
|
||||
"G10",
|
||||
"G54.1",
|
||||
"G59",
|
||||
):
|
||||
outstring.append(param + str(int(c.Parameters[param])))
|
||||
elif command in ("G4", "G04", "G76", "G82", "G86", "G89"):
|
||||
outstring.append(param + str(float(c.Parameters[param])))
|
||||
elif command in ("G5", "G05", "G64"):
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
else: # anything else that is supported
|
||||
outstring.append(param + str(c.Parameters[param]))
|
||||
elif param == "Q":
|
||||
if command == "G10":
|
||||
outstring.append(param + str(int(c.Parameters[param])))
|
||||
elif command in ("G64", "G73", "G83"):
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
elif param == "S":
|
||||
outstring.append(
|
||||
param
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters[param], values["SPINDLE_DECIMALS"], "G21"
|
||||
)
|
||||
)
|
||||
else:
|
||||
if (
|
||||
(not values["OUTPUT_DOUBLES"])
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(values["UNIT_FORMAT"])),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
|
||||
if values["OUTPUT_ADAPTIVE"]:
|
||||
if adaptiveOp and command in values["RAPID_MOVES"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
if "Z" not in c.Parameters:
|
||||
outstring.append(
|
||||
"F"
|
||||
+ format(
|
||||
float(
|
||||
opHorizRapid.getValueAs(
|
||||
values["UNIT_SPEED_FORMAT"]
|
||||
)
|
||||
),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
outstring.append(
|
||||
"F"
|
||||
+ format(
|
||||
float(
|
||||
opVertRapid.getValueAs(
|
||||
values["UNIT_SPEED_FORMAT"]
|
||||
)
|
||||
),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
currLocation.update(c.Parameters)
|
||||
# Memorizes the current position for calculating the related movements
|
||||
# and the withdrawal plan
|
||||
if command in values["MOTION_COMMANDS"]:
|
||||
if "X" in c.Parameters:
|
||||
values["CURRENT_X"] = Units.Quantity(
|
||||
c.Parameters["X"], FreeCAD.Units.Length
|
||||
)
|
||||
if "Y" in c.Parameters:
|
||||
values["CURRENT_Y"] = Units.Quantity(
|
||||
c.Parameters["Y"], FreeCAD.Units.Length
|
||||
)
|
||||
if "Z" in c.Parameters:
|
||||
values["CURRENT_Z"] = Units.Quantity(
|
||||
c.Parameters["Z"], FreeCAD.Units.Length
|
||||
)
|
||||
|
||||
if command in ("G98", "G99"):
|
||||
values["DRILL_RETRACT_MODE"] = command
|
||||
|
||||
if command in ("G90", "G91"):
|
||||
values["MOTION_MODE"] = command
|
||||
|
||||
if values["TRANSLATE_DRILL_CYCLES"]:
|
||||
if command in values["DRILL_CYCLES_TO_TRANSLATE"]:
|
||||
out += drill_translate(values, outstring, command, c.Parameters)
|
||||
# Erase the line we just translated
|
||||
outstring = []
|
||||
|
||||
if values["SPINDLE_WAIT"] > 0:
|
||||
if command in ("M3", "M03", "M4", "M04"):
|
||||
out += (
|
||||
linenumber(values) + format_outstring(values, outstring) + "\n"
|
||||
)
|
||||
out += (
|
||||
linenumber(values)
|
||||
+ format_outstring(
|
||||
values, ["G4", "P%s" % values["SPINDLE_WAIT"]]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
outstring = []
|
||||
|
||||
# Check for Tool Change:
|
||||
if command in ("M6", "M06"):
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
comment = create_comment(
|
||||
"Begin toolchange", values["COMMENT_SYMBOL"]
|
||||
)
|
||||
out += linenumber(values) + comment + "\n"
|
||||
if values["OUTPUT_TOOL_CHANGE"]:
|
||||
if values["STOP_SPINDLE_FOR_TOOL_CHANGE"]:
|
||||
# stop the spindle
|
||||
out += linenumber(values) + "M5\n"
|
||||
for line in values["TOOL_CHANGE"].splitlines(False):
|
||||
out += linenumber(values) + line + "\n"
|
||||
else:
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
# convert the tool change to a comment
|
||||
comment = create_comment(
|
||||
values["COMMAND_SPACE"]
|
||||
+ format_outstring(values, outstring)
|
||||
+ values["COMMAND_SPACE"],
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
out += linenumber(values) + comment + "\n"
|
||||
outstring = []
|
||||
|
||||
if command == "message" and values["REMOVE_MESSAGES"]:
|
||||
if values["OUTPUT_COMMENTS"] is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
if command in values["SUPPRESS_COMMANDS"]:
|
||||
if values["OUTPUT_COMMENTS"]:
|
||||
# convert the command to a comment
|
||||
comment = create_comment(
|
||||
values["COMMAND_SPACE"]
|
||||
+ format_outstring(values, outstring)
|
||||
+ values["COMMAND_SPACE"],
|
||||
values["COMMENT_SYMBOL"],
|
||||
)
|
||||
out += linenumber(values) + comment + "\n"
|
||||
# remove the command
|
||||
outstring = []
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if values["OUTPUT_LINE_NUMBERS"]:
|
||||
# In this case we don't want a space after the line number
|
||||
# because the space is added in the join just below.
|
||||
outstring.insert(0, (linenumber(values, "")))
|
||||
|
||||
# append the line to the final output
|
||||
out += values["COMMAND_SPACE"].join(outstring)
|
||||
# Note: Do *not* strip `out`, since that forces the allocation
|
||||
# of a contiguous string & thus quadratic complexity.
|
||||
out += "\n"
|
||||
|
||||
# add height offset
|
||||
if command in ("M6", "M06") and values["USE_TLO"]:
|
||||
out += linenumber(values) + "G43 H" + str(int(c.Parameters["T"])) + "\n"
|
||||
|
||||
# Check for comments containing machine-specific commands
|
||||
# to pass literally to the controller
|
||||
if values["ENABLE_MACHINE_SPECIFIC_COMMANDS"]:
|
||||
m = re.match(r"^\(MC_RUN_COMMAND: ([^)]+)\)$", command)
|
||||
if m:
|
||||
raw_command = m.group(1)
|
||||
out += linenumber(values) + raw_command + "\n"
|
||||
|
||||
return out
|
||||
@@ -1,438 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
|
||||
# ****************************************************************************
|
||||
# * Modifications by Samuel Mayer (samuel.mayer@posteo.de) *
|
||||
# * 2021 *
|
||||
# * *
|
||||
# * This postprocessor is based on the linuxcnc_post coming with FreeCAD *
|
||||
# * 0.19 and modified to work with Kinetic-NC (cnc-step.com) and Beamicon2 *
|
||||
# * (benezan-electronics.de) (up to 4 Axis) *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for the KineticNC/Beamicon2 Control Software for up to 4 Axis (3 plus rotary).
|
||||
The CORNER_MAX Values are set for a mill with max travel of 1000mm in X, 600mm in Y and 300mm in Z direction.
|
||||
This postprocessor, once placed in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import KineticNCBeamicon2_post
|
||||
KineticNCBeamicon2_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog="linuxcnc", add_help=False)
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--modal",
|
||||
action="store_true",
|
||||
help="Output the Same G-command Name USE NonModal Mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
OUTPUT_DOUBLES = (
|
||||
True # if false duplicate axis values are suppressed if the same as previous line.
|
||||
)
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
MACHINE_NAME = "not set"
|
||||
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 1000, "y": 600, "z": 300}
|
||||
PRECISION = 3
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """%
|
||||
G17 G21 G40 G49 G80 G90
|
||||
M08
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05 M09
|
||||
G17 G90 G80 G40
|
||||
M30
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """M05
|
||||
M09"""
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
if args.modal:
|
||||
MODAL = True
|
||||
if args.axis_modal:
|
||||
print("here")
|
||||
OUTPUT_DOUBLES = False
|
||||
|
||||
except:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
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:
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
# 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 linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global PRECISION
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
currLocation = {} # keep track for no doubles
|
||||
|
||||
# the order of parameters
|
||||
# linuxcnc doesn't want K properties on XY plane Arcs need work.
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
|
||||
outstring = []
|
||||
command = c.Name
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
|
||||
):
|
||||
if c.Name not in [
|
||||
"G0",
|
||||
"G00",
|
||||
]: # linuxcnc doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param == "T":
|
||||
outstring.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "H":
|
||||
outstring.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "D":
|
||||
outstring.append(param + str(int(c.Parameters["D"])))
|
||||
elif param == "S":
|
||||
outstring.append(param + str(int(c.Parameters["S"])))
|
||||
else:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(begin toolchange)\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
@@ -1,362 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * Copyright (c) 2020 Schildkroet *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import datetime
|
||||
import PathScripts
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a centroid 3 axis mill. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import centroid_post
|
||||
centroid_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
TOOLTIP_ARGS = """
|
||||
Arguments for centroid:
|
||||
--header,--no-header ... output headers (--header)
|
||||
--comments,--no-comments ... output comments (--comments)
|
||||
--line-numbers,--no-line-numbers ... prefix with line numbers (--no-lin-numbers)
|
||||
--show-editor, --no-show-editor ... pop up editor before writing output(--show-editor)
|
||||
--feed-precision=1 ... number of digits of precision for feed rate. Default=1
|
||||
--axis-precision=4 ... number of digits of precision for axis moves. Default=4
|
||||
--inches ... Convert output for US imperial mode (G20)
|
||||
"""
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if FreeCAD.GuiUp:
|
||||
SHOW_EDITOR = True
|
||||
else:
|
||||
SHOW_EDITOR = False
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_FORMAT = "mm"
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
MACHINE_NAME = "Centroid"
|
||||
CORNER_MIN = {"x": -609.6, "y": -152.4, "z": 0} # use metric for internal units
|
||||
CORNER_MAX = {"x": 609.6, "y": 152.4, "z": 304.8} # use metric for internal units
|
||||
AXIS_PRECISION = 4
|
||||
FEED_PRECISION = 1
|
||||
SPINDLE_DECIMALS = 0
|
||||
|
||||
COMMENT = ";"
|
||||
|
||||
# gCode header with information about CAD-software, post-processor
|
||||
# and date/time
|
||||
if FreeCAD.ActiveDocument:
|
||||
cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName)
|
||||
else:
|
||||
cam_file = "<None>"
|
||||
|
||||
HEADER = """;Exported by FreeCAD
|
||||
;Post Processor: {}
|
||||
;CAM file: {}
|
||||
;Output Time: {}
|
||||
""".format(
|
||||
__name__, cam_file, str(datetime.datetime.now())
|
||||
)
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G53 G00 G17
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M99
|
||||
"""
|
||||
|
||||
TOOLRETURN = """M5
|
||||
M25
|
||||
G49 H0
|
||||
""" # spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle)
|
||||
|
||||
ZAXISRETURN = """G91 G28 X0 Z0
|
||||
G90
|
||||
"""
|
||||
|
||||
SAFETYBLOCK = """G90 G80 G40 G49
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global AXIS_PRECISION
|
||||
global FEED_PRECISION
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global UNITS
|
||||
|
||||
for arg in argstring.split():
|
||||
if arg == "--header":
|
||||
OUTPUT_HEADER = True
|
||||
elif arg == "--no-header":
|
||||
OUTPUT_HEADER = False
|
||||
elif arg == "--comments":
|
||||
OUTPUT_COMMENTS = True
|
||||
elif arg == "--no-comments":
|
||||
OUTPUT_COMMENTS = False
|
||||
elif arg == "--line-numbers":
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
elif arg == "--no-line-numbers":
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
elif arg == "--show-editor":
|
||||
SHOW_EDITOR = True
|
||||
elif arg == "--no-show-editor":
|
||||
SHOW_EDITOR = False
|
||||
elif arg.split("=")[0] == "--axis-precision":
|
||||
AXIS_PRECISION = arg.split("=")[1]
|
||||
elif arg.split("=")[0] == "--feed-precision":
|
||||
FEED_PRECISION = arg.split("=")[1]
|
||||
elif arg == "--inches":
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
processArguments(argstring)
|
||||
for i in objectslist:
|
||||
print(i.Name)
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
|
||||
print("postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
# write header
|
||||
if OUTPUT_HEADER:
|
||||
gcode += HEADER
|
||||
|
||||
gcode += SAFETYBLOCK
|
||||
|
||||
# Write the preamble
|
||||
if OUTPUT_COMMENTS:
|
||||
for item in objectslist:
|
||||
if hasattr(item, "Proxy") and isinstance(
|
||||
item.Proxy, PathScripts.PathToolController.ToolController
|
||||
):
|
||||
gcode += ";T{}={}\n".format(item.ToolNumber, item.Name)
|
||||
gcode += linenumber() + ";begin preamble\n"
|
||||
for line in PREAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
gcode += linenumber() + UNITS + "\n"
|
||||
|
||||
for obj in objectslist:
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + ";begin operation\n"
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
gcode += parse(obj)
|
||||
|
||||
# do the post_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + ";end operation: %s\n" % obj.Label
|
||||
for line in POST_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
# do the post_amble
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += ";begin postamble\n"
|
||||
for line in TOOLRETURN.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
for line in SAFETYBLOCK.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
if 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 linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
out = ""
|
||||
lastcommand = None
|
||||
axis_precision_string = "." + str(AXIS_PRECISION) + "f"
|
||||
feed_precision_string = "." + str(FEED_PRECISION) + "f"
|
||||
# params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control
|
||||
# the order of parameters
|
||||
# centroid doesn't want K properties on XY plane Arcs need work.
|
||||
params = ["X", "Y", "Z", "A", "B", "I", "J", "F", "S", "T", "Q", "R", "L", "H"]
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
commandlist = [] # list of elements in the command, code and params.
|
||||
command = c.Name # command M or G code or comment string
|
||||
|
||||
if command[0] == "(":
|
||||
command = PostUtils.fcoms(command, COMMENT)
|
||||
|
||||
commandlist.append(command)
|
||||
# if modal: only print the command if it is not the same as the
|
||||
# last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
commandlist.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
if c.Name not in [
|
||||
"G0",
|
||||
"G00",
|
||||
]: # centroid doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
commandlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
feed_precision_string,
|
||||
)
|
||||
)
|
||||
elif param == "H":
|
||||
commandlist.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "S":
|
||||
commandlist.append(
|
||||
param
|
||||
+ PostUtils.fmt(c.Parameters["S"], SPINDLE_DECIMALS, "G21")
|
||||
)
|
||||
elif param == "T":
|
||||
commandlist.append(param + str(int(c.Parameters["T"])))
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
commandlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)),
|
||||
axis_precision_string,
|
||||
)
|
||||
)
|
||||
outstr = str(commandlist)
|
||||
outstr = outstr.replace("[", "")
|
||||
outstr = outstr.replace("]", "")
|
||||
outstr = outstr.replace("'", "")
|
||||
outstr = outstr.replace(",", "")
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(begin toolchange)\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# if command == "message":
|
||||
# if OUTPUT_COMMENTS is False:
|
||||
# out = []
|
||||
# else:
|
||||
# commandlist.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(commandlist) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
commandlist.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in commandlist:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
@@ -1,112 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
|
||||
TOOLTIP = """Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output."""
|
||||
|
||||
SHOW_EDITOR = True
|
||||
|
||||
|
||||
def fmt(num):
|
||||
fnum = ""
|
||||
fnum += "%.3f" % (num)
|
||||
return fnum
|
||||
|
||||
|
||||
def ffmt(num):
|
||||
fnum = ""
|
||||
fnum += "%.1f" % (num)
|
||||
return fnum
|
||||
|
||||
|
||||
class saveVals(object):
|
||||
"""save command info for modal output"""
|
||||
|
||||
def __init__(self, command):
|
||||
self.com = command.Name
|
||||
self.params = command.Parameters
|
||||
|
||||
def retVals(self):
|
||||
return self.com, self.params
|
||||
|
||||
|
||||
def lineout(command, oldvals, modal):
|
||||
line = ""
|
||||
if modal and (oldvals.com == command.Name):
|
||||
line += ""
|
||||
else:
|
||||
line += str(command.Name)
|
||||
if command.Name == "M6":
|
||||
line += "T" + str(int(command.Parameters["T"]))
|
||||
if command.Name == "M3":
|
||||
line += "S" + str(ffmt(command.Parameters["S"]))
|
||||
if command.Name == "M4":
|
||||
line += "S" + str(ffmt(command.Parameters["S"]))
|
||||
if "X" in command.Parameters:
|
||||
line += "X" + str(fmt(command.Parameters["X"]))
|
||||
if "Y" in command.Parameters:
|
||||
line += "Y" + str(fmt(command.Parameters["Y"]))
|
||||
if "Z" in command.Parameters:
|
||||
line += "Z" + str(fmt(command.Parameters["Z"]))
|
||||
if "I" in command.Parameters:
|
||||
line += "I" + str(fmt(command.Parameters["I"]))
|
||||
if "J" in command.Parameters:
|
||||
line += "J" + str(fmt(command.Parameters["J"]))
|
||||
if "F" in command.Parameters:
|
||||
line += "F" + str(ffmt(command.Parameters["F"]))
|
||||
return line
|
||||
|
||||
|
||||
def export(obj, filename, argstring):
|
||||
modal = True
|
||||
gcode = ""
|
||||
safetyblock1 = "G90G40G49\n"
|
||||
gcode += safetyblock1
|
||||
|
||||
units = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units")
|
||||
if units.GetInt("UserSchema") == 0:
|
||||
firstcommand = Path.Command("G21") # metric mode
|
||||
else:
|
||||
firstcommand = Path.Command("G20") # inch mode
|
||||
oldvals = saveVals(firstcommand) # save first command for modal use
|
||||
fp = obj[0]
|
||||
gcode += firstcommand.Name
|
||||
|
||||
if hasattr(fp, "Path"):
|
||||
for c in fp.Path.Commands:
|
||||
gcode += lineout(c, oldvals, modal) + "\n"
|
||||
oldvals = saveVals(c)
|
||||
gcode += "M2\n"
|
||||
gfile = open(filename, "w")
|
||||
gfile.write(gcode)
|
||||
gfile.close()
|
||||
else:
|
||||
FreeCAD.Console.PrintError("Select a path object and try again\n")
|
||||
|
||||
if SHOW_EDITOR:
|
||||
FreeCAD.Console.PrintMessage("Editor Activated\n")
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
dia.exec_()
|
||||
@@ -1,103 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
TOOLTIP = """
|
||||
Dumper is an extremely simple postprocessor file for the Path workbench. It is used
|
||||
to dump the command list from one or more Path objects for simple inspection. This post
|
||||
doesn't do any manipulation of the path and doesn't write anything to disk. It just
|
||||
shows the dialog so you can see it. Useful for debugging, but not much else.
|
||||
"""
|
||||
import datetime
|
||||
from PathScripts import PostUtils
|
||||
|
||||
now = datetime.datetime.now()
|
||||
SHOW_EDITOR = True
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"called when freecad exports a list of objects"
|
||||
|
||||
output = """(This output produced with the dump post processor)
|
||||
(Dump is useful for inspecting the raw commands in your paths)
|
||||
(but is not useful for driving machines.)
|
||||
(Consider setting a default postprocessor in your project or )
|
||||
(exporting your paths using a specific post that matches your machine)
|
||||
|
||||
"""
|
||||
|
||||
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
|
||||
print("postprocessing...")
|
||||
output += parse(obj)
|
||||
|
||||
if SHOW_EDITOR:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(output)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = output
|
||||
else:
|
||||
final = output
|
||||
|
||||
print("done postprocessing.")
|
||||
return final
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
out = ""
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
out += "(Group: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
if not hasattr(
|
||||
pathobj, "Path"
|
||||
): # groups might contain non-path things like stock.
|
||||
return out
|
||||
|
||||
out += "(Path: " + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
out += str(c) + "\n"
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,144 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 sliptonic <shopinghewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Path
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import datetime
|
||||
import importDXF
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
a dxf file.
|
||||
Operations are output to layers.
|
||||
vertical moves are ignore
|
||||
All path moves are flattened to z=0
|
||||
|
||||
Does NOT remove redundant lines. If you have multiple step-downs in your
|
||||
operation, you'll get multiple redundant lines in your dxf.
|
||||
|
||||
import dxf_post
|
||||
"""
|
||||
|
||||
TOOLTIP_ARGS = """
|
||||
Arguments for dxf:
|
||||
"""
|
||||
now = datetime.datetime.now()
|
||||
|
||||
# # These globals set common customization preferences
|
||||
OUTPUT_HEADER = True
|
||||
|
||||
if False:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
pass
|
||||
# global OUTPUT_HEADER
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
doc = FreeCAD.ActiveDocument
|
||||
print("postprocessing...")
|
||||
layers = []
|
||||
processArguments(argstring)
|
||||
for i in objectslist:
|
||||
result = parse(i)
|
||||
if len(result) > 0:
|
||||
layername = i.Name
|
||||
grp = doc.addObject("App::DocumentObjectGroup", layername)
|
||||
for o in result:
|
||||
o.adjustRelativeLinks(grp)
|
||||
grp.addObject(o)
|
||||
layers.append(grp)
|
||||
|
||||
dxfWrite(layers, filename)
|
||||
|
||||
|
||||
def dxfWrite(objlist, filename):
|
||||
importDXF.export(objlist, filename)
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
"""accepts a Path object. Returns a list of wires"""
|
||||
|
||||
feedcommands = PathGeom.CmdMove
|
||||
rapidcommands = PathGeom.CmdMoveRapid
|
||||
|
||||
edges = []
|
||||
objlist = []
|
||||
|
||||
# Gotta start somewhere. Assume 0,0,0
|
||||
curPoint = FreeCAD.Vector(0, 0, 0)
|
||||
for c in pathobj.Path.Commands:
|
||||
Path.Log.debug("{} -> {}".format(curPoint, c))
|
||||
if "Z" in c.Parameters:
|
||||
newparams = c.Parameters
|
||||
newparams.pop("Z", None)
|
||||
flatcommand = Path.Command(c.Name, newparams)
|
||||
c.Parameters = newparams
|
||||
else:
|
||||
flatcommand = c
|
||||
|
||||
# ignore gcode that isn't moving
|
||||
if flatcommand.Name not in feedcommands + rapidcommands:
|
||||
Path.Log.debug("non move")
|
||||
continue
|
||||
|
||||
# ignore pure vertical feed and rapid
|
||||
if (
|
||||
flatcommand.Parameters.get("X", curPoint.x) == curPoint.x
|
||||
and flatcommand.Parameters.get("Y", curPoint.y) == curPoint.y
|
||||
):
|
||||
Path.Log.debug("vertical")
|
||||
continue
|
||||
|
||||
# feeding move. Build an edge
|
||||
if flatcommand.Name in feedcommands:
|
||||
edges.append(PathGeom.edgeForCmd(flatcommand, curPoint))
|
||||
Path.Log.debug("feeding move")
|
||||
|
||||
# update the curpoint
|
||||
curPoint.x = flatcommand.Parameters.get("X", curPoint.x)
|
||||
curPoint.y = flatcommand.Parameters.get("Y", curPoint.y)
|
||||
|
||||
if len(edges) > 0:
|
||||
candidates = Part.sortEdges(edges)
|
||||
for c in candidates:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Wire")
|
||||
obj.Shape = Part.Wire(c)
|
||||
objlist.append(obj)
|
||||
|
||||
return objlist
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# * This file has been modified from Sliptonic original Linux CNC post *
|
||||
# * for use with a Dynapath 20 controller all changes and Modifications *
|
||||
# * (c) Linden (Linden@aktfast.net) 2016 *
|
||||
# * *
|
||||
# * Additional changes 2020 adding arguments to control units, precision *
|
||||
# * editor, header, etc. Now uses FreeCAD unit system to correctly *
|
||||
# * set Feed rate - sliptonic *
|
||||
# * *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a Tree Journyman 325 3 axis mill with Dynapath 20
|
||||
controller in MM. This is a work in progress and very few of the functions
|
||||
available on the Dynapath have been implemented at this time.
|
||||
This postprocessor, once placed in the appropriate PathScripts folder,
|
||||
can be used directly from inside FreeCAD, via the GUI importer or via python
|
||||
scripts with:
|
||||
|
||||
Done
|
||||
Coordinate Decimal limited to 3 places
|
||||
Feed limited to hole number no decimal
|
||||
Speed Limited to hole number no decimal
|
||||
Machine Travel Limits Set to approximate values
|
||||
Line numbers start at one and incremental by 1
|
||||
Set preamble
|
||||
Set postamble
|
||||
|
||||
To Do
|
||||
Change G20 and G21 to G70 and G71 for metric or imperial units
|
||||
Convert arcs to absolute
|
||||
Strip comments and white spaces
|
||||
Add file name in brackets limited to 8 alpha numeric no spaces all caps as
|
||||
first line in file
|
||||
Change Q to K For depth of peck on G83
|
||||
Fix tool change
|
||||
Limit comments length and characters to Uppercase, alpha numeric and
|
||||
spaces add / prior to comments
|
||||
|
||||
import dynapath_post
|
||||
dynapath_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(prog="dynapath_post", add_help=False)
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90\nG80\nG40"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M09\nM05\nG80\nG40\nG17\nG90\nM30"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 1 # line number starting value
|
||||
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
MACHINE_NAME = "Tree MM"
|
||||
CORNER_MIN = {"x": -340, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 340, "y": -355, "z": -150}
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G17
|
||||
G90
|
||||
;G90.1 ;needed for simulation only
|
||||
G80
|
||||
G40
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M09
|
||||
M05
|
||||
G80
|
||||
G40
|
||||
G17
|
||||
G90
|
||||
M30
|
||||
"""
|
||||
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global USE_TLO
|
||||
global OUTPUT_DOUBLES
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
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
|
||||
|
||||
print("postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
# Find the machine.
|
||||
# The user my have overridden post processor defaults in the GUI. Make sure we're using the current values in the Machine Def.
|
||||
myMachine = None
|
||||
for pathobj in objectslist:
|
||||
if hasattr(pathobj, "MachineName"):
|
||||
myMachine = pathobj.MachineName
|
||||
if hasattr(pathobj, "MachineUnits"):
|
||||
if pathobj.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
else:
|
||||
UNITS = "G20"
|
||||
if myMachine is None:
|
||||
print("No machine found in this selection")
|
||||
|
||||
# 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(True):
|
||||
gcode += linenumber() + line
|
||||
gcode += linenumber() + UNITS + "\n"
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: " + obj.Label + ")\n"
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
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
|
||||
|
||||
# do the post_amble
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "(begin postamble)\n"
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
print("show editor: {}".format(SHOW_EDITOR))
|
||||
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.")
|
||||
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 1
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global PRECISION
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
|
||||
# params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters
|
||||
params = ["X", "Y", "Z", "A", "B", "I", "J", "F", "S", "T", "Q", "R", "L"]
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
if not hasattr(
|
||||
pathobj, "Path"
|
||||
): # groups might contain non-path things like stock.
|
||||
return out
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
outstring = []
|
||||
command = c.Name
|
||||
outstring.append(command)
|
||||
# if modal: only print the command if it is not the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
elif param == "S":
|
||||
outstring.append(
|
||||
param + format(c.Parameters[param], precision_string)
|
||||
)
|
||||
elif param == "T":
|
||||
outstring.append(
|
||||
param + format(c.Parameters["T"], precision_string)
|
||||
)
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(begin toolchange)\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,104 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
TOOLTIP = """
|
||||
This is an example postprocessor file for the Path workbench. It is used
|
||||
to save a list of FreeCAD Path objects to a file.
|
||||
|
||||
Read the Path Workbench documentation to know how to convert Path objects
|
||||
to GCode.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"called when freecad exports a list of objects"
|
||||
if len(objectslist) > 1:
|
||||
print("This script is unable to write more than one Path object")
|
||||
return
|
||||
obj = objectslist[0]
|
||||
if not hasattr(obj, "Path"):
|
||||
print("the given object is not a path")
|
||||
gcode = obj.Path.toGCode()
|
||||
gcode = parse(gcode)
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(gcode)
|
||||
gfile.close()
|
||||
|
||||
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a parsed output string"
|
||||
print("postprocessing...")
|
||||
|
||||
output = ""
|
||||
|
||||
# write some stuff first
|
||||
output += "N10 ;time:" + str(now) + "\n"
|
||||
output += "N20 G17 G20 G80 G40 G90\n"
|
||||
output += "N30 (Exported by FreeCAD)\n"
|
||||
|
||||
linenr = 100
|
||||
lastcommand = None
|
||||
# treat the input line by line
|
||||
lines = inputstring.split("\n")
|
||||
for line in lines:
|
||||
# split the G/M command from the arguments
|
||||
if " " in line:
|
||||
command, args = line.split(" ", 1)
|
||||
else:
|
||||
# no space found, which means there are no arguments
|
||||
command = line
|
||||
args = ""
|
||||
# add a line number
|
||||
output += "N" + str(linenr) + " "
|
||||
# only print the command if it is not the same as the last one
|
||||
if command != lastcommand:
|
||||
output += command + " "
|
||||
output += args + "\n"
|
||||
# increment the line number
|
||||
linenr += 10
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# write some more stuff at the end
|
||||
output += "N" + str(linenr) + " M05\n"
|
||||
output += "N" + str(linenr + 10) + " M25\n"
|
||||
output += "N" + str(linenr + 20) + " G00 X-1.0 Y1.0\n"
|
||||
output += "N" + str(linenr + 30) + " G17 G80 G40 G90\n"
|
||||
output += "N" + str(linenr + 40) + " M99\n"
|
||||
|
||||
print("done postprocessing.")
|
||||
return output
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,117 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
|
||||
"""
|
||||
This is an example preprocessor file for the Path workbench. Its aim is to
|
||||
open a gcode file, parse its contents, and create the appropriate objects
|
||||
in FreeCAD. This preprocessor will not add imported gcode to an existing
|
||||
job. For a more useful preprocessor, look at the gcode_pre.py file
|
||||
|
||||
Read the Path Workbench documentation to know how to create Path objects
|
||||
from GCode.
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import os
|
||||
|
||||
# LEVEL = Path.Log.Level.DEBUG
|
||||
LEVEL = Path.Log.Level.INFO
|
||||
Path.Log.setLevel(LEVEL, Path.Log.thisModule())
|
||||
|
||||
if LEVEL == Path.Log.Level.DEBUG:
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def open(filename):
|
||||
"called when freecad opens a file."
|
||||
Path.Log.track(filename)
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
insert(filename, doc.Name)
|
||||
|
||||
|
||||
def insert(filename, docname):
|
||||
"called when freecad imports a file"
|
||||
Path.Log.track(filename)
|
||||
gfile = pythonopen(filename)
|
||||
gcode = gfile.read()
|
||||
gfile.close()
|
||||
gcode = parse(gcode)
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
obj = doc.addObject("Path::Feature", "Path")
|
||||
path = Path.Path(gcode)
|
||||
obj.Path = path
|
||||
|
||||
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a parsed output string"
|
||||
print("preprocessing...")
|
||||
Path.Log.track(inputstring)
|
||||
# split the input by line
|
||||
lines = inputstring.split("\n")
|
||||
output = []
|
||||
lastcommand = None
|
||||
|
||||
for lin in lines:
|
||||
# remove any leftover trailing and preceding spaces
|
||||
lin = lin.strip()
|
||||
if not lin:
|
||||
# discard empty lines
|
||||
continue
|
||||
if lin[0].upper() in ["N"]:
|
||||
# remove line numbers
|
||||
lin = lin.split(" ", 1)
|
||||
if len(lin) >= 1:
|
||||
lin = lin[1].strip()
|
||||
else:
|
||||
continue
|
||||
|
||||
if lin[0] in ["(", "%", "#", ";"]:
|
||||
# discard comment and other non strictly gcode lines
|
||||
continue
|
||||
if lin[0].upper() in ["G", "M"]:
|
||||
# found a G or M command: we store it
|
||||
output.append(Path.Command(str(lin)))
|
||||
last = lin[0].upper()
|
||||
for c in lin[1:]:
|
||||
if not c.isdigit():
|
||||
break
|
||||
else:
|
||||
last += c
|
||||
lastcommand = last
|
||||
elif lastcommand:
|
||||
# no G or M command: we repeat the last one
|
||||
output.append(Path.Command(str(lastcommand + " " + lin)))
|
||||
|
||||
print("done preprocessing.")
|
||||
return output
|
||||
|
||||
|
||||
print(__name__ + " gcode preprocessor loaded.")
|
||||
@@ -1,321 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2017 imarin *
|
||||
# * *
|
||||
# * Heavily based on gbrl post-procesor by: *
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import datetime
|
||||
from PathScripts import PostUtils
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
"""
|
||||
Generate g-code compatible with fablin from a Path.
|
||||
|
||||
import fablin_post
|
||||
fablin_post.export(object,"/path/to/file.ncc")
|
||||
"""
|
||||
|
||||
TOOLTIP_ARGS = """
|
||||
Arguments for fablin:
|
||||
--rapids-feedrate ... feedrate to be used for rapids (e.g. --rapids-feedrate=300)
|
||||
--header,--no-header ... output headers (--header)
|
||||
--comments,--no-comments ... output comments (--comments)
|
||||
--line-numbers,--no-line-numbers ... prefix with line numbers (--no-lin-numbers)
|
||||
--show-editor, --no-show-editor ... pop up editor before writing output(--show-editor)
|
||||
"""
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = False # Fablin does not support parenthesis, it will echo the command complaining. As a side effect the spinner may turn at a very reduced speed (do not ask me why).
|
||||
OUTPUT_HEADER = False # Same as above. You can enable this by passing arguments to the post-processor in case you need them for example for debugging the gcode.
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
OUTPUT_TOOL_CHANGE = False
|
||||
|
||||
SHOW_EDITOR = True
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "" # only metric, G20/G21 is ignored
|
||||
MACHINE_NAME = "FABLIN"
|
||||
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 500, "y": 300, "z": 300}
|
||||
|
||||
RAPID_MOVES = ["G0", "G00"]
|
||||
|
||||
RAPID_FEEDRATE = 10000
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M5
|
||||
G00 X-1.0 Y1.0
|
||||
G90
|
||||
"""
|
||||
|
||||
# These commands are ignored by commenting them out
|
||||
SUPPRESS_COMMANDS = ["G98", "G80", "M6", "G17"]
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global RAPID_FEEDRATE
|
||||
for arg in argstring.split():
|
||||
if arg == "--header":
|
||||
OUTPUT_HEADER = True
|
||||
elif arg == "--no-header":
|
||||
OUTPUT_HEADER = False
|
||||
elif arg == "--comments":
|
||||
OUTPUT_COMMENTS = True
|
||||
elif arg == "--no-comments":
|
||||
OUTPUT_COMMENTS = False
|
||||
elif arg == "--line-numbers":
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
elif arg == "--no-line-numbers":
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
elif arg == "--show-editor":
|
||||
SHOW_EDITOR = True
|
||||
elif arg == "--no-show-editor":
|
||||
SHOW_EDITOR = False
|
||||
|
||||
params = arg.split("=")
|
||||
|
||||
if params[0] == "--rapids-feedrate":
|
||||
RAPID_FEEDRATE = params[1]
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
processArguments(argstring)
|
||||
global UNITS
|
||||
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
|
||||
|
||||
print("postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
# Find the machine.
|
||||
# The user my have overridden post processor defaults in the GUI.
|
||||
# Make sure we're using the current values in the Machine Def.
|
||||
myMachine = None
|
||||
for pathobj in objectslist:
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
for p in pathobj.Group:
|
||||
if p.Name == "Machine":
|
||||
myMachine = p
|
||||
if myMachine is None:
|
||||
print("No machine found in this project")
|
||||
else:
|
||||
if myMachine.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
else:
|
||||
UNITS = "G20"
|
||||
|
||||
# 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(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: " + obj.Label + ")\n"
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
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
|
||||
|
||||
# do the post_amble
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "(begin postamble)\n"
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
if 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.")
|
||||
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
out = ""
|
||||
lastcommand = None
|
||||
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
] # linuxcnc doesn't want K properties on XY plane Arcs need work.
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
if not hasattr(
|
||||
pathobj, "Path"
|
||||
): # groups might contain non-path things like stock.
|
||||
return out
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
outstring = []
|
||||
command = c.Name
|
||||
|
||||
# fablin does not support parenthesis syntax, so removing that (pocket) in the agnostic gcode
|
||||
if command[0] == "(":
|
||||
if not OUTPUT_COMMENTS:
|
||||
pass
|
||||
else:
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: only print the command if it is not the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
if command not in RAPID_MOVES:
|
||||
outstring.append(param + format(c.Parameters["F"], ".2f"))
|
||||
elif param == "T":
|
||||
outstring.append(param + str(c.Parameters["T"]))
|
||||
else:
|
||||
outstring.append(param + format(c.Parameters[param], ".4f"))
|
||||
|
||||
if command in RAPID_MOVES and command != lastcommand:
|
||||
outstring.append("F" + format(RAPID_FEEDRATE))
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(begin toolchange)\n"
|
||||
if not OUTPUT_TOOL_CHANGE:
|
||||
outstring.insert(0, ";")
|
||||
else:
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
if command in SUPPRESS_COMMANDS:
|
||||
outstring = []
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,634 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2021 shadowbane1000 <tyler@colberts.us> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
import os.path
|
||||
from PathScripts import PostUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable should be suitable for most Fanuc controllers.
|
||||
It has only been tested on a 21i-MB controller on a 3 axis mill.
|
||||
This postprocessor, once placed in the appropriate PathScripts folder,
|
||||
can be used directly from inside FreeCAD, via the GUI importer or via
|
||||
python scripts with:
|
||||
|
||||
import fanuc_post
|
||||
fanuc_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog="fanuc", add_help=False)
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-modal",
|
||||
action="store_true",
|
||||
help="Don't output the Same G-command Name USE NonModal Mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-axis-modal", action="store_true", help="Don't output the Same Axis Value Mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-tlo",
|
||||
action="store_true",
|
||||
help="suppress tool length offset (G43) following tool changes",
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = True # if true commands are suppressed if the same as previous line.
|
||||
USE_TLO = True # if true G43 will be output following tool changes
|
||||
OUTPUT_DOUBLES = (
|
||||
False # if false duplicate axis values are suppressed if the same as previous line.
|
||||
)
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
MACHINE_NAME = "fanuc"
|
||||
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 500, "y": 300, "z": 300}
|
||||
PRECISION = 3
|
||||
|
||||
# this global is used to pass spindle speed from the tool command into the machining command for
|
||||
# rigid tapping.
|
||||
tapSpeed = 0
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G17 G54 G40 G49 G80 G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05
|
||||
G17 G54 G90 G80 G40
|
||||
M6 T0
|
||||
M2
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global USE_TLO
|
||||
global OUTPUT_DOUBLES
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
if args.no_modal:
|
||||
MODAL = False
|
||||
if args.no_tlo:
|
||||
USE_TLO = False
|
||||
if args.no_axis_modal:
|
||||
OUTPUT_DOUBLES = True
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
if not processArguments(argstring):
|
||||
return None
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
global HORIZRAPID
|
||||
global VERTRAPID
|
||||
|
||||
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 += "%\n"
|
||||
gcode += ";\n"
|
||||
gcode += (
|
||||
os.path.split(filename)[-1]
|
||||
+ " ("
|
||||
+ "FREECAD-FILENAME-GOES-HERE"
|
||||
+ ", "
|
||||
+ "JOB-NAME-GOES-HERE"
|
||||
+ ")\n"
|
||||
)
|
||||
gcode += linenumber() + "(" + filename.upper() + ",EXPORTED BY FREECAD!)\n"
|
||||
gcode += linenumber() + "(POST PROCESSOR: " + __name__.upper() + ")\n"
|
||||
gcode += linenumber() + "(OUTPUT TIME:" + str(now).upper() + ")\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 hasattr(obj, "Active"):
|
||||
if not obj.Active:
|
||||
continue
|
||||
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper()
|
||||
gcode += linenumber() + "(MACHINE UNITS: %s)\n" % (
|
||||
UNIT_SPEED_FORMAT.upper()
|
||||
)
|
||||
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.upper() + ")\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.upper()
|
||||
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.upper() + ")\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
|
||||
gcode += "%\n"
|
||||
|
||||
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 linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global PRECISION
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
global tapSpeed
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
currLocation = {} # keep track for no doubles
|
||||
print("Startup!")
|
||||
|
||||
# the order of parameters
|
||||
# arcs need work. original code from mach3_4 doesn't want K properties on XY plane. Not sure
|
||||
# what fanuc does here.
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
adaptiveOp = False
|
||||
opHorizRapid = 0
|
||||
opVertRapid = 0
|
||||
|
||||
if "Adaptive" in pathobj.Name:
|
||||
adaptiveOp = True
|
||||
if hasattr(pathobj, "ToolController"):
|
||||
if (
|
||||
hasattr(pathobj.ToolController, "HorizRapid")
|
||||
and pathobj.ToolController.HorizRapid > 0
|
||||
):
|
||||
opHorizRapid = Units.Quantity(
|
||||
pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Tool Controller Horizontal Rapid Values are unset" + "\n"
|
||||
)
|
||||
|
||||
if (
|
||||
hasattr(pathobj.ToolController, "VertRapid")
|
||||
and pathobj.ToolController.VertRapid > 0
|
||||
):
|
||||
opVertRapid = Units.Quantity(
|
||||
pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Tool Controller Vertical Rapid Values are unset" + "\n"
|
||||
)
|
||||
|
||||
for index, c in enumerate(pathobj.Path.Commands):
|
||||
|
||||
outstring = []
|
||||
command = c.Name
|
||||
if index + 1 == len(pathobj.Path.Commands):
|
||||
nextcommand = ""
|
||||
else:
|
||||
nextcommand = pathobj.Path.Commands[index + 1].Name
|
||||
|
||||
if adaptiveOp and c.Name in ["G0", "G00"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
command = "G1"
|
||||
else:
|
||||
outstring.append("(TOOL CONTROLLER RAPID VALUES ARE UNSET)" + "\n")
|
||||
|
||||
# suppress moves in fixture selection
|
||||
if pathobj.Label == "Fixture":
|
||||
if command == "G0":
|
||||
continue
|
||||
|
||||
# if it's a tap, we rigid tap, so don't start the spindle yet...
|
||||
if command == "M03" or command == "M3":
|
||||
if pathobj.Tool.ToolType == "Tap":
|
||||
tapSpeed = int(pathobj.SpindleSpeed)
|
||||
continue
|
||||
|
||||
# convert drill cycles to tap cycles if tool is a tap
|
||||
if command == "G81" or command == "G83":
|
||||
if (
|
||||
hasattr(pathobj, "ToolController")
|
||||
and pathobj.ToolController.Tool.ToolType == "Tap"
|
||||
):
|
||||
command = "G84"
|
||||
out += linenumber() + "G95\n"
|
||||
paramstring = ""
|
||||
for param in ["X", "Y"]:
|
||||
if param in c.Parameters:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
paramstring += (
|
||||
" "
|
||||
+ param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
if paramstring != "":
|
||||
out += linenumber() + "G00" + paramstring + "\n"
|
||||
|
||||
if "S" in c.Parameters:
|
||||
tapSpeed = int(c.Parameters["S"])
|
||||
out += "M29 S" + str(tapSpeed) + "\n"
|
||||
|
||||
for param in ["Z", "R"]:
|
||||
if param in c.Parameters:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
paramstring += (
|
||||
" "
|
||||
+ param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
# in this mode, F is the distance per revolution of the thread (pitch)
|
||||
# P is the dwell time in seconds at the bottom of the thread
|
||||
# Q is the peck depth of the threading operation
|
||||
for param in ["F", "P", "Q"]:
|
||||
if param in c.Parameters:
|
||||
value = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
paramstring += (
|
||||
" "
|
||||
+ param
|
||||
+ format(
|
||||
float(value.getValueAs(UNIT_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
|
||||
out += linenumber() + "G84" + paramstring + "\n"
|
||||
out += linenumber() + "G80\n"
|
||||
out += linenumber() + "G94\n"
|
||||
continue
|
||||
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# suppress a G80 between two identical command
|
||||
if command == "G80" and lastcommand == nextcommand:
|
||||
continue
|
||||
|
||||
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
|
||||
):
|
||||
if c.Name not in [
|
||||
"G0",
|
||||
"G00",
|
||||
]: # fanuc doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param == "T":
|
||||
outstring.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "H":
|
||||
outstring.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "D":
|
||||
outstring.append(param + str(int(c.Parameters["D"])))
|
||||
elif param == "S":
|
||||
outstring.append(param + str(int(c.Parameters["S"])))
|
||||
else:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
if adaptiveOp and c.Name in ["G0", "G00"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
if "Z" not in c.Parameters:
|
||||
outstring.append(
|
||||
"F"
|
||||
+ format(
|
||||
float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
outstring.append(
|
||||
"F"
|
||||
+ format(
|
||||
float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# stop the spindle
|
||||
out += linenumber() + "M5\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# add height offset
|
||||
if USE_TLO:
|
||||
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
|
||||
outstring.append(tool_height)
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w.upper() + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,252 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
|
||||
|
||||
"""
|
||||
This is an example preprocessor file for the Path workbench. Its aim is to
|
||||
open a gcode file, parse its contents, and create the appropriate objects
|
||||
in FreeCAD.
|
||||
|
||||
This preprocessor will split gcode on tool changes and create one or more
|
||||
PathCustom objects in the job. Tool Change commands themselves are not
|
||||
preserved. It is up to the user to create and assign appropriate tool
|
||||
controllers.
|
||||
|
||||
Only gcodes that are supported by Path are imported. Thus things like G43
|
||||
are suppressed.
|
||||
|
||||
Importing gcode is inherently dangerous because context cannot be safely
|
||||
assumed. The user should carefully examine the resulting gcode!
|
||||
|
||||
Read the Path Workbench documentation to know how to create Path objects
|
||||
from GCode.
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import os
|
||||
import re
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import PathScripts.PathCustomGui as PathCustomGui
|
||||
|
||||
PathCustom = PathCustomGui.PathCustom
|
||||
else:
|
||||
import PathScripts.PathCustom as PathCustom
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
if False:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
class PathNoActiveDocumentException(Exception):
|
||||
"""PathNoActiveDocumentException is raised when no active document is found."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("No active document")
|
||||
|
||||
|
||||
class PathNoJobException(Exception):
|
||||
"""PathNoJobException is raised when no target Job object is available."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("No job object")
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def open(filename):
|
||||
"""called when freecad opens a file."""
|
||||
Path.Log.track(filename)
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
insert(filename, doc.Name)
|
||||
|
||||
|
||||
def matchToolController(op, toolnumber):
|
||||
"""Try to match a tool controller in the job by number"""
|
||||
toolcontrollers = PathUtils.getToolControllers(op)
|
||||
for tc in toolcontrollers:
|
||||
if tc.ToolNumber == toolnumber:
|
||||
return tc
|
||||
return toolcontrollers[0]
|
||||
|
||||
|
||||
def _isImportEnvironmentReady():
|
||||
"""_isImportEnvironmentReady(docname)...
|
||||
Helper function to verify an active document exists, and that a Job object is available
|
||||
as a receiver for the Custom operation(s) that will be created as a result of the import process."""
|
||||
|
||||
# Verify active document exists
|
||||
if FreeCAD.ActiveDocument is None:
|
||||
raise PathNoActiveDocumentException()
|
||||
|
||||
# Verify a Job object is available, and add one if not
|
||||
if not PathUtils.GetJobs():
|
||||
raise PathNoJobException()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a parsed output string"
|
||||
|
||||
supported = [
|
||||
"G0",
|
||||
"G00",
|
||||
"G1",
|
||||
"G01",
|
||||
"G2",
|
||||
"G02",
|
||||
"G3",
|
||||
"G03",
|
||||
"G81",
|
||||
"G82",
|
||||
"G83",
|
||||
"G90",
|
||||
"G91",
|
||||
]
|
||||
|
||||
axis = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"]
|
||||
|
||||
FreeCAD.Console.PrintMessage("preprocessing...\n")
|
||||
Path.Log.track(inputstring)
|
||||
# split the input by line
|
||||
lines = inputstring.splitlines()
|
||||
output = []
|
||||
lastcommand = None
|
||||
|
||||
for lin in lines:
|
||||
# remove any leftover trailing and preceding spaces
|
||||
lin = lin.strip()
|
||||
|
||||
# discard empty lines
|
||||
if not lin:
|
||||
continue
|
||||
|
||||
# remove line numbers
|
||||
if lin[0].upper() in ["N"]:
|
||||
lin = lin.split(" ", 1)
|
||||
if len(lin) >= 1:
|
||||
lin = lin[1].strip()
|
||||
else:
|
||||
continue
|
||||
|
||||
# Anything else not a G/M code or an axis move is ignored.
|
||||
if lin[0] not in ["G", "M", "X", "Y", "Z", "A", "B", "C", "U", "V", "W"]:
|
||||
continue
|
||||
|
||||
# if the remaining line is supported, store it
|
||||
currcommand = lin.split()[0]
|
||||
|
||||
if currcommand in supported:
|
||||
output.append(lin)
|
||||
lastcommand = currcommand
|
||||
|
||||
# modal commands have no G or M but have axis moves. append those too.
|
||||
elif currcommand[0] in axis and lastcommand:
|
||||
output.append(lastcommand + " " + lin)
|
||||
|
||||
FreeCAD.Console.PrintMessage("done preprocessing.\n")
|
||||
return output
|
||||
|
||||
|
||||
def _identifygcodeByToolNumberList(filename):
|
||||
"""called when freecad imports a file"""
|
||||
Path.Log.track(filename)
|
||||
gcodeByToolNumberList = []
|
||||
|
||||
gfile = pythonopen(filename)
|
||||
gcode = gfile.read()
|
||||
gfile.close()
|
||||
|
||||
# Regular expression to match tool changes in the format 'M6 Tn'
|
||||
p = re.compile("[mM]+?\s?0?6\s?T\d*\s")
|
||||
|
||||
# split the gcode on tool changes
|
||||
paths = re.split("([mM]+?\s?0?6\s?T\d*\s)", gcode)
|
||||
|
||||
# iterate the gcode sections and add customs for each
|
||||
toolnumber = 0
|
||||
|
||||
for path in paths:
|
||||
|
||||
# if the section is a tool change, extract the tool number
|
||||
m = p.match(path)
|
||||
if m:
|
||||
toolnumber = int(m.group().split("T")[-1])
|
||||
continue
|
||||
|
||||
# Parse the gcode and throw away any empty lists
|
||||
gcode = parse(path)
|
||||
if len(gcode) > 0:
|
||||
gcodeByToolNumberList.append((gcode, toolnumber))
|
||||
|
||||
return gcodeByToolNumberList
|
||||
|
||||
|
||||
def insert(filename, docname=None):
|
||||
"""called when freecad imports a file"""
|
||||
Path.Log.track(filename)
|
||||
|
||||
try:
|
||||
if not _isImportEnvironmentReady():
|
||||
return
|
||||
except PathNoActiveDocumentException:
|
||||
Path.Log.error(translate("Path_Gcode_pre", "No active document"))
|
||||
return
|
||||
except PathNoJobException:
|
||||
Path.Log.error(translate("Path_Gcode_pre", "No job object"))
|
||||
return
|
||||
|
||||
# Create a Custom operation for each gcode-toolNumber pair
|
||||
for gcode, toolNumber in _identifygcodeByToolNumberList(filename):
|
||||
obj = PathCustom.Create("Custom")
|
||||
|
||||
# Set the gcode and try to match a tool controller
|
||||
obj.Gcode = gcode
|
||||
obj.ToolController = matchToolController(obj, toolNumber)
|
||||
if docname:
|
||||
obj.Label = obj.Label + "_" + docname
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# Add a view provider to a Custom operation object
|
||||
obj.ViewObject.Proxy = PathCustomGui.PathOpGui.ViewProvider(
|
||||
obj.ViewObject, PathCustomGui.Command.res
|
||||
)
|
||||
obj.ViewObject.Proxy.setDeleteObjectsOnReject(False)
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
print(__name__ + " gcode preprocessor loaded.")
|
||||
@@ -1,740 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2018, 2019 Gauthier Briere *
|
||||
# * Copyright (c) 2019, 2020 Schildkroet *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import re
|
||||
|
||||
|
||||
TOOLTIP = """
|
||||
Generate g-code from a Path that is compatible with the grbl controller.
|
||||
import grbl_post
|
||||
grbl_post.export(object, "/path/to/file.ncc")
|
||||
"""
|
||||
|
||||
|
||||
# ***************************************************************************
|
||||
# * Globals set customization preferences
|
||||
# ***************************************************************************
|
||||
|
||||
# Default values for command line arguments:
|
||||
OUTPUT_COMMENTS = True # default output of comments in output gCode file
|
||||
OUTPUT_HEADER = True # default output header in output gCode file
|
||||
OUTPUT_LINE_NUMBERS = False # default doesn't output line numbers in output gCode file
|
||||
OUTPUT_BCNC = (
|
||||
False # default doesn't add bCNC operation block headers in output gCode file
|
||||
)
|
||||
SHOW_EDITOR = True # default show the resulting file dialog output in GUI
|
||||
PRECISION = 3 # Default precision for metric (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices)
|
||||
TRANSLATE_DRILL_CYCLES = False # If true, G81, G82 & G83 are translated in G0/G1 moves
|
||||
PREAMBLE = """G17 G90
|
||||
""" # default preamble text will appear at the beginning of the gCode output file.
|
||||
POSTAMBLE = """M5
|
||||
G17 G90
|
||||
M2
|
||||
""" # default postamble text will appear following the last operation.
|
||||
|
||||
SPINDLE_WAIT = 0 # no waiting after M3 / M4 by default
|
||||
RETURN_TO = None # no movements after end of program
|
||||
|
||||
# Customisation with no command line argument
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
LINENR = 100 # line number starting value
|
||||
LINEINCR = 10 # line number increment
|
||||
OUTPUT_TOOL_CHANGE = False # default don't output M6 tool changes (comment it) as grbl currently does not handle it
|
||||
DRILL_RETRACT_MODE = "G98" # Default value of drill retractations (CURRENT_Z) other possible value is G99
|
||||
MOTION_MODE = "G90" # G90 for absolute moves, G91 for relative
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_FORMAT = "mm"
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
PRE_OPERATION = """""" # Pre operation text will be inserted before every operation
|
||||
POST_OPERATION = """""" # Post operation text will be inserted after every operation
|
||||
TOOL_CHANGE = """""" # Tool Change commands will be inserted before a tool change
|
||||
|
||||
# ***************************************************************************
|
||||
# * End of customization
|
||||
# ***************************************************************************
|
||||
|
||||
# Parser arguments list & definition
|
||||
parser = argparse.ArgumentParser(prog="grbl", add_help=False)
|
||||
parser.add_argument("--comments", action="store_true", help="output comment (default)")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument("--header", action="store_true", help="output headers (default)")
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-line-numbers",
|
||||
action="store_true",
|
||||
help="don't prefix with line numbers (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-editor",
|
||||
action="store_true",
|
||||
help="pop up editor before writing output (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--translate_drill",
|
||||
action="store_true",
|
||||
help="translate drill cycles G81, G82 & G83 in G0/G1 movements",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-translate_drill",
|
||||
action="store_true",
|
||||
help="don't translate drill cycles G81, G82 & G83 in G0/G1 movements (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17 G90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M5\nG17 G90\n;M2"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool-change", action="store_true", help="Insert M6 for all tool changes"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wait-for-spindle",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Wait for spindle to reach desired speed after M3 / M4, default=0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--return-to",
|
||||
default="",
|
||||
help="Move to the specified coordinates at the end, e.g. --return-to=0,0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bcnc",
|
||||
action="store_true",
|
||||
help="Add Job operations as bCNC block headers. Consider suppressing existing comments: Add argument --no-comments",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-bcnc", action="store_true", help="suppress bCNC block header output (default)"
|
||||
)
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
|
||||
# ***************************************************************************
|
||||
# * Internal global variables
|
||||
# ***************************************************************************
|
||||
MOTION_COMMANDS = [
|
||||
"G0",
|
||||
"G00",
|
||||
"G1",
|
||||
"G01",
|
||||
"G2",
|
||||
"G02",
|
||||
"G3",
|
||||
"G03",
|
||||
] # Motion gCode commands definition
|
||||
RAPID_MOVES = ["G0", "G00"] # Rapid moves gCode commands definition
|
||||
SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out
|
||||
COMMAND_SPACE = " "
|
||||
# Global variables storing current position
|
||||
CURRENT_X = 0
|
||||
CURRENT_Y = 0
|
||||
CURRENT_Z = 0
|
||||
|
||||
|
||||
# ***************************************************************************
|
||||
# * to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global TRANSLATE_DRILL_CYCLES
|
||||
global OUTPUT_TOOL_CHANGE
|
||||
global SPINDLE_WAIT
|
||||
global RETURN_TO
|
||||
global OUTPUT_BCNC
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.header:
|
||||
OUTPUT_HEADER = True
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.comments:
|
||||
OUTPUT_COMMENTS = True
|
||||
if args.no_line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
if args.show_editor:
|
||||
SHOW_EDITOR = True
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.no_translate_drill:
|
||||
TRANSLATE_DRILL_CYCLES = False
|
||||
if args.translate_drill:
|
||||
TRANSLATE_DRILL_CYCLES = True
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
if args.tool_change:
|
||||
OUTPUT_TOOL_CHANGE = True
|
||||
if args.wait_for_spindle > 0:
|
||||
SPINDLE_WAIT = args.wait_for_spindle
|
||||
if args.return_to != "":
|
||||
RETURN_TO = [int(v) for v in args.return_to.split(",")]
|
||||
if len(RETURN_TO) != 2:
|
||||
RETURN_TO = None
|
||||
print("--return-to coordinates must be specified as <x>,<y>, ignoring")
|
||||
if args.bcnc:
|
||||
OUTPUT_BCNC = True
|
||||
if args.no_bcnc:
|
||||
OUTPUT_BCNC = False
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# For debug...
|
||||
def dump(obj):
|
||||
for attr in dir(obj):
|
||||
print("obj.%s = %s" % (attr, getattr(obj, attr)))
|
||||
|
||||
|
||||
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"
|
||||
|
||||
if RETURN_TO:
|
||||
gcode += linenumber() + "G0 X%s Y%s\n" % tuple(RETURN_TO)
|
||||
|
||||
# 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
|
||||
|
||||
# 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()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
if not OUTPUT_LINE_NUMBERS:
|
||||
return ""
|
||||
global LINENR
|
||||
global LINEINCR
|
||||
s = "N" + str(LINENR) + " "
|
||||
LINENR += LINEINCR
|
||||
return s
|
||||
|
||||
|
||||
def format_outstring(strTable):
|
||||
global COMMAND_SPACE
|
||||
# construct the line for the final output
|
||||
s = ""
|
||||
for w in strTable:
|
||||
s += w + COMMAND_SPACE
|
||||
s = s.strip()
|
||||
return s
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
|
||||
global DRILL_RETRACT_MODE
|
||||
global MOTION_MODE
|
||||
global CURRENT_X
|
||||
global CURRENT_Y
|
||||
global CURRENT_Z
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"P",
|
||||
]
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
|
||||
else: # parsing simple path
|
||||
if not hasattr(
|
||||
pathobj, "Path"
|
||||
): # groups might contain non-path things like stock.
|
||||
return out
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
outstring = []
|
||||
command = c.Name
|
||||
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: only print the command if it is not the same as the last one
|
||||
if MODAL:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
if command not in RAPID_MOVES:
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
elif param in ["T", "H", "S"]:
|
||||
outstring.append(param + str(int(c.Parameters[param])))
|
||||
elif param in ["D", "P", "L"]:
|
||||
outstring.append(param + str(c.Parameters[param]))
|
||||
elif param in ["A", "B", "C"]:
|
||||
outstring.append(
|
||||
param + format(c.Parameters[param], precision_string)
|
||||
)
|
||||
else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Conversion eventuelle mm/inches)
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# Memorizes the current position for calculating the related movements and the withdrawal plan
|
||||
if command in MOTION_COMMANDS:
|
||||
if "X" in c.Parameters:
|
||||
CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length)
|
||||
if "Y" in c.Parameters:
|
||||
CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length)
|
||||
if "Z" in c.Parameters:
|
||||
CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length)
|
||||
|
||||
if command in ("G98", "G99"):
|
||||
DRILL_RETRACT_MODE = command
|
||||
|
||||
if command in ("G90", "G91"):
|
||||
MOTION_MODE = command
|
||||
|
||||
if TRANSLATE_DRILL_CYCLES:
|
||||
if command in ("G81", "G82", "G83"):
|
||||
out += drill_translate(outstring, command, c.Parameters)
|
||||
# Erase the line we just translated
|
||||
outstring = []
|
||||
|
||||
if SPINDLE_WAIT > 0:
|
||||
if command in ("M3", "M03", "M4", "M04"):
|
||||
out += linenumber() + format_outstring(outstring) + "\n"
|
||||
out += (
|
||||
linenumber()
|
||||
+ format_outstring(["G4", "P%s" % SPINDLE_WAIT])
|
||||
+ "\n"
|
||||
)
|
||||
outstring = []
|
||||
|
||||
# Check for Tool Change:
|
||||
if command in ("M6", "M06"):
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Begin toolchange)\n"
|
||||
if not OUTPUT_TOOL_CHANGE:
|
||||
outstring.insert(0, "(")
|
||||
outstring.append(")")
|
||||
else:
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
if command in SUPPRESS_COMMANDS:
|
||||
outstring.insert(0, "(")
|
||||
outstring.append(")")
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
out += linenumber() + format_outstring(outstring) + "\n"
|
||||
|
||||
# Check for comments containing machine-specific commands to pass literally to the controller
|
||||
m = re.match(r"^\(MC_RUN_COMMAND: ([^)]+)\)$", command)
|
||||
if m:
|
||||
raw_command = m.group(1)
|
||||
out += linenumber() + raw_command + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def drill_translate(outstring, cmd, params):
|
||||
global DRILL_RETRACT_MODE
|
||||
global MOTION_MODE
|
||||
global CURRENT_X
|
||||
global CURRENT_Y
|
||||
global CURRENT_Z
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
|
||||
strFormat = "." + str(PRECISION) + "f"
|
||||
|
||||
trBuff = ""
|
||||
|
||||
if OUTPUT_COMMENTS: # Comment the original command
|
||||
outstring[0] = "(" + outstring[0]
|
||||
outstring[-1] = outstring[-1] + ")"
|
||||
trBuff += linenumber() + format_outstring(outstring) + "\n"
|
||||
|
||||
# cycle conversion
|
||||
# currently only cycles in XY are provided (G17)
|
||||
# other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only.
|
||||
drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length)
|
||||
drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length)
|
||||
drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length)
|
||||
RETRACT_Z = Units.Quantity(params["R"], FreeCAD.Units.Length)
|
||||
# R less than Z is error
|
||||
if RETRACT_Z < drill_Z:
|
||||
trBuff += linenumber() + "(drill cycle error: R less than Z )\n"
|
||||
return trBuff
|
||||
|
||||
if MOTION_MODE == "G91": # G91 relative movements
|
||||
drill_X += CURRENT_X
|
||||
drill_Y += CURRENT_Y
|
||||
drill_Z += CURRENT_Z
|
||||
RETRACT_Z += CURRENT_Z
|
||||
|
||||
if DRILL_RETRACT_MODE == "G98" and CURRENT_Z >= RETRACT_Z:
|
||||
RETRACT_Z = CURRENT_Z
|
||||
|
||||
# get the other parameters
|
||||
drill_feedrate = Units.Quantity(params["F"], FreeCAD.Units.Velocity)
|
||||
if cmd == "G83":
|
||||
drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length)
|
||||
a_bit = (
|
||||
drill_Step * 0.05
|
||||
) # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit."
|
||||
elif cmd == "G82":
|
||||
drill_DwellTime = params["P"]
|
||||
|
||||
# wrap this block to ensure machine MOTION_MODE is restored in case of error
|
||||
try:
|
||||
if MOTION_MODE == "G91":
|
||||
trBuff += linenumber() + "G90\n" # force absolute coordinates during cycles
|
||||
|
||||
strG0_RETRACT_Z = (
|
||||
"G0 Z" + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
|
||||
)
|
||||
strF_Feedrate = (
|
||||
" F"
|
||||
+ format(float(drill_feedrate.getValueAs(UNIT_SPEED_FORMAT)), ".2f")
|
||||
+ "\n"
|
||||
)
|
||||
print(strF_Feedrate)
|
||||
|
||||
# preliminary movement(s)
|
||||
if CURRENT_Z < RETRACT_Z:
|
||||
trBuff += linenumber() + strG0_RETRACT_Z
|
||||
trBuff += (
|
||||
linenumber()
|
||||
+ "G0 X"
|
||||
+ format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat)
|
||||
+ " Y"
|
||||
+ format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat)
|
||||
+ "\n"
|
||||
)
|
||||
if CURRENT_Z > RETRACT_Z:
|
||||
# NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z. Here use G1 since retract height may be below surface !
|
||||
trBuff += (
|
||||
linenumber()
|
||||
+ "G1 Z"
|
||||
+ format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
last_Stop_Z = RETRACT_Z
|
||||
|
||||
# drill moves
|
||||
if cmd in ("G81", "G82"):
|
||||
trBuff += (
|
||||
linenumber()
|
||||
+ "G1 Z"
|
||||
+ format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
# pause where applicable
|
||||
if cmd == "G82":
|
||||
trBuff += linenumber() + "G4 P" + str(drill_DwellTime) + "\n"
|
||||
trBuff += linenumber() + strG0_RETRACT_Z
|
||||
else: # 'G83'
|
||||
if params["Q"] != 0:
|
||||
while 1:
|
||||
if last_Stop_Z != RETRACT_Z:
|
||||
clearance_depth = (
|
||||
last_Stop_Z + a_bit
|
||||
) # rapid move to just short of last drilling depth
|
||||
trBuff += (
|
||||
linenumber()
|
||||
+ "G0 Z"
|
||||
+ format(
|
||||
float(clearance_depth.getValueAs(UNIT_FORMAT)),
|
||||
strFormat,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
next_Stop_Z = last_Stop_Z - drill_Step
|
||||
if next_Stop_Z > drill_Z:
|
||||
trBuff += (
|
||||
linenumber()
|
||||
+ "G1 Z"
|
||||
+ format(
|
||||
float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat
|
||||
)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
trBuff += linenumber() + strG0_RETRACT_Z
|
||||
last_Stop_Z = next_Stop_Z
|
||||
else:
|
||||
trBuff += (
|
||||
linenumber()
|
||||
+ "G1 Z"
|
||||
+ format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat)
|
||||
+ strF_Feedrate
|
||||
)
|
||||
trBuff += linenumber() + strG0_RETRACT_Z
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if MOTION_MODE == "G91":
|
||||
trBuff += linenumber() + "G91" # Restore if changed
|
||||
|
||||
return trBuff
|
||||
|
||||
|
||||
# print(__name__ + ": GCode postprocessor loaded.")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,409 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a jtech photonics laser. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import jtech_post
|
||||
jtech_post.export(object,"/path/to/file.ngc","")
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog="jtech", add_help=False)
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="M05 S0\nG90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05 S0\nM2"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--modal",
|
||||
action="store_true",
|
||||
help="Output the Same G-command Name USE NonModal Mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--power-on-delay",
|
||||
default="255",
|
||||
help="milliseconds - Add a delay after laser on before moving to pre-heat material. Default=0",
|
||||
)
|
||||
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
OUTPUT_DOUBLES = (
|
||||
True # if false duplicate axis values are suppressed if the same as previous line.
|
||||
)
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
MACHINE_NAME = "JTECH Photonic Laser"
|
||||
PRECISION = 3
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """M05 S0
|
||||
G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05 S0
|
||||
M2
|
||||
"""
|
||||
|
||||
PRE_FEED = """M03
|
||||
G4 P{}
|
||||
"""
|
||||
|
||||
POST_FEED = """M05
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
POWER_ON_DELAY = 0
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ == "__builtin__":
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
global POWER_ON_DELAY
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
if args.modal:
|
||||
MODAL = True
|
||||
if args.axis_modal:
|
||||
OUTPUT_DOUBLES = False
|
||||
POWER_ON_DELAY = float(args.power_on_delay) / 1000 # milliseconds
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
if not processArguments(argstring):
|
||||
return None
|
||||
|
||||
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:
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
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
|
||||
|
||||
# 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, "wb")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
currLocation = {} # keep track for no doubles
|
||||
RAPID_MOVES = ["G0", "G00"]
|
||||
FEED_MOVES = ["G1", "G01", "G2", "G02", "G3", "G03"]
|
||||
# the order of parameters
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
|
||||
outstring = []
|
||||
command = c.Name
|
||||
|
||||
controlstring = ""
|
||||
if command in FEED_MOVES and lastcommand in RAPID_MOVES:
|
||||
controlstring = PRE_FEED.format(POWER_ON_DELAY)
|
||||
elif command in RAPID_MOVES and lastcommand in FEED_MOVES:
|
||||
controlstring = POST_FEED
|
||||
if len(controlstring) > 0:
|
||||
out += controlstring
|
||||
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
|
||||
):
|
||||
if (
|
||||
c.Name not in RAPID_MOVES
|
||||
): # linuxcnc doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param == "T":
|
||||
outstring.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "H":
|
||||
outstring.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "D":
|
||||
outstring.append(param + str(int(c.Parameters["D"])))
|
||||
elif param == "S":
|
||||
outstring.append(param + str(int(c.Parameters["S"])))
|
||||
else:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
continue
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,459 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a linuxcnc 3 axis mill. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import linuxcnc_post
|
||||
linuxcnc_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog="linuxcnc", add_help=False)
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--modal",
|
||||
action="store_true",
|
||||
help="Output the Same G-command Name USE NonModal Mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-tlo",
|
||||
action="store_true",
|
||||
help="suppress tool length offset (G43) following tool changes",
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
USE_TLO = True # if true G43 will be output following tool changes
|
||||
OUTPUT_DOUBLES = (
|
||||
True # if false duplicate axis values are suppressed if the same as previous line.
|
||||
)
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
MACHINE_NAME = "LinuxCNC"
|
||||
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 500, "y": 300, "z": 300}
|
||||
PRECISION = 3
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G17 G54 G40 G49 G80 G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global USE_TLO
|
||||
global OUTPUT_DOUBLES
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
if args.modal:
|
||||
MODAL = True
|
||||
if args.no_tlo:
|
||||
USE_TLO = False
|
||||
if args.axis_modal:
|
||||
print("here")
|
||||
OUTPUT_DOUBLES = False
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
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 hasattr(obj, "Active"):
|
||||
if not obj.Active:
|
||||
continue
|
||||
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
|
||||
gcode += linenumber() + "(machine units: %s)\n" % (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:
|
||||
final = gcode
|
||||
if len(gcode) > 100000:
|
||||
print("Skipping editor since output is greater than 100kb")
|
||||
else:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = gcode
|
||||
|
||||
print("done postprocessing.")
|
||||
|
||||
if not filename == "-":
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global PRECISION
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
currLocation = {} # keep track for no doubles
|
||||
|
||||
# the order of parameters
|
||||
# linuxcnc doesn't want K properties on XY plane Arcs need work.
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
|
||||
outstring = []
|
||||
command = c.Name
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
|
||||
):
|
||||
if c.Name not in [
|
||||
"G0",
|
||||
"G00",
|
||||
]: # linuxcnc doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param == "T":
|
||||
outstring.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "H":
|
||||
outstring.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "D":
|
||||
outstring.append(param + str(int(c.Parameters["D"])))
|
||||
elif param == "S":
|
||||
outstring.append(param + str(int(c.Parameters["S"])))
|
||||
else:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# stop the spindle
|
||||
out += linenumber() + "M5\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# add height offset
|
||||
if USE_TLO:
|
||||
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
|
||||
outstring.append(tool_height)
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
# Note: Do *not* strip `out`, since that forces the allocation
|
||||
# of a contiguous string & thus quadratic complexity.
|
||||
out += "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,514 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a mach3_4 3 axis mill. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import mach3_4_post
|
||||
mach3_4_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog="mach3_4", add_help=False)
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--modal",
|
||||
action="store_true",
|
||||
help="Output the Same G-command Name USE NonModal Mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-tlo",
|
||||
action="store_true",
|
||||
help="suppress tool length offset (G43) following tool changes",
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
USE_TLO = True # if true G43 will be output following tool changes
|
||||
OUTPUT_DOUBLES = (
|
||||
True # if false duplicate axis values are suppressed if the same as previous line.
|
||||
)
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
MACHINE_NAME = "mach3_4"
|
||||
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 500, "y": 300, "z": 300}
|
||||
PRECISION = 3
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G17 G54 G40 G49 G80 G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global USE_TLO
|
||||
global OUTPUT_DOUBLES
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
if args.modal:
|
||||
MODAL = True
|
||||
if args.no_tlo:
|
||||
USE_TLO = False
|
||||
if args.axis_modal:
|
||||
print("here")
|
||||
OUTPUT_DOUBLES = False
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
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 hasattr(obj, "Active"):
|
||||
if not obj.Active:
|
||||
continue
|
||||
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
|
||||
gcode += linenumber() + "(machine: %s, %s)\n" % (
|
||||
MACHINE_NAME,
|
||||
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 linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global PRECISION
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
currLocation = {} # keep track for no doubles
|
||||
|
||||
# the order of parameters
|
||||
# mach3_4 doesn't want K properties on XY plane Arcs need work.
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
adaptiveOp = False
|
||||
opHorizRapid = 0
|
||||
opVertRapid = 0
|
||||
|
||||
if "Adaptive" in pathobj.Name:
|
||||
adaptiveOp = True
|
||||
if hasattr(pathobj, "ToolController"):
|
||||
if (
|
||||
hasattr(pathobj.ToolController, "HorizRapid")
|
||||
and pathobj.ToolController.HorizRapid > 0
|
||||
):
|
||||
opHorizRapid = Units.Quantity(
|
||||
pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Tool Controller Horizontal Rapid Values are unset" + "\n"
|
||||
)
|
||||
|
||||
if (
|
||||
hasattr(pathobj.ToolController, "VertRapid")
|
||||
and pathobj.ToolController.VertRapid > 0
|
||||
):
|
||||
opVertRapid = Units.Quantity(
|
||||
pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Tool Controller Vertical Rapid Values are unset" + "\n"
|
||||
)
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
|
||||
outstring = []
|
||||
command = c.Name
|
||||
|
||||
if adaptiveOp and c.Name in ["G0", "G00"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
command = "G1"
|
||||
else:
|
||||
outstring.append("(Tool Controller Rapid Values are unset)" + "\n")
|
||||
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
|
||||
):
|
||||
if c.Name not in [
|
||||
"G0",
|
||||
"G00",
|
||||
]: # mach3_4 doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param == "T":
|
||||
outstring.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "H":
|
||||
outstring.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "D":
|
||||
outstring.append(param + str(int(c.Parameters["D"])))
|
||||
elif param == "S":
|
||||
outstring.append(param + str(int(c.Parameters["S"])))
|
||||
else:
|
||||
if (
|
||||
(not OUTPUT_DOUBLES)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
if adaptiveOp and c.Name in ["G0", "G00"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
if "Z" not in c.Parameters:
|
||||
outstring.append(
|
||||
"F"
|
||||
+ format(
|
||||
float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
outstring.append(
|
||||
"F"
|
||||
+ format(
|
||||
float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# stop the spindle
|
||||
out += linenumber() + "M5\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# add height offset
|
||||
if USE_TLO:
|
||||
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
|
||||
outstring.append(tool_height)
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,789 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# * *
|
||||
# * (c) sliptonic (shopinthewoods@gmail.com) 2014 *
|
||||
# * (c) Gauthier Briere - 2018, 2019 *
|
||||
# * (c) Schildkroet - 2019-2020 *
|
||||
# * (c) Gary L Hasson - 2020 *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import shlex
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
|
||||
Revised = "2020-11-03" # Revision date for this file.
|
||||
|
||||
# *****************************************************************************
|
||||
# * Due to the fundamentals of the FreeCAD pre-processor, *
|
||||
# * this post processor can only operate in the following modes: *
|
||||
# * G90 Absolute positions *
|
||||
# * G21 Metric units (mm) *
|
||||
# * G17 XY plane (3 axis vertical milling only) *
|
||||
# * *
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
TOOLTIP = """
|
||||
Generate g-code from a Path that is compatible with the Marlin controller.
|
||||
import marlin_post
|
||||
marlin_post.export(object, "/path/to/file.nc")
|
||||
"""
|
||||
|
||||
# *****************************************************************************
|
||||
# * Initial configuration, not changeable *
|
||||
# *****************************************************************************
|
||||
MOTION_MODE = "G90" # G90 only, for absolute moves
|
||||
WORK_PLANE = "G17" # G17 only, XY plane, for vertical milling
|
||||
UNITS = "G21" # G21 only, for metric
|
||||
UNIT_FORMAT = "mm"
|
||||
UNIT_FEED_FORMAT = "mm/min"
|
||||
|
||||
# *****************************************************************************
|
||||
# * Initial configuration, changeable via command line arguments *
|
||||
# *****************************************************************************
|
||||
PRECISION = 3 # Decimal places displayed for metric
|
||||
DRILL_RETRACT_MODE = "G98" # End of drill-cycle retractation type. G99
|
||||
# is the alternative.
|
||||
TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated
|
||||
# into G0/G1 moves
|
||||
OUTPUT_TOOL_CHANGE = False # Do not output M6 tool change (comment it)
|
||||
RETURN_TO = None # None = No movement at end of program
|
||||
SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4
|
||||
MODAL = False # True: Commands are suppressed if they are
|
||||
# the same as the previous line
|
||||
LINENR = 100 # Line number starting value
|
||||
LINEINCR = 10 # Line number increment
|
||||
PRE_OPERATION = """""" # Pre operation text will be inserted before
|
||||
# every operation
|
||||
POST_OPERATION = """""" # Post operation text will be inserted after
|
||||
# every operation
|
||||
TOOL_CHANGE = """""" # Tool Change commands will be inserted
|
||||
# before a tool change
|
||||
|
||||
# *****************************************************************************
|
||||
# * Initial gcode output options, changeable via command line arguments *
|
||||
# *****************************************************************************
|
||||
OUTPUT_HEADER = True # Output header in output gcode file
|
||||
OUTPUT_COMMENTS = True # Comments in output gcode file
|
||||
OUTPUT_FINISH = False # Include an operation finished comment
|
||||
OUTPUT_PATH = False # Include a Path: comment
|
||||
OUTPUT_MARLIN_CONFIG = False # Display expected #defines for Marlin config
|
||||
OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file
|
||||
OUTPUT_BCNC = False # Add bCNC operation block headers in output
|
||||
# gcode file
|
||||
SHOW_EDITOR = True # Display the resulting gcode file
|
||||
|
||||
# *****************************************************************************
|
||||
# * Command line arguments *
|
||||
# *****************************************************************************
|
||||
parser = argparse.ArgumentParser(prog="marlin", add_help=False)
|
||||
parser.add_argument("--header", action="store_true", help="output headers (default)")
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument("--comments", action="store_true", help="output comment (default)")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--finish-comments", action="store_true", help="output finish-comment"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-finish-comments",
|
||||
action="store_true",
|
||||
help="suppress finish-comment output (default)",
|
||||
)
|
||||
parser.add_argument("--path-comments", action="store_true", help="output path-comment")
|
||||
parser.add_argument(
|
||||
"--no-path-comments",
|
||||
action="store_true",
|
||||
help="suppress path-comment output (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--marlin-config", action="store_true", help="output #defines for Marlin"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-marlin-config",
|
||||
action="store_true",
|
||||
help="suppress output #defines for Marlin (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-line-numbers",
|
||||
action="store_true",
|
||||
help="do not prefix with line numbers (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-editor",
|
||||
action="store_true",
|
||||
help="pop up editor before writing output (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="do not pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--translate_drill",
|
||||
action="store_true",
|
||||
help="translate drill cycles G81, G82, G83 into G0/G1 movements (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-translate_drill",
|
||||
action="store_true",
|
||||
help="do not translate drill cycles G81, G82, G83 into G0/G1 movements",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble", help='set commands to be issued before the first command, default=""'
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble", help='set commands to be issued after the last command, default="M5"'
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool-change", action="store_true", help="Insert M6 for all tool changes"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wait-for-spindle",
|
||||
type=int,
|
||||
default=3,
|
||||
help="Wait for spindle to reach desired speed after M3 or M4, default=0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--return-to",
|
||||
default="",
|
||||
help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bcnc",
|
||||
action="store_true",
|
||||
help="Add Job operations as bCNC block headers. \
|
||||
Consider suppressing existing comments: Add argument --no-comments",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-bcnc", action="store_true", help="suppress bCNC block header output (default)"
|
||||
)
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# *****************************************************************************
|
||||
# * Marlin 2.x: *
|
||||
# * Ignores commands that it does not implement. *
|
||||
# * Some machining-related commands may conflict with gcodes that Marlin *
|
||||
# * has assigned to 3D printing commands. *
|
||||
# * Therefore, check FreeCAD gcodes for conflicts with Marlin. *
|
||||
# * Marlin 2.x ignores the ENTIRE COMMAND LINE if there is more than *
|
||||
# * one command per line. *
|
||||
# *****************************************************************************
|
||||
|
||||
# Default preamble text will appear at the beginning of the gcode output file.
|
||||
PREAMBLE = """"""
|
||||
|
||||
# Default postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M5
|
||||
"""
|
||||
|
||||
# *****************************************************************************
|
||||
# * Internal global variables *
|
||||
# *****************************************************************************
|
||||
MOTION_COMMANDS = ["G0", "G00", "G1", "G01", "G2", "G02", "G3", "G03"]
|
||||
RAPID_MOVES = ["G0", "G00"] # Rapid moves gcode commands definition
|
||||
SUPPRESS_COMMANDS = [""] # These commands are ignored by commenting them out
|
||||
COMMAND_SPACE = " "
|
||||
# Global variables storing current position (Use None for safety.)
|
||||
CURRENT_X = None
|
||||
CURRENT_Y = None
|
||||
CURRENT_Z = None
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_FINISH
|
||||
global OUTPUT_PATH
|
||||
global OUTPUT_MARLIN_CONFIG
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_FEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global TRANSLATE_DRILL_CYCLES
|
||||
global OUTPUT_TOOL_CHANGE
|
||||
global SPINDLE_WAIT
|
||||
global RETURN_TO
|
||||
global OUTPUT_BCNC
|
||||
global PRECISION
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.header:
|
||||
OUTPUT_HEADER = True
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.comments:
|
||||
OUTPUT_COMMENTS = True
|
||||
if args.no_finish_comments:
|
||||
OUTPUT_FINISH = False
|
||||
if args.finish_comments:
|
||||
OUTPUT_FINISH = True
|
||||
if args.no_path_comments:
|
||||
OUTPUT_PATH = False
|
||||
if args.path_comments:
|
||||
OUTPUT_PATH = True
|
||||
if args.no_marlin_config:
|
||||
OUTPUT_MARLIN_CONFIG = False
|
||||
if args.marlin_config:
|
||||
OUTPUT_MARLIN_CONFIG = True
|
||||
if args.no_line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
if args.show_editor:
|
||||
SHOW_EDITOR = True
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.no_translate_drill:
|
||||
TRANSLATE_DRILL_CYCLES = False
|
||||
if args.translate_drill:
|
||||
TRANSLATE_DRILL_CYCLES = True
|
||||
if args.tool_change:
|
||||
OUTPUT_TOOL_CHANGE = True
|
||||
if args.return_to:
|
||||
RETURN_TO = args.return_to
|
||||
if RETURN_TO.find(",") == -1:
|
||||
RETURN_TO = None
|
||||
print("--return-to coordinates must be specified as:")
|
||||
print('--return-to "x.n,y.n,z.n"')
|
||||
if args.bcnc:
|
||||
OUTPUT_BCNC = True
|
||||
if args.no_bcnc:
|
||||
OUTPUT_BCNC = False
|
||||
SPINDLE_WAIT = args.wait_for_spindle
|
||||
PRECISION = args.precision
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# For debug...
|
||||
def dump(obj):
|
||||
for attr in dir(obj):
|
||||
try:
|
||||
if attr.startswith("__"):
|
||||
continue
|
||||
print(">" + attr + "<")
|
||||
attr_text = "%s = %s" % (attr, getattr(obj, attr))
|
||||
if attr in ["HorizFeed", "VertFeed"]:
|
||||
print("==============\n", attr_text)
|
||||
if "mm/s" in attr_text:
|
||||
print("===> metric values <===")
|
||||
except Exception: # Insignificant errors
|
||||
# print('==>', obj, attr)
|
||||
pass
|
||||
|
||||
|
||||
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 linenumber():
|
||||
if not OUTPUT_LINE_NUMBERS:
|
||||
return ""
|
||||
global LINENR
|
||||
global LINEINCR
|
||||
LINENR += LINEINCR
|
||||
return "N" + str(LINENR) + " "
|
||||
|
||||
|
||||
def format_outlist(strTable):
|
||||
# construct the line for the final output
|
||||
global COMMAND_SPACE
|
||||
s = ""
|
||||
for w in strTable:
|
||||
s += w + COMMAND_SPACE
|
||||
return s.strip()
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global DRILL_RETRACT_MODE
|
||||
global MOTION_MODE
|
||||
global CURRENT_X
|
||||
global CURRENT_Y
|
||||
global CURRENT_Z
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"P",
|
||||
]
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
|
||||
else: # Parsing simple path
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
if OUTPUT_COMMENTS and OUTPUT_PATH:
|
||||
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
outlist = []
|
||||
command = c.Name
|
||||
outlist.append(command)
|
||||
# Debug:
|
||||
# print('pathobj.Path.Commands:', c)
|
||||
|
||||
# If modal is True, delete duplicate commands:
|
||||
if MODAL:
|
||||
if command == lastcommand:
|
||||
outlist.pop(0)
|
||||
|
||||
# Add the remaining parameters in order:
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
if command not in RAPID_MOVES:
|
||||
feedRate = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if feedRate.getValueAs(UNIT_FEED_FORMAT) > 0.0:
|
||||
outlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(feedRate.getValueAs(UNIT_FEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
elif param in ["T", "H", "D", "S", "P", "L"]:
|
||||
outlist.append(param + str(c.Parameters[param]))
|
||||
elif param in ["A", "B", "C"]:
|
||||
outlist.append(
|
||||
param + format(c.Parameters[param], precision_string)
|
||||
)
|
||||
# [X, Y, Z, U, V, W, I, J, K, R, Q]
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# Store the latest command:
|
||||
lastcommand = command
|
||||
|
||||
# Capture the current position for subsequent calculations:
|
||||
if command in MOTION_COMMANDS:
|
||||
if "X" in c.Parameters:
|
||||
CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length)
|
||||
if "Y" in c.Parameters:
|
||||
CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length)
|
||||
if "Z" in c.Parameters:
|
||||
CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length)
|
||||
|
||||
if command in ("G98", "G99"):
|
||||
DRILL_RETRACT_MODE = command
|
||||
|
||||
if TRANSLATE_DRILL_CYCLES:
|
||||
if command in ("G81", "G82", "G83"):
|
||||
out += drill_translate(outlist, command, c.Parameters)
|
||||
# Erase the line just translated:
|
||||
outlist = []
|
||||
|
||||
if SPINDLE_WAIT > 0:
|
||||
if command in ("M3", "M03", "M4", "M04"):
|
||||
out += linenumber() + format_outlist(outlist) + "\n"
|
||||
# Marlin: P for milliseconds, S for seconds, change P to S
|
||||
out += linenumber()
|
||||
out += format_outlist(["G4", "S%s" % SPINDLE_WAIT])
|
||||
out += "\n"
|
||||
outlist = []
|
||||
|
||||
# Check for Tool Change:
|
||||
if command in ("M6", "M06"):
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Begin toolchange)\n"
|
||||
if OUTPUT_TOOL_CHANGE:
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
if not OUTPUT_TOOL_CHANGE and OUTPUT_COMMENTS:
|
||||
outlist[0] = "(" + outlist[0]
|
||||
outlist[-1] = outlist[-1] + ")"
|
||||
if not OUTPUT_TOOL_CHANGE and not OUTPUT_COMMENTS:
|
||||
outlist = []
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS:
|
||||
outlist.pop(0) # remove the command
|
||||
else:
|
||||
out = []
|
||||
|
||||
if command in SUPPRESS_COMMANDS:
|
||||
outlist[0] = "(" + outlist[0]
|
||||
outlist[-1] = outlist[-1] + ")"
|
||||
|
||||
# Remove embedded comments:
|
||||
if not OUTPUT_COMMENTS:
|
||||
tmplist = []
|
||||
list_index = 0
|
||||
while list_index < len(outlist):
|
||||
left_index = outlist[list_index].find("(")
|
||||
if left_index == -1: # Not a comment
|
||||
tmplist.append(outlist[list_index])
|
||||
else: # This line contains a comment, and possibly more
|
||||
right_index = outlist[list_index].find(")")
|
||||
comment_area = outlist[list_index][left_index : right_index + 1]
|
||||
line_minus_comment = (
|
||||
outlist[list_index].replace(comment_area, "").strip()
|
||||
)
|
||||
if line_minus_comment:
|
||||
# Line contained more than just a comment
|
||||
tmplist.append(line_minus_comment)
|
||||
list_index += 1
|
||||
# Done removing comments
|
||||
outlist = tmplist
|
||||
|
||||
# Prepend a line number and append a newline
|
||||
if len(outlist) >= 1:
|
||||
out += linenumber() + format_outlist(outlist) + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# *****************************************************************************
|
||||
# * As of Marlin 2.0.7.bugfix, canned drill cycles do not exist. *
|
||||
# * The following code converts FreeCAD's canned drill cycles into *
|
||||
# * gcode that Marlin can use. *
|
||||
# *****************************************************************************
|
||||
def drill_translate(outlist, cmd, params):
|
||||
global DRILL_RETRACT_MODE
|
||||
global MOTION_MODE
|
||||
global CURRENT_X
|
||||
global CURRENT_Y
|
||||
global CURRENT_Z
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_FEED_FORMAT
|
||||
|
||||
class Drill: # Using a class is necessary for the nested functions.
|
||||
gcode = ""
|
||||
|
||||
strFormat = "." + str(PRECISION) + "f"
|
||||
|
||||
if OUTPUT_COMMENTS: # Comment the original command
|
||||
outlist[0] = "(" + outlist[0]
|
||||
outlist[-1] = outlist[-1] + ")"
|
||||
Drill.gcode += linenumber() + format_outlist(outlist) + "\n"
|
||||
|
||||
# Cycle conversion only converts the cycles in the XY plane (G17).
|
||||
# --> ZX (G18) and YZ (G19) planes produce false gcode.
|
||||
drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length)
|
||||
drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length)
|
||||
drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length)
|
||||
drill_R = Units.Quantity(params["R"], FreeCAD.Units.Length)
|
||||
drill_F = Units.Quantity(params["F"], FreeCAD.Units.Velocity)
|
||||
if cmd == "G82":
|
||||
drill_DwellTime = params["P"]
|
||||
elif cmd == "G83":
|
||||
drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length)
|
||||
|
||||
# R less than Z is error
|
||||
if drill_R < drill_Z:
|
||||
Drill.gcode += linenumber() + "(drill cycle error: R less than Z )\n"
|
||||
return Drill.gcode
|
||||
|
||||
# Z height to retract to when drill cycle is done:
|
||||
if DRILL_RETRACT_MODE == "G98" and CURRENT_Z > drill_R:
|
||||
RETRACT_Z = CURRENT_Z
|
||||
else:
|
||||
RETRACT_Z = drill_R
|
||||
|
||||
# Z motion nested functions:
|
||||
def rapid_Z_to(new_Z):
|
||||
Drill.gcode += linenumber() + "G0 Z"
|
||||
Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
|
||||
|
||||
def feed_Z_to(new_Z):
|
||||
Drill.gcode += linenumber() + "G1 Z"
|
||||
Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + " F"
|
||||
Drill.gcode += format(float(drill_F.getValueAs(UNIT_FEED_FORMAT)), ".2f") + "\n"
|
||||
|
||||
# Make sure that Z is not below RETRACT_Z:
|
||||
if CURRENT_Z < RETRACT_Z:
|
||||
rapid_Z_to(RETRACT_Z)
|
||||
|
||||
# Rapid to hole position XY:
|
||||
Drill.gcode += linenumber() + "G0 X"
|
||||
Drill.gcode += format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + " Y"
|
||||
Drill.gcode += format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
|
||||
|
||||
# Rapid to R:
|
||||
rapid_Z_to(drill_R)
|
||||
|
||||
# *************************************************************************
|
||||
# * Drill cycles: *
|
||||
# * G80 Cancel the drill cycle *
|
||||
# * G81 Drill full depth in one pass *
|
||||
# * G82 Drill full depth in one pass, and pause at the bottom *
|
||||
# * G83 Drill in pecks, raising the drill to R height after each peck *
|
||||
# * In preparation for a rapid to the next hole position: *
|
||||
# * G98 After the hole has been drilled, retract to the initial Z value *
|
||||
# * G99 After the hole has been drilled, retract to R height *
|
||||
# * Select G99 only if safe to move from hole to hole at the R height *
|
||||
# *************************************************************************
|
||||
if cmd in ("G81", "G82"):
|
||||
feed_Z_to(drill_Z) # Drill hole in one step
|
||||
if cmd == "G82": # Dwell time delay at the bottom of the hole
|
||||
Drill.gcode += linenumber() + "G4 S" + str(drill_DwellTime) + "\n"
|
||||
# Marlin uses P for milliseconds, S for seconds, change P to S
|
||||
|
||||
elif cmd == "G83": # Peck drill cycle:
|
||||
chip_Space = drill_Step * 0.5
|
||||
next_Stop_Z = drill_R - drill_Step
|
||||
while next_Stop_Z >= drill_Z:
|
||||
feed_Z_to(next_Stop_Z) # Drill one peck of depth
|
||||
|
||||
# Set next depth, next_Stop_Z is still at the current hole depth
|
||||
if (next_Stop_Z - drill_Step) >= drill_Z:
|
||||
# Rapid up to clear chips:
|
||||
rapid_Z_to(drill_R)
|
||||
# Rapid down to just above last peck depth:
|
||||
rapid_Z_to(next_Stop_Z + chip_Space)
|
||||
# Update next_Stop_Z to next depth:
|
||||
next_Stop_Z -= drill_Step
|
||||
elif next_Stop_Z == drill_Z:
|
||||
break # Done
|
||||
else: # More to drill, but less than drill_Step
|
||||
# Rapid up to clear chips:
|
||||
rapid_Z_to(drill_R)
|
||||
# Rapid down to just above last peck depth:
|
||||
rapid_Z_to(next_Stop_Z + chip_Space)
|
||||
# Dril remainder of the hole depth:
|
||||
feed_Z_to(drill_Z)
|
||||
break # Done
|
||||
rapid_Z_to(RETRACT_Z) # Done, retract the drill
|
||||
|
||||
return Drill.gcode
|
||||
|
||||
|
||||
# print(__name__ + ': GCode postprocessor loaded.')
|
||||
|
||||
# PEP8 format passed using: http://pep8online.com/, which primarily covers
|
||||
# indentation and line length. Some other aspects of PEP8 which have not
|
||||
# been applied yet may be applied in future updates.
|
||||
@@ -1,135 +0,0 @@
|
||||
# *****************************************************************************
|
||||
# * Copyright (c) 2020 Rene Bartsch, B.Sc. Informatics <rene@bartschnet.de> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ****************************************************************************/
|
||||
"""Postprocessor to output real GCode for Max Computer GmbH nccad9."""
|
||||
import FreeCAD
|
||||
from PathScripts import PostUtils
|
||||
import datetime
|
||||
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to take
|
||||
a pseudo-gcode fragment output by a Path object and output real GCode
|
||||
suitable for the Max Computer GmbH nccad9 Computer Numeric Control.
|
||||
|
||||
Supported features:
|
||||
|
||||
- 3-axis milling
|
||||
- manual tool change with tool number as comment
|
||||
- spindle speed as comment
|
||||
|
||||
!!! gCode files must use the suffix .knc !!!
|
||||
|
||||
import nccad_post
|
||||
nccad_post.export([object], "/path/to/file.knc", "")
|
||||
"""
|
||||
|
||||
MACHINE_NAME = """Max Computer GmbH nccad9 MCS/KOSY"""
|
||||
|
||||
# gCode for changing tools
|
||||
# M01 <String> ; Displays <String> and waits for user interaction
|
||||
TOOL_CHANGE = """G77 ; Move to release position
|
||||
M10 O6.0 ; Stop spindle
|
||||
M01 Insert tool TOOL
|
||||
G76 ; Move to reference point to ensure correct coordinates after tool change
|
||||
M10 O6.1 ; Start spindle"""
|
||||
|
||||
|
||||
# gCode finishing the program
|
||||
POSTAMBLE = """G77 ; Move to release position
|
||||
M10 O6.0 ; Stop spindle"""
|
||||
|
||||
|
||||
# gCode header with information about CAD-software, post-processor
|
||||
# and date/time
|
||||
if FreeCAD.ActiveDocument:
|
||||
cam_file = FreeCAD.ActiveDocument.FileName
|
||||
else:
|
||||
cam_file = "<None>"
|
||||
|
||||
HEADER = """;Exported by FreeCAD
|
||||
;Post Processor: {}
|
||||
;CAM file: {}
|
||||
;Output Time: {}
|
||||
""".format(
|
||||
__name__, cam_file, str(datetime.datetime.now())
|
||||
)
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"""Export the list of objects into a filename.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
objectslists: list
|
||||
List of objects.
|
||||
|
||||
filename: str
|
||||
Name of the output file ending in `'.knc'`.
|
||||
"""
|
||||
gcode = HEADER
|
||||
|
||||
for obj in objectslist:
|
||||
for command in obj.Path.Commands:
|
||||
# Manipulate tool change commands
|
||||
if "M6" == command.Name:
|
||||
gcode += TOOL_CHANGE.replace("TOOL", str(int(command.Parameters["T"])))
|
||||
elif "M3" == command.Name:
|
||||
# Convert spindle speed (rpm) command to comment
|
||||
gcode += (
|
||||
"M01 Set spindle speed to "
|
||||
+ str(int(command.Parameters["S"]))
|
||||
+ " rounds per minute"
|
||||
)
|
||||
else:
|
||||
# Add other commands
|
||||
gcode += command.Name
|
||||
|
||||
# Loop through command parameters
|
||||
for parameter, value in command.Parameters.items():
|
||||
# Multiply F parameter value by 10,
|
||||
# FreeCAD = mm/s, nccad = 1/10 mm/s
|
||||
if "F" == parameter:
|
||||
value *= 10
|
||||
# Add command parameters and values and round float
|
||||
# as nccad9 does not support exponents
|
||||
gcode += " " + parameter + str(round(value, 5))
|
||||
|
||||
gcode += "\n"
|
||||
|
||||
gcode += POSTAMBLE + "\n"
|
||||
|
||||
# Open editor window
|
||||
if FreeCAD.GuiUp:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
gcode = dia.editor.toPlainText()
|
||||
|
||||
# Save to file
|
||||
if filename != "-":
|
||||
gfile = open(filename, "w")
|
||||
gfile.write(gcode)
|
||||
gfile.close()
|
||||
|
||||
return filename
|
||||
@@ -1,373 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime
|
||||
from PathScripts import PostUtils
|
||||
|
||||
|
||||
TOOLTIP = """
|
||||
This is an postprocessor file for the Path workbench. It will output path data
|
||||
in a format suitable for OpenSBP controllers like shopbot. This postprocessor,
|
||||
once placed in the appropriate PathScripts folder, can be used directly from
|
||||
inside FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import Path
|
||||
Path.write(object,"/path/to/file.ncc","post_opensbp")
|
||||
"""
|
||||
|
||||
"""
|
||||
DONE:
|
||||
uses native commands
|
||||
handles feed and jog moves
|
||||
handles XY, Z, and XYZ feed speeds
|
||||
handles arcs
|
||||
support for inch output
|
||||
ToDo
|
||||
comments may not format correctly
|
||||
drilling. Haven't looked at it.
|
||||
many other things
|
||||
|
||||
"""
|
||||
|
||||
TOOLTIP_ARGS = """
|
||||
Arguments for opensbp:
|
||||
--comments ... insert comments - mostly for debugging
|
||||
--inches ... convert output to inches
|
||||
--no-header ... suppress header output
|
||||
--no-show-editor ... don't show editor, just save result
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
OUTPUT_COMMENTS = False
|
||||
OUTPUT_HEADER = True
|
||||
SHOW_EDITOR = True
|
||||
COMMAND_SPACE = ","
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """"""
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
CurrentState = {}
|
||||
|
||||
|
||||
def getMetricValue(val):
|
||||
return val
|
||||
|
||||
|
||||
def getImperialValue(val):
|
||||
return val / 25.4
|
||||
|
||||
|
||||
GetValue = getMetricValue
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_HEADER
|
||||
global SHOW_EDITOR
|
||||
global CurrentState
|
||||
global GetValue
|
||||
|
||||
for arg in argstring.split():
|
||||
if arg == "--comments":
|
||||
OUTPUT_COMMENTS = True
|
||||
if arg == "--inches":
|
||||
GetValue = getImperialValue
|
||||
if arg == "--no-header":
|
||||
OUTPUT_HEADER = False
|
||||
if arg == "--no-show-editor":
|
||||
SHOW_EDITOR = False
|
||||
|
||||
for obj in objectslist:
|
||||
if not hasattr(obj, "Path"):
|
||||
s = "the object " + obj.Name
|
||||
s += " is not a path. Please select only path and Compounds."
|
||||
print(s)
|
||||
return
|
||||
|
||||
CurrentState = {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0,
|
||||
"F": 0,
|
||||
"S": 0,
|
||||
"JSXY": 0,
|
||||
"JSZ": 0,
|
||||
"MSXY": 0,
|
||||
"MSZ": 0,
|
||||
}
|
||||
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(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "'(begin operation: " + obj.Label + ")\n"
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
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
|
||||
|
||||
# do the post_amble
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "'(begin postamble)\n"
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
if 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 output
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
|
||||
def move(command):
|
||||
txt = ""
|
||||
|
||||
# if 'F' in command.Parameters:
|
||||
# txt += feedrate(command)
|
||||
|
||||
axis = ""
|
||||
for p in ["X", "Y", "Z"]:
|
||||
if p in command.Parameters:
|
||||
if command.Parameters[p] != CurrentState[p]:
|
||||
axis += p
|
||||
|
||||
if "F" in command.Parameters:
|
||||
speed = command.Parameters["F"]
|
||||
if command.Name in ["G1", "G01"]: # move
|
||||
movetype = "MS"
|
||||
else: # jog
|
||||
movetype = "JS"
|
||||
zspeed = ""
|
||||
xyspeed = ""
|
||||
if "Z" in axis:
|
||||
speedKey = "{}Z".format(movetype)
|
||||
speedVal = GetValue(speed)
|
||||
if CurrentState[speedKey] != speedVal:
|
||||
CurrentState[speedKey] = speedVal
|
||||
zspeed = "{:f}".format(speedVal)
|
||||
if ("X" in axis) or ("Y" in axis):
|
||||
speedKey = "{}XY".format(movetype)
|
||||
speedVal = GetValue(speed)
|
||||
if CurrentState[speedKey] != speedVal:
|
||||
CurrentState[speedKey] = speedVal
|
||||
xyspeed = "{:f}".format(speedVal)
|
||||
if zspeed or xyspeed:
|
||||
txt += "{},{},{}\n".format(movetype, xyspeed, zspeed)
|
||||
|
||||
if command.Name in ["G0", "G00"]:
|
||||
pref = "J"
|
||||
else:
|
||||
pref = "M"
|
||||
|
||||
if axis == "X":
|
||||
txt += pref + "X"
|
||||
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "Y":
|
||||
txt += pref + "Y"
|
||||
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "Z":
|
||||
txt += pref + "Z"
|
||||
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "XY":
|
||||
txt += pref + "2"
|
||||
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
|
||||
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "XZ":
|
||||
txt += pref + "3"
|
||||
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
|
||||
txt += ","
|
||||
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "XYZ":
|
||||
txt += pref + "3"
|
||||
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
|
||||
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
|
||||
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "YZ":
|
||||
txt += pref + "3"
|
||||
txt += ","
|
||||
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
|
||||
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
|
||||
txt += "\n"
|
||||
elif axis == "":
|
||||
print("warning: skipping duplicate move.")
|
||||
else:
|
||||
print(CurrentState)
|
||||
print(command)
|
||||
print("I don't know how to handle '{}' for a move.".format(axis))
|
||||
|
||||
return txt
|
||||
|
||||
|
||||
def arc(command):
|
||||
if command.Name == "G2": # CW
|
||||
dirstring = "1"
|
||||
else: # G3 means CCW
|
||||
dirstring = "-1"
|
||||
txt = "CG,,"
|
||||
txt += format(GetValue(command.Parameters["X"]), ".4f") + ","
|
||||
txt += format(GetValue(command.Parameters["Y"]), ".4f") + ","
|
||||
txt += format(GetValue(command.Parameters["I"]), ".4f") + ","
|
||||
txt += format(GetValue(command.Parameters["J"]), ".4f") + ","
|
||||
txt += "T" + ","
|
||||
txt += dirstring
|
||||
txt += "\n"
|
||||
return txt
|
||||
|
||||
|
||||
def tool_change(command):
|
||||
txt = ""
|
||||
if OUTPUT_COMMENTS:
|
||||
txt += "'a tool change happens now\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
txt += line
|
||||
txt += "&ToolName=" + str(int(command.Parameters["T"]))
|
||||
txt += "\n"
|
||||
txt += "&Tool=" + str(int(command.Parameters["T"]))
|
||||
txt += "\n"
|
||||
return txt
|
||||
|
||||
|
||||
def comment(command):
|
||||
print("a comment", command)
|
||||
return
|
||||
|
||||
|
||||
def spindle(command):
|
||||
txt = ""
|
||||
if command.Name == "M3": # CW
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
txt += "TR," + str(command.Parameters["S"]) + "\n"
|
||||
txt += "C6\n"
|
||||
txt += "PAUSE 2\n"
|
||||
return txt
|
||||
|
||||
|
||||
# Supported Commands
|
||||
scommands = {
|
||||
"G0": move,
|
||||
"G1": move,
|
||||
"G2": arc,
|
||||
"G3": arc,
|
||||
"M6": tool_change,
|
||||
"M3": spindle,
|
||||
"G00": move,
|
||||
"G01": move,
|
||||
"G02": arc,
|
||||
"G03": arc,
|
||||
"M06": tool_change,
|
||||
"M03": spindle,
|
||||
"message": comment,
|
||||
}
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
output = ""
|
||||
# Above list controls the order of parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if OUTPUT_COMMENTS:
|
||||
output += linenumber() + "'(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
output += parse(p)
|
||||
else: # parsing simple path
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return output
|
||||
if OUTPUT_COMMENTS:
|
||||
output += linenumber() + "'(Path: " + pathobj.Label + ")\n"
|
||||
for c in pathobj.Path.Commands:
|
||||
command = c.Name
|
||||
if command in scommands:
|
||||
output += scommands[command](c)
|
||||
if c.Parameters:
|
||||
CurrentState.update(c.Parameters)
|
||||
elif command[0] == "(":
|
||||
output += "' " + command + "\n"
|
||||
else:
|
||||
print("I don't know what the hell the command: ", end="")
|
||||
print(command + " means. Maybe I should support it.")
|
||||
return output
|
||||
|
||||
|
||||
def linenumber():
|
||||
return ""
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,259 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods<at>gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
"""
|
||||
This is a preprocessor file for the Path workbench. Its aim is to
|
||||
parse the contents of a given OpenSBP file, and transform it to make it
|
||||
suitable for use in a Path object. This preprocessor, once placed in the
|
||||
appropriate PathScripts folder, can be used directly from inside FreeCAD,
|
||||
via the GUI importer or via python scripts with:
|
||||
|
||||
import opensbp_pre
|
||||
opensbp_pre.insert("/path/to/myfile.ngc","DocumentName")
|
||||
|
||||
|
||||
DONE
|
||||
Correctly imports single axis and multi axis moves.
|
||||
Stores Jog and Feed speeds
|
||||
Appends Multiaxis Feed speed to G1 moves
|
||||
Jog rates don't append to G0 moves
|
||||
Make single axis feed rates work
|
||||
Imports CG (non-diameter) arcs.
|
||||
Handles CW and CCW spindle speeds
|
||||
if operations are preceded by a comment ('New Path ...) They are split into multiple paths
|
||||
|
||||
TODO
|
||||
Many other OpenSBP commands not handled
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import os
|
||||
import Path
|
||||
|
||||
AXIS = (
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
) # OpenSBP always puts multiaxis move parameters in this order
|
||||
SPEEDS = "XY", "Z", "A", "B"
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def open(filename):
|
||||
"called when freecad opens a file."
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
insert(filename, doc.Name)
|
||||
|
||||
|
||||
def insert(filename, docname):
|
||||
"""called when freecad imports a file
|
||||
This insert expects parse to return a list of strings
|
||||
each string will become a separate path"""
|
||||
gfile = pythonopen(filename)
|
||||
gcode = gfile.read()
|
||||
gfile.close()
|
||||
gcode = parse(gcode)
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
for subpath in gcode:
|
||||
obj = doc.addObject("Path::Feature", "Path")
|
||||
path = Path.Path(subpath)
|
||||
obj.Path = path
|
||||
|
||||
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a list of parsed output string"
|
||||
print("preprocessing...")
|
||||
# split the input by line
|
||||
lines = inputstring.split("\n")
|
||||
return_output = []
|
||||
output = ""
|
||||
last = {"X": None, "Y": None, "Z": None, "A": None, "B": None}
|
||||
lastrapidspeed = {
|
||||
"XY": "50",
|
||||
"Z": "50",
|
||||
"A": "50",
|
||||
"B": "50",
|
||||
} # set default rapid speeds
|
||||
lastfeedspeed = {
|
||||
"XY": "50",
|
||||
"Z": "50",
|
||||
"A": "50",
|
||||
"B": "50",
|
||||
} # set default feed speed
|
||||
movecommand = ["G1", "G0", "G02", "G03"]
|
||||
|
||||
for line in lines:
|
||||
# remove any leftover trailing and preceding spaces
|
||||
line = line.strip()
|
||||
if not line:
|
||||
# discard empty lines
|
||||
continue
|
||||
if line[0] in ["'", "&"]:
|
||||
# discard comment and other non strictly gcode lines
|
||||
if line[0:9] == "'New Path":
|
||||
# starting new path
|
||||
if any(
|
||||
x in output for x in movecommand
|
||||
): # make sure the path has at least one move command.
|
||||
return_output.append(output)
|
||||
output = ""
|
||||
continue
|
||||
|
||||
words = [a.strip() for a in line.split(",")]
|
||||
words[0] = words[0].upper()
|
||||
if words[0] in [
|
||||
"J2",
|
||||
"J3",
|
||||
"J4",
|
||||
"J5",
|
||||
"M2",
|
||||
"M3",
|
||||
"M4",
|
||||
"M5",
|
||||
]: # multi-axis jogs and moves
|
||||
if words[0][0] == "J": # jog move
|
||||
s = "G0 "
|
||||
else: # feed move
|
||||
s = "G1 "
|
||||
speed = lastfeedspeed["XY"]
|
||||
|
||||
for i in range(1, len(words)):
|
||||
if words[i] == "":
|
||||
if last[AXIS[i - 1]] is None:
|
||||
continue
|
||||
else:
|
||||
s += AXIS[i - 1] + last[AXIS[i - 1]]
|
||||
else:
|
||||
s += AXIS[i - 1] + words[i]
|
||||
last[AXIS[i - 1]] = words[i]
|
||||
output += s + " F" + speed + "\n"
|
||||
|
||||
if words[0] in [
|
||||
"JA",
|
||||
"JB",
|
||||
"JX",
|
||||
"JY",
|
||||
"JZ",
|
||||
"MA",
|
||||
"MB",
|
||||
"MX",
|
||||
"MY",
|
||||
"MZ",
|
||||
]: # single axis jogs and moves
|
||||
if words[0][0] == "J": # jog move
|
||||
s = "G0 "
|
||||
if words[0][1] in ["X", "Y"]:
|
||||
speed = lastrapidspeed["XY"]
|
||||
else:
|
||||
speed = lastrapidspeed[words[0][1]]
|
||||
|
||||
else: # feed move
|
||||
s = "G1 "
|
||||
if words[0][1] in ["X", "Y"]:
|
||||
speed = lastfeedspeed["XY"]
|
||||
else:
|
||||
speed = lastfeedspeed[words[0][1]]
|
||||
|
||||
last[words[0][1]] = words[1]
|
||||
output += s
|
||||
for key, val in PathUtil.keyValueIter(last):
|
||||
if val is not None:
|
||||
output += key + str(val) + " F" + speed + "\n"
|
||||
|
||||
if words[0] in ["JS"]: # set jog speed
|
||||
for i in range(1, len(words)):
|
||||
if words[i] == "":
|
||||
continue
|
||||
else:
|
||||
lastrapidspeed[SPEEDS[i - 1]] = words[i]
|
||||
|
||||
if words[0] in ["MD"]: # move distance with distance and angle.
|
||||
# unsupported at this time
|
||||
continue
|
||||
if words[0] in ["MH"]: # move home
|
||||
# unsupported at this time
|
||||
continue
|
||||
if words[0] in ["MS"]: # set move speed
|
||||
for i in range(1, len(words)):
|
||||
if words[i] == "":
|
||||
continue
|
||||
else:
|
||||
lastfeedspeed[SPEEDS[i - 1]] = words[i]
|
||||
if words[0] in ["MO"]: # motors off
|
||||
# unsupported at this time
|
||||
continue
|
||||
|
||||
if words[0] in ["TR"]: # Setting spindle speed
|
||||
if float(words[1]) < 0:
|
||||
s = "M4 S"
|
||||
else:
|
||||
s = "M3 S"
|
||||
s += str(abs(float(words[1])))
|
||||
output += s + "\n"
|
||||
|
||||
if words[0] in ["CG"]: # Gcode circle/arc
|
||||
if words[1] != "": # diameter mode
|
||||
print("diameter mode not supported")
|
||||
continue
|
||||
|
||||
else:
|
||||
if words[7] == "1": # CW
|
||||
s = "G2"
|
||||
else: # CCW
|
||||
s = "G3"
|
||||
|
||||
s += (
|
||||
" X"
|
||||
+ words[2]
|
||||
+ " Y"
|
||||
+ words[3]
|
||||
+ " I"
|
||||
+ words[4]
|
||||
+ " J"
|
||||
+ words[5]
|
||||
+ " F"
|
||||
+ str(lastfeedspeed["XY"])
|
||||
)
|
||||
output += s + "\n"
|
||||
|
||||
last["X"] = words[2]
|
||||
last["Y"] = words[3]
|
||||
|
||||
# Make sure all appended paths have at least one move command.
|
||||
if any(x in output for x in movecommand):
|
||||
return_output.append(output)
|
||||
print("done preprocessing.")
|
||||
|
||||
return return_output
|
||||
|
||||
|
||||
print(__name__ + " gcode preprocessor loaded.")
|
||||
@@ -1,627 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2016 Christoph Blaue <blaue@fh-westkueste.de> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
# 03-24-2021 Sliptonic: I've removed the PathUtils import and job lookup
|
||||
# post processors shouldn't be reaching back to the job. This can cause a
|
||||
# proxy error.
|
||||
|
||||
import FreeCAD
|
||||
import argparse
|
||||
import time
|
||||
from PathScripts import PostUtils
|
||||
import math
|
||||
|
||||
TOOLTIP = """Post processor for Maho M 600E mill
|
||||
|
||||
Machines with Philips or Heidenhain control should be very easy to adapt.
|
||||
|
||||
The post processor is configurable by changing the values of constants.
|
||||
No programming experience required. This can make a generated g-code
|
||||
program more readable and since older machines have very
|
||||
limited memory it seems sensible to reduce the number of commands and
|
||||
parameters, like e.g. suppress the units in the header and at every hop.
|
||||
"""
|
||||
|
||||
# ***************************************************************************
|
||||
# user editable stuff here
|
||||
|
||||
COMMAND_SPACE = " "
|
||||
|
||||
MACHINE_NAME = "Maho 600E"
|
||||
CORNER_MIN = {"x": -51.877, "y": 0, "z": 0} # use metric for internal units
|
||||
CORNER_MAX = {"x": 591.5, "y": 391.498, "z": 391.5} # use metric for internal units
|
||||
|
||||
UNITS = "G21" # use metric units
|
||||
# possible values:
|
||||
# 'G20' for inches,
|
||||
# 'G21' for metric units.
|
||||
# a mapping to different GCodes is handled in the GCODE MAP below
|
||||
|
||||
UNITS_INCLUDED = False # do not include the units in the GCode program
|
||||
# possible values:
|
||||
# True if units should be included
|
||||
# False if units should not be included
|
||||
# usually the units to be used are defined in the machine constants and almost never change,
|
||||
# so this can be set to False.
|
||||
|
||||
COMMENT = ""
|
||||
# possible values:
|
||||
# ';' centroid or sinumerik comment symbol,
|
||||
# '' leave blank for bracketed comments style "(comment comment comment)"
|
||||
# '...' any other symbol to start comments
|
||||
# currently this can be only one symbol, if it should be a sequence of characters
|
||||
# in PostUtils.py the line
|
||||
# if len(commentsym)==1:
|
||||
# should be changed to
|
||||
# if len(commentsym)>1:
|
||||
|
||||
SHOW_EDITOR = True
|
||||
# possible values:
|
||||
# True before the file is written it is shown to the user for inspection
|
||||
# False the file is written directly
|
||||
|
||||
LINENUMBERS = True
|
||||
# possible values:
|
||||
# True if linenumbers of the form N1 N2 ... should be included
|
||||
# False linennumbers are suppressed
|
||||
# if linenumbers are used, header and footer get numbered as well
|
||||
|
||||
STARTLINENR = 1 # first linenumber used
|
||||
# possible values:
|
||||
# any integer value >= 0
|
||||
# to have the possibility to change some initial values directly at the CNC machine
|
||||
# without renumbering the rest it is possible to start the numbering of
|
||||
# the file with some value > 0
|
||||
|
||||
LINENUMBER_INCREMENT = 1
|
||||
# possible values:
|
||||
# any integer value > 0
|
||||
# similar to STARTLINENR it is possible to leave gaps in the linenumbering
|
||||
# of subsequent lines
|
||||
|
||||
MODAL = True
|
||||
# possible values:
|
||||
# True repeated GCodes in subsequent lines are suppressed, like in the following snippet
|
||||
# G1 X10 Y20
|
||||
# X15 Y30
|
||||
# False repeated GCodes in subsequent lines are repeated in the GCode file
|
||||
# G1 X10 Y20
|
||||
# G1 X15 Y30
|
||||
|
||||
# suppress these parameters if they haven't changed
|
||||
MODALPARAMS = ["X", "Y", "Z", "S", "F"]
|
||||
# possible values:
|
||||
# any list of GCode parameters
|
||||
# if a parameter doesn't change from one line to the next ( or even further) it is suppressed.
|
||||
# Example:
|
||||
# G1 X10 Y20
|
||||
# G1 Y30
|
||||
# If in addition MODAL is set to True, the generated GCode changes to
|
||||
# G1 X10 Y20
|
||||
# Y30
|
||||
|
||||
SWAP_G2_G3 = True # some machines have the sign of the X-axis swapped, so they behave like milling from the bottom
|
||||
# possible values:
|
||||
# True if left and right turns are to be swapped
|
||||
# False don't swap
|
||||
# this might be special with some maho machines or even with mine and
|
||||
# might be changed in the machine constants as well
|
||||
|
||||
SWAP_Y_Z = (
|
||||
True # machines with an angle milling head do not switch axes, so we do it here
|
||||
)
|
||||
# possible values:
|
||||
# True if Y and Z values have to be swapped
|
||||
# False do not swap
|
||||
# For vertical milling machines the Z-axis is horizontal (of course).
|
||||
# If they have an angle milling head, they mill vertical, alas the Z-axis stays horizontal.
|
||||
# With this parameter we can swap the output values of Y and Z.
|
||||
# For commands G2 and G3 this means that J and K are swapped as well
|
||||
|
||||
ABSOLUTE_CIRCLE_CENTER = True
|
||||
# possible values:
|
||||
# True use absolute values for the circle center in commands G2, G3
|
||||
# False values for I, J, K are given relative to the last point
|
||||
|
||||
USE_RADIUS_IF_POSSIBLE = True
|
||||
# possible values:
|
||||
# True if in commands G2 and G3 the usage of radius R is preferred
|
||||
# False if in commands G2 and G3 we use always I and J
|
||||
# When milling arcs there are two reasons to use the radius instead of the center:
|
||||
# 1. the GCode program might be easier to understand
|
||||
# 2. Some machines seem to have a different scheme for calculating / rounding the values of the center
|
||||
# Thus it is possible that the machine complains, that the endpoint of the arc does not lie on the arc.
|
||||
# Using the radius instead avoids this problem.
|
||||
# The post processor takes care of the fact, that only angles <= 180 degrees can be used with R
|
||||
# for larger angles the center is used independent of the setting of this
|
||||
# constant
|
||||
|
||||
RADIUS_COMMENT = True
|
||||
# possible values:
|
||||
# True for better understanding the radius of an arc is included as a comment
|
||||
# False no additional comment is included
|
||||
# In case the comments are included they are always included with the bracketing syntax like '(R20.456)'
|
||||
# and never with the comment symbol, because the radius might appear in
|
||||
# the middle of a line.
|
||||
|
||||
GCODE_MAP = {
|
||||
"M1": "M0",
|
||||
"M6": "M66",
|
||||
"G20": "G70",
|
||||
"G21": "G71",
|
||||
} # cb: this could be used to swap G2/G3
|
||||
# possible values:
|
||||
# Comma separated list of values of the form 'sourceGCode':'targetGCode'
|
||||
#
|
||||
# Although the basic movement commands G0, G1, G2 seem to be used uniformly in different GCode dialects,
|
||||
# this is not the case for all commands.
|
||||
# E.g the Maho dialect uses G70 and G71 for the units inches vs. metric.
|
||||
# The map {'M1':'M0', 'G20':'G70', 'G21':'G71'} maps the optional stop command M1 to M0,
|
||||
# because some Maho machines do not have the optional button on its panel
|
||||
# in addition it maps inches G20 to G70 and metric G21 to G71
|
||||
|
||||
AXIS_DECIMALS = 3
|
||||
# possible values:
|
||||
# integer >= 0
|
||||
|
||||
FEED_DECIMALS = 2
|
||||
# possible values:
|
||||
# integer >= 0
|
||||
|
||||
SPINDLE_DECIMALS = 0
|
||||
# possible values:
|
||||
# integer >= 0
|
||||
|
||||
SUPPRESS_ZERO_FEED = True
|
||||
# possible values: True if feed is zero the F command is suppressed
|
||||
# False F commands are written even if they are zero
|
||||
# This is useful for machines without special speeds for the G0 command. They could be
|
||||
# left zero and are suppressed in the output
|
||||
|
||||
# The header is divided into two parts, one is dynamic, the other is a static GCode header.
|
||||
# If the current selection and the current time should be included in the header,
|
||||
# it has to be generated at execution time, and thus it cannot be held in constant values.
|
||||
# The last linefeed should be omitted, it is inserted automatically
|
||||
# linenumbers are inserted automatically if LINENUMBERS is True
|
||||
# if you don't want to use this header you have to provide a minimal function
|
||||
# def mkHeader(selection):
|
||||
# return ''
|
||||
|
||||
parser = argparse.ArgumentParser(prog="philips", add_help=False)
|
||||
parser.add_argument("--header", action="store_true", help="create header output")
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
|
||||
parser.add_argument("--comments", action="store_true", help="create comment output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-line-numbers", action="store_true", help="omit line number prefixes"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--show-editor", action="store_true", help="pop up editor before writing output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global LINENUMBERS
|
||||
global SHOW_EDITOR
|
||||
|
||||
for arg in argstring.split():
|
||||
if arg == "--line-numbers":
|
||||
LINENUMBERS = True
|
||||
elif arg == "--no-line-numbers":
|
||||
LINENUMBERS = False
|
||||
elif arg == "--show-editor":
|
||||
SHOW_EDITOR = True
|
||||
elif arg == "--no-show-editor":
|
||||
SHOW_EDITOR = False
|
||||
|
||||
|
||||
def mkHeader(selection):
|
||||
# job = PathUtils.findParentJob(selection[0])
|
||||
# this is within a function, because otherwise filename and time don't change when changing the FreeCAD project
|
||||
# now = datetime.datetime.now()
|
||||
now = time.strftime("%Y-%m-%d %H:%M")
|
||||
originfile = FreeCAD.ActiveDocument.FileName
|
||||
headerNoNumber = "%PM\n" # this line gets no linenumber
|
||||
# if hasattr(job, "Description"):
|
||||
# description = job.Description
|
||||
# else:
|
||||
# description = ""
|
||||
description = ""
|
||||
# this line gets no linenumber, it is already a specially numbered
|
||||
headerNoNumber += "N9XXX (" + description + ", " + now + ")\n"
|
||||
header = ""
|
||||
# header += "(Output Time:" + str(now) + ")\n"
|
||||
header += "(" + originfile + ")\n"
|
||||
# header += "(Exported by FreeCAD)\n"
|
||||
header += "(Post Processor: " + __name__ + ")\n"
|
||||
# header += "(Target machine: " + MACHINE_NAME + ")\n"
|
||||
header += "G18\n" # Select XY plane
|
||||
header += "G90\n" # Absolute coordinates
|
||||
header += "G51\n" # Reset Zero
|
||||
header += "G52 (ersetze G55-G59)" # set zero
|
||||
return headerNoNumber + linenumberify(header)
|
||||
|
||||
|
||||
GCODE_HEADER = "" # do not terminate with a newline, it is inserted by linenumberify
|
||||
# GCODE_HEADER = "G40 G90" # do not terminate with a newline, it is inserted by linenumberify
|
||||
# possible values:
|
||||
# any sequence of GCode, multiple lines are welcome
|
||||
# this constant header follows the text generated by the function mkheader
|
||||
# linenumbers are inserted automatically if LINENUMBERS is True
|
||||
|
||||
# do not terminate with a newline, it is inserted by linenumberify
|
||||
GCODE_FOOTER = "M30"
|
||||
# possible values:
|
||||
# any sequence of GCode, multiple lines are welcome
|
||||
# the footer is used to clean things up, reset modal commands and stop the machine
|
||||
# linenumbers are inserted automatically if LINENUMBERS is True
|
||||
|
||||
# don't edit with the stuff below the next line unless you know what you're doing :)
|
||||
# ***************************************************************************
|
||||
|
||||
linenr = 0 # variable has to be global because it is used by linenumberify and export
|
||||
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def angleUnder180(command, lastX, lastY, x, y, i, j):
|
||||
# radius R can be used iff angle is < 180.
|
||||
# This is the case
|
||||
# if the previous point is left of the current and the center is below (or on) the connection line
|
||||
# or if the previous point is right of the current and the center is above
|
||||
# (or on) the connection line
|
||||
middleOfLineY = (lastY + y) / 2
|
||||
centerY = lastY + j
|
||||
if (
|
||||
command == "G2"
|
||||
and (
|
||||
(lastX == x and ((lastY < y and i >= 0) or (lastY > y and i <= 0)))
|
||||
or (lastX < x and centerY <= middleOfLineY)
|
||||
or (lastX > x and centerY >= middleOfLineY)
|
||||
)
|
||||
) or (
|
||||
command == "G3"
|
||||
and (
|
||||
(lastX == x and ((lastY < y and i <= 0) or (lastY > y and i >= 0)))
|
||||
or (lastX < x and centerY >= middleOfLineY)
|
||||
or (lastX > x and centerY <= middleOfLineY)
|
||||
)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def mapGCode(command):
|
||||
if command in GCODE_MAP:
|
||||
mappedCommand = GCODE_MAP[command]
|
||||
else:
|
||||
mappedCommand = command
|
||||
if SWAP_G2_G3:
|
||||
if command == "G2":
|
||||
mappedCommand = "G3"
|
||||
elif command == "G3":
|
||||
mappedCommand = "G2"
|
||||
return mappedCommand
|
||||
|
||||
|
||||
def linenumberify(GCodeString):
|
||||
# add a linenumber at every beginning of line
|
||||
global linenr
|
||||
if not LINENUMBERS:
|
||||
result = GCodeString + "\n"
|
||||
else:
|
||||
result = ""
|
||||
strList = GCodeString.split("\n")
|
||||
for s in strList:
|
||||
if s:
|
||||
# only non empty lines get numbered. the special lines "%PM"
|
||||
# and prognumber "N9XXX" are skipped
|
||||
|
||||
result += "N" + str(linenr) + " " + s + "\n"
|
||||
linenr += LINENUMBER_INCREMENT
|
||||
else:
|
||||
result += s + "\n"
|
||||
return result
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
global UNITS
|
||||
global linenr
|
||||
|
||||
linenr = STARTLINENR
|
||||
lastX = 0
|
||||
lastY = 0
|
||||
lastZ = 0
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"H",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
] # Using XY plane most of the time so skipping K
|
||||
modalParamsDict = dict()
|
||||
for mp in MODALPARAMS:
|
||||
modalParamsDict[mp] = None
|
||||
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
|
||||
myMachine = None
|
||||
for pathobj in objectslist:
|
||||
if hasattr(pathobj, "MachineName"):
|
||||
myMachine = pathobj.MachineName
|
||||
if hasattr(pathobj, "MachineUnits"):
|
||||
if pathobj.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
else:
|
||||
UNITS = "G20"
|
||||
if myMachine is None:
|
||||
print("philips_post: No machine found in this selection")
|
||||
|
||||
gcode = ""
|
||||
gcode += mkHeader(objectslist)
|
||||
gcode += linenumberify(GCODE_HEADER)
|
||||
if UNITS_INCLUDED:
|
||||
gcode += linenumberify(mapGCode(UNITS))
|
||||
lastcommand = None
|
||||
for obj in objectslist:
|
||||
if hasattr(obj, "Comment"):
|
||||
gcode += linenumberify("(" + obj.Comment + ")")
|
||||
for c in obj.Path.Commands:
|
||||
outstring = []
|
||||
command = c.Name
|
||||
if command != "G0":
|
||||
command = command.replace("G0", "G") # normalize: G01 -> G1
|
||||
|
||||
if command != UNITS or UNITS_INCLUDED:
|
||||
if command[0] == "(":
|
||||
command = PostUtils.fcoms(command, COMMENT)
|
||||
# the mapping is done for output only! For internal things we
|
||||
# still use the old value.
|
||||
mappedCommand = mapGCode(command)
|
||||
|
||||
if not MODAL or command != lastcommand:
|
||||
outstring.append(mappedCommand)
|
||||
# if MODAL:
|
||||
# #\better: append iff MODAL == False
|
||||
# if command == lastcommand:
|
||||
# outstring.pop(0)
|
||||
if len(c.Parameters) >= 1:
|
||||
for param in params:
|
||||
# test print("param: " + param + ", command: " + command)
|
||||
if param in c.Parameters:
|
||||
if (param in MODALPARAMS) and (
|
||||
modalParamsDict[str(param)] == c.Parameters[str(param)]
|
||||
):
|
||||
# do nothing or append white space
|
||||
outstring.append(" ")
|
||||
elif param == "F":
|
||||
feed = c.Parameters["F"]
|
||||
if SUPPRESS_ZERO_FEED and feed == 0:
|
||||
pass
|
||||
else:
|
||||
outstring.append(
|
||||
param
|
||||
+ PostUtils.fmt(feed, FEED_DECIMALS, UNITS)
|
||||
)
|
||||
elif param == "H":
|
||||
outstring.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "S":
|
||||
# rpm is unitless-therefore I had to 'fake it
|
||||
# out' by using metric units which don't get
|
||||
# converted from entered value
|
||||
outstring.append(
|
||||
param
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters["S"], SPINDLE_DECIMALS, "G21"
|
||||
)
|
||||
)
|
||||
elif param == "T":
|
||||
outstring.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "I" and (command == "G2" or command == "G3"):
|
||||
# test print("param = 'I'")
|
||||
# this is the special case for circular paths,
|
||||
# where relative coordinates have to be changed
|
||||
# to absolute
|
||||
i = c.Parameters["I"]
|
||||
# calculate the radius r
|
||||
j = c.Parameters["J"]
|
||||
r = math.sqrt(i**2 + j**2)
|
||||
if USE_RADIUS_IF_POSSIBLE and angleUnder180(
|
||||
command,
|
||||
lastX,
|
||||
lastY,
|
||||
c.Parameters["X"],
|
||||
c.Parameters["Y"],
|
||||
i,
|
||||
j,
|
||||
):
|
||||
outstring.append(
|
||||
"R" + PostUtils.fmt(r, AXIS_DECIMALS, UNITS)
|
||||
)
|
||||
else:
|
||||
if RADIUS_COMMENT:
|
||||
outstring.append(
|
||||
"(R"
|
||||
+ PostUtils.fmt(r, AXIS_DECIMALS, UNITS)
|
||||
+ ")"
|
||||
)
|
||||
if ABSOLUTE_CIRCLE_CENTER:
|
||||
i += lastX
|
||||
outstring.append(
|
||||
param + PostUtils.fmt(i, AXIS_DECIMALS, UNITS)
|
||||
)
|
||||
elif param == "J" and (command == "G2" or command == "G3"):
|
||||
# this is the special case for circular paths,
|
||||
# where incremental center has to be changed to
|
||||
# absolute center
|
||||
i = c.Parameters["I"]
|
||||
j = c.Parameters["J"]
|
||||
if USE_RADIUS_IF_POSSIBLE and angleUnder180(
|
||||
command,
|
||||
lastX,
|
||||
lastY,
|
||||
c.Parameters["X"],
|
||||
c.Parameters["Y"],
|
||||
i,
|
||||
j,
|
||||
):
|
||||
# R is handled with the I parameter, here:
|
||||
# do nothing at all, keep the structure as
|
||||
# with I command
|
||||
pass
|
||||
else:
|
||||
if ABSOLUTE_CIRCLE_CENTER:
|
||||
j += lastY
|
||||
if SWAP_Y_Z:
|
||||
# we have to swap j and k as well
|
||||
outstring.append(
|
||||
"K" + PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
|
||||
)
|
||||
else:
|
||||
outstring.append(
|
||||
param
|
||||
+ PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
|
||||
)
|
||||
elif param == "K" and (command == "G2" or command == "G3"):
|
||||
# this is the special case for circular paths,
|
||||
# where incremental center has to be changed to
|
||||
# absolute center
|
||||
outstring.append(
|
||||
"("
|
||||
+ param
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters[param], AXIS_DECIMALS, UNITS
|
||||
)
|
||||
+ ")"
|
||||
)
|
||||
z = c.Parameters["Z"]
|
||||
k = c.Parameters["K"]
|
||||
if USE_RADIUS_IF_POSSIBLE and angleUnder180(
|
||||
command,
|
||||
lastX,
|
||||
lastY,
|
||||
c.Parameters["X"],
|
||||
c.Parameters["Y"],
|
||||
i,
|
||||
j,
|
||||
):
|
||||
# R is handled with the I parameter, here:
|
||||
# do nothing at all, keep the structure as
|
||||
# with I command
|
||||
pass
|
||||
else:
|
||||
if ABSOLUTE_CIRCLE_CENTER:
|
||||
k += lastZ
|
||||
if SWAP_Y_Z:
|
||||
# we have to swap j and k as well
|
||||
outstring.append(
|
||||
"J" + PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
|
||||
)
|
||||
else:
|
||||
outstring.append(
|
||||
param + PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
|
||||
)
|
||||
elif param == "Y" and SWAP_Y_Z:
|
||||
outstring.append(
|
||||
"Z"
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters[param], AXIS_DECIMALS, UNITS
|
||||
)
|
||||
)
|
||||
elif param == "Z" and SWAP_Y_Z:
|
||||
outstring.append(
|
||||
"Y"
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters[param], AXIS_DECIMALS, UNITS
|
||||
)
|
||||
)
|
||||
else:
|
||||
# To Do: suppress unknown commands, if this is done here, all X parameters are suppressed
|
||||
# this is an unknown command, don't create GCode for it
|
||||
# print("parameter " + param + " for command " + command + " ignored")
|
||||
outstring.append(
|
||||
param
|
||||
+ PostUtils.fmt(
|
||||
c.Parameters[param], AXIS_DECIMALS, UNITS
|
||||
)
|
||||
)
|
||||
|
||||
if param in MODALPARAMS:
|
||||
modalParamsDict[str(param)] = c.Parameters[param]
|
||||
# save the last X, Y, Z values
|
||||
if "X" in c.Parameters:
|
||||
lastX = c.Parameters["X"]
|
||||
if "Y" in c.Parameters:
|
||||
lastY = c.Parameters["Y"]
|
||||
if "Z" in c.Parameters:
|
||||
lastZ = c.Parameters["Z"]
|
||||
|
||||
outstr = ""
|
||||
for w in outstring:
|
||||
outstr += w + COMMAND_SPACE
|
||||
outstr = outstr.replace("]", "")
|
||||
outstr = outstr.replace("[", "")
|
||||
outstr = outstr.replace("'", "")
|
||||
outstr = outstr.replace(",", ".")
|
||||
if LINENUMBERS:
|
||||
gcode += "N" + str(linenr) + " "
|
||||
linenr += LINENUMBER_INCREMENT
|
||||
gcode += outstr + "\n"
|
||||
lastcommand = c.Name
|
||||
gcode = gcode.replace("_", "-")
|
||||
gcode += linenumberify(GCODE_FOOTER)
|
||||
if SHOW_EDITOR:
|
||||
PostUtils.editor(gcode)
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(gcode)
|
||||
gfile.close()
|
||||
@@ -1,265 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * Copyright (c) 2020 Schildkroet *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PathScripts import PostUtilsArguments
|
||||
from PathScripts import PostUtilsExport
|
||||
|
||||
#
|
||||
# The following variables need to be global variables
|
||||
# to keep the PathPostProcessor.load method happy:
|
||||
#
|
||||
# TOOLTIP
|
||||
# TOOLTIP_ARGS
|
||||
# UNITS
|
||||
#
|
||||
# The "argument_defaults", "arguments_visible", and the "values" hashes
|
||||
# need to be defined before the "init_shared_arguments" routine can be
|
||||
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
|
||||
# TOOLTIP_ARGS can be defined, so they end up being global variables also.
|
||||
#
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a centroid 3 axis mill. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import refactored_centroid_post
|
||||
refactored_centroid_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
#
|
||||
# Default to metric mode
|
||||
#
|
||||
UNITS = "G21"
|
||||
|
||||
|
||||
def init_values(values):
|
||||
"""Initialize values that are used throughout the postprocessor."""
|
||||
#
|
||||
global UNITS
|
||||
|
||||
PostUtilsArguments.init_shared_values(values)
|
||||
#
|
||||
# Set any values here that need to override the default values set
|
||||
# in the init_shared_values routine.
|
||||
#
|
||||
# Use 4 digits for axis precision by default.
|
||||
#
|
||||
values["AXIS_PRECISION"] = 4
|
||||
values["DEFAULT_AXIS_PRECISION"] = 4
|
||||
values["DEFAULT_INCH_AXIS_PRECISION"] = 4
|
||||
#
|
||||
# Use ";" as the comment symbol
|
||||
#
|
||||
values["COMMENT_SYMBOL"] = ";"
|
||||
#
|
||||
# Use 1 digit for feed precision by default.
|
||||
#
|
||||
values["FEED_PRECISION"] = 1
|
||||
values["DEFAULT_FEED_PRECISION"] = 1
|
||||
values["DEFAULT_INCH_FEED_PRECISION"] = 1
|
||||
#
|
||||
# This value usually shows up in the post_op comment as "Finish operation:".
|
||||
# Change it to "End" to produce "End operation:".
|
||||
#
|
||||
values["FINISH_LABEL"] = "End"
|
||||
#
|
||||
# If this value is True, then a list of tool numbers
|
||||
# with their labels are output just before the preamble.
|
||||
#
|
||||
values["LIST_TOOLS_IN_PREAMBLE"] = True
|
||||
#
|
||||
# Used in the argparser code as the "name" of the postprocessor program.
|
||||
# This would normally show up in the usage message in the TOOLTIP_ARGS,
|
||||
# but we are suppressing the usage message, so it doesn't show up after all.
|
||||
#
|
||||
values["MACHINE_NAME"] = "Centroid"
|
||||
#
|
||||
# This list controls the order of parameters in a line during output.
|
||||
# centroid doesn't want K properties on XY plane; Arcs need work.
|
||||
#
|
||||
values["PARAMETER_ORDER"] = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
]
|
||||
#
|
||||
# Any commands in this value will be output as the last commands
|
||||
# in the G-code file.
|
||||
#
|
||||
values["POSTAMBLE"] = """M99"""
|
||||
values["POSTPROCESSOR_FILE_NAME"] = __name__
|
||||
#
|
||||
# Any commands in this value will be output after the header and
|
||||
# safety block at the beginning of the G-code file.
|
||||
#
|
||||
values["PREAMBLE"] = """G53 G00 G17"""
|
||||
#
|
||||
# Output any messages.
|
||||
#
|
||||
values["REMOVE_MESSAGES"] = False
|
||||
#
|
||||
# Any commands in this value are output after the header but before the preamble,
|
||||
# then again after the TOOLRETURN but before the POSTAMBLE.
|
||||
#
|
||||
values["SAFETYBLOCK"] = """G90 G80 G40 G49"""
|
||||
#
|
||||
# Do not show the current machine units just before the PRE_OPERATION.
|
||||
#
|
||||
values["SHOW_MACHINE_UNITS"] = False
|
||||
#
|
||||
# Do not show the current operation label just before the PRE_OPERATION.
|
||||
#
|
||||
values["SHOW_OPERATION_LABELS"] = False
|
||||
#
|
||||
# Do not output an M5 command to stop the spindle for tool changes.
|
||||
#
|
||||
values["STOP_SPINDLE_FOR_TOOL_CHANGE"] = False
|
||||
#
|
||||
# spindle off, height offset canceled, spindle retracted
|
||||
# (M25 is a centroid command to retract spindle)
|
||||
#
|
||||
values[
|
||||
"TOOLRETURN"
|
||||
] = """M5
|
||||
M25
|
||||
G49 H0"""
|
||||
values["UNITS"] = UNITS
|
||||
#
|
||||
# Default to not outputting a G43 following tool changes
|
||||
#
|
||||
values["USE_TLO"] = False
|
||||
#
|
||||
# This was in the original centroid postprocessor file
|
||||
# but does not appear to be used anywhere.
|
||||
#
|
||||
# ZAXISRETURN = """G91 G28 X0 Z0 G90"""
|
||||
#
|
||||
|
||||
|
||||
def init_argument_defaults(argument_defaults):
|
||||
"""Initialize which arguments (in a pair) are shown as the default argument."""
|
||||
PostUtilsArguments.init_argument_defaults(argument_defaults)
|
||||
#
|
||||
# Modify which argument to show as the default in flag-type arguments here.
|
||||
# If the value is True, the first argument will be shown as the default.
|
||||
# If the value is False, the second argument will be shown as the default.
|
||||
#
|
||||
# For example, if you want to show Metric mode as the default, use:
|
||||
# argument_defaults["metric_inch"] = True
|
||||
#
|
||||
# If you want to show that "Don't pop up editor for writing output" is
|
||||
# the default, use:
|
||||
# argument_defaults["show-editor"] = False.
|
||||
#
|
||||
# Note: You also need to modify the corresponding entries in the "values" hash
|
||||
# to actually make the default value(s) change to match.
|
||||
#
|
||||
|
||||
|
||||
def init_arguments_visible(arguments_visible):
|
||||
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
|
||||
PostUtilsArguments.init_arguments_visible(arguments_visible)
|
||||
#
|
||||
# Modify the visibility of any arguments from the defaults here.
|
||||
#
|
||||
arguments_visible["axis-modal"] = False
|
||||
arguments_visible["precision"] = False
|
||||
arguments_visible["tlo"] = False
|
||||
|
||||
|
||||
def init_arguments(values, argument_defaults, arguments_visible):
|
||||
"""Initialize the shared argument definitions."""
|
||||
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# Add any argument definitions that are not shared with all other postprocessors here.
|
||||
#
|
||||
return parser
|
||||
|
||||
|
||||
#
|
||||
# Creating global variables and using functions to modify them
|
||||
# is useful for being able to test things later.
|
||||
#
|
||||
values = {}
|
||||
init_values(values)
|
||||
argument_defaults = {}
|
||||
init_argument_defaults(argument_defaults)
|
||||
arguments_visible = {}
|
||||
init_arguments_visible(arguments_visible)
|
||||
parser = init_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# The TOOLTIP_ARGS value is created from the help information about the arguments.
|
||||
#
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
#
|
||||
# Create another parser just to get a list of all possible arguments
|
||||
# that may be output using --output_all_arguments.
|
||||
#
|
||||
all_arguments_visible = {}
|
||||
for k in iter(arguments_visible):
|
||||
all_arguments_visible[k] = True
|
||||
all_visible = init_arguments(values, argument_defaults, all_arguments_visible)
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"""Postprocess the objects in objectslist to filename."""
|
||||
#
|
||||
global parser
|
||||
global UNITS
|
||||
global values
|
||||
|
||||
# print(parser.format_help())
|
||||
|
||||
(flag, args) = PostUtilsArguments.process_shared_arguments(
|
||||
values, parser, argstring, all_visible, filename
|
||||
)
|
||||
if not flag:
|
||||
return args
|
||||
#
|
||||
# Process any additional arguments here
|
||||
#
|
||||
|
||||
#
|
||||
# Update the global variables that might have been modified
|
||||
# while processing the arguments.
|
||||
#
|
||||
UNITS = values["UNITS"]
|
||||
|
||||
return PostUtilsExport.export_common(values, objectslist, filename)
|
||||
@@ -1,232 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2018, 2019 Gauthier Briere *
|
||||
# * Copyright (c) 2019, 2020 Schildkroet *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PathScripts import PostUtilsArguments
|
||||
from PathScripts import PostUtilsExport
|
||||
|
||||
#
|
||||
# The following variables need to be global variables
|
||||
# to keep the PathPostProcessor.load method happy:
|
||||
#
|
||||
# TOOLTIP
|
||||
# TOOLTIP_ARGS
|
||||
# UNITS
|
||||
#
|
||||
# The "argument_defaults", "arguments_visible", and the "values" hashes
|
||||
# need to be defined before the "init_shared_arguments" routine can be
|
||||
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
|
||||
#
|
||||
TOOLTIP = """
|
||||
Generate g-code from a Path that is compatible with the grbl controller:
|
||||
|
||||
import refactored_grbl_post
|
||||
refactored_grbl_post.export(object, "/path/to/file.ncc")
|
||||
"""
|
||||
#
|
||||
# Default to metric mode
|
||||
#
|
||||
UNITS = "G21"
|
||||
|
||||
|
||||
def init_values(values):
|
||||
"""Initialize values that are used throughout the postprocessor."""
|
||||
#
|
||||
global UNITS
|
||||
|
||||
PostUtilsArguments.init_shared_values(values)
|
||||
#
|
||||
# Set any values here that need to override the default values set
|
||||
# in the init_shared_values routine.
|
||||
#
|
||||
#
|
||||
# If this is set to True, then commands that are placed in
|
||||
# comments that look like (MC_RUN_COMMAND: blah) will be output.
|
||||
#
|
||||
values["ENABLE_MACHINE_SPECIFIC_COMMANDS"] = True
|
||||
#
|
||||
# Used in the argparser code as the "name" of the postprocessor program.
|
||||
# This would normally show up in the usage message in the TOOLTIP_ARGS,
|
||||
# but we are suppressing the usage message, so it doesn't show up after all.
|
||||
#
|
||||
values["MACHINE_NAME"] = "Grbl"
|
||||
#
|
||||
# Default to outputting Path labels at the beginning of each Path.
|
||||
#
|
||||
values["OUTPUT_PATH_LABELS"] = True
|
||||
#
|
||||
# Default to not outputting M6 tool changes (comment it) as grbl currently does not handle it
|
||||
#
|
||||
values["OUTPUT_TOOL_CHANGE"] = False
|
||||
#
|
||||
# The order of the parameters.
|
||||
# Arcs may only work on the XY plane (this needs to be verified).
|
||||
#
|
||||
values["PARAMETER_ORDER"] = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"P",
|
||||
]
|
||||
#
|
||||
# Any commands in this value will be output as the last commands
|
||||
# in the G-code file.
|
||||
#
|
||||
values[
|
||||
"POSTAMBLE"
|
||||
] = """M5
|
||||
G17 G90
|
||||
M2"""
|
||||
values["POSTPROCESSOR_FILE_NAME"] = __name__
|
||||
#
|
||||
# Any commands in this value will be output after the header and
|
||||
# safety block at the beginning of the G-code file.
|
||||
#
|
||||
values["PREAMBLE"] = """G17 G90"""
|
||||
#
|
||||
# Do not show the current machine units just before the PRE_OPERATION.
|
||||
#
|
||||
values["SHOW_MACHINE_UNITS"] = False
|
||||
values["UNITS"] = UNITS
|
||||
#
|
||||
# Default to not outputting a G43 following tool changes
|
||||
#
|
||||
values["USE_TLO"] = False
|
||||
|
||||
|
||||
def init_argument_defaults(argument_defaults):
|
||||
"""Initialize which arguments (in a pair) are shown as the default argument."""
|
||||
PostUtilsArguments.init_argument_defaults(argument_defaults)
|
||||
#
|
||||
# Modify which argument to show as the default in flag-type arguments here.
|
||||
# If the value is True, the first argument will be shown as the default.
|
||||
# If the value is False, the second argument will be shown as the default.
|
||||
#
|
||||
# For example, if you want to show Metric mode as the default, use:
|
||||
# argument_defaults["metric_inch"] = True
|
||||
#
|
||||
# If you want to show that "Don't pop up editor for writing output" is
|
||||
# the default, use:
|
||||
# argument_defaults["show-editor"] = False.
|
||||
#
|
||||
# Note: You also need to modify the corresponding entries in the "values" hash
|
||||
# to actually make the default value(s) change to match.
|
||||
#
|
||||
argument_defaults["tlo"] = False
|
||||
argument_defaults["tool_change"] = False
|
||||
|
||||
|
||||
def init_arguments_visible(arguments_visible):
|
||||
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
|
||||
PostUtilsArguments.init_arguments_visible(arguments_visible)
|
||||
#
|
||||
# Modify the visibility of any arguments from the defaults here.
|
||||
#
|
||||
arguments_visible["bcnc"] = True
|
||||
arguments_visible["axis-modal"] = False
|
||||
arguments_visible["return-to"] = True
|
||||
arguments_visible["tlo"] = False
|
||||
arguments_visible["tool_change"] = True
|
||||
arguments_visible["translate_drill"] = True
|
||||
arguments_visible["wait-for-spindle"] = True
|
||||
|
||||
|
||||
def init_arguments(values, argument_defaults, arguments_visible):
|
||||
"""Initialize the shared argument definitions."""
|
||||
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# Add any argument definitions that are not shared with all other postprocessors here.
|
||||
#
|
||||
return parser
|
||||
|
||||
|
||||
#
|
||||
# Creating global variables and using functions to modify them
|
||||
# is useful for being able to test things later.
|
||||
#
|
||||
values = {}
|
||||
init_values(values)
|
||||
argument_defaults = {}
|
||||
init_argument_defaults(argument_defaults)
|
||||
arguments_visible = {}
|
||||
init_arguments_visible(arguments_visible)
|
||||
parser = init_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# The TOOLTIP_ARGS value is created from the help information about the arguments.
|
||||
#
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
#
|
||||
# Create another parser just to get a list of all possible arguments
|
||||
# that may be output using --output_all_arguments.
|
||||
#
|
||||
all_arguments_visible = {}
|
||||
for k in iter(arguments_visible):
|
||||
all_arguments_visible[k] = True
|
||||
all_visible = init_arguments(values, argument_defaults, all_arguments_visible)
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"""Postprocess the objects in objectslist to filename."""
|
||||
#
|
||||
global parser
|
||||
global UNITS
|
||||
global values
|
||||
|
||||
# print(parser.format_help())
|
||||
|
||||
(flag, args) = PostUtilsArguments.process_shared_arguments(
|
||||
values, parser, argstring, all_visible, filename
|
||||
)
|
||||
if not flag:
|
||||
return args
|
||||
#
|
||||
# Process any additional arguments here
|
||||
#
|
||||
|
||||
#
|
||||
# Update the global variables that might have been modified
|
||||
# while processing the arguments.
|
||||
#
|
||||
UNITS = values["UNITS"]
|
||||
|
||||
return PostUtilsExport.export_common(values, objectslist, filename)
|
||||
@@ -1,199 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PathScripts import PostUtilsArguments
|
||||
from PathScripts import PostUtilsExport
|
||||
|
||||
#
|
||||
# The following variables need to be global variables
|
||||
# to keep the PathPostProcessor.load method happy:
|
||||
#
|
||||
# TOOLTIP
|
||||
# TOOLTIP_ARGS
|
||||
# UNITS
|
||||
#
|
||||
# The "argument_defaults", "arguments_visible", and the "values" hashes
|
||||
# need to be defined before the "init_shared_arguments" routine can be
|
||||
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
|
||||
#
|
||||
TOOLTIP = """This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a linuxcnc 3 axis mill. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import refactored_linuxcnc_post
|
||||
refactored_linuxcnc_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
#
|
||||
# Default to metric mode
|
||||
#
|
||||
UNITS = "G21"
|
||||
|
||||
|
||||
def init_values(values):
|
||||
"""Initialize values that are used throughout the postprocessor."""
|
||||
#
|
||||
global UNITS
|
||||
|
||||
PostUtilsArguments.init_shared_values(values)
|
||||
#
|
||||
# Set any values here that need to override the default values set
|
||||
# in the init_shared_values routine.
|
||||
#
|
||||
values["ENABLE_COOLANT"] = True
|
||||
# the order of parameters
|
||||
# linuxcnc doesn't want K properties on XY plane; Arcs need work.
|
||||
values["PARAMETER_ORDER"] = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
#
|
||||
# Used in the argparser code as the "name" of the postprocessor program.
|
||||
# This would normally show up in the usage message in the TOOLTIP_ARGS,
|
||||
# but we are suppressing the usage message, so it doesn't show up after all.
|
||||
#
|
||||
values["MACHINE_NAME"] = "LinuxCNC"
|
||||
#
|
||||
# Any commands in this value will be output as the last commands
|
||||
# in the G-code file.
|
||||
#
|
||||
values[
|
||||
"POSTAMBLE"
|
||||
] = """M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2"""
|
||||
values["POSTPROCESSOR_FILE_NAME"] = __name__
|
||||
#
|
||||
# Any commands in this value will be output after the header and
|
||||
# safety block at the beginning of the G-code file.
|
||||
#
|
||||
values["PREAMBLE"] = """G17 G54 G40 G49 G80 G90"""
|
||||
values["UNITS"] = UNITS
|
||||
|
||||
|
||||
def init_argument_defaults(argument_defaults):
|
||||
"""Initialize which arguments (in a pair) are shown as the default argument."""
|
||||
PostUtilsArguments.init_argument_defaults(argument_defaults)
|
||||
#
|
||||
# Modify which argument to show as the default in flag-type arguments here.
|
||||
# If the value is True, the first argument will be shown as the default.
|
||||
# If the value is False, the second argument will be shown as the default.
|
||||
#
|
||||
# For example, if you want to show Metric mode as the default, use:
|
||||
# argument_defaults["metric_inch"] = True
|
||||
#
|
||||
# If you want to show that "Don't pop up editor for writing output" is
|
||||
# the default, use:
|
||||
# argument_defaults["show-editor"] = False.
|
||||
#
|
||||
# Note: You also need to modify the corresponding entries in the "values" hash
|
||||
# to actually make the default value(s) change to match.
|
||||
#
|
||||
|
||||
|
||||
def init_arguments_visible(arguments_visible):
|
||||
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
|
||||
PostUtilsArguments.init_arguments_visible(arguments_visible)
|
||||
#
|
||||
# Modify the visibility of any arguments from the defaults here.
|
||||
#
|
||||
|
||||
|
||||
def init_arguments(values, argument_defaults, arguments_visible):
|
||||
"""Initialize the shared argument definitions."""
|
||||
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# Add any argument definitions that are not shared with all other postprocessors here.
|
||||
#
|
||||
return parser
|
||||
|
||||
|
||||
#
|
||||
# Creating global variables and using functions to modify them
|
||||
# is useful for being able to test things later.
|
||||
#
|
||||
values = {}
|
||||
init_values(values)
|
||||
argument_defaults = {}
|
||||
init_argument_defaults(argument_defaults)
|
||||
arguments_visible = {}
|
||||
init_arguments_visible(arguments_visible)
|
||||
parser = init_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# The TOOLTIP_ARGS value is created from the help information about the arguments.
|
||||
#
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
#
|
||||
# Create another parser just to get a list of all possible arguments
|
||||
# that may be output using --output_all_arguments.
|
||||
#
|
||||
all_arguments_visible = {}
|
||||
for k in iter(arguments_visible):
|
||||
all_arguments_visible[k] = True
|
||||
all_visible = init_arguments(values, argument_defaults, all_arguments_visible)
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"""Postprocess the objects in objectslist to filename."""
|
||||
#
|
||||
global parser
|
||||
global UNITS
|
||||
global values
|
||||
|
||||
# print(parser.format_help())
|
||||
|
||||
(flag, args) = PostUtilsArguments.process_shared_arguments(
|
||||
values, parser, argstring, all_visible, filename
|
||||
)
|
||||
if not flag:
|
||||
return args
|
||||
#
|
||||
# Process any additional arguments here
|
||||
#
|
||||
|
||||
#
|
||||
# Update the global variables that might have been modified
|
||||
# while processing the arguments.
|
||||
#
|
||||
UNITS = values["UNITS"]
|
||||
|
||||
return PostUtilsExport.export_common(values, objectslist, filename)
|
||||
@@ -1,206 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
from __future__ import print_function
|
||||
|
||||
from PathScripts import PostUtilsArguments
|
||||
from PathScripts import PostUtilsExport
|
||||
|
||||
#
|
||||
# The following variables need to be global variables
|
||||
# to keep the PathPostProcessor.load method happy:
|
||||
#
|
||||
# TOOLTIP
|
||||
# TOOLTIP_ARGS
|
||||
# UNITS
|
||||
#
|
||||
# The "argument_defaults", "arguments_visible", and the "values" hashes
|
||||
# need to be defined before the "init_shared_arguments" routine can be
|
||||
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
|
||||
#
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a mach3_4 3 axis mill. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import mach3_mach4_post
|
||||
mach3_mach4_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
#
|
||||
# Default to metric mode
|
||||
#
|
||||
UNITS = "G21"
|
||||
|
||||
|
||||
def init_values(values):
|
||||
"""Initialize values that are used throughout the postprocessor."""
|
||||
#
|
||||
global UNITS
|
||||
|
||||
PostUtilsArguments.init_shared_values(values)
|
||||
#
|
||||
# Set any values here that need to override the default values set
|
||||
# in the init_shared_values routine.
|
||||
#
|
||||
values["ENABLE_COOLANT"] = True
|
||||
#
|
||||
# Used in the argparser code as the "name" of the postprocessor program.
|
||||
# This would normally show up in the usage message in the TOOLTIP_ARGS,
|
||||
# but we are suppressing the usage message, so it doesn't show up after all.
|
||||
#
|
||||
values["MACHINE_NAME"] = "mach3_4"
|
||||
# Enable special processing for operations with "Adaptive" in the name
|
||||
values["OUTPUT_ADAPTIVE"] = True
|
||||
# Output the machine name for mach3_mach4 instead of the machine units alone.
|
||||
values["OUTPUT_MACHINE_NAME"] = True
|
||||
# the order of parameters
|
||||
# mach3_mach4 doesn't want K properties on XY plane; Arcs need work.
|
||||
values["PARAMETER_ORDER"] = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"H",
|
||||
"D",
|
||||
"P",
|
||||
]
|
||||
#
|
||||
# Any commands in this value will be output as the last commands
|
||||
# in the G-code file.
|
||||
#
|
||||
values[
|
||||
"POSTAMBLE"
|
||||
] = """M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2"""
|
||||
values["POSTPROCESSOR_FILE_NAME"] = __name__
|
||||
#
|
||||
# Any commands in this value will be output after the header and
|
||||
# safety block at the beginning of the G-code file.
|
||||
#
|
||||
values["PREAMBLE"] = """G17 G54 G40 G49 G80 G90"""
|
||||
# Output the machine name for mach3_mach4 instead of the machine units alone.
|
||||
values["SHOW_MACHINE_UNITS"] = False
|
||||
values["UNITS"] = UNITS
|
||||
|
||||
|
||||
def init_argument_defaults(argument_defaults):
|
||||
"""Initialize which arguments (in a pair) are shown as the default argument."""
|
||||
PostUtilsArguments.init_argument_defaults(argument_defaults)
|
||||
#
|
||||
# Modify which argument to show as the default in flag-type arguments here.
|
||||
# If the value is True, the first argument will be shown as the default.
|
||||
# If the value is False, the second argument will be shown as the default.
|
||||
#
|
||||
# For example, if you want to show Metric mode as the default, use:
|
||||
# argument_defaults["metric_inch"] = True
|
||||
#
|
||||
# If you want to show that "Don't pop up editor for writing output" is
|
||||
# the default, use:
|
||||
# argument_defaults["show-editor"] = False.
|
||||
#
|
||||
# Note: You also need to modify the corresponding entries in the "values" hash
|
||||
# to actually make the default value(s) change to match.
|
||||
#
|
||||
|
||||
|
||||
def init_arguments_visible(arguments_visible):
|
||||
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
|
||||
PostUtilsArguments.init_arguments_visible(arguments_visible)
|
||||
#
|
||||
# Modify the visibility of any arguments from the defaults here.
|
||||
#
|
||||
arguments_visible["axis-modal"] = True
|
||||
|
||||
|
||||
def init_arguments(values, argument_defaults, arguments_visible):
|
||||
"""Initialize the shared argument definitions."""
|
||||
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# Add any argument definitions that are not shared with all other postprocessors here.
|
||||
#
|
||||
return parser
|
||||
|
||||
|
||||
#
|
||||
# Creating global variables and using functions to modify them
|
||||
# is useful for being able to test things later.
|
||||
#
|
||||
values = {}
|
||||
init_values(values)
|
||||
argument_defaults = {}
|
||||
init_argument_defaults(argument_defaults)
|
||||
arguments_visible = {}
|
||||
init_arguments_visible(arguments_visible)
|
||||
parser = init_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# The TOOLTIP_ARGS value is created from the help information about the arguments.
|
||||
#
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
#
|
||||
# Create another parser just to get a list of all possible arguments
|
||||
# that may be output using --output_all_arguments.
|
||||
#
|
||||
all_arguments_visible = {}
|
||||
for k in iter(arguments_visible):
|
||||
all_arguments_visible[k] = True
|
||||
all_visible = init_arguments(values, argument_defaults, all_arguments_visible)
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"""Postprocess the objects in objectslist to filename."""
|
||||
#
|
||||
global parser
|
||||
global UNITS
|
||||
global values
|
||||
|
||||
# print(parser.format_help())
|
||||
|
||||
(flag, args) = PostUtilsArguments.process_shared_arguments(
|
||||
values, parser, argstring, all_visible, filename
|
||||
)
|
||||
if not flag:
|
||||
return args
|
||||
#
|
||||
# Process any additional arguments here
|
||||
#
|
||||
|
||||
#
|
||||
# Update the global variables that might have been modified
|
||||
# while processing the arguments.
|
||||
#
|
||||
UNITS = values["UNITS"]
|
||||
|
||||
return PostUtilsExport.export_common(values, objectslist, filename)
|
||||
@@ -1,201 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PathScripts import PostUtilsArguments
|
||||
from PathScripts import PostUtilsExport
|
||||
|
||||
#
|
||||
# The following variables need to be global variables
|
||||
# to keep the PathPostProcessor.load method happy:
|
||||
#
|
||||
# TOOLTIP
|
||||
# TOOLTIP_ARGS
|
||||
# UNITS
|
||||
#
|
||||
# The "argument_defaults", "arguments_visible", and the "values" hashes
|
||||
# need to be defined before the "init_shared_arguments" routine can be
|
||||
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
|
||||
#
|
||||
TOOLTIP = """This is a postprocessor file for the Path workbench. It is used to
|
||||
test the postprocessor code. It probably isn't useful for "real" gcode.
|
||||
|
||||
import refactored_test_post
|
||||
refactored_test_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
#
|
||||
# Default to metric mode
|
||||
#
|
||||
UNITS = "G21"
|
||||
|
||||
|
||||
def init_values(values):
|
||||
"""Initialize values that are used throughout the postprocessor."""
|
||||
#
|
||||
global UNITS
|
||||
|
||||
PostUtilsArguments.init_shared_values(values)
|
||||
#
|
||||
# Set any values here that need to override the default values set
|
||||
# in the init_shared_values routine.
|
||||
#
|
||||
# Turn off as much functionality as possible by default.
|
||||
# Then the tests can turn back on the appropriate options as needed.
|
||||
#
|
||||
# Used in the argparser code as the "name" of the postprocessor program.
|
||||
# This would normally show up in the usage message in the TOOLTIP_ARGS,
|
||||
# but we are suppressing the usage message, so it doesn't show up after all.
|
||||
#
|
||||
values["MACHINE_NAME"] = "test"
|
||||
#
|
||||
# Don't output comments by default
|
||||
#
|
||||
values["OUTPUT_COMMENTS"] = False
|
||||
#
|
||||
# Don't output the header by default
|
||||
#
|
||||
values["OUTPUT_HEADER"] = False
|
||||
#
|
||||
# Convert M56 tool change commands to comments,
|
||||
# which are then suppressed by default.
|
||||
#
|
||||
values["OUTPUT_TOOL_CHANGE"] = False
|
||||
values["POSTPROCESSOR_FILE_NAME"] = __name__
|
||||
#
|
||||
# Do not show the editor by default since we are testing.
|
||||
#
|
||||
values["SHOW_EDITOR"] = False
|
||||
#
|
||||
# Don't show the current machine units by default
|
||||
#
|
||||
values["SHOW_MACHINE_UNITS"] = False
|
||||
#
|
||||
# Don't show the current operation label by default.
|
||||
#
|
||||
values["SHOW_OPERATION_LABELS"] = False
|
||||
#
|
||||
# Don't output an M5 command to stop the spindle after an M6 tool change by default.
|
||||
#
|
||||
values["STOP_SPINDLE_FOR_TOOL_CHANGE"] = False
|
||||
#
|
||||
# Don't output a G43 tool length command following tool changes by default.
|
||||
#
|
||||
values["USE_TLO"] = False
|
||||
values["UNITS"] = UNITS
|
||||
|
||||
|
||||
def init_argument_defaults(argument_defaults):
|
||||
"""Initialize which arguments (in a pair) are shown as the default argument."""
|
||||
PostUtilsArguments.init_argument_defaults(argument_defaults)
|
||||
#
|
||||
# Modify which argument to show as the default in flag-type arguments here.
|
||||
# If the value is True, the first argument will be shown as the default.
|
||||
# If the value is False, the second argument will be shown as the default.
|
||||
#
|
||||
# For example, if you want to show Metric mode as the default, use:
|
||||
# argument_defaults["metric_inch"] = True
|
||||
#
|
||||
# If you want to show that "Don't pop up editor for writing output" is
|
||||
# the default, use:
|
||||
# argument_defaults["show-editor"] = False.
|
||||
#
|
||||
# Note: You also need to modify the corresponding entries in the "values" hash
|
||||
# to actually make the default value(s) change to match.
|
||||
#
|
||||
|
||||
|
||||
def init_arguments_visible(arguments_visible):
|
||||
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
|
||||
PostUtilsArguments.init_arguments_visible(arguments_visible)
|
||||
#
|
||||
# Modify the visibility of any arguments from the defaults here.
|
||||
#
|
||||
#
|
||||
# Make all arguments invisible by default.
|
||||
#
|
||||
for k in iter(arguments_visible):
|
||||
arguments_visible[k] = False
|
||||
|
||||
|
||||
def init_arguments(values, argument_defaults, arguments_visible):
|
||||
"""Initialize the shared argument definitions."""
|
||||
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# Add any argument definitions that are not shared with all other postprocessors here.
|
||||
#
|
||||
return parser
|
||||
|
||||
|
||||
#
|
||||
# Creating global variables and using functions to modify them
|
||||
# is useful for being able to test things later.
|
||||
#
|
||||
values = {}
|
||||
init_values(values)
|
||||
argument_defaults = {}
|
||||
init_argument_defaults(argument_defaults)
|
||||
arguments_visible = {}
|
||||
init_arguments_visible(arguments_visible)
|
||||
parser = init_arguments(values, argument_defaults, arguments_visible)
|
||||
#
|
||||
# The TOOLTIP_ARGS value is created from the help information about the arguments.
|
||||
#
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
#
|
||||
# Create another parser just to get a list of all possible arguments
|
||||
# that may be output using --output_all_arguments.
|
||||
#
|
||||
all_arguments_visible = {}
|
||||
for k in iter(arguments_visible):
|
||||
all_arguments_visible[k] = True
|
||||
all_visible = init_arguments(values, argument_defaults, all_arguments_visible)
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
"""Postprocess the objects in objectslist to filename."""
|
||||
#
|
||||
global all_visible
|
||||
global parser
|
||||
global UNITS
|
||||
global values
|
||||
|
||||
# print(parser.format_help())
|
||||
|
||||
(flag, args) = PostUtilsArguments.process_shared_arguments(
|
||||
values, parser, argstring, all_visible, filename
|
||||
)
|
||||
if not flag:
|
||||
return args
|
||||
#
|
||||
# Process any additional arguments here
|
||||
#
|
||||
|
||||
#
|
||||
# Update the global variables that might have been modified
|
||||
# while processing the arguments.
|
||||
#
|
||||
UNITS = values["UNITS"]
|
||||
|
||||
return PostUtilsExport.export_common(values, objectslist, filename)
|
||||
@@ -1,275 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Jon Nordby <jononor@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
|
||||
TOOLTIP = """
|
||||
FreeCAD Path post-processor to output code for the Roland Modela MDX-## machines.
|
||||
|
||||
The machine speaks RML-1, specified in 'Roland RML-1 Programming Guidelines'
|
||||
https://itp.nyu.edu/classes/cdp-spring2014/files/2014/09/RML1_Manual.pdf
|
||||
http://altlab.org/d/content/m/pangelo/ideas/rml_command_guide_en_v100.pdf
|
||||
|
||||
The format has some overlap with HPGL:
|
||||
https://en.wikipedia.org/wiki/HPGL
|
||||
http://paulbourke.net/dataformats/hpgl/
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
# Entrypoint used by FreeCAD
|
||||
def export(objectslist, filename, argstring):
|
||||
"Export objects as Roland Modela code."
|
||||
|
||||
code = ""
|
||||
for obj in objectslist:
|
||||
code += convertobject(obj)
|
||||
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(code)
|
||||
gfile.close()
|
||||
|
||||
|
||||
def convertobject(obj):
|
||||
gcode = obj.Path.toGCode()
|
||||
gcode = parse(gcode)
|
||||
return gcode
|
||||
|
||||
|
||||
def motoron():
|
||||
return ["!MC1;"]
|
||||
|
||||
|
||||
def motoroff():
|
||||
return ["!MC0;"]
|
||||
|
||||
|
||||
def home():
|
||||
return ["H;"]
|
||||
|
||||
|
||||
def setjog():
|
||||
# "!PZ%d,%d;",iz_down,iz_up); // set z down, jog
|
||||
return ""
|
||||
|
||||
|
||||
def addheader():
|
||||
return ["PA;PA;"] # absolute positioning
|
||||
|
||||
|
||||
def addfooter():
|
||||
return []
|
||||
|
||||
|
||||
def mm2cord(mm):
|
||||
mm = float(mm)
|
||||
return int(40.0 * mm)
|
||||
|
||||
|
||||
def feed(x=None, y=None, z=None, state=None):
|
||||
c = []
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
if x is not None:
|
||||
x = float(x)
|
||||
state["X"] = x
|
||||
if y is not None:
|
||||
y = float(y)
|
||||
state["Y"] = y
|
||||
if z is not None:
|
||||
z = float(z)
|
||||
state["Z"] = z
|
||||
|
||||
if x is not None and y is not None and z is not None:
|
||||
# 3d motion
|
||||
c.append("Z%d,%d,%d;" % (mm2cord(x), mm2cord(y), mm2cord(z)))
|
||||
elif x is not None and y is not None:
|
||||
# 2d in XY plane
|
||||
c.append("PD%d,%d;" % (mm2cord(x), mm2cord(y)))
|
||||
elif z is not None:
|
||||
pass
|
||||
return c
|
||||
|
||||
|
||||
def jog(x=None, y=None, z=None, state=None):
|
||||
c = []
|
||||
if state is None:
|
||||
state = {}
|
||||
if x is not None and y is not None:
|
||||
x, y = float(x), float(y)
|
||||
c.append("PU%d,%d;" % (mm2cord(x), mm2cord(y)))
|
||||
state["X"] = x
|
||||
state["Y"] = y
|
||||
if z is not None:
|
||||
z = float(z)
|
||||
c.append("PU;")
|
||||
state["Z"] = z
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def xyarc(args, state):
|
||||
# no native support in RML/Modela, convert to linear line segments
|
||||
c = []
|
||||
|
||||
lastPoint = FreeCAD.Vector(state["X"], state["Y"])
|
||||
newPoint = FreeCAD.Vector(float(args["X"]), float(args["Y"]))
|
||||
centerOffset = FreeCAD.Vector(float(args["I"]), float(args["J"]))
|
||||
center = lastPoint + centerOffset
|
||||
radius = (center - lastPoint).Length
|
||||
xyNormal = FreeCAD.Vector(0, 0, 1)
|
||||
circle = Part.Circle(center, xyNormal, radius)
|
||||
p0 = circle.parameter(lastPoint)
|
||||
p1 = circle.parameter(newPoint)
|
||||
arc = Part.ArcOfCircle(circle, p0, p1)
|
||||
steps = 64 # specify max error instead?
|
||||
points = arc.discretize(steps)
|
||||
# consider direction?
|
||||
# print('p = Part.ArcOfCircle(Part.Circle(FreeCAD.Vector(%f, %f), FreeCAD.Vector(0, 0, 1), %f), %f, %f)' % (center.x, center.y, radius, p0, p1))
|
||||
for p in points:
|
||||
c += feed(p.x, p.y, state["Z"], state)
|
||||
return c
|
||||
|
||||
|
||||
def speed(xy=None, z=None, state=None):
|
||||
c = []
|
||||
if state is None:
|
||||
state = {}
|
||||
print(xy, z, state)
|
||||
if xy is not None:
|
||||
xy = float(xy)
|
||||
if xy > 0.0 and xy != state["XYspeed"]:
|
||||
c.append("VS%.1f;" % xy)
|
||||
state["XYspeed"] = xy
|
||||
if z is not None:
|
||||
z = float(z)
|
||||
if z > 0.0 and z != state["Zspeed"]:
|
||||
c.append("!VZ%.1f;" % z)
|
||||
state["Zspeed"] = z
|
||||
return c
|
||||
|
||||
|
||||
def convertgcode(cmd, args, state):
|
||||
"""Convert a single gcode command to equivalent Roland code"""
|
||||
if cmd == "G0":
|
||||
# jog
|
||||
return jog(args["X"], args["Y"], args["Z"], state)
|
||||
elif cmd == "G1":
|
||||
# linear feed
|
||||
c = []
|
||||
# feedrate
|
||||
c += speed(xy=args["F"], z=args["F"], state=state)
|
||||
# motion
|
||||
c += feed(args["X"], args["Y"], args["Z"], state)
|
||||
return c
|
||||
elif cmd == "G2" or cmd == "G3":
|
||||
# arc feed
|
||||
c = []
|
||||
# feedrate
|
||||
c += speed(xy=args["F"], state=state)
|
||||
# motion
|
||||
if args["X"] and args["Y"] and args["Z"]:
|
||||
# helical motion
|
||||
pass
|
||||
elif args["X"] and args["Y"]:
|
||||
# arc in plane
|
||||
c += xyarc(args, state)
|
||||
return c
|
||||
elif cmd == "G20":
|
||||
# inches mode
|
||||
raise ValueError("rml_post: Inches mode not supported")
|
||||
elif cmd == "G21":
|
||||
# millimeter mode
|
||||
return ""
|
||||
elif cmd == "G40":
|
||||
# tool compensation off
|
||||
return ""
|
||||
elif cmd == "G80":
|
||||
# cancel all cycles (drill normally)
|
||||
return "PU;"
|
||||
elif cmd == "G81":
|
||||
c = []
|
||||
# feedrate
|
||||
c += speed(z=args["F"], state=state)
|
||||
# motion
|
||||
c += jog(args["X"], args["Y"], state=state)
|
||||
c += feed(args["X"], args["Y"], args["Z"], state)
|
||||
return c
|
||||
elif cmd == "G90":
|
||||
# absolute mode?
|
||||
return ""
|
||||
elif cmd == "G98":
|
||||
# feedrate
|
||||
return ""
|
||||
else:
|
||||
raise NotImplementedError("rml_post: GCode command %s not understood" % (cmd,))
|
||||
|
||||
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a parsed output string"
|
||||
|
||||
state = {"X": 0.0, "Y": 0.0, "Z": 0.0, "XYspeed": -1.0, "Zspeed": -1.0}
|
||||
output = []
|
||||
|
||||
# header
|
||||
output += addheader()
|
||||
output += motoron()
|
||||
|
||||
output += speed(2.0, 1.0, state) # defaults
|
||||
|
||||
# respect clearance height?
|
||||
|
||||
# treat the input line by line
|
||||
lines = inputstring.split("\n")
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
parsed = PostUtils.stringsplit(line)
|
||||
command = parsed["command"]
|
||||
print("cmd", line)
|
||||
try:
|
||||
if command:
|
||||
code = convertgcode(command, parsed, state)
|
||||
if not isinstance(code, list):
|
||||
code = [code]
|
||||
if len(code) and code[0]:
|
||||
output += code
|
||||
except NotImplementedError as e:
|
||||
print(e)
|
||||
|
||||
# footer
|
||||
output += motoroff()
|
||||
output += home()
|
||||
output += addfooter()
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
# print (__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,790 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2018-2019 Gauthier Briere *
|
||||
# * Copyright (c) 2019-2020 Schildkroet *
|
||||
# * Copyright (c) 2020 Gary L Hasson *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import shlex
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
|
||||
Revised = "2021-10-21" # Revision date for this file.
|
||||
|
||||
# *****************************************************************************
|
||||
# * Due to the fundamentals of the FreeCAD pre-processor, *
|
||||
# * this post processor can only operate in the following modes: *
|
||||
# * G90 Absolute positions *
|
||||
# * G21 Metric units (mm) *
|
||||
# * G17 XY plane (3 axis vertical milling only) *
|
||||
# * *
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
TOOLTIP = """
|
||||
Generate g-code from a Path that is compatible with the Duet controller (RepRapFirmware).
|
||||
import rrf_post
|
||||
rrf_post.export(object, "/path/to/file.nc")
|
||||
"""
|
||||
|
||||
# *****************************************************************************
|
||||
# * Initial configuration, not changeable *
|
||||
# *****************************************************************************
|
||||
MOTION_MODE = "G90" # G90 only, for absolute moves
|
||||
WORK_PLANE = "G17" # G17 only, XY plane, for vertical milling
|
||||
UNITS = "G21" # G21 only, for metric
|
||||
UNIT_FORMAT = "mm"
|
||||
UNIT_FEED_FORMAT = "mm/min"
|
||||
|
||||
# *****************************************************************************
|
||||
# * Initial configuration, changeable via command line arguments *
|
||||
# *****************************************************************************
|
||||
PRECISION = 3 # Decimal places displayed for metric
|
||||
DRILL_RETRACT_MODE = "G98" # End of drill-cycle retractation type. G99
|
||||
# is the alternative.
|
||||
TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated
|
||||
# into G0/G1 moves
|
||||
RETURN_TO = None # None = No movement at end of program
|
||||
SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4
|
||||
MODAL = False # True: Commands are suppressed if they are
|
||||
# the same as the previous line
|
||||
LINENR = 100 # Line number starting value
|
||||
LINEINCR = 10 # Line number increment
|
||||
PRE_OPERATION = """""" # Pre operation text will be inserted before
|
||||
# every operation
|
||||
POST_OPERATION = """""" # Post operation text will be inserted after
|
||||
# every operation
|
||||
TOOL_CHANGE = """""" # Tool Change commands will be inserted
|
||||
# before a tool change
|
||||
|
||||
# *****************************************************************************
|
||||
# * Initial gcode output options, changeable via command line arguments *
|
||||
# *****************************************************************************
|
||||
OUTPUT_HEADER = True # Output header in output gcode file
|
||||
OUTPUT_COMMENTS = True # Comments in output gcode file
|
||||
OUTPUT_FINISH = False # Include an operation finished comment
|
||||
OUTPUT_PATH = False # Include a Path: comment
|
||||
OUTPUT_RRF_CONFIG = True # Display expected #defines for RRF config
|
||||
OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file
|
||||
OUTPUT_BCNC = False # Add bCNC operation block headers in output
|
||||
# gcode file
|
||||
SHOW_EDITOR = True # Display the resulting gcode file
|
||||
OUTPUT_TOOL_CHANGE = True
|
||||
# *****************************************************************************
|
||||
# * Command line arguments *
|
||||
# *****************************************************************************
|
||||
parser = argparse.ArgumentParser(prog="rrf", add_help=False)
|
||||
parser.add_argument("--header", action="store_true", help="output headers (default)")
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument("--comments", action="store_true", help="output comment (default)")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--finish-comments", action="store_true", help="output finish-comment"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-finish-comments",
|
||||
action="store_true",
|
||||
help="suppress finish-comment output (default)",
|
||||
)
|
||||
parser.add_argument("--path-comments", action="store_true", help="output path-comment")
|
||||
parser.add_argument(
|
||||
"--no-path-comments",
|
||||
action="store_true",
|
||||
help="suppress path-comment output (default)",
|
||||
)
|
||||
parser.add_argument("--rrf-config", action="store_true", help="output #defines for RRF")
|
||||
parser.add_argument(
|
||||
"--no-rrf-config",
|
||||
action="store_true",
|
||||
help="suppress output #defines for RRF (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-line-numbers",
|
||||
action="store_true",
|
||||
help="do not prefix with line numbers (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-editor",
|
||||
action="store_true",
|
||||
help="pop up editor before writing output (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="do not pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--translate_drill",
|
||||
action="store_true",
|
||||
help="translate drill cycles G81, G82, G83 into G0/G1 movements (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-translate_drill",
|
||||
action="store_true",
|
||||
help="do not translate drill cycles G81, G82, G83 into G0/G1 movements",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble", help='set commands to be issued before the first command, default=""'
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble", help='set commands to be issued after the last command, default="M5"'
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool-change", action="store_true", help="Insert M6 for all tool changes"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wait-for-spindle",
|
||||
type=int,
|
||||
default=3,
|
||||
help="Wait for spindle to reach desired speed after M3 or M4, default=0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--return-to",
|
||||
default="",
|
||||
help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bcnc",
|
||||
action="store_true",
|
||||
help="Add Job operations as bCNC block headers. \
|
||||
Consider suppressing existing comments: Add argument --no-comments",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-bcnc", action="store_true", help="suppress bCNC block header output (default)"
|
||||
)
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# *****************************************************************************
|
||||
# * RRF *
|
||||
# * Ignores commands that it does not implement. *
|
||||
# * Some machining-related commands may conflict with gcodes that RRF *
|
||||
# * has assigned to 3D printing commands. *
|
||||
# * Therefore, check FreeCAD gcodes for conflicts with RRF. *
|
||||
# * RRF ignores the ENTIRE COMMAND LINE if there is more than *
|
||||
# * one command per line. *
|
||||
# *****************************************************************************
|
||||
|
||||
# Default preamble text will appear at the beginning of the gcode output file.
|
||||
PREAMBLE = """"""
|
||||
|
||||
# Default postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M5
|
||||
"""
|
||||
|
||||
# *****************************************************************************
|
||||
# * Internal global variables *
|
||||
# *****************************************************************************
|
||||
MOTION_COMMANDS = ["G0", "G00", "G1", "G01", "G2", "G02", "G3", "G03"]
|
||||
RAPID_MOVES = ["G0", "G00"] # Rapid moves gcode commands definition
|
||||
SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out
|
||||
COMMAND_SPACE = " "
|
||||
# Global variables storing current position (Use None for safety.)
|
||||
CURRENT_X = None
|
||||
CURRENT_Y = None
|
||||
CURRENT_Z = None
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_FINISH
|
||||
global OUTPUT_PATH
|
||||
global OUTPUT_RRF_CONFIG
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_FEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global TRANSLATE_DRILL_CYCLES
|
||||
global OUTPUT_TOOL_CHANGE
|
||||
global SPINDLE_WAIT
|
||||
global RETURN_TO
|
||||
global OUTPUT_BCNC
|
||||
global PRECISION
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.header:
|
||||
OUTPUT_HEADER = True
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.comments:
|
||||
OUTPUT_COMMENTS = True
|
||||
if args.no_finish_comments:
|
||||
OUTPUT_FINISH = False
|
||||
if args.finish_comments:
|
||||
OUTPUT_FINISH = True
|
||||
if args.no_path_comments:
|
||||
OUTPUT_PATH = False
|
||||
if args.path_comments:
|
||||
OUTPUT_PATH = True
|
||||
if args.no_rrf_config:
|
||||
OUTPUT_RRF_CONFIG = False
|
||||
if args.rrf_config:
|
||||
OUTPUT_RRF_CONFIG = True
|
||||
if args.no_line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
if args.show_editor:
|
||||
SHOW_EDITOR = True
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.no_translate_drill:
|
||||
TRANSLATE_DRILL_CYCLES = False
|
||||
if args.translate_drill:
|
||||
TRANSLATE_DRILL_CYCLES = True
|
||||
if args.tool_change:
|
||||
OUTPUT_TOOL_CHANGE = True
|
||||
if args.return_to:
|
||||
RETURN_TO = args.return_to
|
||||
if RETURN_TO.find(",") == -1:
|
||||
RETURN_TO = None
|
||||
print("--return-to coordinates must be specified as:")
|
||||
print('--return-to "x.n,y.n,z.n"')
|
||||
if args.bcnc:
|
||||
OUTPUT_BCNC = True
|
||||
if args.no_bcnc:
|
||||
OUTPUT_BCNC = False
|
||||
SPINDLE_WAIT = args.wait_for_spindle
|
||||
PRECISION = args.precision
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# For debug...
|
||||
def dump(obj):
|
||||
for attr in dir(obj):
|
||||
try:
|
||||
if attr.startswith("__"):
|
||||
continue
|
||||
print(">" + attr + "<")
|
||||
attr_text = "%s = %s" % (attr, getattr(obj, attr))
|
||||
if attr in ["HorizFeed", "VertFeed"]:
|
||||
print("==============\n", attr_text)
|
||||
if "mm/s" in attr_text:
|
||||
print("===> metric values <===")
|
||||
except Exception: # Insignificant errors
|
||||
# print('==>', obj, attr)
|
||||
pass
|
||||
|
||||
|
||||
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 RRF configuration to gcode file:
|
||||
if OUTPUT_RRF_CONFIG:
|
||||
gcode += linenumber() + "(RRF Configuration)\n"
|
||||
gcode += linenumber() + "(The following should be enabled in)\n"
|
||||
gcode += linenumber() + "(the config.g)\n"
|
||||
gcode += linenumber() + "(M453)\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 linenumber():
|
||||
if not OUTPUT_LINE_NUMBERS:
|
||||
return ""
|
||||
global LINENR
|
||||
global LINEINCR
|
||||
LINENR += LINEINCR
|
||||
return "N" + str(LINENR) + " "
|
||||
|
||||
|
||||
def format_outlist(strTable):
|
||||
# construct the line for the final output
|
||||
global COMMAND_SPACE
|
||||
s = ""
|
||||
for w in strTable:
|
||||
s += w + COMMAND_SPACE
|
||||
return s.strip()
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global DRILL_RETRACT_MODE
|
||||
global MOTION_MODE
|
||||
global CURRENT_X
|
||||
global CURRENT_Y
|
||||
global CURRENT_Z
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"Q",
|
||||
"R",
|
||||
"L",
|
||||
"P",
|
||||
]
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
|
||||
else: # Parsing simple path
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
if OUTPUT_COMMENTS and OUTPUT_PATH:
|
||||
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
outlist = []
|
||||
command = c.Name
|
||||
outlist.append(command)
|
||||
# Debug:
|
||||
# print('pathobj.Path.Commands:', c)
|
||||
|
||||
# If modal is True, delete duplicate commands:
|
||||
if MODAL:
|
||||
if command == lastcommand:
|
||||
outlist.pop(0)
|
||||
|
||||
# Add the remaining parameters in order:
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
if command not in RAPID_MOVES:
|
||||
feedRate = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if feedRate.getValueAs(UNIT_FEED_FORMAT) > 0.0:
|
||||
outlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(feedRate.getValueAs(UNIT_FEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
elif param == "T":
|
||||
outlist.append(param + str(int(c.Parameters[param])))
|
||||
|
||||
elif param in ["H", "D", "S", "P", "L"]:
|
||||
outlist.append(param + str(c.Parameters[param]))
|
||||
elif param in ["A", "B", "C"]:
|
||||
outlist.append(
|
||||
param + format(c.Parameters[param], precision_string)
|
||||
)
|
||||
# [X, Y, Z, U, V, W, I, J, K, R, Q]
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# Store the latest command:
|
||||
lastcommand = command
|
||||
|
||||
# Capture the current position for subsequent calculations:
|
||||
if command in MOTION_COMMANDS:
|
||||
if "X" in c.Parameters:
|
||||
CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length)
|
||||
if "Y" in c.Parameters:
|
||||
CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length)
|
||||
if "Z" in c.Parameters:
|
||||
CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length)
|
||||
|
||||
if command in ("G98", "G99"):
|
||||
DRILL_RETRACT_MODE = command
|
||||
|
||||
if TRANSLATE_DRILL_CYCLES:
|
||||
if command in ("G81", "G82", "G83"):
|
||||
out += drill_translate(outlist, command, c.Parameters)
|
||||
# Erase the line just translated:
|
||||
outlist = []
|
||||
|
||||
if SPINDLE_WAIT > 0:
|
||||
if command in ("M3", "M03", "M4", "M04"):
|
||||
out += linenumber() + format_outlist(outlist) + "\n"
|
||||
# RRF: P for milliseconds, S for seconds, change P to S
|
||||
out += linenumber()
|
||||
out += format_outlist(["G4", "S%s" % SPINDLE_WAIT])
|
||||
out += "\n"
|
||||
outlist = []
|
||||
|
||||
# Check for Tool Change:
|
||||
if command in ("M6", "M06"):
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
out += linenumber() + "(Begin toolchange)\n"
|
||||
|
||||
if OUTPUT_TOOL_CHANGE:
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line + "\n"
|
||||
outlist[0] = " "
|
||||
outlist[-1] = "T" + str(int(c.Parameters["T"]))
|
||||
|
||||
if not OUTPUT_TOOL_CHANGE and OUTPUT_COMMENTS:
|
||||
# next 2 lines could also be replaced by a single line as "outlist = []"
|
||||
outlist[0] = " "
|
||||
outlist[-1] = " "
|
||||
|
||||
if not OUTPUT_TOOL_CHANGE and not OUTPUT_COMMENTS:
|
||||
outlist = []
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS:
|
||||
outlist.pop(0) # remove the command
|
||||
else:
|
||||
out = []
|
||||
|
||||
if command in SUPPRESS_COMMANDS:
|
||||
outlist[0] = "(" + outlist[0]
|
||||
outlist[-1] = outlist[-1] + ")"
|
||||
|
||||
# Remove embedded comments:
|
||||
if not OUTPUT_COMMENTS:
|
||||
tmplist = []
|
||||
list_index = 0
|
||||
while list_index < len(outlist):
|
||||
left_index = outlist[list_index].find("(")
|
||||
if left_index == -1: # Not a comment
|
||||
tmplist.append(outlist[list_index])
|
||||
else: # This line contains a comment, and possibly more
|
||||
right_index = outlist[list_index].find(")")
|
||||
comment_area = outlist[list_index][left_index : right_index + 1]
|
||||
line_minus_comment = (
|
||||
outlist[list_index].replace(comment_area, "").strip()
|
||||
)
|
||||
if line_minus_comment:
|
||||
# Line contained more than just a comment
|
||||
tmplist.append(line_minus_comment)
|
||||
list_index += 1
|
||||
# Done removing comments
|
||||
outlist = tmplist
|
||||
|
||||
# Prepend a line number and append a newline
|
||||
if len(outlist) >= 1:
|
||||
out += linenumber() + format_outlist(outlist) + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# *****************************************************************************
|
||||
# * As of RRF 3.3 canned drill cycles do not exist. *
|
||||
# * The following code converts FreeCAD's canned drill cycles into *
|
||||
# * gcode that RRF can use. *
|
||||
# *****************************************************************************
|
||||
def drill_translate(outlist, cmd, params):
|
||||
global DRILL_RETRACT_MODE
|
||||
global MOTION_MODE
|
||||
global CURRENT_X
|
||||
global CURRENT_Y
|
||||
global CURRENT_Z
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_FEED_FORMAT
|
||||
|
||||
class Drill: # Using a class is necessary for the nested functions.
|
||||
gcode = ""
|
||||
|
||||
strFormat = "." + str(PRECISION) + "f"
|
||||
|
||||
if OUTPUT_COMMENTS: # Comment the original command
|
||||
outlist[0] = "(" + outlist[0]
|
||||
outlist[-1] = outlist[-1] + ")"
|
||||
Drill.gcode += linenumber() + format_outlist(outlist) + "\n"
|
||||
|
||||
# Cycle conversion only converts the cycles in the XY plane (G17).
|
||||
# --> ZX (G18) and YZ (G19) planes produce false gcode.
|
||||
drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length)
|
||||
drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length)
|
||||
drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length)
|
||||
drill_R = Units.Quantity(params["R"], FreeCAD.Units.Length)
|
||||
drill_F = Units.Quantity(params["F"], FreeCAD.Units.Velocity)
|
||||
if cmd == "G82":
|
||||
drill_DwellTime = params["P"]
|
||||
elif cmd == "G83":
|
||||
drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length)
|
||||
|
||||
# R less than Z is error
|
||||
if drill_R < drill_Z:
|
||||
Drill.gcode += linenumber() + "(drill cycle error: R less than Z )\n"
|
||||
return Drill.gcode
|
||||
|
||||
# Z height to retract to when drill cycle is done:
|
||||
if DRILL_RETRACT_MODE == "G98" and CURRENT_Z > drill_R:
|
||||
RETRACT_Z = CURRENT_Z
|
||||
else:
|
||||
RETRACT_Z = drill_R
|
||||
|
||||
# Z motion nested functions:
|
||||
def rapid_Z_to(new_Z):
|
||||
Drill.gcode += linenumber() + "G0 Z"
|
||||
Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
|
||||
|
||||
def feed_Z_to(new_Z):
|
||||
Drill.gcode += linenumber() + "G1 Z"
|
||||
Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + " F"
|
||||
Drill.gcode += format(float(drill_F.getValueAs(UNIT_FEED_FORMAT)), ".2f") + "\n"
|
||||
|
||||
# Make sure that Z is not below RETRACT_Z:
|
||||
if CURRENT_Z < RETRACT_Z:
|
||||
rapid_Z_to(RETRACT_Z)
|
||||
|
||||
# Rapid to hole position XY:
|
||||
Drill.gcode += linenumber() + "G0 X"
|
||||
Drill.gcode += format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + " Y"
|
||||
Drill.gcode += format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
|
||||
|
||||
# Rapid to R:
|
||||
rapid_Z_to(drill_R)
|
||||
|
||||
# *************************************************************************
|
||||
# * Drill cycles: *
|
||||
# * G80 Cancel the drill cycle *
|
||||
# * G81 Drill full depth in one pass *
|
||||
# * G82 Drill full depth in one pass, and pause at the bottom *
|
||||
# * G83 Drill in pecks, raising the drill to R height after each peck *
|
||||
# * In preparation for a rapid to the next hole position: *
|
||||
# * G98 After the hole has been drilled, retract to the initial Z value *
|
||||
# * G99 After the hole has been drilled, retract to R height *
|
||||
# * Select G99 only if safe to move from hole to hole at the R height *
|
||||
# *************************************************************************
|
||||
if cmd in ("G81", "G82"):
|
||||
feed_Z_to(drill_Z) # Drill hole in one step
|
||||
if cmd == "G82": # Dwell time delay at the bottom of the hole
|
||||
Drill.gcode += linenumber() + "G4 S" + str(drill_DwellTime) + "\n"
|
||||
# RRF uses P for milliseconds, S for seconds, change P to S
|
||||
|
||||
elif cmd == "G83": # Peck drill cycle:
|
||||
chip_Space = drill_Step * 0.5
|
||||
next_Stop_Z = drill_R - drill_Step
|
||||
while next_Stop_Z >= drill_Z:
|
||||
feed_Z_to(next_Stop_Z) # Drill one peck of depth
|
||||
|
||||
# Set next depth, next_Stop_Z is still at the current hole depth
|
||||
if (next_Stop_Z - drill_Step) >= drill_Z:
|
||||
# Rapid up to clear chips:
|
||||
rapid_Z_to(drill_R)
|
||||
# Rapid down to just above last peck depth:
|
||||
rapid_Z_to(next_Stop_Z + chip_Space)
|
||||
# Update next_Stop_Z to next depth:
|
||||
next_Stop_Z -= drill_Step
|
||||
elif next_Stop_Z == drill_Z:
|
||||
break # Done
|
||||
else: # More to drill, but less than drill_Step
|
||||
# Rapid up to clear chips:
|
||||
rapid_Z_to(drill_R)
|
||||
# Rapid down to just above last peck depth:
|
||||
rapid_Z_to(next_Stop_Z + chip_Space)
|
||||
# Dril remainder of the hole depth:
|
||||
feed_Z_to(drill_Z)
|
||||
break # Done
|
||||
rapid_Z_to(RETRACT_Z) # Done, retract the drill
|
||||
|
||||
return Drill.gcode
|
||||
|
||||
|
||||
# print(__name__ + ': GCode postprocessor loaded.')
|
||||
|
||||
# PEP8 format passed using: http://pep8online.com/, which primarily covers
|
||||
# indentation and line length. Some other aspects of PEP8 which have not
|
||||
# been applied yet may be applied in future updates.
|
||||
@@ -1,96 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
"""
|
||||
This is an preprocessor to read gcode files produced from slic3r.
|
||||
"""
|
||||
|
||||
import os
|
||||
import Path
|
||||
import FreeCAD
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def open(filename):
|
||||
"called when freecad opens a file."
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
insert(filename, doc.Name)
|
||||
|
||||
|
||||
def insert(filename, docname):
|
||||
"called when freecad imports a file"
|
||||
gfile = pythonopen(filename)
|
||||
gcode = gfile.read()
|
||||
gfile.close()
|
||||
gcode = parse(gcode)
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
obj = doc.addObject("Path::Feature", "Path")
|
||||
path = Path.Path(gcode)
|
||||
obj.Path = path
|
||||
|
||||
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a parsed output string"
|
||||
print("preprocessing...")
|
||||
|
||||
# split the input by line
|
||||
lines = inputstring.split("\n")
|
||||
output = ""
|
||||
lastcommand = None
|
||||
|
||||
for l in lines:
|
||||
# remove any leftover trailing and preceding spaces
|
||||
l = l.strip()
|
||||
if not l:
|
||||
# discard empty lines
|
||||
continue
|
||||
if l[0].upper() in ["N"]:
|
||||
# remove line numbers
|
||||
l = l.split(" ", 1)[1]
|
||||
if ";" in l:
|
||||
# replace ; comments with ()
|
||||
l = l.replace(";", "(")
|
||||
l = l + ")"
|
||||
if l[0].upper() in ["G", "M", "("]:
|
||||
# found a G or M command: we store it
|
||||
output += l + "\n"
|
||||
last = l[0].upper()
|
||||
for c in l[1:]:
|
||||
if not c.isdigit():
|
||||
break
|
||||
else:
|
||||
last += c
|
||||
lastcommand = last
|
||||
elif lastcommand:
|
||||
# no G or M command: we repeat the last one
|
||||
output += lastcommand + " " + l + "\n"
|
||||
|
||||
print("done preprocessing.")
|
||||
return output
|
||||
|
||||
|
||||
print(__name__ + " gcode preprocessor loaded.")
|
||||
@@ -1,475 +0,0 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
from PathScripts import PostUtils
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import shlex
|
||||
|
||||
TOOLTIP = """
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable for a smoothieboard. This postprocessor, once placed
|
||||
in the appropriate PathScripts folder, can be used directly from inside
|
||||
FreeCAD, via the GUI importer or via python scripts with:
|
||||
|
||||
import smoothie_post
|
||||
smoothie_post.export(object,"/path/to/file.ncc","")
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog="linuxcnc", add_help=False)
|
||||
parser.add_argument("--header", action="store_true", help="output headers (default)")
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument("--comments", action="store_true", help="output comment (default)")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-line-numbers",
|
||||
action="store_true",
|
||||
help="don't prefix with line numbers (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-editor",
|
||||
action="store_true",
|
||||
help="pop up editor before writing output (default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="4", help="number of digits of precision, default=4"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"',
|
||||
)
|
||||
parser.add_argument("--IP_ADDR", help="IP Address for machine target machine")
|
||||
parser.add_argument(
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help='verbose output for debugging, default="False"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
IP_ADDR = None
|
||||
VERBOSE = False
|
||||
|
||||
SPINDLE_SPEED = 0.0
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
SHOW_EDITOR = True
|
||||
else:
|
||||
SHOW_EDITOR = False
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
|
||||
MACHINE_NAME = "SmoothieBoard"
|
||||
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
|
||||
CORNER_MAX = {"x": 500, "y": 300, "z": 300}
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G17 G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05
|
||||
G17 G90
|
||||
M2
|
||||
"""
|
||||
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
# Number of digits after the decimal point
|
||||
PRECISION = 5
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global IP_ADDR
|
||||
global VERBOSE
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.header:
|
||||
OUTPUT_HEADER = True
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.comments:
|
||||
OUTPUT_COMMENTS = True
|
||||
if args.no_line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
if args.show_editor:
|
||||
SHOW_EDITOR = True
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
|
||||
IP_ADDR = args.IP_ADDR
|
||||
VERBOSE = args.verbose
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
processArguments(argstring)
|
||||
global UNITS
|
||||
for obj in objectslist:
|
||||
if not hasattr(obj, "Path"):
|
||||
FreeCAD.Console.PrintError(
|
||||
"the object "
|
||||
+ obj.Name
|
||||
+ " is not a path. Please select only path and Compounds.\n"
|
||||
)
|
||||
return
|
||||
|
||||
FreeCAD.Console.PrintMessage("postprocessing...\n")
|
||||
gcode = ""
|
||||
|
||||
# Find the machine.
|
||||
# The user my have overridden post processor defaults in the GUI. Make
|
||||
# sure we're using the current values in the Machine Def.
|
||||
myMachine = None
|
||||
for pathobj in objectslist:
|
||||
if hasattr(pathobj, "MachineName"):
|
||||
myMachine = pathobj.MachineName
|
||||
if hasattr(pathobj, "MachineUnits"):
|
||||
if pathobj.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
else:
|
||||
UNITS = "G20"
|
||||
if myMachine is None:
|
||||
FreeCAD.Console.PrintWarning("No machine found in this selection\n")
|
||||
|
||||
# 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(True):
|
||||
gcode += linenumber() + line
|
||||
gcode += linenumber() + UNITS + "\n"
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: " + obj.Label + ")\n"
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
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
|
||||
|
||||
# do the post_amble
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "(begin postamble)\n"
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
if SHOW_EDITOR:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = gcode
|
||||
else:
|
||||
final = gcode
|
||||
|
||||
if IP_ADDR is not None:
|
||||
sendToSmoothie(IP_ADDR, final, filename)
|
||||
else:
|
||||
|
||||
if not filename == "-":
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
FreeCAD.Console.PrintMessage("done postprocessing.\n")
|
||||
return final
|
||||
|
||||
|
||||
def sendToSmoothie(ip, GCODE, fname):
|
||||
import sys
|
||||
import socket
|
||||
import os
|
||||
|
||||
fname = os.path.basename(fname)
|
||||
FreeCAD.Console.PrintMessage("sending to smoothie: {}\n".format(fname))
|
||||
|
||||
f = GCODE.rstrip()
|
||||
filesize = len(f)
|
||||
# make connection to sftp server
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(4.0)
|
||||
s.connect((ip, 115))
|
||||
tn = s.makefile(mode="rw")
|
||||
|
||||
# read startup prompt
|
||||
ln = tn.readline()
|
||||
if not ln.startswith("+"):
|
||||
FreeCAD.Console.PrintMessage("Failed to connect with sftp: {}\n".format(ln))
|
||||
sys.exit()
|
||||
|
||||
if VERBOSE:
|
||||
print("RSP: " + ln.strip())
|
||||
|
||||
# Issue initial store command
|
||||
tn.write("STOR OLD /sd/" + fname + "\n")
|
||||
tn.flush()
|
||||
|
||||
ln = tn.readline()
|
||||
if not ln.startswith("+"):
|
||||
FreeCAD.Console.PrintError("Failed to create file: {}\n".format(ln))
|
||||
sys.exit()
|
||||
|
||||
if VERBOSE:
|
||||
print("RSP: " + ln.strip())
|
||||
|
||||
# send size of file
|
||||
tn.write("SIZE " + str(filesize) + "\n")
|
||||
tn.flush()
|
||||
|
||||
ln = tn.readline()
|
||||
if not ln.startswith("+"):
|
||||
FreeCAD.Console.PrintError("Failed: {}\n".format(ln))
|
||||
sys.exit()
|
||||
|
||||
if VERBOSE:
|
||||
print("RSP: " + ln.strip())
|
||||
|
||||
cnt = 0
|
||||
# now send file
|
||||
for line in f.splitlines(1):
|
||||
tn.write(line)
|
||||
if VERBOSE:
|
||||
cnt += len(line)
|
||||
print("SND: " + line.strip())
|
||||
print(str(cnt) + "/" + str(filesize) + "\r", end="")
|
||||
|
||||
tn.flush()
|
||||
|
||||
ln = tn.readline()
|
||||
if not ln.startswith("+"):
|
||||
FreeCAD.Console.PrintError("Failed to save file: {}\n".format(ln))
|
||||
sys.exit()
|
||||
|
||||
if VERBOSE:
|
||||
print("RSP: " + ln.strip())
|
||||
|
||||
# exit
|
||||
tn.write("DONE\n")
|
||||
tn.flush()
|
||||
tn.close()
|
||||
|
||||
FreeCAD.Console.PrintMessage("Upload complete\n")
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
global SPINDLE_SPEED
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
|
||||
# params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control
|
||||
# the order of parameters
|
||||
# linuxcnc doesn't want K properties on XY plane Arcs need work.
|
||||
params = ["X", "Y", "Z", "A", "B", "I", "J", "F", "S", "T", "Q", "R", "L"]
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
outstring = []
|
||||
command = c.Name
|
||||
outstring.append(command)
|
||||
# if modal: only print the command if it is not the same as the
|
||||
# last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F":
|
||||
if c.Name not in [
|
||||
"G0",
|
||||
"G00",
|
||||
]: # linuxcnc doesn't use rapid speeds
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
elif param == "T":
|
||||
outstring.append(param + str(c.Parameters["T"]))
|
||||
elif param == "S":
|
||||
outstring.append(param + str(c.Parameters["S"]))
|
||||
SPINDLE_SPEED = c.Parameters["S"]
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
if command in ["G1", "G01", "G2", "G02", "G3", "G03"]:
|
||||
outstring.append("S" + str(SPINDLE_SPEED))
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(begin toolchange)\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -1,696 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * based upon linuxcnc_post.py (c) sliptonic (shopinthewoods@gmail.com) *
|
||||
# * *
|
||||
# * changed, but not enough to claim copyrights 2019-2021 *
|
||||
# * maintainer: A.H.M. Steenveld *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
# See: https://wiki.freecadweb.org/Path_Post
|
||||
# https://wiki.freecadweb.org/Path_Postprocessor_Customization
|
||||
# for details on post processors like this one.
|
||||
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
|
||||
# import shlex
|
||||
from PathScripts import PostUtils
|
||||
|
||||
VERSION = "0.0.4"
|
||||
|
||||
TOOLTIP = """ Post processor for UC-CNC.
|
||||
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode. This postprocessor, once placed in the appropriate
|
||||
PathScripts folder, can be used directly from inside FreeCAD,
|
||||
via the GUI importer or via python scripts with:
|
||||
|
||||
import UCCNC_post
|
||||
UCCNC_post.export(object,"/path/to/file.ncc","")
|
||||
|
||||
This postprocessor was tested on UC-CNC v1.2111, an UC100 and a Stepcraft 420.
|
||||
It was tested on FreeCAD v0.17, v0.18 and v0.19
|
||||
|
||||
Other (Stepcraft) machines using UC-CNC and UC* controllers should be easy to adapt.
|
||||
"""
|
||||
|
||||
# PREAMBLE_ possible values:
|
||||
# Multi line text with gcode. Preamble gcode
|
||||
# The preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE_DEFAULT = """G17 (Default: XY-plane)
|
||||
G54 (Default: First coordinate system)
|
||||
G40 (Default: Cutter radius compensation none)
|
||||
G49 (Default: Tool Length Offsets: cancel tool length)
|
||||
G90 (Default: Absolute distance mode selection)
|
||||
G80 (Cancel canned cycle)
|
||||
"""
|
||||
|
||||
PREAMBLE_DEFAULT_NO_COMMENT = """G17
|
||||
G54
|
||||
G40
|
||||
G49
|
||||
G90
|
||||
G80
|
||||
"""
|
||||
|
||||
|
||||
# POSTAMBLE possible values:
|
||||
# Multi line text with gcode. Postable gcode
|
||||
# The postamble text will appear following the last operation.
|
||||
POSTAMBLE_DEFAULT = """M05 (stop spindle)
|
||||
G17 (Default: XY-plane)
|
||||
G54 (Default: First coordinate system)
|
||||
G40 (Default: Cutter radius compensation none)
|
||||
G90 (Default: Absolute distance mode selection)
|
||||
G80 (Cancel canned cycle)
|
||||
M30 (Stop program and rewind code)
|
||||
"""
|
||||
|
||||
POSTAMBLE_DEFAULT_NO_COMMENT = """M05
|
||||
G17
|
||||
G54
|
||||
G40
|
||||
G90
|
||||
G80
|
||||
M30
|
||||
"""
|
||||
|
||||
# PRE_OPERATION: Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = """"""
|
||||
|
||||
# POST_OPERATION: Post operation text will be inserted after every operation
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# TOOL_CHANGE: Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
|
||||
################################
|
||||
# Other configuration settings #
|
||||
################################
|
||||
|
||||
# GCODE_PROCESSOR possible options
|
||||
# string The target GCode processor name.
|
||||
GCODE_PROCESSOR = "UC-CNC"
|
||||
|
||||
# SHOW_EDITOR possible values:
|
||||
# bool Show gcode before saving.
|
||||
# True before the file is written it is shown to the user for inspection
|
||||
# False the file is written directly
|
||||
# set with --no-show-editor
|
||||
if FreeCAD.GuiUp:
|
||||
SHOW_EDITOR = True
|
||||
else:
|
||||
SHOW_EDITOR = False
|
||||
|
||||
# PROG_NAME possible values:
|
||||
# text Name of the G-Code program
|
||||
# set with --name
|
||||
PROG_NAME = "prog1"
|
||||
|
||||
# OUTPUT_HEADER possible values:
|
||||
# bool Use of a document header
|
||||
# True Use a predefined header.
|
||||
# False Do not use a predefined header.
|
||||
# set with --no-header
|
||||
OUTPUT_HEADER = True
|
||||
|
||||
# OUTPUT_COMMENTS possible values:
|
||||
# bool (Dont) use comments in output
|
||||
# True Use comments in output.
|
||||
# False Suppress comments in output.
|
||||
# set with --no-comment
|
||||
OUTPUT_COMMENTS = True
|
||||
|
||||
# OUTPUT_LINE_NUMBERS possible values:
|
||||
# bool (Dont) use line numbers in output
|
||||
# True Add a line number to each output line.
|
||||
# False Do not add a line number.
|
||||
# set with --line-numbers
|
||||
# note: line numbers Nxxxx are not supported by UC-CNC and are silently ignored.
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
|
||||
# LINE_NUMBER_START possible values:
|
||||
# int unsigned integer. Line number starting value
|
||||
# 0..N
|
||||
LINE_NUMBER_START = 0
|
||||
|
||||
# LINE_NUMBER_STEP possible values:
|
||||
# int unsigned integer. Line number increment value
|
||||
# 1..N
|
||||
LINE_NUMBER_STEP = 1
|
||||
|
||||
# PREAMBLE possible values:
|
||||
# Multi line text with gcode. Preamble gcode
|
||||
# The preamble text will appear at the beginning of the GCODE output file.
|
||||
# set with --preamble
|
||||
PREAMBLE = PREAMBLE_DEFAULT
|
||||
|
||||
# POSTAMBLE possible values:
|
||||
# Multi line text with gcode. Postable gcode
|
||||
# The postamble text will appear following the last operation.
|
||||
# set with --postable
|
||||
POSTAMBLE = POSTAMBLE_DEFAULT
|
||||
|
||||
# MODAL possible values:
|
||||
# bool Repeat/suppress repeated command arguments.
|
||||
# True commands are suppressed if the same as previous line.
|
||||
# False commands are repeated ith the same as previout line.
|
||||
# set with --modal
|
||||
MODAL = False
|
||||
|
||||
# REPEAT_ARGUMENTS possible values:
|
||||
# bool Duplicate/suppressed axis values from the previous line.
|
||||
# True All arguments are repreated in each command.
|
||||
# False Equal values for arguments from the previous command are not repeated.
|
||||
# set with --repeat
|
||||
REPEAT_ARGUMENTS = False
|
||||
|
||||
# USE_TLO possible values:
|
||||
# bool Set tool length offset.
|
||||
# True G43 will be output following tool changes
|
||||
# False No G43 used.
|
||||
# set with --tool-length-offset
|
||||
USE_TLO = False
|
||||
|
||||
# PRECISION possible values:
|
||||
# int Number of digits in axis positions
|
||||
# 0...N
|
||||
# set with --precision N
|
||||
PRECISION = 3
|
||||
|
||||
# UNITS possible values:
|
||||
# GCODE Code to switch to specific units
|
||||
# G20 US imperial [inch]
|
||||
# G21 Metric [mm]
|
||||
# set with --inches
|
||||
# note: G20/G21 are not supported by UC-CNC, units are configured in a program profile.
|
||||
# In code G20/G21 commands are silently ignored by UC-CNC
|
||||
# UNITS is included in the post processor to mirror the profile settings.
|
||||
UNITS_US_IMP = "G20"
|
||||
UNITS_METRIC = "G21"
|
||||
UNITS = UNITS_METRIC
|
||||
|
||||
# UNIT_FORMAT possible values: (see UNITS)
|
||||
# text Text with specific units
|
||||
# "inch" US imperial [inch]
|
||||
# "mm" Metric [mm]
|
||||
# note: G20/G21 are not supported by UC-CNC, units are configured in a program profile.
|
||||
# In code G20/G21 commands are silently ignored by UC-CNC
|
||||
# UNITS is included in the post processor to mirror the profile settings.
|
||||
UNIT_FORMAT_US_IMP = "in"
|
||||
UNIT_FORMAT_METRIC = "mm"
|
||||
UNIT_FORMAT = UNIT_FORMAT_METRIC
|
||||
|
||||
# UNIT_SPEED_FORMAT possible values: (see UNITS)
|
||||
# text Text with specific units over time units
|
||||
# "inch/min" US imperial [inch]
|
||||
# "mm/min" Metric [mm]
|
||||
# note: G20/G21 are not supported by UC-CNC, units are configured in a program profile.
|
||||
# In code G20/G21 commands are silently ignored by UC-CNC
|
||||
# UNITS is included in the post processor to mirror the profile settings.
|
||||
UNIT_SPEED_FORMAT_US_IMP = "in/min"
|
||||
UNIT_SPEED_FORMAT_METRIC = "mm/min"
|
||||
UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_METRIC
|
||||
|
||||
##################################################
|
||||
# No more configuration settings after this line #
|
||||
##################################################
|
||||
|
||||
# see: https://docs.python.org/3/library/argparse.html
|
||||
parser = argparse.ArgumentParser(prog=__name__, add_help=False)
|
||||
parser.add_argument("--name", help="GCode program name")
|
||||
parser.add_argument("--no-header", action="store_true", help="suppress header output")
|
||||
parser.add_argument(
|
||||
"--no-comments", action="store_true", help="suppress comment output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-numbers", action="store_true", help="suppress prefix with line numbers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-show-editor",
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--precision", default="3", help="number of digits of precision, default=3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17\nG90\nG54"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\nM30"',
|
||||
)
|
||||
parser.add_argument("--inches", action="store_true", help="lengths in [in], G20")
|
||||
parser.add_argument("--metric", action="store_true", help="lengths in [mm], G21")
|
||||
parser.add_argument(
|
||||
"--modal", action="store_true", help="repeat/suppress repeated command arguments"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool-length-offset",
|
||||
action="store_true",
|
||||
help="suppress tool length offset G43 following tool changes",
|
||||
)
|
||||
parser.add_argument("--repeat", action="store_true", help="repeat axis arguments")
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ["__builtin__", "io"]:
|
||||
pythonopen = open
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ == "__builtin__":
|
||||
pythonopen = open
|
||||
|
||||
# debug option, trace to screen while processing to see where things break up.
|
||||
trace_gcode = False
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
LINENR = 0
|
||||
COMMAND_SPACE = " "
|
||||
UNIT_DEFAULT_CHANGED = False
|
||||
|
||||
# counting warnings and problems.
|
||||
# Each warning/problem will appear as a WARNING:/PROBLEM: comment in the GCode output.
|
||||
warnings_count = 0
|
||||
problems_count = 0
|
||||
|
||||
HEADER = """(Exported by FreeCAD for {})
|
||||
(Post Processor: {}, version {})
|
||||
(CAM file: {})
|
||||
(Output Time: {})
|
||||
"""
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
global SHOW_EDITOR # Show gcode before saving.
|
||||
global PROG_NAME # Name of the G-Code program
|
||||
global OUTPUT_HEADER # Use of a document header
|
||||
global OUTPUT_COMMENTS # (Dont) use comments in output
|
||||
global OUTPUT_LINE_NUMBERS # (Dont) use line numbers in output
|
||||
global PREAMBLE # Preamble gcode
|
||||
global POSTAMBLE # Postable gcode
|
||||
global MODAL # Repeat/suppress repeated command arguments.
|
||||
global USE_TLO # Set tool length offset
|
||||
global PRECISION # Number of digits in feed and axis values
|
||||
global UNITS # Code to switch to specific units
|
||||
global UNIT_FORMAT # Text with specific units
|
||||
global UNIT_SPEED_FORMAT # Text with specific units over time units
|
||||
global UNIT_DEFAULT_CHANGED # tracing changes in UNIT settings.
|
||||
global REPEAT_ARGUMENTS # Repeat or suppress axis values if the same as previous line.
|
||||
|
||||
try:
|
||||
UNIT_DEFAULT_CHANGED = False
|
||||
args = parser.parse_args(argstring.split())
|
||||
|
||||
if args.name is not None:
|
||||
PROG_NAME = args.name
|
||||
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
|
||||
PRECISION = args.precision
|
||||
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
elif OUTPUT_COMMENTS:
|
||||
PREAMBLE = PREAMBLE_DEFAULT
|
||||
else:
|
||||
PREAMBLE = PREAMBLE_DEFAULT_NO_COMMENT
|
||||
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
elif OUTPUT_COMMENTS:
|
||||
POSTAMBLE = POSTAMBLE_DEFAULT
|
||||
else:
|
||||
POSTAMBLE = POSTAMBLE_DEFAULT_NO_COMMENT
|
||||
|
||||
if args.inches and (UNITS != UNITS_US_IMP):
|
||||
print("Units: US Imperial [inch], check your UC-CNC profile.")
|
||||
UNITS = UNITS_US_IMP
|
||||
UNIT_FORMAT = UNIT_FORMAT_US_IMP
|
||||
UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_US_IMP
|
||||
UNIT_DEFAULT_CHANGED = True
|
||||
|
||||
if args.metric and (UNITS != UNITS_METRIC):
|
||||
print("Units: Metric [mm], check your UC-CNC profile.")
|
||||
UNITS = UNITS_METRIC
|
||||
UNIT_FORMAT = UNIT_FORMAT_METRIC
|
||||
UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_METRIC
|
||||
UNIT_DEFAULT_CHANGED = True
|
||||
|
||||
if args.modal:
|
||||
MODAL = True
|
||||
|
||||
if args.tool_length_offset:
|
||||
USE_TLO = True
|
||||
|
||||
if args.repeat:
|
||||
REPEAT_ARGUMENTS = True
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def append0(line):
|
||||
result = line
|
||||
if trace_gcode:
|
||||
print("export: >>" + result)
|
||||
return result
|
||||
|
||||
|
||||
def append(line):
|
||||
result = linenumber() + line
|
||||
if trace_gcode:
|
||||
print("export: >>" + result)
|
||||
return result
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
|
||||
if not processArguments(argstring):
|
||||
print("export: process arguments failed, '{}'".format(argstring))
|
||||
return None
|
||||
|
||||
global warnings_count
|
||||
global problems_count
|
||||
|
||||
warnings_count = 0
|
||||
problems_count = 0
|
||||
|
||||
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("export: postprocessing...")
|
||||
gcode = append0("%" + PROG_NAME + "\n")
|
||||
if not argstring:
|
||||
gcode += append("(" + __name__ + " with default settings)\n")
|
||||
else:
|
||||
gcode += append("({} {})\n".format(__name__, argstring))
|
||||
|
||||
# write header
|
||||
if OUTPUT_HEADER:
|
||||
for line in HEADER.format(
|
||||
GCODE_PROCESSOR,
|
||||
__name__,
|
||||
VERSION,
|
||||
FreeCAD.ActiveDocument.FileName,
|
||||
str(now),
|
||||
).splitlines(False):
|
||||
if line:
|
||||
gcode += append(line + "\n")
|
||||
|
||||
# Write the preamble
|
||||
# G20/G21 not supported by UC-CNC, *always* report the configured units.
|
||||
gcode += append("(Units: '" + UNIT_FORMAT + "' and '" + UNIT_SPEED_FORMAT + "')\n")
|
||||
if UNIT_DEFAULT_CHANGED:
|
||||
gcode += append("(WARNING: Units default changed, check your UC-CNC profile)\n")
|
||||
warnings_count += 1
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(preamble: begin)\n")
|
||||
# for obj in objectslist:
|
||||
# if isinstance(obj.Proxy, PathScripts.PathToolController.ToolController):
|
||||
# gcode += append("(T{}={})\n".format(obj.ToolNumber, item.Name))
|
||||
# error: global name 'PathScripts' is not defined
|
||||
for line in PREAMBLE.splitlines(False):
|
||||
gcode += append(line + "\n")
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(preamble: done)\n")
|
||||
|
||||
# write the code body
|
||||
for obj in objectslist:
|
||||
|
||||
# pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(operation initialise: %s)\n" % obj.Label)
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += append(line)
|
||||
|
||||
# turn coolant on if required
|
||||
if hasattr(obj, "CoolantMode"):
|
||||
coolantMode = obj.CoolantMode
|
||||
if coolantMode == "Mist":
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("M7 (coolant: mist on)\n")
|
||||
else:
|
||||
gcode += append("M7\n")
|
||||
if coolantMode == "Flood":
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("M8 (coolant: flood on)\n")
|
||||
else:
|
||||
gcode += append("M8\n")
|
||||
|
||||
# process the operation gcode
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(operation start: %s)\n" % obj.Label)
|
||||
gcode += parse(obj)
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(operation done: %s)\n" % obj.Label)
|
||||
|
||||
# post_op
|
||||
for line in POST_OPERATION.splitlines(True):
|
||||
gcode += append(line)
|
||||
|
||||
# turn coolant off if required
|
||||
if hasattr(obj, "CoolantMode"):
|
||||
coolantMode = obj.CoolantMode
|
||||
if not coolantMode == "None":
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("M9 (coolant: off)\n")
|
||||
else:
|
||||
gcode += append("M9\n")
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(operation finalised: %s)\n" % obj.Label)
|
||||
|
||||
# do the post_amble
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(postamble: begin)\n")
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += append(line)
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += append("(postamble: done)\n")
|
||||
|
||||
# Show the results
|
||||
if SHOW_EDITOR:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = gcode
|
||||
else:
|
||||
final = gcode
|
||||
|
||||
if (0 < problems_count) or (0 < warnings_count):
|
||||
print(
|
||||
"export: postprocessing: done, warnings: {}, problems: {}, see GCode for details.".format(
|
||||
warnings_count, problems_count
|
||||
)
|
||||
)
|
||||
else:
|
||||
print("export: postprocessing: done (none of the problems detected).")
|
||||
|
||||
if not filename == "-":
|
||||
print("export: writing to '{}'".format(filename))
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
|
||||
if LINENR <= 0:
|
||||
LINENR = LINE_NUMBER_START
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
line = LINENR
|
||||
LINENR += LINE_NUMBER_STEP
|
||||
return "N{:03d} ".format(line)
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = "." + str(PRECISION) + "f"
|
||||
currLocation = {} # keep track for no doubles
|
||||
|
||||
# The params list control the order of parameters
|
||||
params = [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"R",
|
||||
"F",
|
||||
"S",
|
||||
"T",
|
||||
"H",
|
||||
"L",
|
||||
"Q",
|
||||
]
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"):
|
||||
# We have a compound or project.
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else:
|
||||
# parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
commandlist = [] # list of elements in the command, code and params.
|
||||
command = c.Name.strip() # command M or G code or comment string
|
||||
commandlist.append(command)
|
||||
|
||||
# if modal: only print the command if it is not the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
commandlist.pop(0)
|
||||
|
||||
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == "F" and (
|
||||
currLocation[param] != c.Parameters[param] or REPEAT_ARGUMENTS
|
||||
):
|
||||
if c.Name not in ["G0", "G00"]: # No F in G0
|
||||
speed = Units.Quantity(
|
||||
c.Parameters["F"], FreeCAD.Units.Velocity
|
||||
)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
commandlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
|
||||
precision_string,
|
||||
)
|
||||
)
|
||||
else:
|
||||
continue
|
||||
elif param == "T":
|
||||
commandlist.append(param + str(int(c.Parameters["T"])))
|
||||
elif param == "H":
|
||||
commandlist.append(param + str(int(c.Parameters["H"])))
|
||||
elif param == "D":
|
||||
commandlist.append(param + str(int(c.Parameters["D"])))
|
||||
elif param == "S":
|
||||
commandlist.append(param + str(int(c.Parameters["S"])))
|
||||
else:
|
||||
if (
|
||||
(not REPEAT_ARGUMENTS)
|
||||
and (param in currLocation)
|
||||
and (currLocation[param] == c.Parameters[param])
|
||||
):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(
|
||||
c.Parameters[param], FreeCAD.Units.Length
|
||||
)
|
||||
commandlist.append(
|
||||
param
|
||||
+ format(
|
||||
float(pos.getValueAs(UNIT_FORMAT)), precision_string
|
||||
)
|
||||
)
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# add height offset
|
||||
if USE_TLO:
|
||||
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
|
||||
commandlist.append(tool_height)
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
commandlist.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(commandlist) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
commandlist.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in commandlist:
|
||||
out += w.strip() + COMMAND_SPACE
|
||||
if trace_gcode:
|
||||
print("parse : >>{}".format(out))
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
Reference in New Issue
Block a user