Merge pull request #3600 from sliptonic/feature/customimprove
[PATH] make -custom- op compliant. Improve gcode_pre importer
This commit is contained in:
@@ -34,6 +34,7 @@ SET(PathScripts_SRCS
|
||||
PathScripts/PathComment.py
|
||||
PathScripts/PathCopy.py
|
||||
PathScripts/PathCustom.py
|
||||
PathScripts/PathCustomGui.py
|
||||
PathScripts/PathDeburr.py
|
||||
PathScripts/PathDeburrGui.py
|
||||
PathScripts/PathDressup.py
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
<file>panels/PageBaseLocationEdit.ui</file>
|
||||
<file>panels/PageDepthsEdit.ui</file>
|
||||
<file>panels/PageHeightsEdit.ui</file>
|
||||
<file>panels/PageOpCustomEdit.ui</file>
|
||||
<file>panels/PageOpDeburrEdit.ui</file>
|
||||
<file>panels/PageOpDrillingEdit.ui</file>
|
||||
<file>panels/PageOpEngraveEdit.ui</file>
|
||||
|
||||
93
src/Mod/Path/Gui/Resources/panels/PageOpCustomEdit.ui
Normal file
93
src/Mod/Path/Gui/Resources/panels/PageOpCustomEdit.ui
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>424</width>
|
||||
<height>376</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_2">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="toolController">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>ToolController</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Coolant Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="coolantController"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>G Gode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="txtGCode">
|
||||
<property name="lineWrapMode">
|
||||
<enum>QTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
<property name="acceptRichText">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -22,84 +22,58 @@
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
from PySide import QtCore
|
||||
from copy import copy
|
||||
|
||||
__title__ = "Path Custom Operation"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Path Custom object and FreeCAD command"
|
||||
|
||||
__doc__ = """Path Custom object and FreeCAD command"""
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
class ObjectCustom:
|
||||
def __init__(self, obj):
|
||||
class ObjectCustom(PathOp.ObjectOp):
|
||||
def opFeatures(self, obj):
|
||||
return PathOp.FeatureTool | PathOp.FeatureCoolant
|
||||
|
||||
def initOperation(self, obj):
|
||||
obj.addProperty("App::PropertyStringList", "Gcode", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted"))
|
||||
obj.addProperty("App::PropertyLink", "ToolController", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path"))
|
||||
obj.addProperty("App::PropertyPlacement", "Offset", "Path",
|
||||
"Placement Offset")
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
def execute(self, obj):
|
||||
newpath = Path.Path()
|
||||
def opExecute(self, obj):
|
||||
self.commandlist.append(Path.Command("(Begin Custom)"))
|
||||
if obj.Gcode:
|
||||
for l in obj.Gcode:
|
||||
newcommand = Path.Command(str(l))
|
||||
if newcommand.Name in movecommands:
|
||||
if 'X' in newcommand.Parameters:
|
||||
newcommand.x += obj.Offset.Base.x
|
||||
if 'Y' in newcommand.Parameters:
|
||||
newcommand.y += obj.Offset.Base.y
|
||||
if 'Z' in newcommand.Parameters:
|
||||
newcommand.z += obj.Offset.Base.z
|
||||
self.commandlist.append(newcommand)
|
||||
|
||||
newpath.insertCommand(newcommand)
|
||||
|
||||
obj.Path=newpath
|
||||
self.commandlist.append(Path.Command("(End Custom)"))
|
||||
|
||||
|
||||
|
||||
class CommandPathCustom:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path-Custom',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Creates a path object based on custom G-code")}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Custom Path")
|
||||
FreeCADGui.addModule("PathScripts.PathCustom")
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom")')
|
||||
FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy = 0')
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)')
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
def SetupProperties():
|
||||
setup = []
|
||||
return setup
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Custom', CommandPathCustom())
|
||||
def Create(name, obj=None):
|
||||
'''Create(name) ... Creates and returns a Custom operation.'''
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
proxy = ObjectCustom(obj, name)
|
||||
return obj
|
||||
|
||||
86
src/Mod/Path/PathScripts/PathCustomGui.py
Normal file
86
src/Mod/Path/PathScripts/PathCustomGui.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@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 FreeCADGui
|
||||
import PathScripts.PathCustom as PathCustom
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__title__ = "Path Custom Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Custom operation page controller and command implementation."
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
# class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
# '''Page controller for the base geometry.'''
|
||||
|
||||
# def getForm(self):
|
||||
# return None
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Custom operation.'''
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpCustomEdit.ui")
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's properties'''
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.form.txtGCode.setText("\n".join(obj.Gcode))
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
|
||||
signals = []
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
signals.append(self.form.coolantController.currentIndexChanged)
|
||||
self.form.txtGCode.textChanged.connect(self.setGCode)
|
||||
return signals
|
||||
|
||||
def setGCode(self):
|
||||
self.obj.Gcode = self.form.txtGCode.toPlainText().splitlines()
|
||||
|
||||
|
||||
Command = PathOpGui.SetupOperation('Custom', PathCustom.Create, TaskPanelOpPage,
|
||||
'Path-Custom',
|
||||
QtCore.QT_TRANSLATE_NOOP("Custom", "Custom"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Custom", "Create custom gcode snippet"),
|
||||
PathCustom.SetupProperties)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathCustomGui... done\n")
|
||||
@@ -43,7 +43,8 @@ def Startup():
|
||||
from PathScripts import PathAdaptiveGui
|
||||
from PathScripts import PathArray
|
||||
from PathScripts import PathComment
|
||||
from PathScripts import PathCustom
|
||||
# from PathScripts import PathCustom
|
||||
from PathScripts import PathCustomGui
|
||||
from PathScripts import PathDeburrGui
|
||||
from PathScripts import PathDressupAxisMap
|
||||
from PathScripts import PathDressupDogbone
|
||||
|
||||
@@ -65,7 +65,7 @@ FeatureBasePanels = 0x0800 # Base
|
||||
FeatureLocations = 0x1000 # Locations
|
||||
FeatureCoolant = 0x2000 # Coolant
|
||||
|
||||
FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels | FeatureCoolant
|
||||
FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels
|
||||
|
||||
|
||||
class ObjectOp(object):
|
||||
@@ -245,7 +245,7 @@ class ObjectOp(object):
|
||||
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation.
|
||||
The default implementation returns "FeatureTool | FeatureDeptsh | FeatureHeights | FeatureStartPoint"
|
||||
The default implementation returns "FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint"
|
||||
Should be overwritten by subclasses.'''
|
||||
# pylint: disable=unused-argument
|
||||
return FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint | FeatureBaseGeometry | FeatureFinishDepth | FeatureCoolant
|
||||
|
||||
@@ -91,6 +91,11 @@ class ViewProvider(object):
|
||||
PathLog.track()
|
||||
return hasattr(self, 'deleteOnReject') and self.deleteOnReject
|
||||
|
||||
def setDeleteObjectsOnReject(self, state=False):
|
||||
PathLog.track()
|
||||
self.deleteOnReject = state
|
||||
return self.deleteOnReject
|
||||
|
||||
def setEdit(self, vobj=None, mode=0):
|
||||
'''setEdit(vobj, mode=0) ... initiate editing of receivers model.'''
|
||||
PathLog.track()
|
||||
@@ -198,7 +203,7 @@ class TaskPanelPage(object):
|
||||
Do not overwrite, implement initPage(obj) instead.'''
|
||||
self.obj = obj
|
||||
self.job = PathUtils.findParentJob(obj)
|
||||
self.form = self.getForm() # pylint: disable=assignment-from-no-return
|
||||
self.form = self.getForm() # pylint: disable=assignment-from-no-return
|
||||
self.signalDirtyChanged = None
|
||||
self.setClean()
|
||||
self.setTitle('-')
|
||||
@@ -285,32 +290,32 @@ class TaskPanelPage(object):
|
||||
Note that this function is invoked after all page controllers have been created.
|
||||
Should be overwritten by subclasses.'''
|
||||
# pylint: disable=unused-argument
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def cleanupPage(self, obj):
|
||||
'''cleanupPage(obj) ... overwrite to perform any cleanup tasks before page is destroyed.
|
||||
Can safely be overwritten by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def modifyStandardButtons(self, buttonBox):
|
||||
'''modifyStandardButtons(buttonBox) ... overwrite if the task panel standard buttons need to be modified.
|
||||
Can safely be overwritten by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... return UI form for this page.
|
||||
Must be overwritten by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... overwrite to transfer values from UI to obj's properties.
|
||||
Can safely be overwritten by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... overwrite to transfer obj's property values to UI.
|
||||
Can safely be overwritten by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return signals which, when triggered, cause the receiver to update the model.
|
||||
@@ -326,7 +331,7 @@ class TaskPanelPage(object):
|
||||
manually.
|
||||
Can safely be overwritten by subclasses.'''
|
||||
# pylint: disable=unused-argument
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
'''updateData(obj, prop) ... overwrite if the receiver needs to react to property changes that might not have been caused by the receiver itself.
|
||||
@@ -339,13 +344,13 @@ class TaskPanelPage(object):
|
||||
In such a scenario the first property assignment will cause all changes in the UI of the other fields to be overwritten by setFields(obj).
|
||||
You have been warned.'''
|
||||
# pylint: disable=unused-argument
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def updateSelection(self, obj, sel):
|
||||
'''updateSelection(obj, sel) ... overwrite to customize UI depending on current selection.
|
||||
Can safely be overwritten by subclasses.'''
|
||||
# pylint: disable=unused-argument
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
# helpers
|
||||
def selectInComboBox(self, name, combo):
|
||||
@@ -432,7 +437,6 @@ class TaskPanelBaseGeometryPage(TaskPanelPage):
|
||||
|
||||
if len(availableOps) > 0:
|
||||
# Populate the operations list
|
||||
addInputs = True
|
||||
panel.geometryImportList.blockSignals(True)
|
||||
panel.geometryImportList.clear()
|
||||
availableOps.sort()
|
||||
@@ -738,7 +742,7 @@ class TaskPanelHeightsPage(TaskPanelPage):
|
||||
self.safeHeight.updateProperty()
|
||||
self.clearanceHeight.updateProperty()
|
||||
|
||||
def setFields(self, obj):
|
||||
def setFields(self, obj):
|
||||
self.safeHeight.updateSpinBox()
|
||||
self.clearanceHeight.updateSpinBox()
|
||||
|
||||
@@ -933,7 +937,7 @@ class TaskPanel(object):
|
||||
else:
|
||||
self.featurePages.append(TaskPanelBaseLocationPage(obj, features))
|
||||
|
||||
if PathOp.FeatureDepths & features or PathOp.FeatureStepDown:
|
||||
if PathOp.FeatureDepths & features or PathOp.FeatureStepDown & features:
|
||||
if hasattr(opPage, 'taskPanelDepthsPage'):
|
||||
self.featurePages.append(opPage.taskPanelDepthsPage(obj, features))
|
||||
else:
|
||||
@@ -1186,9 +1190,9 @@ class CommandPathOp:
|
||||
self.res = resources
|
||||
|
||||
def GetResources(self):
|
||||
ress = {'Pixmap': self.res.pixmap,
|
||||
ress = {'Pixmap': self.res.pixmap,
|
||||
'MenuText': self.res.menuText,
|
||||
'ToolTip': self.res.toolTip}
|
||||
'ToolTip': self.res.toolTip}
|
||||
if self.res.accelKey:
|
||||
ress['Accel'] = self.res.accelKey
|
||||
return ress
|
||||
@@ -1236,7 +1240,7 @@ def SetupOperation(name,
|
||||
command = CommandPathOp(res)
|
||||
FreeCADGui.addCommand("Path_%s" % name.replace(' ', '_'), command)
|
||||
|
||||
if not setupProperties is None:
|
||||
if setupProperties is not None:
|
||||
PathSetupSheet.RegisterOperation(name, objFactory, setupProperties)
|
||||
|
||||
return command
|
||||
|
||||
@@ -290,6 +290,10 @@ def probeselect():
|
||||
FreeCADGui.Selection.addSelectionGate(PROBEGate())
|
||||
FreeCAD.Console.PrintWarning("Probe Select Mode\n")
|
||||
|
||||
def customselect():
|
||||
FreeCAD.Console.PrintWarning("Custom Select Mode\n")
|
||||
|
||||
|
||||
|
||||
def select(op):
|
||||
opsel = {}
|
||||
@@ -309,6 +313,7 @@ def select(op):
|
||||
opsel['Waterline'] = surfaceselect
|
||||
opsel['Adaptive'] = adaptiveselect
|
||||
opsel['Probe'] = probeselect
|
||||
opsel['Custom'] = customselect
|
||||
return opsel[op]
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
'''
|
||||
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.
|
||||
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.
|
||||
@@ -76,8 +77,8 @@ def parse(inputstring):
|
||||
PathLog.track(inputstring)
|
||||
# split the input by line
|
||||
lines = inputstring.split("\n")
|
||||
output = [] #""
|
||||
lastcommand = None
|
||||
output = []
|
||||
lastcommand = None
|
||||
|
||||
for lin in lines:
|
||||
# remove any leftover trailing and preceding spaces
|
||||
@@ -98,8 +99,7 @@ def parse(inputstring):
|
||||
continue
|
||||
if lin[0].upper() in ["G", "M"]:
|
||||
# found a G or M command: we store it
|
||||
#output += lin + "\n"
|
||||
output.append(Path.Command(str(lin))) # + "\n"
|
||||
output.append(Path.Command(str(lin)))
|
||||
last = lin[0].upper()
|
||||
for c in lin[1:]:
|
||||
if not c.isdigit():
|
||||
@@ -109,7 +109,7 @@ def parse(inputstring):
|
||||
lastcommand = last
|
||||
elif lastcommand:
|
||||
# no G or M command: we repeat the last one
|
||||
output.append(Path.Command(str(lastcommand + " " + lin))) # + "\n"
|
||||
output.append(Path.Command(str(lastcommand + " " + lin)))
|
||||
|
||||
print("done preprocessing.")
|
||||
return output
|
||||
|
||||
@@ -27,16 +27,29 @@ 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 os
|
||||
import Path
|
||||
import FreeCAD
|
||||
import PathScripts.PathUtils
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import PathScripts.PathLog as PathLog
|
||||
import re
|
||||
import PathScripts.PathCustom as PathCustom
|
||||
import PathScripts.PathCustomGui as PathCustomGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
# LEVEL = PathLog.Level.DEBUG
|
||||
LEVEL = PathLog.Level.INFO
|
||||
@@ -59,71 +72,109 @@ def open(filename):
|
||||
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 insert(filename, docname):
|
||||
"called when freecad imports a file"
|
||||
PathLog.track(filename)
|
||||
gfile = pythonopen(filename)
|
||||
gcode = gfile.read()
|
||||
gfile.close()
|
||||
# split on tool changes
|
||||
paths = re.split('(?=[mM]+\s?0?6)', gcode)
|
||||
# if there are any tool changes combine the preamble with the default tool
|
||||
if len(paths) > 1:
|
||||
paths = ["\n".join(paths[0:2])] + paths[2:]
|
||||
|
||||
# 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)
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom")
|
||||
PathScripts.PathCustom.ObjectCustom(obj)
|
||||
obj.ViewObject.Proxy = 0
|
||||
if len(gcode) == 0:
|
||||
continue
|
||||
|
||||
# Create a custom and viewobject
|
||||
obj = PathCustom.Create("Custom")
|
||||
res = PathOpGui.CommandResources('Custom', PathCustom.Create, PathCustomGui.TaskPanelOpPage, 'Path-Custom', 'Path-Custom', '', '')
|
||||
obj.ViewObject.Proxy = PathOpGui.ViewProvider(obj.ViewObject, res)
|
||||
obj.ViewObject.Proxy.setDeleteObjectsOnReject(False)
|
||||
|
||||
# Set the gcode and try to match a tool controller
|
||||
obj.Gcode = gcode
|
||||
PathScripts.PathUtils.addToJob(obj)
|
||||
obj.ToolController = PathScripts.PathUtils.findToolController(obj)
|
||||
obj.ToolController = matchToolController(obj, toolnumber)
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
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"]
|
||||
|
||||
print("preprocessing...")
|
||||
PathLog.track(inputstring)
|
||||
# split the input by line
|
||||
lines = inputstring.split("\n")
|
||||
output = [] #""
|
||||
lastcommand = None
|
||||
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:
|
||||
# discard empty lines
|
||||
continue
|
||||
|
||||
# remove line numbers
|
||||
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
|
||||
# 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 lin[0].upper() in ["G", "M"]:
|
||||
# found a G or M command: we store it
|
||||
#output += lin + "\n"
|
||||
output.append(lin) # + "\n"
|
||||
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(lastcommand + " " + lin) # + "\n"
|
||||
|
||||
# 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)
|
||||
|
||||
print("done preprocessing.")
|
||||
return output
|
||||
|
||||
|
||||
print(__name__ + " gcode preprocessor loaded.")
|
||||
|
||||
Reference in New Issue
Block a user