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.
This commit is contained in:
Christian Mesh
2023-02-12 11:50:02 -05:00
parent 3937a746b8
commit 55f9e4d1dd
27 changed files with 151 additions and 95 deletions

View File

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

View File

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

View File

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

View File

@@ -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 = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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