Merge pull request #6662 from Russ4262/fix/import_gcode

Path:  Fixes #6629 - Add missing Job for import G-code [Bug]
This commit is contained in:
sliptonic
2022-03-30 11:16:00 -05:00
committed by GitHub
4 changed files with 281 additions and 50 deletions
+5 -2
View File
@@ -1631,7 +1631,7 @@ class TaskPanel:
self.updateSelection()
def Create(base, template=None):
def Create(base, template=None, openTaskPanel=True):
"""Create(base, template) ... creates a job instance for the given base object
using template to configure it."""
FreeCADGui.addModule("PathScripts.PathJob")
@@ -1642,7 +1642,10 @@ def Create(base, template=None):
obj.ViewObject.addExtension("Gui::ViewProviderGroupExtensionPython")
FreeCAD.ActiveDocument.commitTransaction()
obj.Document.recompute()
obj.ViewObject.Proxy.editObject(obj.Stock)
if openTaskPanel:
obj.ViewObject.Proxy.editObject(obj.Stock)
else:
obj.ViewObject.Proxy.deleteOnReject = False
return obj
except Exception as exc:
PathLog.error(exc)
+102 -48
View File
@@ -47,11 +47,17 @@ import FreeCAD
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
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:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
@@ -60,6 +66,20 @@ else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.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
@@ -82,54 +102,20 @@ def matchToolController(op, toolnumber):
return toolcontrollers[0]
def insert(filename, docname):
"""called when freecad imports a file"""
PathLog.track(filename)
gfile = pythonopen(filename)
gcode = gfile.read()
gfile.close()
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."""
# Regular expression to match tool changes in the format 'M6 Tn'
p = re.compile("[mM]+?\s?0?6\s?T\d*\s")
# Verify active document exists
if FreeCAD.ActiveDocument is None:
raise PathNoActiveDocumentException()
# split the gcode on tool changes
paths = re.split("([mM]+?\s?0?6\s?T\d*\s)", gcode)
# Verify a Job object is available, and add one if not
if not PathUtils.GetJobs():
raise PathNoJobException()
# 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:
continue
# Create a custom and viewobject
obj = PathCustom.Create("Custom")
res = PathOpGui.CommandResources(
"Custom",
PathCustom.Create,
PathCustomGui.TaskPanelOpPage,
"Path_Custom",
QT_TRANSLATE_NOOP("Path_Custom", "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
obj.ToolController = matchToolController(obj, toolnumber)
FreeCAD.ActiveDocument.recompute()
return True
def parse(inputstring):
@@ -195,4 +181,72 @@ def parse(inputstring):
return output
def _identifygcodeByToolNumberList(filename):
"""called when freecad imports a file"""
PathLog.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"""
PathLog.track(filename)
try:
if not _isImportEnvironmentReady():
return
except PathNoActiveDocumentException:
PathLog.error(translate("Path_Gcode_pre", "No active document"))
return
except PathNoJobException:
PathLog.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.")
+172
View File
@@ -152,3 +152,175 @@ class TestPathPostUtils(unittest.TestCase):
self.assertTrue(
len([c for c in results.Commands if c.Name in ["G2", "G3"]]) == 0
)
class TestPathPostImport(unittest.TestCase):
def test001(self):
"""test001()... Verify 'No active document' exception thrown if no document open."""
from PathScripts.post import gcode_pre as gcode_pre
self.assertRaises(
gcode_pre.PathNoActiveDocumentException,
gcode_pre._isImportEnvironmentReady,
)
def test002(self):
"""test002()... Verify 'No job object' exception thrown if Job object available."""
from PathScripts.post import gcode_pre as gcode_pre
doc = FreeCAD.newDocument("TestPathPost")
self.assertRaises(
gcode_pre.PathNoJobException,
gcode_pre._isImportEnvironmentReady,
)
FreeCAD.closeDocument(doc.Name)
def test003(self, close=True):
"""test003()... Verify 'No job object' exception thrown if Job object available."""
from PathScripts.post import gcode_pre as gcode_pre
doc = FreeCAD.newDocument("TestPathPost")
# Add temporary receiving Job object
box = FreeCAD.ActiveDocument.addObject("Part::Box", "Box")
box.Label = "Temporary Box"
# Add Job object with view provider support when possible
if FreeCAD.GuiUp:
import PathScripts.PathJobGui as PathJobGui
box.ViewObject.Visibility = False
job = PathJobGui.Create([box], openTaskPanel=False)
else:
import PathScripts.PathJob as PathJob
job = PathJob.Create("Job", [box])
importFile = FreeCAD.getHomePath() + "Mod/Path/PathTests/test_centroid_00.ngc"
gcode_pre.insert(importFile, "test_centroid_00")
# self.assertTrue(doc.Name == "test_centroid_00")
opList = doc.Job.Operations.Group
self.assertTrue(
len(opList) == 2,
"Expected 2 Custom operations to be created from source g-code file, test_centroid_00.ngc",
)
self.assertTrue(
opList[0].Name == "Custom", "Expected first operation to be Custom"
)
self.assertTrue(
opList[1].Name == "Custom001", "Expected second operation to be Custom001"
)
if close:
FreeCAD.closeDocument(doc.Name)
def test004(self):
"""test004()... Verify g-code imported with g-code pre-processor"""
self.test003(close=False)
doc = FreeCAD.ActiveDocument
op1 = doc.Job.Operations.Group[0]
op2 = doc.Job.Operations.Group[1]
# Verify g-code sizes
self.assertTrue(
op1.Path.Size == 4, "Expected Custom g-code command count to be 4."
)
self.assertTrue(
op2.Path.Size == 60, "Expected Custom g-code command count to be 60."
)
# Verify g-code commands
op1_code = (
"(Custom_test_centroid_00)\n(Begin Custom)\nG90 G49.000000\n(End Custom)\n"
)
op2_code = "(Custom001_test_centroid_00)\n(Begin Custom)\nG0 Z15.000000\nG90\nG0 Z15.000000\nG0 X10.000000 Y10.000000\nG0 Z10.000000\nG1 X10.000000 Y10.000000 Z9.000000\nG1 X10.000000 Y0.000000 Z9.000000\nG1 X0.000000 Y0.000000 Z9.000000\nG1 X0.000000 Y10.000000 Z9.000000\nG1 X10.000000 Y10.000000 Z9.000000\nG1 X10.000000 Y10.000000 Z8.000000\nG1 X10.000000 Y0.000000 Z8.000000\nG1 X0.000000 Y0.000000 Z8.000000\nG1 X0.000000 Y10.000000 Z8.000000\nG1 X10.000000 Y10.000000 Z8.000000\nG1 X10.000000 Y10.000000 Z7.000000\nG1 X10.000000 Y0.000000 Z7.000000\nG1 X0.000000 Y0.000000 Z7.000000\nG1 X0.000000 Y10.000000 Z7.000000\nG1 X10.000000 Y10.000000 Z7.000000\nG1 X10.000000 Y10.000000 Z6.000000\nG1 X10.000000 Y0.000000 Z6.000000\nG1 X0.000000 Y0.000000 Z6.000000\nG1 X0.000000 Y10.000000 Z6.000000\nG1 X10.000000 Y10.000000 Z6.000000\nG1 X10.000000 Y10.000000 Z5.000000\nG1 X10.000000 Y0.000000 Z5.000000\nG1 X0.000000 Y0.000000 Z5.000000\nG1 X0.000000 Y10.000000 Z5.000000\nG1 X10.000000 Y10.000000 Z5.000000\nG1 X10.000000 Y10.000000 Z4.000000\nG1 X10.000000 Y0.000000 Z4.000000\nG1 X0.000000 Y0.000000 Z4.000000\nG1 X0.000000 Y10.000000 Z4.000000\nG1 X10.000000 Y10.000000 Z4.000000\nG1 X10.000000 Y10.000000 Z3.000000\nG1 X10.000000 Y0.000000 Z3.000000\nG1 X0.000000 Y0.000000 Z3.000000\nG1 X0.000000 Y10.000000 Z3.000000\nG1 X10.000000 Y10.000000 Z3.000000\nG1 X10.000000 Y10.000000 Z2.000000\nG1 X10.000000 Y0.000000 Z2.000000\nG1 X0.000000 Y0.000000 Z2.000000\nG1 X0.000000 Y10.000000 Z2.000000\nG1 X10.000000 Y10.000000 Z2.000000\nG1 X10.000000 Y10.000000 Z1.000000\nG1 X10.000000 Y0.000000 Z1.000000\nG1 X0.000000 Y0.000000 Z1.000000\nG1 X0.000000 Y10.000000 Z1.000000\nG1 X10.000000 Y10.000000 Z1.000000\nG1 X10.000000 Y10.000000 Z0.000000\nG1 X10.000000 Y0.000000 Z0.000000\nG1 X0.000000 Y0.000000 Z0.000000\nG1 X0.000000 Y10.000000 Z0.000000\nG1 X10.000000 Y10.000000 Z0.000000\nG0 Z15.000000\nG90 G49.000000\n(End Custom)\n"
code1 = op1.Path.toGCode()
self.assertTrue(
code1 == op1_code,
f"Gcode is not what is expected:\n~~~\n{code1}\n~~~\n\n\n~~~\n{op1_code}\n~~~",
)
code2 = op2.Path.toGCode()
self.assertTrue(
code2 == op2_code,
f"Gcode is not what is expected:\n~~~\n{code2}\n~~~\n\n\n~~~\n{op2_code}\n~~~",
)
FreeCAD.closeDocument(doc.Name)
def test005(self):
"""test005()... verify `_identifygcodeByToolNumberList()` produces correct output"""
from PathScripts.post import gcode_pre as gcode_pre
importFile = FreeCAD.getHomePath() + "Mod/Path/PathTests/test_centroid_00.ngc"
gcodeByToolNumberList = gcode_pre._identifygcodeByToolNumberList(importFile)
self.assertTrue(gcodeByToolNumberList[0][0] == ["G90 G80 G40 G49"])
self.assertTrue(gcodeByToolNumberList[0][1] == 0)
self.assertTrue(
gcodeByToolNumberList[1][0]
== [
"G0 Z15.00",
"G90",
"G0 Z15.00",
"G0 X10.00 Y10.00",
"G0 Z10.00",
"G1 X10.00 Y10.00 Z9.00",
"G1 X10.00 Y0.00 Z9.00",
"G1 X0.00 Y0.00 Z9.00",
"G1 X0.00 Y10.00 Z9.00",
"G1 X10.00 Y10.00 Z9.00",
"G1 X10.00 Y10.00 Z8.00",
"G1 X10.00 Y0.00 Z8.00",
"G1 X0.00 Y0.00 Z8.00",
"G1 X0.00 Y10.00 Z8.00",
"G1 X10.00 Y10.00 Z8.00",
"G1 X10.00 Y10.00 Z7.00",
"G1 X10.00 Y0.00 Z7.00",
"G1 X0.00 Y0.00 Z7.00",
"G1 X0.00 Y10.00 Z7.00",
"G1 X10.00 Y10.00 Z7.00",
"G1 X10.00 Y10.00 Z6.00",
"G1 X10.00 Y0.00 Z6.00",
"G1 X0.00 Y0.00 Z6.00",
"G1 X0.00 Y10.00 Z6.00",
"G1 X10.00 Y10.00 Z6.00",
"G1 X10.00 Y10.00 Z5.00",
"G1 X10.00 Y0.00 Z5.00",
"G1 X0.00 Y0.00 Z5.00",
"G1 X0.00 Y10.00 Z5.00",
"G1 X10.00 Y10.00 Z5.00",
"G1 X10.00 Y10.00 Z4.00",
"G1 X10.00 Y0.00 Z4.00",
"G1 X0.00 Y0.00 Z4.00",
"G1 X0.00 Y10.00 Z4.00",
"G1 X10.00 Y10.00 Z4.00",
"G1 X10.00 Y10.00 Z3.00",
"G1 X10.00 Y0.00 Z3.00",
"G1 X0.00 Y0.00 Z3.00",
"G1 X0.00 Y10.00 Z3.00",
"G1 X10.00 Y10.00 Z3.00",
"G1 X10.00 Y10.00 Z2.00",
"G1 X10.00 Y0.00 Z2.00",
"G1 X0.00 Y0.00 Z2.00",
"G1 X0.00 Y10.00 Z2.00",
"G1 X10.00 Y10.00 Z2.00",
"G1 X10.00 Y10.00 Z1.00",
"G1 X10.00 Y0.00 Z1.00",
"G1 X0.00 Y0.00 Z1.00",
"G1 X0.00 Y10.00 Z1.00",
"G1 X10.00 Y10.00 Z1.00",
"G1 X10.00 Y10.00 Z0.00",
"G1 X10.00 Y0.00 Z0.00",
"G1 X0.00 Y0.00 Z0.00",
"G1 X0.00 Y10.00 Z0.00",
"G1 X10.00 Y10.00 Z0.00",
"G0 Z15.00",
"G90 G80 G40 G49",
]
)
self.assertTrue(gcodeByToolNumberList[1][1] == 2)
+2
View File
@@ -40,6 +40,7 @@ from PathTests.TestPathOpTools import TestPathOpTools
# from PathTests.TestPathPost import PathPostTestCases
from PathTests.TestPathPost import TestPathPostUtils
from PathTests.TestPathPost import TestPathPostImport
from PathTests.TestPathPreferences import TestPathPreferences
from PathTests.TestPathPropertyBag import TestPathPropertyBag
from PathTests.TestPathRotationGenerator import TestPathRotationGenerator
@@ -70,6 +71,7 @@ False if TestPathHelpers.__name__ else True
# False if TestPathHelix.__name__ else True
False if TestPathLog.__name__ else True
False if TestPathOpTools.__name__ else True
False if TestPathPostImport.__name__ else True
# False if TestPathPost.__name__ else True
False if TestPathPostUtils.__name__ else True
False if TestPathPreferences.__name__ else True