Moved post processing files into new Path python module

This commit is contained in:
Markus Lampert
2022-08-10 19:03:14 -07:00
parent 8c05a46174
commit 1d27fb00ec
56 changed files with 127 additions and 116 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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())

View File

@@ -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)

View File

@@ -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
#

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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_()

View File

@@ -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.")

View File

@@ -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

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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.

View File

@@ -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

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.")

View File

@@ -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.

View File

@@ -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.")

View File

@@ -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.")

View File

@@ -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