From 55f9e4d1dda22ff8867eb5c18310a969929a3d4e Mon Sep 17 00:00:00 2001 From: Christian Mesh Date: Sun, 12 Feb 2023 11:50:02 -0500 Subject: [PATCH 1/4] Apply Placement to simulated/exported GCode This changed is inspired by (and slightly refactors) the Array Path modifier. It currently takes the Placement XYZ and Z Angle into consideration. Future work could also implement rotating the path around the X and Y axes. --- src/Mod/Path/Path/Main/Gui/Inspect.py | 2 +- src/Mod/Path/Path/Main/Gui/Simulator.py | 7 +- src/Mod/Path/Path/Op/Gui/Array.py | 66 +------------- src/Mod/Path/Path/Post/UtilsParse.py | 3 +- .../Post/scripts/KineticNCBeamicon2_post.py | 3 +- .../Path/Path/Post/scripts/centroid_post.py | 3 +- .../Path/Path/Post/scripts/comparams_post.py | 3 +- src/Mod/Path/Path/Post/scripts/dumper_post.py | 3 +- src/Mod/Path/Path/Post/scripts/dxf_post.py | 3 +- .../Path/Post/scripts/dynapath_4060_post.py | 3 +- .../Path/Path/Post/scripts/dynapath_post.py | 3 +- src/Mod/Path/Path/Post/scripts/fablin_post.py | 3 +- src/Mod/Path/Path/Post/scripts/fanuc_post.py | 8 +- src/Mod/Path/Path/Post/scripts/grbl_post.py | 3 +- .../Path/Path/Post/scripts/heidenhain_post.py | 13 ++- src/Mod/Path/Path/Post/scripts/jtech_post.py | 3 +- .../Path/Path/Post/scripts/linuxcnc_post.py | 3 +- .../Path/Post/scripts/mach3_mach4_post.py | 3 +- src/Mod/Path/Path/Post/scripts/marlin_post.py | 3 +- src/Mod/Path/Path/Post/scripts/nccad_post.py | 3 +- .../Path/Path/Post/scripts/opensbp_post.py | 3 +- .../Path/Path/Post/scripts/philips_post.py | 3 +- .../Post/scripts/refactored_centroid_post.py | 1 + src/Mod/Path/Path/Post/scripts/rrf_post.py | 3 +- .../Path/Path/Post/scripts/smoothie_post.py | 3 +- src/Mod/Path/Path/Post/scripts/uccnc_post.py | 3 +- src/Mod/Path/PathScripts/PathUtils.py | 89 +++++++++++++++++++ 27 files changed, 151 insertions(+), 95 deletions(-) diff --git a/src/Mod/Path/Path/Main/Gui/Inspect.py b/src/Mod/Path/Path/Main/Gui/Inspect.py index e24e537817..fe4967ba03 100644 --- a/src/Mod/Path/Path/Main/Gui/Inspect.py +++ b/src/Mod/Path/Path/Main/Gui/Inspect.py @@ -182,7 +182,7 @@ class GCodeEditorDialog(QtGui.QDialog): cursor.setPosition(ep) endrow = cursor.blockNumber() - commands = self.PathObj.Commands + commands = PathUtils.getPathWithPlacement(self.PathObj).Commands # Derive the starting position for the first selected command prevX = prevY = prevZ = None diff --git a/src/Mod/Path/Path/Main/Gui/Simulator.py b/src/Mod/Path/Path/Main/Gui/Simulator.py index 430f12b559..888fd0205e 100644 --- a/src/Mod/Path/Path/Main/Gui/Simulator.py +++ b/src/Mod/Path/Path/Main/Gui/Simulator.py @@ -24,6 +24,7 @@ import FreeCAD import Path import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup +import PathScripts.PathUtils as PathUtils import Path.Main.Job as PathJob import PathGui import PathSimulator @@ -202,7 +203,7 @@ class PathSimulation: self.icmd = 0 self.curpos = FreeCAD.Placement(self.initialPos, self.stdrot) self.cutTool.Placement = self.curpos - self.opCommands = self.operation.Path.Commands + self.opCommands = PathUtils.getPathWithPlacement(self.operation).Commands def SimulateMill(self): self.job = self.jobs[self.taskForm.form.comboJobs.currentIndex()] @@ -258,7 +259,7 @@ class PathSimulation: return self.busy = True - cmd = self.operation.Path.Commands[self.icmd] + cmd = self.opCommands[self.icmd] pathSolid = None if cmd.Name in ["G0"]: @@ -302,7 +303,7 @@ class PathSimulation: self.icmd += 1 self.iprogress += 1 self.UpdateProgress() - if self.icmd >= len(self.operation.Path.Commands): + if self.icmd >= len(self.opCommands): self.ioperation += 1 if self.ioperation >= len(self.activeOps): self.EndSimulation() diff --git a/src/Mod/Path/Path/Op/Gui/Array.py b/src/Mod/Path/Path/Op/Gui/Array.py index b7daea3400..4388ed0135 100644 --- a/src/Mod/Path/Path/Op/Gui/Array.py +++ b/src/Mod/Path/Path/Op/Gui/Array.py @@ -24,6 +24,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts +from PathScripts.PathUtils import rotatePath from Path.Dressup.Utils import toolController from PySide import QtCore import math @@ -285,69 +286,6 @@ class PathArray: else: self.baseList = [baseList] - def rotatePath(self, path, angle, centre): - """ - Rotates Path around given centre vector - Only X and Y is considered - """ - CmdMoveRapid = ["G0", "G00"] - CmdMoveStraight = ["G1", "G01"] - CmdMoveCW = ["G2", "G02"] - CmdMoveCCW = ["G3", "G03"] - CmdDrill = ["G73", "G81", "G82", "G83"] - CmdMoveArc = CmdMoveCW + CmdMoveCCW - CmdMove = CmdMoveStraight + CmdMoveArc - - commands = [] - ang = angle / 180 * math.pi - currX = 0 - currY = 0 - for cmd in path.Commands: - if ( - (cmd.Name in CmdMoveRapid) - or (cmd.Name in CmdMove) - or (cmd.Name in CmdDrill) - ): - params = cmd.Parameters - x = params.get("X") - if x is None: - x = currX - currX = x - y = params.get("Y") - if y is None: - y = currY - currY = y - - # "move" the centre to origin - x = x - centre.x - y = y - centre.y - - # rotation around origin: - nx = x * math.cos(ang) - y * math.sin(ang) - ny = y * math.cos(ang) + x * math.sin(ang) - - # "move" the centre back and update - params.update({"X": nx + centre.x, "Y": ny + centre.y}) - - # Arcs need to have the I and J params rotated as well - if cmd.Name in CmdMoveArc: - i = params.get("I") - if i is None: - i = 0 - j = params.get("J") - if j is None: - j = 0 - - ni = i * math.cos(ang) - j * math.sin(ang) - nj = j * math.cos(ang) + i * math.sin(ang) - params.update({"I": ni, "J": nj}) - - cmd.Parameters = params - commands.append(cmd) - newPath = Path.Path(commands) - - return newPath - # Private method def _calculateJitter(self, pos): """_calculateJitter(pos) ... @@ -474,7 +412,7 @@ class PathArray: ang = 360 if self.copies > 0: ang = self.angle / self.copies * (1 + i) - np = self.rotatePath(b.Path, ang, self.centre) + np = rotatePath(b.Path, ang, self.centre) output += np.toGCode() # return output diff --git a/src/Mod/Path/Path/Post/UtilsParse.py b/src/Mod/Path/Path/Post/UtilsParse.py index 906118e2ab..7158cb483e 100644 --- a/src/Mod/Path/Path/Post/UtilsParse.py +++ b/src/Mod/Path/Path/Post/UtilsParse.py @@ -34,6 +34,7 @@ from FreeCAD import Units import Path import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils def create_comment(values, comment_string): """Create a comment from a string using the correct comment symbol.""" @@ -354,7 +355,7 @@ def parse_a_path(values, pathobj): f"Tool Controller Vertical Rapid Values are unset{nl}" ) - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: # List of elements in the command, code, and params. outstring = [] diff --git a/src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py b/src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py index 1248eaf37b..1ca3f74e8c 100644 --- a/src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py +++ b/src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py @@ -40,6 +40,7 @@ import argparse import datetime import shlex from PathScripts import PathUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to @@ -345,7 +346,7 @@ def parse(pathobj): # if OUTPUT_COMMENTS: # out += linenumber() + "(" + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name diff --git a/src/Mod/Path/Path/Post/scripts/centroid_post.py b/src/Mod/Path/Path/Post/scripts/centroid_post.py index b61899da5f..f14ac7ba43 100644 --- a/src/Mod/Path/Path/Post/scripts/centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/centroid_post.py @@ -28,6 +28,7 @@ import os import FreeCAD from FreeCAD import Units import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import datetime import Path @@ -277,7 +278,7 @@ def parse(pathobj): # if OUTPUT_COMMENTS: # out += linenumber() + "(" + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: commandlist = [] # list of elements in the command, code and params. command = c.Name # command M or G code or comment string diff --git a/src/Mod/Path/Path/Post/scripts/comparams_post.py b/src/Mod/Path/Path/Post/scripts/comparams_post.py index d4b1af4018..1b4dda0412 100644 --- a/src/Mod/Path/Path/Post/scripts/comparams_post.py +++ b/src/Mod/Path/Path/Post/scripts/comparams_post.py @@ -23,6 +23,7 @@ import FreeCAD import Path import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output.""" @@ -95,7 +96,7 @@ def export(obj, filename, argstring): gcode += firstcommand.Name if hasattr(fp, "Path"): - for c in fp.Path.Commands: + for c in PathUtils.getPathWithPlacement(fp).Commands: gcode += lineout(c, oldvals, modal) + "\n" oldvals = saveVals(c) gcode += "M2\n" diff --git a/src/Mod/Path/Path/Post/scripts/dumper_post.py b/src/Mod/Path/Path/Post/scripts/dumper_post.py index 753c367f42..b3e6df4451 100644 --- a/src/Mod/Path/Path/Post/scripts/dumper_post.py +++ b/src/Mod/Path/Path/Post/scripts/dumper_post.py @@ -31,6 +31,7 @@ shows the dialog so you can see it. Useful for debugging, but not much else. """ import datetime import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils now = datetime.datetime.now() SHOW_EDITOR = True @@ -95,7 +96,7 @@ def parse(pathobj): out += "(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: out += str(c) + "\n" return out diff --git a/src/Mod/Path/Path/Post/scripts/dxf_post.py b/src/Mod/Path/Path/Post/scripts/dxf_post.py index 3b1052ad7f..e169099e27 100644 --- a/src/Mod/Path/Path/Post/scripts/dxf_post.py +++ b/src/Mod/Path/Path/Post/scripts/dxf_post.py @@ -24,6 +24,7 @@ from __future__ import print_function import FreeCAD import Part import Path +import PathScripts.PathUtils as PathUtils import datetime import importDXF @@ -100,7 +101,7 @@ def parse(pathobj): # Gotta start somewhere. Assume 0,0,0 curPoint = FreeCAD.Vector(0, 0, 0) - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: Path.Log.debug("{} -> {}".format(curPoint, c)) if "Z" in c.Parameters: newparams = c.Parameters diff --git a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py index a0de64f569..64732c9c8a 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -33,6 +33,7 @@ import argparse import datetime import shlex import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ This is a post processor file for the FreeCAD Path workbench. It is used to @@ -421,7 +422,7 @@ def parse(pathobj): if not hasattr(pathobj, "Path"): return out - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name # Convert G54-G59 Fixture offsets to E01-E06 for Dynapath Delta Control diff --git a/src/Mod/Path/Path/Post/scripts/dynapath_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_post.py index 5f1798d50a..a556844821 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_post.py @@ -34,6 +34,7 @@ from __future__ import print_function import FreeCAD from FreeCAD import Units import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import argparse import datetime import shlex @@ -325,7 +326,7 @@ def parse(pathobj): if OUTPUT_COMMENTS: out += linenumber() + "(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name outstring.append(command) diff --git a/src/Mod/Path/Path/Post/scripts/fablin_post.py b/src/Mod/Path/Path/Post/scripts/fablin_post.py index 0c106f17f1..d2e08fabb6 100644 --- a/src/Mod/Path/Path/Post/scripts/fablin_post.py +++ b/src/Mod/Path/Path/Post/scripts/fablin_post.py @@ -26,6 +26,7 @@ import datetime import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils now = datetime.datetime.now() @@ -253,7 +254,7 @@ def parse(pathobj): if OUTPUT_COMMENTS: out += linenumber() + "(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name diff --git a/src/Mod/Path/Path/Post/scripts/fanuc_post.py b/src/Mod/Path/Path/Post/scripts/fanuc_post.py index afb7b8244f..7660ed7676 100644 --- a/src/Mod/Path/Path/Post/scripts/fanuc_post.py +++ b/src/Mod/Path/Path/Post/scripts/fanuc_post.py @@ -30,6 +30,7 @@ import datetime import shlex import os.path import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to @@ -410,14 +411,15 @@ def parse(pathobj): "Tool Controller Vertical Rapid Values are unset" + "\n" ) - for index, c in enumerate(pathobj.Path.Commands): + commands = PathUtils.getPathWithPlacement(pathobj).Commands + for index, c in enumerate(commands): outstring = [] command = c.Name - if index + 1 == len(pathobj.Path.Commands): + if index + 1 == len(commands): nextcommand = "" else: - nextcommand = pathobj.Path.Commands[index + 1].Name + nextcommand = commands[index + 1].Name if adaptiveOp and c.Name in ["G0", "G00"]: if opHorizRapid and opVertRapid: diff --git a/src/Mod/Path/Path/Post/scripts/grbl_post.py b/src/Mod/Path/Path/Post/scripts/grbl_post.py index 5c27cd5fcf..c7f71c91ee 100755 --- a/src/Mod/Path/Path/Post/scripts/grbl_post.py +++ b/src/Mod/Path/Path/Post/scripts/grbl_post.py @@ -29,6 +29,7 @@ from FreeCAD import Units import Path import Path.Base.Util as PathUtil import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import argparse import datetime import shlex @@ -481,7 +482,7 @@ def parse(pathobj): if OUTPUT_COMMENTS: out += linenumber() + "(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name diff --git a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py index 418db4ea64..66e3f55ffb 100644 --- a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py +++ b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py @@ -22,6 +22,7 @@ import argparse import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import Path import PathScripts import shlex @@ -361,12 +362,14 @@ def export(objectslist, filename, argstring): elif isinstance(obj.Proxy, Path.Op.Helix.ObjectHelix): Object_Kind = "HELIX" + commands = PathUtils.getPathWithPlacement(obj).Commands + # If used compensated path, store, recompute and diff when asked if hasattr(obj, "UseComp") and SOLVE_COMPENSATION_ACTIVE: if obj.UseComp: if hasattr(obj.Path, "Commands") and Object_Kind == "PROFILE": # Take a copy of compensated path - STORED_COMPENSATED_OBJ = obj.Path.Commands + STORED_COMPENSATED_OBJ = commands # Find mill compensation if hasattr(obj, "Side") and hasattr(obj, "Direction"): if obj.Side == "Outside" and obj.Direction == "CW": @@ -380,14 +383,16 @@ def export(objectslist, filename, argstring): # set obj.UseComp to false and recompute() to get uncompensated path obj.UseComp = False obj.recompute() + commands = PathUtils.getPathWithPlacement(obj).Commands # small edges could be skipped and movements joints can add edges NameStr = "" if hasattr(obj, "Label"): NameStr = str(obj.Label) - if len(obj.Path.Commands) != len(STORED_COMPENSATED_OBJ): + if len(commands) != len(STORED_COMPENSATED_OBJ): # not same number of edges obj.UseComp = True obj.recompute() + commands = PathUtils.getPathWithPlacement(obj).Commands POSTGCODE.append("; MISSING EDGES UNABLE TO GET COMPENSATION") if not SKIP_WARNS: ( @@ -422,7 +427,7 @@ def export(objectslist, filename, argstring): POSTGCODE.append("; COMPENSATION ACTIVE") COMPENSATION_DIFF_STATUS[0] = True - for c in obj.Path.Commands: + for c in commands: Cmd_Count += 1 command = c.Name if command != "G0": @@ -458,7 +463,7 @@ def export(objectslist, filename, argstring): Spindle_Status += str(MACHINE_SPINDLE_DIRECTION) # Activate spindle Spindle_Active = True else: # At last rapid movement we turn off spindle - if Cmd_Count == len(obj.Path.Commands): + if Cmd_Count == len(commands): Spindle_Status += "5" # Deactivate spindle Spindle_Active = False else: diff --git a/src/Mod/Path/Path/Post/scripts/jtech_post.py b/src/Mod/Path/Path/Post/scripts/jtech_post.py index 36e5b1d498..57521dba4b 100644 --- a/src/Mod/Path/Path/Post/scripts/jtech_post.py +++ b/src/Mod/Path/Path/Post/scripts/jtech_post.py @@ -29,6 +29,7 @@ import argparse import datetime import shlex import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to @@ -308,7 +309,7 @@ def parse(pathobj): if not hasattr(pathobj, "Path"): return out - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name diff --git a/src/Mod/Path/Path/Post/scripts/linuxcnc_post.py b/src/Mod/Path/Path/Post/scripts/linuxcnc_post.py index d21eba1bad..17e79f9b19 100644 --- a/src/Mod/Path/Path/Post/scripts/linuxcnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/linuxcnc_post.py @@ -29,6 +29,7 @@ import argparse import datetime import shlex import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to @@ -356,7 +357,7 @@ def parse(pathobj): # if OUTPUT_COMMENTS: # out += linenumber() + "(" + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name diff --git a/src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py b/src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py index d4cb8ecb66..8b06ca1aeb 100644 --- a/src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py +++ b/src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py @@ -28,6 +28,7 @@ import argparse import datetime import shlex import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to @@ -387,7 +388,7 @@ def parse(pathobj): "Tool Controller Vertical Rapid Values are unset" + "\n" ) - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name diff --git a/src/Mod/Path/Path/Post/scripts/marlin_post.py b/src/Mod/Path/Path/Post/scripts/marlin_post.py index a948ac9df5..abebe51ae5 100644 --- a/src/Mod/Path/Path/Post/scripts/marlin_post.py +++ b/src/Mod/Path/Path/Post/scripts/marlin_post.py @@ -35,6 +35,7 @@ from FreeCAD import Units import Path import Path.Base.Util as PathUtil import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils Revised = "2020-11-03" # Revision date for this file. @@ -542,7 +543,7 @@ def parse(pathobj): if OUTPUT_COMMENTS and OUTPUT_PATH: out += linenumber() + "(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outlist = [] command = c.Name outlist.append(command) diff --git a/src/Mod/Path/Path/Post/scripts/nccad_post.py b/src/Mod/Path/Path/Post/scripts/nccad_post.py index 7661c888e1..2b3791f88a 100644 --- a/src/Mod/Path/Path/Post/scripts/nccad_post.py +++ b/src/Mod/Path/Path/Post/scripts/nccad_post.py @@ -23,6 +23,7 @@ """Postprocessor to output real GCode for Max Computer GmbH nccad9.""" import FreeCAD import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import datetime @@ -89,7 +90,7 @@ def export(objectslist, filename, argstring): gcode = HEADER for obj in objectslist: - for command in obj.Path.Commands: + for command in PathUtils.getPathWithPlacement(obj).Commands: # Manipulate tool change commands if "M6" == command.Name: gcode += TOOL_CHANGE.replace("TOOL", str(int(command.Parameters["T"]))) diff --git a/src/Mod/Path/Path/Post/scripts/opensbp_post.py b/src/Mod/Path/Path/Post/scripts/opensbp_post.py index 3d2e2af199..b1bbc23b8d 100644 --- a/src/Mod/Path/Path/Post/scripts/opensbp_post.py +++ b/src/Mod/Path/Path/Post/scripts/opensbp_post.py @@ -24,6 +24,7 @@ from __future__ import print_function import datetime import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils TOOLTIP = """ @@ -352,7 +353,7 @@ def parse(pathobj): return output if OUTPUT_COMMENTS: output += linenumber() + "'(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: command = c.Name if command in scommands: output += scommands[command](c) diff --git a/src/Mod/Path/Path/Post/scripts/philips_post.py b/src/Mod/Path/Path/Post/scripts/philips_post.py index d6e57cee6d..d52cb6eb59 100644 --- a/src/Mod/Path/Path/Post/scripts/philips_post.py +++ b/src/Mod/Path/Path/Post/scripts/philips_post.py @@ -28,6 +28,7 @@ import FreeCAD import argparse import time import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import math TOOLTIP = """Post processor for Maho M 600E mill @@ -416,7 +417,7 @@ def export(objectslist, filename, argstring): for obj in objectslist: if hasattr(obj, "Comment"): gcode += linenumberify("(" + obj.Comment + ")") - for c in obj.Path.Commands: + for c in PathUtils.getPathWithPlacement(obj).Commands: outstring = [] command = c.Name if command != "G0": diff --git a/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py b/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py index b326bdf13c..793e628fa0 100644 --- a/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py @@ -29,6 +29,7 @@ from __future__ import print_function import Path.Post.UtilsArguments as PostUtilsArguments import Path.Post.UtilsExport as PostUtilsExport + # # The following variables need to be global variables # to keep the PathPostProcessor.load method happy: diff --git a/src/Mod/Path/Path/Post/scripts/rrf_post.py b/src/Mod/Path/Path/Post/scripts/rrf_post.py index 4d09d7b9ba..5dd9c9f158 100644 --- a/src/Mod/Path/Path/Post/scripts/rrf_post.py +++ b/src/Mod/Path/Path/Post/scripts/rrf_post.py @@ -34,6 +34,7 @@ from FreeCAD import Units import Path import Path.Base.Util as PathUtil import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils Revised = "2021-10-21" # Revision date for this file. @@ -533,7 +534,7 @@ def parse(pathobj): if OUTPUT_COMMENTS and OUTPUT_PATH: out += linenumber() + "(Path: " + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outlist = [] command = c.Name outlist.append(command) diff --git a/src/Mod/Path/Path/Post/scripts/smoothie_post.py b/src/Mod/Path/Path/Post/scripts/smoothie_post.py index 0429997475..0ac62b3f5e 100644 --- a/src/Mod/Path/Path/Post/scripts/smoothie_post.py +++ b/src/Mod/Path/Path/Post/scripts/smoothie_post.py @@ -26,6 +26,7 @@ from __future__ import print_function import argparse import datetime import Path.Post.Utils as PostUtils +import PathScripts.PathUtils as PathUtils import FreeCAD from FreeCAD import Units import shlex @@ -399,7 +400,7 @@ def parse(pathobj): # if OUTPUT_COMMENTS: # out += linenumber() + "(" + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name outstring.append(command) diff --git a/src/Mod/Path/Path/Post/scripts/uccnc_post.py b/src/Mod/Path/Path/Post/scripts/uccnc_post.py index 3a70425294..8a65e0babe 100644 --- a/src/Mod/Path/Path/Post/scripts/uccnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/uccnc_post.py @@ -33,6 +33,7 @@ from __future__ import print_function import FreeCAD from FreeCAD import Units import Path +import PathScripts.PathUtils as PathUtils import argparse import datetime @@ -602,7 +603,7 @@ def parse(pathobj): # if OUTPUT_COMMENTS: # out += linenumber() + "(" + pathobj.Label + ")\n" - for c in pathobj.Path.Commands: + for c in PathUtils.getPathWithPlacement(pathobj).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) diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index fb16c65ae6..03c4950148 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -838,3 +838,92 @@ def RtoIJ(startpoint, command): newcommand.Parameters = params return newcommand + +def rotatePath(path, angle, centre): + """ + Rotates Path around given centre vector + Only X and Y is considered + """ + CmdMoveRapid = ["G0", "G00"] + CmdMoveStraight = ["G1", "G01"] + CmdMoveCW = ["G2", "G02"] + CmdMoveCCW = ["G3", "G03"] + CmdDrill = ["G73", "G81", "G82", "G83"] + CmdMoveArc = CmdMoveCW + CmdMoveCCW + CmdMove = CmdMoveStraight + CmdMoveArc + + commands = [] + ang = angle / 180 * math.pi + currX = 0 + currY = 0 + for cmd in path.Commands: + if ( + (cmd.Name in CmdMoveRapid) + or (cmd.Name in CmdMove) + or (cmd.Name in CmdDrill) + ): + params = cmd.Parameters + x = params.get("X") + if x is None: + x = currX + currX = x + y = params.get("Y") + if y is None: + y = currY + currY = y + + # "move" the centre to origin + x = x - centre.x + y = y - centre.y + + # rotation around origin: + nx = x * math.cos(ang) - y * math.sin(ang) + ny = y * math.cos(ang) + x * math.sin(ang) + + # "move" the centre back and update + nx += centre.x + ny += centre.y + + if nx != currX: + params.update({"X": nx}) + if ny != currY: + params.update({"Y": ny}) + + # Arcs need to have the I and J params rotated as well + if cmd.Name in CmdMoveArc: + i = params.get("I") + if i is None: + i = 0 + j = params.get("J") + if j is None: + j = 0 + + ni = i * math.cos(ang) - j * math.sin(ang) + nj = j * math.cos(ang) + i * math.sin(ang) + + if ni != i: + params.update({"I": ni}) + if nj != j: + params.update({"J": nj}) + + cmd.Parameters = params + commands.append(cmd) + newPath = Path.Path(commands) + + return newPath + +def getPathWithPlacement(pathobj): + """ + This function takes the pathobj's Placement Position, along with + the Z rotation and computes a new path. + + A more "complete" version of this function would take the X and Y axis into + account for the rotation. Currently it is limited by the rotatePath + function that was refactored out of the Array Operation. + """ + placement = pathobj.Placement + xAngle, yAngle, zAngle = placement.Rotation.toEulerAngles('XYZ') + if xAngle != 0 or yAngle != 0: + Path.Log.warning("Invalid Path Placement: Rotations about X and Y axes are not currently supported and will be ignored") + commands = rotatePath(pathobj.Path, zAngle, FreeCAD.Vector(0, 0, 0)).Commands + return Path.Path([cm.transform(placement) for cm in commands]) From e74559097b4f25fbb497526e5de703ace092b6e3 Mon Sep 17 00:00:00 2001 From: Christian Mesh Date: Mon, 13 Feb 2023 10:00:19 -0500 Subject: [PATCH 2/4] Allow XYZ rotation in placement applied to path Also fixes an issue with path export where _TempObject (no Placement) would throw an exception. --- src/Mod/Path/Path/Op/Gui/Array.py | 7 ++- src/Mod/Path/PathScripts/PathUtils.py | 89 ++++++++++----------------- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/src/Mod/Path/Path/Op/Gui/Array.py b/src/Mod/Path/Path/Op/Gui/Array.py index 4388ed0135..89c09bf220 100644 --- a/src/Mod/Path/Path/Op/Gui/Array.py +++ b/src/Mod/Path/Path/Op/Gui/Array.py @@ -24,7 +24,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts -from PathScripts.PathUtils import rotatePath +from PathScripts.PathUtils import applyPlacementToPath from Path.Dressup.Utils import toolController from PySide import QtCore import math @@ -412,7 +412,10 @@ class PathArray: ang = 360 if self.copies > 0: ang = self.angle / self.copies * (1 + i) - np = rotatePath(b.Path, ang, self.centre) + + pl = FreeCAD.Placement() + pl.rotate(self.centre, FreeCAD.Vector(0, 0, 1), ang) + np = applyPlacementToPath(pl, b.Path) output += np.toGCode() # return output diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 03c4950148..3a172fc8e7 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -839,11 +839,22 @@ def RtoIJ(startpoint, command): return newcommand -def rotatePath(path, angle, centre): +def getPathWithPlacement(pathobj): """ - Rotates Path around given centre vector - Only X and Y is considered + Applies the rotation, and then postition of the obj's Placement + to the obj's path """ + + if not hasattr(pathobj, "Placement") or pathobj.Path is None: + return pathobj.Path + + return applyPlacementToPath(pathobj.Placement, pathobj.Path) + +def applyPlacementToPath(placement, path): + """ + Applies the rotation, and then postition of the placement to path + """ + CmdMoveRapid = ["G0", "G00"] CmdMoveStraight = ["G1", "G01"] CmdMoveCW = ["G2", "G02"] @@ -853,9 +864,9 @@ def rotatePath(path, angle, centre): CmdMove = CmdMoveStraight + CmdMoveArc commands = [] - ang = angle / 180 * math.pi currX = 0 currY = 0 + currZ = 0 for cmd in path.Commands: if ( (cmd.Name in CmdMoveRapid) @@ -863,67 +874,33 @@ def rotatePath(path, angle, centre): or (cmd.Name in CmdDrill) ): params = cmd.Parameters - x = params.get("X") - if x is None: - x = currX - currX = x - y = params.get("Y") - if y is None: - y = currY - currY = y + currX = x = params.get("X", currX) + currY = y = params.get("Y", currY) + currZ = z = params.get("Z", currZ) - # "move" the centre to origin - x = x - centre.x - y = y - centre.y + x, y, z = placement.Rotation.multVec(FreeCAD.Vector(x, y ,z)) - # rotation around origin: - nx = x * math.cos(ang) - y * math.sin(ang) - ny = y * math.cos(ang) + x * math.sin(ang) - - # "move" the centre back and update - nx += centre.x - ny += centre.y - - if nx != currX: - params.update({"X": nx}) - if ny != currY: - params.update({"Y": ny}) + if x != currX: + params.update({"X": x}) + if y != currY: + params.update({"Y": y}) + if z != currZ: + params.update({"Z": z}) # Arcs need to have the I and J params rotated as well if cmd.Name in CmdMoveArc: - i = params.get("I") - if i is None: - i = 0 - j = params.get("J") - if j is None: - j = 0 + currI = i = params.get("I", 0) + currJ = j = params.get("J", 0) - ni = i * math.cos(ang) - j * math.sin(ang) - nj = j * math.cos(ang) + i * math.sin(ang) + i, j, k = placement.Rotation.multVec(FreeCAD.Vector(i, j, 0)) - if ni != i: - params.update({"I": ni}) - if nj != j: - params.update({"J": nj}) + if currI != i: + params.update({"I": i}) + if currJ != j: + params.update({"J": j}) cmd.Parameters = params - commands.append(cmd) + commands.append(cmd.transform(placement)) newPath = Path.Path(commands) return newPath - -def getPathWithPlacement(pathobj): - """ - This function takes the pathobj's Placement Position, along with - the Z rotation and computes a new path. - - A more "complete" version of this function would take the X and Y axis into - account for the rotation. Currently it is limited by the rotatePath - function that was refactored out of the Array Operation. - """ - placement = pathobj.Placement - xAngle, yAngle, zAngle = placement.Rotation.toEulerAngles('XYZ') - if xAngle != 0 or yAngle != 0: - Path.Log.warning("Invalid Path Placement: Rotations about X and Y axes are not currently supported and will be ignored") - commands = rotatePath(pathobj.Path, zAngle, FreeCAD.Vector(0, 0, 0)).Commands - return Path.Path([cm.transform(placement) for cm in commands]) From feea1340f50a23c51efb3fffb861945049ff3183 Mon Sep 17 00:00:00 2001 From: Christian Mesh Date: Sun, 26 Feb 2023 11:18:07 -0500 Subject: [PATCH 3/4] Integrate getPathWithPlacement into Dressup --- src/Mod/Path/Path/Dressup/Boundary.py | 7 ++++--- src/Mod/Path/Path/Dressup/DogboneII.py | 3 ++- src/Mod/Path/Path/Dressup/Gui/AxisMap.py | 2 +- src/Mod/Path/Path/Dressup/Gui/Dogbone.py | 2 +- src/Mod/Path/Path/Dressup/Gui/Dragknife.py | 2 +- src/Mod/Path/Path/Dressup/Gui/LeadInOut.py | 4 ++-- src/Mod/Path/Path/Dressup/Gui/RampEntry.py | 2 +- src/Mod/Path/Path/Dressup/Gui/ZCorrect.py | 7 ++++--- src/Mod/Path/Path/Dressup/Tags.py | 7 ++++--- 9 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/Path/Dressup/Boundary.py b/src/Mod/Path/Path/Dressup/Boundary.py index 9194b0d6d7..08a4293fd6 100644 --- a/src/Mod/Path/Path/Dressup/Boundary.py +++ b/src/Mod/Path/Path/Dressup/Boundary.py @@ -149,7 +149,8 @@ class PathBoundary: ): return None - if len(self.baseOp.Path.Commands) == 0: + path = PathUtils.getPathWithPlacement(self.baseOp) + if len(path.Commands) == 0: Path.Log.warning("No Path Commands for %s" % self.baseOp.Label) return [] @@ -164,13 +165,13 @@ class PathBoundary: ) self.strG0ZclearanceHeight = Path.Command("G0", {"Z": self.clearanceHeight}) - cmd = self.baseOp.Path.Commands[0] + cmd = path.Commands[0] pos = cmd.Placement.Base # bogus m/c position to create first edge bogusX = True bogusY = True commands = [cmd] lastExit = None - for cmd in self.baseOp.Path.Commands[1:]: + for cmd in path.Commands[1:]: if cmd.Name in Path.Geom.CmdMoveAll: if bogusX: bogusX = "X" not in cmd.Parameters diff --git a/src/Mod/Path/Path/Dressup/DogboneII.py b/src/Mod/Path/Path/Dressup/DogboneII.py index f4fa0243f6..2e4f74bd32 100644 --- a/src/Mod/Path/Path/Dressup/DogboneII.py +++ b/src/Mod/Path/Path/Dressup/DogboneII.py @@ -25,6 +25,7 @@ import FreeCAD import Path import Path.Base.Generator.dogboneII as dogboneII import Path.Base.Language as PathLanguage +import PathScripts.PathUtils as PathUtils import math if False: @@ -294,7 +295,7 @@ class Proxy(object): dressingUpDogbone = hasattr(obj.Base, "BoneBlacklist") if obj.Base and obj.Base.Path and obj.Base.Path.Commands: for i, instr in enumerate( - PathLanguage.Maneuver.FromPath(obj.Base.Path).instr + PathLanguage.Maneuver.FromPath(PathUtils.getPathWithPlacement(obj.Base)).instr ): # Path.Log.debug(f"instr: {instr}") if instr.isMove(): diff --git a/src/Mod/Path/Path/Dressup/Gui/AxisMap.py b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py index 2cd1497a06..8a980d18c6 100644 --- a/src/Mod/Path/Path/Dressup/Gui/AxisMap.py +++ b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py @@ -114,7 +114,7 @@ class ObjectDressup: if obj.Base.isDerivedFrom("Path::Feature"): if obj.Base.Path: if obj.Base.Path.Commands: - pp = obj.Base.Path.Commands + pp = PathUtils.getPathWithPlacement(obj.Base).Commands if len([i for i in pp if i.Name in Path.Geom.CmdMoveArc]) == 0: pathlist = pp else: diff --git a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py index 8dbc31bded..74e85eea41 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py @@ -911,7 +911,7 @@ class ObjectDressup(object): self.length = 0 # boneIserted = False - for (i, thisCommand) in enumerate(obj.Base.Path.Commands): + for (i, thisCommand) in enumerate(PathUtils.getPathWithPlacement(obj.Base).Commands): # if i > 14: # if lastCommand: # commands.append(lastCommand) diff --git a/src/Mod/Path/Path/Dressup/Gui/Dragknife.py b/src/Mod/Path/Path/Dressup/Gui/Dragknife.py index 56f36b8790..783c0a77e5 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Dragknife.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dragknife.py @@ -389,7 +389,7 @@ class ObjectDressup: queue = [] - for curCommand in obj.Base.Path.Commands: + for curCommand in PathUtils.getPathWithPlacement(obj.Base).Commands: replace = None # don't worry about non-move commands, just add to output if curCommand.Name not in movecommands + rapidcommands: diff --git a/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py b/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py index 9ce7e97fde..e965ab257d 100644 --- a/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py @@ -189,7 +189,7 @@ class ObjectDressup: + "\n" ) obj.Length = 0.1 - self.wire, self.rapids = Path.Geom.wireForPath(obj.Base.Path) + self.wire, self.rapids = Path.Geom.wireForPath(PathUtils.getPathWithPlacement(obj.Base)) obj.Path = self.generateLeadInOutCurve(obj) def getDirectionOfPath(self, obj): @@ -557,7 +557,7 @@ class ObjectDressup: layers = [] # Read in all commands - for curCommand in obj.Base.Path.Commands: + for curCommand in PathUtils.getPathWithPlacement(obj.Base).Commands: Path.Log.debug("CurCMD: {}".format(curCommand)) if curCommand.Name not in movecommands + rapidcommands: # Don't worry about non-move commands, just add to output diff --git a/src/Mod/Path/Path/Dressup/Gui/RampEntry.py b/src/Mod/Path/Path/Dressup/Gui/RampEntry.py index 820f9516eb..697dd87501 100644 --- a/src/Mod/Path/Path/Dressup/Gui/RampEntry.py +++ b/src/Mod/Path/Path/Dressup/Gui/RampEntry.py @@ -222,7 +222,7 @@ class ObjectDressup: self.angle = obj.Angle self.method = obj.Method - self.wire, self.rapids = Path.Geom.wireForPath(obj.Base.Path) + self.wire, self.rapids = Path.Geom.wireForPath(PathUtils.getPathWithPlacement(obj.Base)) if self.method in ["RampMethod1", "RampMethod2", "RampMethod3"]: self.outedges = self.generateRamps() else: diff --git a/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py b/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py index c591a02867..baa9fc0ee8 100644 --- a/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py +++ b/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py @@ -155,7 +155,7 @@ class ObjectDressup: curveD = obj.ArcInterpolate.Value if obj.interpSurface.isNull(): # No valid probe data. return unchanged path - obj.Path = obj.Base.Path + obj.Path = PathUtils.getPathWithPlacement(obj.Base) return surface = obj.interpSurface.toNurbs().Faces[0].Surface @@ -163,8 +163,9 @@ class ObjectDressup: if obj.Base: if obj.Base.isDerivedFrom("Path::Feature"): if obj.Base.Path: - if obj.Base.Path.Commands: - pathlist = obj.Base.Path.Commands + path = PathUtils.getPathWithPlacement(obj.Base) + if path.Commands: + pathlist = path.Commands newcommandlist = [] currLocation = {"X": 0, "Y": 0, "Z": 0, "F": 0} diff --git a/src/Mod/Path/Path/Dressup/Tags.py b/src/Mod/Path/Path/Dressup/Tags.py index bc4adec927..f951724909 100644 --- a/src/Mod/Path/Path/Dressup/Tags.py +++ b/src/Mod/Path/Path/Dressup/Tags.py @@ -675,7 +675,8 @@ class PathData: def __init__(self, obj): Path.Log.track(obj.Base.Name) self.obj = obj - self.wire, rapid = Path.Geom.wireForPath(obj.Base.Path) + path = PathUtils.getPathWithPlacement(obj.Base) + self.wire, rapid = Path.Geom.wireForPath(path) self.rapid = _RapidEdges(rapid) if self.wire: self.edges = self.wire.Edges @@ -1256,7 +1257,7 @@ class ObjectTagDressup: if not self.tags: Path.Log.debug("execute - no tags") - obj.Path = obj.Base.Path + obj.Path = PathUtils.getPathWithPlacement(obj.Base) return try: @@ -1265,7 +1266,7 @@ class ObjectTagDressup: Path.Log.error( "processing tags failed clearing all tags ... '%s'" % (e.args[0]) ) - obj.Path = obj.Base.Path + obj.Path = PathUtils.getPathWithPlacement(obj.Base) # update disabled in case there are some additional ones disabled = copy.copy(self.obj.Disabled) From dd31b7f0db67cee57bb5cb1cef19166b04bdaff4 Mon Sep 17 00:00:00 2001 From: Christian Mesh Date: Mon, 13 Mar 2023 07:49:20 -0400 Subject: [PATCH 4/4] Update Array/Copy to handle placement correctly --- src/Mod/Path/Path/Op/Gui/Array.py | 10 +++++----- src/Mod/Path/Path/Op/Gui/Copy.py | 2 ++ src/Mod/Path/Path/Op/Gui/SimpleCopy.py | 5 ++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/Path/Op/Gui/Array.py b/src/Mod/Path/Path/Op/Gui/Array.py index 89c09bf220..0031a1adba 100644 --- a/src/Mod/Path/Path/Op/Gui/Array.py +++ b/src/Mod/Path/Path/Op/Gui/Array.py @@ -24,7 +24,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts -from PathScripts.PathUtils import applyPlacementToPath +import PathScripts.PathUtils as PathUtils from Path.Dressup.Utils import toolController from PySide import QtCore import math @@ -349,7 +349,7 @@ class PathArray: for b in base: pl = FreeCAD.Placement() pl.move(pos) - np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + np = Path.Path([cm.transform(pl) for cm in PathUtils.getPathWithPlacement(b).Commands]) output += np.toGCode() elif self.arrayType == "Linear2D": @@ -376,7 +376,7 @@ class PathArray: if not (i == 0 and j == 0): pl.move(pos) np = Path.Path( - [cm.transform(pl) for cm in b.Path.Commands] + [cm.transform(pl) for cm in PathUtils.getPathWithPlacement(b).Commands] ) output += np.toGCode() else: @@ -402,7 +402,7 @@ class PathArray: if not (i == 0 and j == 0): pl.move(pos) np = Path.Path( - [cm.transform(pl) for cm in b.Path.Commands] + [cm.transform(pl) for cm in PathUtils.getPathWithPlacement(b).Commands] ) output += np.toGCode() # Eif @@ -415,7 +415,7 @@ class PathArray: pl = FreeCAD.Placement() pl.rotate(self.centre, FreeCAD.Vector(0, 0, 1), ang) - np = applyPlacementToPath(pl, b.Path) + np = PathUtils.applyPlacementToPath(pl, PathUtils.getPathWithPlacement(b)) output += np.toGCode() # return output diff --git a/src/Mod/Path/Path/Op/Gui/Copy.py b/src/Mod/Path/Path/Op/Gui/Copy.py index 939da3c488..001591ba5b 100644 --- a/src/Mod/Path/Path/Op/Gui/Copy.py +++ b/src/Mod/Path/Path/Op/Gui/Copy.py @@ -63,6 +63,8 @@ class ObjectPathCopy: obj.ToolController = obj.Base.ToolController if obj.Base.Path: obj.Path = obj.Base.Path.copy() + if obj.Base.Placement: + obj.Placement = obj.Base.Placement class ViewProviderPathCopy: diff --git a/src/Mod/Path/Path/Op/Gui/SimpleCopy.py b/src/Mod/Path/Path/Op/Gui/SimpleCopy.py index 64f2879108..9edf59a619 100644 --- a/src/Mod/Path/Path/Op/Gui/SimpleCopy.py +++ b/src/Mod/Path/Path/Op/Gui/SimpleCopy.py @@ -68,10 +68,13 @@ class CommandPathSimpleCopy: FreeCAD.ActiveDocument.openTransaction("Simple Copy") FreeCADGui.doCommand( - "srcpath = FreeCADGui.Selection.getSelectionEx()[0].Object.Path\n" + "srcobj = FreeCADGui.Selection.getSelectionEx()[0].Object\n" ) FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand( + "srcpath = PathScripts.PathUtils.getPathWithPlacement(srcobj)\n" + ) FreeCADGui.addModule("Path.Op.Custom") FreeCADGui.doCommand( 'obj = Path.Op.Custom.Create("' + selection[0].Name + '_SimpleCopy")'