diff --git a/src/Mod/Path/Generators/toolchange_generator.py b/src/Mod/Path/Generators/toolchange_generator.py index 9a6f67a725..e7a6f35b7f 100644 --- a/src/Mod/Path/Generators/toolchange_generator.py +++ b/src/Mod/Path/Generators/toolchange_generator.py @@ -75,3 +75,33 @@ def generate( PathLog.track(commands) return commands + + +def generateSubstitute(newTC, oldTC=None): + """ + The specific commands to emit, depend on the state of the machine. + For example, the toolnumber may not change, only the spindle speed. + This routine will generate a list of commands to substitute for a TC + object to be handed to the postprocessor. + It will contain only the commands needed + """ + + if oldTC is None: + return newTC.Path.Commands + + if newTC.ToolNumber != oldTC.ToolNumber: # Full toolchange + return newTC.Path.Commands + + if (newTC.SpindleSpeed != oldTC.SpindleSpeed) or ( + newTC.SpindleDir != oldTC.SpindleDir + ): + if newTC.SpindleDir == "Forward": + sd = SpindleDirection.CW + elif newTC.SpindleDir == "Reverse": + sd = SpindleDirection.CCW + else: + sd = SpindleDirection.OFF + + return [Path.Command(sd.value, {"S": newTC.SpindleSpeed})] + + return [] diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index 6bdd15f411..a39f506c4f 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -21,9 +21,11 @@ # *************************************************************************** import FreeCAD +import FreeCADGui import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil +from PySide import QtGui, QtCore from PySide import QtCore, QtGui @@ -199,3 +201,38 @@ class QuantitySpinBox(QtCore.QObject): if prop == self.prop: return exp return None + + +def getDocNode(): + doc = FreeCADGui.ActiveDocument.Document.Name + tws = FreeCADGui.getMainWindow().findChildren(QtGui.QTreeWidget) + + for tw in tws: + if tw.topLevelItemCount() != 1 or tw.topLevelItem(0).text(0) != "Application": + continue + toptree = tw.topLevelItem(0) + for i in range(0, toptree.childCount()): + docitem = toptree.child(i) + if docitem.text(0) == doc: + return docitem + return None + + +def disableItem(item): + Dragflag = QtCore.Qt.ItemFlag.ItemIsDragEnabled + Dropflag = QtCore.Qt.ItemFlag.ItemIsDropEnabled + item.setFlags(item.flags() & ~Dragflag) + item.setFlags(item.flags() & ~Dropflag) + for idx in range(0, item.childCount()): + disableItem(item.child(idx)) + + +def findItem(docitem, objname): + print(docitem.text(0)) + for i in range(0, docitem.childCount()): + if docitem.child(i).text(0) == objname: + return docitem.child(i) + res = findItem(docitem.child(i), objname) + if res: + return res + return None diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 52aba08b85..d1e02d459c 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -41,8 +41,11 @@ from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = PathLog.thisModule() -PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) - +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) translate = FreeCAD.Qt.translate @@ -50,7 +53,6 @@ translate = FreeCAD.Qt.translate class _TempObject: Path = None Name = "Fixture" - InList = [] Label = "Fixture" @@ -216,6 +218,167 @@ class CommandPathPost: else: return (True, "", filename) + def buildPostList(self, job): + """ + Parses the job and returns the list(s) of objects to be written by the post + Postlist is a list of lists. Each sublist is intended to be a separate file + """ + orderby = job.OrderOutputBy + + fixturelist = [] + for f in job.Fixtures: + # create an object to serve as the fixture path + fobj = _TempObject() + fobj.Label = f + c1 = Path.Command(f) + c2 = Path.Command( + "G0 Z" + + str( + job.Stock.Shape.BoundBox.ZMax + + job.SetupSheet.ClearanceHeightOffset.Value + ) + ) + fobj.Path = Path.Path([c1, c2]) + # fobj.InList.append(job) + fixturelist.append(fobj) + + postlist = [] + + if orderby == "Fixture": + PathLog.debug("Ordering by Fixture") + # Order by fixture means all operations and tool changes will be completed in one + # fixture before moving to the next. + + for f in fixturelist: + scratchpad = [(f, None)] + + # Now generate the gcode + for obj in job.Operations.Group: + if not PathUtil.opProperty(obj, "Active"): + continue + tc = PathUtil.toolControllerForOp(obj) + scratchpad.append((obj, tc)) + + sublist = [] + temptool = None + for item in scratchpad: + if item[1] in [temptool, None]: + sublist.append(item[0]) + else: + sublist.append(item[1]) + temptool = item[1] + sublist.append(item[0]) + postlist.append(sublist) + + elif orderby == "Tool": + PathLog.debug("Ordering by Tool") + # Order by tool means tool changes are minimized. + # all operations with the current tool are processed in the current + # fixture before moving to the next fixture. + + currTool = None + + # Now generate the gcode + curlist = [] # list of ops for tool, will repeat for each fixture + # sublist = [] # list of ops for output splitting + + for idx, obj in enumerate(job.Operations.Group): + + # check if the operation is active + active = PathUtil.opProperty(obj, "Active") + + tc = PathUtil.toolControllerForOp(obj) + + if not active: # pass on any inactive ops + continue + + if tc is None: + curlist.append((obj, None)) + continue + + if tc == currTool: + curlist.append((obj, tc)) + + if tc != currTool and currTool is None: # first TC + currTool = tc + curlist.append((obj, tc)) + continue + + if tc != currTool and currTool is not None: # TC changed + if tc.ToolNumber == currTool.ToolNumber: # Same tool /diff params + curlist.append((obj, tc)) + currTool = tc + else: # Actual Toolchange + # dump current state to postlist + sublist = [] + t = None + for fixture in fixturelist: + sublist.append(fixture) + for item in curlist: + if item[1] == t: + sublist.append(item[0]) + else: + sublist.append(item[1]) + t = item[1] + sublist.append(item[0]) + + postlist.append(sublist) + + # set up for next tool group + currTool = tc + curlist = [(obj, tc)] + + # flush remaining curlist to output + sublist = [] + t = None + for fixture in fixturelist: + sublist.append(fixture) + for item in curlist: + if item[1] == t: + sublist.append(item[0]) + else: + sublist.append(item[1]) + t = item[1] + sublist.append(item[0]) + postlist.append(sublist) + + elif orderby == "Operation": + PathLog.debug("Ordering by Operation") + # Order by operation means ops are done in each fixture in + # sequence. + currTool = None + # firstFixture = True + + # Now generate the gcode + for obj in job.Operations.Group: + scratchpad = [] + tc = PathUtil.toolControllerForOp(obj) + if not PathUtil.opProperty(obj, "Active"): + continue + + PathLog.debug("obj: {}".format(obj.Name)) + for f in fixturelist: + + scratchpad.append((f, None)) + scratchpad.append((obj, tc)) + + sublist = [] + temptool = None + for item in scratchpad: + if item[1] in [temptool, None]: + sublist.append(item[0]) + else: + sublist.append(item[1]) + temptool = item[1] + sublist.append(item[0]) + postlist.append(sublist) + + if job.SplitOutput: + return postlist + else: + finalpostlist = [item for slist in postlist for item in slist] + return [finalpostlist] + def Activated(self): PathLog.track() FreeCAD.ActiveDocument.openTransaction("Post Process the Selected path(s)") @@ -265,150 +428,14 @@ class CommandPathPost: PathLog.debug("about to postprocess job: {}".format(job.Name)) - wcslist = job.Fixtures - orderby = job.OrderOutputBy - split = job.SplitOutput - - postlist = [] - - if orderby == "Fixture": - PathLog.debug("Ordering by Fixture") - # Order by fixture means all operations and tool changes will be completed in one - # fixture before moving to the next. - - currTool = None - for index, f in enumerate(wcslist): - # create an object to serve as the fixture path - fobj = _TempObject() - c1 = Path.Command(f) - fobj.Path = Path.Path([c1]) - if index != 0: - c2 = Path.Command( - "G0 Z" - + str( - job.Stock.Shape.BoundBox.ZMax - + job.SetupSheet.ClearanceHeightOffset.Value - ) - ) - fobj.Path.addCommands(c2) - fobj.InList.append(job) - sublist = [fobj] - - # Now generate the gcode - for obj in job.Operations.Group: - tc = PathUtil.toolControllerForOp(obj) - if tc is not None and PathUtil.opProperty(obj, "Active"): - if tc.ToolNumber != currTool or split is True: - sublist.append(tc) - PathLog.debug("Appending TC: {}".format(tc.Name)) - currTool = tc.ToolNumber - sublist.append(obj) - postlist.append(sublist) - - elif orderby == "Tool": - PathLog.debug("Ordering by Tool") - # Order by tool means tool changes are minimized. - # all operations with the current tool are processed in the current - # fixture before moving to the next fixture. - - currTool = None - fixturelist = [] - for f in wcslist: - # create an object to serve as the fixture path - fobj = _TempObject() - c1 = Path.Command(f) - c2 = Path.Command( - "G0 Z" - + str( - job.Stock.Shape.BoundBox.ZMax - + job.SetupSheet.ClearanceHeightOffset.Value - ) - ) - fobj.Path = Path.Path([c1, c2]) - fobj.InList.append(job) - fixturelist.append(fobj) - - # Now generate the gcode - curlist = [] # list of ops for tool, will repeat for each fixture - sublist = [] # list of ops for output splitting - - for idx, obj in enumerate(job.Operations.Group): - - # check if the operation is active - active = PathUtil.opProperty(obj, "Active") - - tc = PathUtil.toolControllerForOp(obj) - if tc is None or tc.ToolNumber == currTool and active: - curlist.append(obj) - elif ( - tc.ToolNumber != currTool and currTool is None and active - ): # first TC - sublist.append(tc) - curlist.append(obj) - currTool = tc.ToolNumber - elif ( - tc.ToolNumber != currTool and currTool is not None and active - ): # TC - for fixture in fixturelist: - sublist.append(fixture) - sublist.extend(curlist) - postlist.append(sublist) - sublist = [tc] - curlist = [obj] - currTool = tc.ToolNumber - - if idx == len(job.Operations.Group) - 1: # Last operation. - for fixture in fixturelist: - sublist.append(fixture) - sublist.extend(curlist) - postlist.append(sublist) - - elif orderby == "Operation": - PathLog.debug("Ordering by Operation") - # Order by operation means ops are done in each fixture in - # sequence. - currTool = None - firstFixture = True - - # Now generate the gcode - for obj in job.Operations.Group: - if PathUtil.opProperty(obj, "Active"): - sublist = [] - PathLog.debug("obj: {}".format(obj.Name)) - for f in wcslist: - fobj = _TempObject() - c1 = Path.Command(f) - fobj.Path = Path.Path([c1]) - if not firstFixture: - c2 = Path.Command( - "G0 Z" - + str( - job.Stock.Shape.BoundBox.ZMax - + job.SetupSheet.ClearanceHeightOffset.Value - ) - ) - fobj.Path.addCommands(c2) - fobj.InList.append(job) - sublist.append(fobj) - firstFixture = False - tc = PathUtil.toolControllerForOp(obj) - if tc is not None: - if job.SplitOutput or (tc.ToolNumber != currTool): - sublist.append(tc) - currTool = tc.ToolNumber - sublist.append(obj) - postlist.append(sublist) + postlist = self.buildPostList(job) fail = True rc = "" - if split: - for slist in postlist: - (fail, rc, filename) = self.exportObjectsWith(slist, job) - if fail: - break - else: - finalpostlist = [item for slist in postlist for item in slist] - (fail, rc, filename) = self.exportObjectsWith(finalpostlist, job) + for slist in postlist: + (fail, rc, filename) = self.exportObjectsWith(slist, job) + if fail: + break self.subpart = 1 diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index 3bddac16c3..a90d3bf235 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -25,7 +25,7 @@ import PathScripts import PathScripts.post import PathScripts.PathProfileContour import PathScripts.PathJob -import PathScripts.PathPost +import PathScripts.PathPost as PathPost import PathScripts.PathToolController import PathScripts.PathUtil import PathScripts.PostUtils as PostUtils @@ -324,3 +324,303 @@ class TestPathPostImport(unittest.TestCase): ] ) self.assertTrue(gcodeByToolNumberList[1][1] == 2) + + +class OutputOrderingTestCases(unittest.TestCase): + def setUp(self): + testfile = FreeCAD.getHomePath() + "Mod/Path/PathTests/boxtest.fcstd" + self.doc = FreeCAD.open(testfile) + self.job = FreeCAD.ActiveDocument.getObject("Job001") + + def tearDown(self): + FreeCAD.closeDocument("boxtest") + + def test010(self): + # Basic postprocessing: + + self.job.Fixtures = ["G54"] + self.job.SplitOutput = False + self.job.OrderOutputBy = "Fixture" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + + outlist = [i.Label for i in self.postlist[0]] + + self.assertTrue(len(self.postlist) == 1) + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "T3", + "FifthOp-(T3)", + ] + self.assertListEqual(outlist, expected) + + def test020(self): + # Multiple Fixtures + + self.job.Fixtures = ["G54", "G55"] + self.job.SplitOutput = False + self.job.OrderOutputBy = "Fixture" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + + self.assertTrue(len(self.postlist) == 1) + + outlist = [i.Label for i in self.postlist[0]] + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "T3", + "FifthOp-(T3)", + "G55", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "T3", + "FifthOp-(T3)", + ] + + self.assertListEqual(outlist, expected) + + def test030(self): + # Multiple Fixtures - Split output + + self.job.Fixtures = ["G54", "G55"] + self.job.SplitOutput = True + self.job.OrderOutputBy = "Fixture" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + + self.assertTrue(len(self.postlist) == 2) + + outlist = [i.Label for i in self.postlist[0]] + print(outlist) + + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "T3", + "FifthOp-(T3)", + ] + self.assertListEqual(outlist, expected) + + expected = [ + "G55", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "T3", + "FifthOp-(T3)", + ] + outlist = [i.Label for i in self.postlist[1]] + self.assertListEqual(outlist, expected) + + def test040(self): + # Order by 'Tool' + + self.job.Fixtures = ["G54", "G55"] + self.job.SplitOutput = False + self.job.OrderOutputBy = "Tool" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + outlist = [i.Label for i in self.postlist[0]] + + self.assertTrue(len(self.postlist) == 1) + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "G55", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "G54", + "T3", + "FifthOp-(T3)", + "G55", + "FifthOp-(T3)", + ] + + self.assertListEqual(outlist, expected) + + def test050(self): + # Order by 'Tool' and split + + self.job.Fixtures = ["G54", "G55"] + self.job.SplitOutput = True + self.job.OrderOutputBy = "Tool" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + outlist = [i.Label for i in self.postlist[0]] + + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + "G55", + "FirstOp-(T1)", + "SecondOp-(T1)", + "T2", + "ThirdOp-(T2)", + "T1", + "FourthOp-(T1)", + ] + self.assertListEqual(outlist, expected) + + outlist = [i.Label for i in self.postlist[1]] + + expected = [ + "G54", + "T3", + "FifthOp-(T3)", + "G55", + "FifthOp-(T3)", + ] + self.assertListEqual(outlist, expected) + + def test060(self): + # Order by 'Operation' + + self.job.Fixtures = ["G54", "G55"] + self.job.SplitOutput = False + self.job.OrderOutputBy = "Operation" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + outlist = [i.Label for i in self.postlist[0]] + + self.assertTrue(len(self.postlist) == 1) + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "G55", + "FirstOp-(T1)", + "G54", + "T1", + "SecondOp-(T1)", + "G55", + "SecondOp-(T1)", + "G54", + "T2", + "ThirdOp-(T2)", + "G55", + "ThirdOp-(T2)", + "G54", + "T1", + "FourthOp-(T1)", + "G55", + "FourthOp-(T1)", + "G54", + "T3", + "FifthOp-(T3)", + "G55", + "FifthOp-(T3)", + ] + + self.assertListEqual(outlist, expected) + + def test070(self): + # Order by 'Operation' and split + + self.job.Fixtures = ["G54", "G55"] + self.job.SplitOutput = True + self.job.OrderOutputBy = "Operation" + + cpp = PathPost.CommandPathPost + self.postlist = cpp.buildPostList(self, self.job) + self.assertTrue(len(self.postlist) == 5) + + outlist = [i.Label for i in self.postlist[0]] + expected = [ + "G54", + "T1", + "FirstOp-(T1)", + "G55", + "FirstOp-(T1)", + ] + self.assertListEqual(outlist, expected) + + outlist = [i.Label for i in self.postlist[1]] + expected = [ + "G54", + "T1", + "SecondOp-(T1)", + "G55", + "SecondOp-(T1)", + ] + self.assertListEqual(outlist, expected) + + outlist = [i.Label for i in self.postlist[2]] + expected = [ + "G54", + "T2", + "ThirdOp-(T2)", + "G55", + "ThirdOp-(T2)", + ] + self.assertListEqual(outlist, expected) + + outlist = [i.Label for i in self.postlist[3]] + expected = [ + "G54", + "T1", + "FourthOp-(T1)", + "G55", + "FourthOp-(T1)", + ] + self.assertListEqual(outlist, expected) + + outlist = [i.Label for i in self.postlist[4]] + expected = [ + "G54", + "T3", + "FifthOp-(T3)", + "G55", + "FifthOp-(T3)", + ] + self.assertListEqual(outlist, expected) diff --git a/src/Mod/Path/PathTests/boxtest.fcstd b/src/Mod/Path/PathTests/boxtest.fcstd index cc338da2d8..2a6e76e292 100644 Binary files a/src/Mod/Path/PathTests/boxtest.fcstd and b/src/Mod/Path/PathTests/boxtest.fcstd differ diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index f917454ede..b8752f998c 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -39,6 +39,7 @@ from PathTests.TestPathLog import TestPathLog from PathTests.TestPathOpTools import TestPathOpTools # from PathTests.TestPathPost import PathPostTestCases +from PathTests.TestPathPost import OutputOrderingTestCases from PathTests.TestPathPost import TestPathPostUtils from PathTests.TestPathPost import TestPathPostImport from PathTests.TestPathPreferences import TestPathPreferences