Merge pull request #13668 from Ondsel-Development/RefactorPostCommand
Refactor post command
This commit is contained in:
@@ -147,6 +147,7 @@ SET(PathPythonPostScripts_SRCS
|
||||
Path/Post/scripts/fanuc_post.py
|
||||
Path/Post/scripts/fangling_post.py
|
||||
Path/Post/scripts/gcode_pre.py
|
||||
Path/Post/scripts/generic_post.py
|
||||
Path/Post/scripts/grbl_post.py
|
||||
Path/Post/scripts/heidenhain_post.py
|
||||
Path/Post/scripts/jtech_post.py
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from Path.Post.Processor import PostProcessor
|
||||
from Path.Post.Processor import PostProcessorFactory # PostProcessor,
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import FreeCAD
|
||||
@@ -112,7 +112,9 @@ class ObjectJob:
|
||||
"App::PropertyFile",
|
||||
"PostProcessorOutputFile",
|
||||
"Output",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The G-code output file for this project"),
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The G-code output file for this project"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
@@ -549,7 +551,7 @@ class ObjectJob:
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop == "PostProcessor" and obj.PostProcessor:
|
||||
processor = PostProcessor.load(obj.PostProcessor)
|
||||
processor = PostProcessorFactory.get_post_processor(obj, obj.PostProcessor)
|
||||
self.tooltip = processor.tooltip
|
||||
self.tooltipArgs = processor.tooltipArgs
|
||||
|
||||
|
||||
@@ -27,21 +27,17 @@ Processor entries in PathJob """
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
import Path.Base.Util as PathUtil
|
||||
import Path.Main.Job as PathJob
|
||||
from PathScripts import PathUtils
|
||||
from Path.Post.Utils import FilenameGenerator
|
||||
import os
|
||||
import re
|
||||
|
||||
from Path.Post.Processor import PostProcessor
|
||||
from Path.Post.Processor import PostProcessor, PostProcessorFactory
|
||||
from PySide import QtCore, QtGui
|
||||
from datetime import datetime
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
LOG_MODULE = Path.Log.thisModule()
|
||||
|
||||
debugmodule = False
|
||||
if debugmodule:
|
||||
DEBUG = False
|
||||
if DEBUG:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
@@ -51,372 +47,29 @@ else:
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class _TempObject:
|
||||
Path = None
|
||||
Name = "Fixture"
|
||||
InList = []
|
||||
Label = "Fixture"
|
||||
|
||||
|
||||
def processFileNameSubstitutions(
|
||||
job,
|
||||
subpartname,
|
||||
sequencenumber,
|
||||
outputpath,
|
||||
filename,
|
||||
ext,
|
||||
):
|
||||
"""Process any substitutions in the outputpath or filename."""
|
||||
|
||||
# The following section allows substitution within the path part
|
||||
Path.Log.track(f"path before substitution: {outputpath}")
|
||||
|
||||
if "%D" in outputpath: # Directory of active document
|
||||
D = FreeCAD.ActiveDocument.FileName
|
||||
if D:
|
||||
D = os.path.dirname(D)
|
||||
# in case the document is in the current working directory
|
||||
if not D:
|
||||
D = "."
|
||||
else:
|
||||
FreeCAD.Console.PrintError(
|
||||
"Please save document in order to resolve output path!\n"
|
||||
)
|
||||
return None
|
||||
outputpath = outputpath.replace("%D", D)
|
||||
|
||||
if "%M" in outputpath:
|
||||
M = FreeCAD.getUserMacroDir()
|
||||
outputpath = outputpath.replace("%M", M)
|
||||
|
||||
# Use the file label
|
||||
if "%d" in outputpath:
|
||||
d = FreeCAD.ActiveDocument.Label
|
||||
outputpath = outputpath.replace("%d", d)
|
||||
|
||||
# Use the name of the active job object
|
||||
if "%j" in outputpath:
|
||||
j = job.Label
|
||||
outputpath = outputpath.replace("%j", j)
|
||||
|
||||
Path.Log.track(f"path after substitution: {outputpath}")
|
||||
|
||||
# The following section allows substitution within the filename part
|
||||
Path.Log.track(f"filename before substitution: {filename}")
|
||||
|
||||
# Use the file label
|
||||
if "%d" in filename:
|
||||
d = FreeCAD.ActiveDocument.Label
|
||||
filename = filename.replace("%d", d)
|
||||
|
||||
# Use the name of the active job object
|
||||
if "%j" in filename:
|
||||
j = job.Label
|
||||
filename = filename.replace("%j", j)
|
||||
|
||||
# Use the sequence number if explicitly called
|
||||
if "%S" in filename:
|
||||
j = job.Label
|
||||
filename = filename.replace("%S", str(sequencenumber))
|
||||
|
||||
# This section handles unique names for splitting output
|
||||
if job.SplitOutput:
|
||||
Path.Log.track()
|
||||
if "%T" in filename and job.OrderOutputBy == "Tool":
|
||||
filename = filename.replace("%T", subpartname)
|
||||
|
||||
if "%t" in filename and job.OrderOutputBy == "Tool":
|
||||
filename = filename.replace("%t", subpartname)
|
||||
|
||||
if "%W" in filename and job.OrderOutputBy == "Fixture":
|
||||
filename = filename.replace("%W", subpartname)
|
||||
|
||||
if "%O" in filename and job.OrderOutputBy == "Operation":
|
||||
filename = filename.replace("%O", subpartname)
|
||||
|
||||
if (
|
||||
"%S" in filename
|
||||
): # We always add a sequence number but the user can say where
|
||||
filename = filename.replace("%S", str(sequencenumber))
|
||||
else:
|
||||
filename = f"{filename}-{sequencenumber}"
|
||||
|
||||
Path.Log.track(f"filename after substitution: {filename}")
|
||||
|
||||
if not ext:
|
||||
ext = ".nc"
|
||||
Path.Log.track(f"file extension: {ext}")
|
||||
|
||||
fullPath = f"{outputpath}{os.path.sep}{filename}{ext}"
|
||||
|
||||
Path.Log.track(f"full filepath: {fullPath}")
|
||||
return fullPath
|
||||
|
||||
|
||||
def resolveFileName(job, subpartname, sequencenumber):
|
||||
"""Generate the file name to use as output."""
|
||||
|
||||
Path.Log.track(subpartname, sequencenumber)
|
||||
|
||||
validPathSubstitutions = ["D", "d", "M", "j"]
|
||||
validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"]
|
||||
|
||||
# Look for preference default
|
||||
outputpath, filename = os.path.split(Path.Preferences.defaultOutputFile())
|
||||
filename, ext = os.path.splitext(filename)
|
||||
|
||||
# Override with document default if it exists
|
||||
if job.PostProcessorOutputFile:
|
||||
candidateOutputPath, candidateFilename = os.path.split(
|
||||
job.PostProcessorOutputFile
|
||||
)
|
||||
|
||||
if candidateOutputPath:
|
||||
outputpath = candidateOutputPath
|
||||
|
||||
if candidateFilename:
|
||||
filename, ext = os.path.splitext(candidateFilename)
|
||||
|
||||
# Strip any invalid substitutions from the outputpath
|
||||
for match in re.findall("%(.)", outputpath):
|
||||
if match not in validPathSubstitutions:
|
||||
outputpath = outputpath.replace(f"%{match}", "")
|
||||
|
||||
# if nothing else, use current directory
|
||||
if not outputpath:
|
||||
outputpath = "."
|
||||
|
||||
# Strip any invalid substitutions from the filename
|
||||
for match in re.findall("%(.)", filename):
|
||||
if match not in validFilenameSubstitutions:
|
||||
filename = filename.replace(f"%{match}", "")
|
||||
|
||||
# if no filename, use the active document label
|
||||
if not filename:
|
||||
filename = FreeCAD.ActiveDocument.Label
|
||||
|
||||
# if no extension, use something sensible
|
||||
if not ext:
|
||||
ext = ".nc"
|
||||
|
||||
# By now we should have a sanitized path, filename and extension to work with
|
||||
Path.Log.track(f"path: {outputpath} name: {filename} ext: {ext}")
|
||||
|
||||
fullPath = processFileNameSubstitutions(
|
||||
job,
|
||||
subpartname,
|
||||
sequencenumber,
|
||||
outputpath,
|
||||
filename,
|
||||
ext,
|
||||
)
|
||||
|
||||
# This section determines whether user interaction is necessary
|
||||
policy = Path.Preferences.defaultOutputPolicy()
|
||||
|
||||
openDialog = policy == "Open File Dialog"
|
||||
# if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
|
||||
# # Either the entire filename resolves into a directory or the parent
|
||||
# # directory doesn't exist.
|
||||
# # Either way I don't know what to do - ask for help
|
||||
# openDialog = True
|
||||
|
||||
if not FreeCAD.GuiUp: # if testing, or scripting, never open dialog.
|
||||
policy = "Append Unique ID on conflict"
|
||||
openDialog = False
|
||||
|
||||
if os.path.isfile(fullPath) and not openDialog:
|
||||
if policy == "Open File Dialog on conflict":
|
||||
openDialog = True
|
||||
elif policy == "Append Unique ID on conflict":
|
||||
fn, ext = os.path.splitext(fullPath)
|
||||
nr = fn[-3:]
|
||||
n = 1
|
||||
if nr.isdigit():
|
||||
n = int(nr)
|
||||
while os.path.isfile(f"{fn}{n:03d}{ext}"):
|
||||
n = n + 1
|
||||
fullPath = f"{fn}{n:03d}{ext}"
|
||||
|
||||
if openDialog:
|
||||
requestedfile = QtGui.QFileDialog.getSaveFileName(
|
||||
QtGui.QApplication.activeWindow(), "Output File", fullPath
|
||||
)
|
||||
if requestedfile[0]:
|
||||
fullPath = requestedfile[0]
|
||||
else:
|
||||
fullPath = None
|
||||
|
||||
if fullPath:
|
||||
# remove any unused substitution strings:
|
||||
for s in validPathSubstitutions + validFilenameSubstitutions:
|
||||
fullPath = fullPath.replace(f"%{s}", "")
|
||||
fullPath = os.path.normpath(fullPath)
|
||||
Path.Log.track(fullPath)
|
||||
|
||||
return fullPath
|
||||
|
||||
|
||||
def fixtureSetup(order, fixture, job):
|
||||
"""Convert a Fixure setting to _TempObject instance with a G0 move to a
|
||||
safe height every time the fixture coordinate system change. Skip
|
||||
the move for first fixture, to avoid moving before tool and tool
|
||||
height compensation is enabled.
|
||||
|
||||
"""
|
||||
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(fixture)
|
||||
fobj.Path = Path.Path([c1])
|
||||
# Avoid any tool move after G49 in preamble and before tool change
|
||||
# and G43 in case tool height compensation is in use, to avoid
|
||||
# dangerous move without tool compesation.
|
||||
if order != 0:
|
||||
c2 = Path.Command(
|
||||
"G0 Z"
|
||||
+ str(
|
||||
job.Stock.Shape.BoundBox.ZMax
|
||||
+ job.SetupSheet.ClearanceHeightOffset.Value
|
||||
)
|
||||
)
|
||||
fobj.Path.addCommands(c2)
|
||||
fobj.InList.append(job)
|
||||
return fobj
|
||||
|
||||
|
||||
def buildPostList(job):
|
||||
"""Takes the job and determines the specific objects and order to
|
||||
postprocess Returns a list of objects which can be passed to
|
||||
exportObjectsWith() for final posting."""
|
||||
wcslist = job.Fixtures
|
||||
orderby = job.OrderOutputBy
|
||||
|
||||
postlist = []
|
||||
|
||||
if orderby == "Fixture":
|
||||
Path.Log.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
|
||||
sublist = [fixtureSetup(index, f, job)]
|
||||
|
||||
# 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:
|
||||
sublist.append(tc)
|
||||
Path.Log.debug(f"Appending TC: {tc.Name}")
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append((f, sublist))
|
||||
|
||||
elif orderby == "Tool":
|
||||
Path.Log.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.
|
||||
|
||||
toolstring = "None"
|
||||
currTool = None
|
||||
|
||||
# Build the fixture list
|
||||
fixturelist = []
|
||||
for index, f in enumerate(wcslist):
|
||||
# create an object to serve as the fixture path
|
||||
fixturelist.append(fixtureSetup(index, f, job))
|
||||
|
||||
# Now generate the gcode
|
||||
curlist = [] # list of ops for tool, will repeat for each fixture
|
||||
sublist = [] # list of ops for output splitting
|
||||
|
||||
Path.Log.track(job.PostProcessorOutputFile)
|
||||
for idx, obj in enumerate(job.Operations.Group):
|
||||
Path.Log.track(obj.Label)
|
||||
|
||||
# check if the operation is active
|
||||
if not getattr(obj, "Active", True):
|
||||
Path.Log.track()
|
||||
continue
|
||||
|
||||
# Determine the proper string for the Op's TC
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is None:
|
||||
tcstring = "None"
|
||||
elif "%T" in job.PostProcessorOutputFile:
|
||||
tcstring = f"{tc.ToolNumber}"
|
||||
else:
|
||||
tcstring = re.sub(r"[^\w\d-]", "_", tc.Label)
|
||||
Path.Log.track(toolstring)
|
||||
|
||||
if tc is None or tc.ToolNumber == currTool:
|
||||
curlist.append(obj)
|
||||
elif tc.ToolNumber != currTool and currTool is None: # first TC
|
||||
sublist.append(tc)
|
||||
curlist.append(obj)
|
||||
currTool = tc.ToolNumber
|
||||
toolstring = tcstring
|
||||
|
||||
elif tc.ToolNumber != currTool and currTool is not None: # TC
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
postlist.append((toolstring, sublist))
|
||||
sublist = [tc]
|
||||
curlist = [obj]
|
||||
currTool = tc.ToolNumber
|
||||
toolstring = tcstring
|
||||
|
||||
if idx == len(job.Operations.Group) - 1: # Last operation.
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
|
||||
postlist.append((toolstring, sublist))
|
||||
|
||||
elif orderby == "Operation":
|
||||
Path.Log.debug("Ordering by Operation")
|
||||
# Order by operation means ops are done in each fixture in
|
||||
# sequence.
|
||||
currTool = None
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in job.Operations.Group:
|
||||
|
||||
# check if the operation is active
|
||||
if not getattr(obj, "Active", True):
|
||||
continue
|
||||
|
||||
sublist = []
|
||||
Path.Log.debug(f"obj: {obj.Name}")
|
||||
|
||||
for index, f in enumerate(wcslist):
|
||||
sublist.append(fixtureSetup(index, f, job))
|
||||
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((obj.Label, sublist))
|
||||
|
||||
if job.SplitOutput:
|
||||
Path.Log.track()
|
||||
return postlist
|
||||
|
||||
Path.Log.track()
|
||||
finalpostlist = [
|
||||
("allitems", [item for slist in postlist for item in slist[1]])
|
||||
]
|
||||
return finalpostlist
|
||||
def _resolve_post_processor_name(job):
|
||||
Path.Log.debug("_resolve_post_processor_name()")
|
||||
if job.PostProcessor:
|
||||
valid_name = job.PostProcessor
|
||||
elif Path.Preferences.defaultPostProcessor():
|
||||
valid_name = Path.Preferences.defaultPostProcessor()
|
||||
elif FreeCAD.GuiUp:
|
||||
valid_name = (
|
||||
DlgSelectPostProcessor().exec_()
|
||||
) # Ensure DlgSelectPostProcessor is defined
|
||||
else:
|
||||
valid_name = None
|
||||
|
||||
if valid_name and PostProcessor.exists(valid_name):
|
||||
return valid_name
|
||||
else:
|
||||
raise ValueError(f"Post processor not identified.")
|
||||
|
||||
|
||||
class DlgSelectPostProcessor:
|
||||
"""Provide user with list of available and active post processor
|
||||
choices."""
|
||||
|
||||
def __init__(self):
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgSelectPostProcessor.ui")
|
||||
firstItem = None
|
||||
@@ -454,18 +107,6 @@ class DlgSelectPostProcessor:
|
||||
|
||||
|
||||
class CommandPathPost:
|
||||
subpart = 1
|
||||
|
||||
def resolvePostProcessor(self, job):
|
||||
if hasattr(job, "PostProcessor"):
|
||||
post = Path.Preferences.defaultPostProcessor()
|
||||
if job.PostProcessor:
|
||||
post = job.PostProcessor
|
||||
if post and PostProcessor.exists(post):
|
||||
return post
|
||||
dlg = DlgSelectPostProcessor()
|
||||
return dlg.exec_()
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": "CAM_Post",
|
||||
@@ -475,144 +116,97 @@ class CommandPathPost:
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
if FreeCADGui.Selection.getCompleteSelection():
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
selected = FreeCADGui.Selection.getSelectionEx()
|
||||
if len(selected) != 1:
|
||||
return False
|
||||
|
||||
return False
|
||||
selected_object = selected[0].Object
|
||||
self.candidate = PathUtils.findParentJob(selected_object)
|
||||
|
||||
def exportObjectsWith(self, objs, partname, job, sequence, extraargs=None):
|
||||
Path.Log.track(extraargs)
|
||||
# check if the user has a project and has set the default post and
|
||||
# output filename
|
||||
# extraargs can be passed in at this time
|
||||
Path.Log.track(partname, sequence)
|
||||
Path.Log.track(objs)
|
||||
return self.candidate is not None
|
||||
|
||||
# partname = objs[0]
|
||||
# slist = objs[1]
|
||||
Path.Log.track(objs, partname)
|
||||
def _write_file(self, filename, gcode, policy):
|
||||
if policy == "Open File Dialog":
|
||||
dlg = QtGui.QFileDialog()
|
||||
dlg.setFileMode(QtGui.QFileDialog.FileMode.AnyFile)
|
||||
dlg.setAcceptMode(QtGui.QFileDialog.AcceptMode.AcceptSave)
|
||||
dlg.setDirectory(os.path.dirname(filename))
|
||||
dlg.selectFile(os.path.basename(filename))
|
||||
if dlg.exec_():
|
||||
filename = dlg.selectedFiles()[0]
|
||||
Path.Log.debug(filename)
|
||||
with open(filename, "w") as f:
|
||||
f.write(gcode)
|
||||
|
||||
postArgs = Path.Preferences.defaultPostProcessorArgs()
|
||||
if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs:
|
||||
postArgs = job.PostProcessorArgs
|
||||
elif hasattr(job, "PostProcessor") and job.PostProcessor:
|
||||
postArgs = ""
|
||||
elif policy == "Append Unique ID on conflict":
|
||||
while os.path.isfile(filename):
|
||||
base, ext = os.path.splitext(filename)
|
||||
filename = f"{base}-1{ext}"
|
||||
with open(filename, "w") as f:
|
||||
f.write(gcode)
|
||||
|
||||
if extraargs is not None:
|
||||
postArgs += f" {extraargs}"
|
||||
|
||||
Path.Log.track(postArgs)
|
||||
|
||||
postname = self.resolvePostProcessor(job)
|
||||
# filename = "-"
|
||||
filename = resolveFileName(job, partname, sequence)
|
||||
# if postname and needFilename:
|
||||
# filename = resolveFileName(job)
|
||||
|
||||
if postname and filename:
|
||||
print("post: %s(%s, %s)" % (postname, filename, postArgs))
|
||||
processor = PostProcessor.load(postname)
|
||||
gcode = processor.export(objs, filename, postArgs)
|
||||
return (False, gcode, filename)
|
||||
else:
|
||||
return (True, "", filename)
|
||||
elif policy == "Open File Dialog on conflict":
|
||||
if os.path.isfile(filename):
|
||||
dlg = QtGui.QFileDialog()
|
||||
dlg.setFileMode(QtGui.QFileDialog.FileMode.AnyFile)
|
||||
dlg.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||
dlg.setDirectory(os.path.dirname(filename))
|
||||
dlg.selectFile(os.path.basename(filename))
|
||||
if dlg.exec_():
|
||||
filename = dlg.selectedFiles()[0]
|
||||
Path.Log.debug(filename)
|
||||
with open(filename, "w") as f:
|
||||
f.write(gcode)
|
||||
else:
|
||||
with open(filename, "w") as f:
|
||||
f.write(gcode)
|
||||
else: # Overwrite
|
||||
with open(filename, "w") as f:
|
||||
f.write(gcode)
|
||||
FreeCAD.Console.PrintMessage(f"File written to {filename}\n")
|
||||
|
||||
def Activated(self):
|
||||
Path.Log.track()
|
||||
FreeCAD.ActiveDocument.openTransaction("Post Process the Selected path(s)")
|
||||
FreeCADGui.addModule("Path.Post.Command")
|
||||
"""
|
||||
Handles the activation of post processing, initiating the process based
|
||||
on user selection and document context.
|
||||
"""
|
||||
Path.Log.debug(self.candidate.Name)
|
||||
FreeCAD.ActiveDocument.openTransaction("Post Process the Selected Job")
|
||||
|
||||
# Attempt to figure out what the user wants to post-process
|
||||
# If a job is selected, post that.
|
||||
# If there's only one job in a document, post it.
|
||||
# If a user has selected a subobject of a job, post the job.
|
||||
# If multiple jobs and can't guess, ask them.
|
||||
postprocessor_name = _resolve_post_processor_name(self.candidate)
|
||||
Path.Log.debug(f"Post Processor: {postprocessor_name}")
|
||||
|
||||
selected = FreeCADGui.Selection.getSelectionEx()
|
||||
if len(selected) > 1:
|
||||
FreeCAD.Console.PrintError(
|
||||
"Please select a single job or other path object\n"
|
||||
)
|
||||
return
|
||||
elif len(selected) == 1:
|
||||
sel = selected[0].Object
|
||||
if sel.Name[:3] == "Job":
|
||||
job = sel
|
||||
elif hasattr(sel, "Path"):
|
||||
try:
|
||||
job = PathUtils.findParentJob(sel)
|
||||
except Exception:
|
||||
job = None
|
||||
else:
|
||||
job = None
|
||||
|
||||
if job is None:
|
||||
targetlist = []
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if hasattr(o, "Proxy"):
|
||||
if isinstance(o.Proxy, PathJob.ObjectJob):
|
||||
targetlist.append(o.Label)
|
||||
Path.Log.debug(f"Possible post objects: {targetlist}")
|
||||
if len(targetlist) > 1:
|
||||
jobname, result = QtGui.QInputDialog.getItem(
|
||||
None, translate("Path", "Choose a Path Job"), None, targetlist
|
||||
)
|
||||
|
||||
if result is False:
|
||||
return
|
||||
else:
|
||||
jobname = targetlist[0]
|
||||
job = FreeCAD.ActiveDocument.getObject(jobname)
|
||||
|
||||
Path.Log.debug(f"about to postprocess job: {job.Name}")
|
||||
|
||||
postlist = buildPostList(job)
|
||||
# filename = resolveFileName(job, "allitems", 0)
|
||||
|
||||
filenames = []
|
||||
|
||||
success = True
|
||||
finalgcode = ""
|
||||
for idx, section in enumerate(postlist):
|
||||
partname = section[0]
|
||||
sublist = section[1]
|
||||
|
||||
result, gcode, name = self.exportObjectsWith(sublist, partname, job, idx)
|
||||
filenames.append(name)
|
||||
Path.Log.track(result, gcode, name)
|
||||
|
||||
if name is None:
|
||||
success = False
|
||||
else:
|
||||
finalgcode += gcode
|
||||
|
||||
# if job.SplitOutput:
|
||||
# for idx, sublist in enumerate(postlist): # name, slist in postlist:
|
||||
# result = self.exportObjectsWith(sublist[1], sublist[0], job, idx)
|
||||
|
||||
# if result is None:
|
||||
# success = False
|
||||
# else:
|
||||
# gcode += result
|
||||
|
||||
# else:
|
||||
# finalpostlist = [item for (_, slist) in postlist for item in slist]
|
||||
# gcode = self.exportObjectsWith(finalpostlist, "allitems", job, 1)
|
||||
# success = gcode is not None
|
||||
|
||||
Path.Log.track(success)
|
||||
if success:
|
||||
if hasattr(job, "LastPostProcessDate"):
|
||||
job.LastPostProcessDate = str(datetime.now())
|
||||
if hasattr(job, "LastPostProcessOutput"):
|
||||
job.LastPostProcessOutput = " \n".join(filenames)
|
||||
Path.Log.track(job.LastPostProcessOutput)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
else:
|
||||
if not postprocessor_name:
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
return
|
||||
|
||||
# get a postprocessor
|
||||
postprocessor = PostProcessorFactory.get_post_processor(
|
||||
self.candidate, postprocessor_name
|
||||
)
|
||||
|
||||
post_data = postprocessor.export()
|
||||
if not post_data:
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
return
|
||||
|
||||
policy = Path.Preferences.defaultOutputPolicy()
|
||||
generator = FilenameGenerator(job=self.candidate)
|
||||
generated_filename = generator.generate_filenames()
|
||||
|
||||
for item in post_data:
|
||||
subpart, gcode = item
|
||||
|
||||
# get a name for the file
|
||||
subpart = "" if subpart == "allitems" else subpart
|
||||
Path.Log.debug(subpart)
|
||||
generator.set_subpartname(subpart)
|
||||
fname = next(generated_filename)
|
||||
|
||||
# write the results to the file
|
||||
self._write_file(fname, gcode, policy)
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
|
||||
@@ -19,62 +19,327 @@
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import Path
|
||||
import sys
|
||||
"""
|
||||
The base classes for post processors in CAM workbench.
|
||||
"""
|
||||
from PySide import QtCore, QtGui
|
||||
from importlib import reload
|
||||
import FreeCAD
|
||||
import Path
|
||||
import Path.Base.Util as PathUtil
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
debug = False
|
||||
if debug:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
class _TempObject:
|
||||
Path = None
|
||||
Name = "Fixture"
|
||||
InList = []
|
||||
Label = "Fixture"
|
||||
|
||||
|
||||
class PostProcessorFactory:
|
||||
"""Factory class for creating post processors."""
|
||||
|
||||
@staticmethod
|
||||
def get_post_processor(job, postname):
|
||||
# Log initial debug message
|
||||
Path.Log.debug("PostProcessorFactory.get_post_processor()")
|
||||
|
||||
# Posts have to be in a place we can find them
|
||||
syspath = sys.path
|
||||
paths = Path.Preferences.searchPathsPost()
|
||||
paths.extend(sys.path)
|
||||
|
||||
module_name = f"{postname}_post"
|
||||
class_name = postname.title()
|
||||
|
||||
# Iterate all the paths to find the module
|
||||
for path in paths:
|
||||
module_path = os.path.join(path, f"{module_name}.py")
|
||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
||||
|
||||
if spec and spec.loader:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
Path.Log.debug(f"found module {module_name} at {module_path}")
|
||||
|
||||
except (FileNotFoundError, ImportError, ModuleNotFoundError):
|
||||
continue
|
||||
|
||||
try:
|
||||
PostClass = getattr(module, class_name)
|
||||
return PostClass(job)
|
||||
except AttributeError:
|
||||
# Return an instance of WrapperPost if no valid module is found
|
||||
Path.Log.debug(f"Post processor {postname} is a script")
|
||||
return WrapperPost(job, module_path)
|
||||
|
||||
|
||||
class PostProcessor:
|
||||
"""Base Class. All postprocessors should inherit from this class."""
|
||||
|
||||
def __init__(self, job, tooltip, tooltipargs, units, *args, **kwargs):
|
||||
self._tooltip = tooltip
|
||||
self._tooltipargs = tooltipargs
|
||||
self._units = units
|
||||
self._job = job
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
@classmethod
|
||||
def exists(cls, processor):
|
||||
return processor in Path.Preferences.allAvailablePostProcessors()
|
||||
|
||||
@classmethod
|
||||
def load(cls, processor):
|
||||
Path.Log.track(processor)
|
||||
syspath = sys.path
|
||||
paths = Path.Preferences.searchPathsPost()
|
||||
paths.extend(sys.path)
|
||||
sys.path = paths
|
||||
def export(self):
|
||||
raise NotImplementedError("Subclass must implement abstract method")
|
||||
|
||||
postname = processor + "_post"
|
||||
namespace = {}
|
||||
@property
|
||||
def tooltip(self):
|
||||
"""Get the tooltip text for the post processor."""
|
||||
raise NotImplementedError("Subclass must implement abstract method")
|
||||
# return self._tooltip
|
||||
|
||||
# can't modify function local scope with exec in python3
|
||||
exec("import %s as current_post" % postname, namespace)
|
||||
current_post = namespace["current_post"]
|
||||
@property
|
||||
def tooltipArgs(self):
|
||||
"""Get the tooltip arguments for the post processor."""
|
||||
raise NotImplementedError("Subclass must implement abstract method")
|
||||
# return self._tooltipargs
|
||||
|
||||
# make sure the script is reloaded if it was previously loaded
|
||||
# should the script have been imported for the first time above
|
||||
# then the initialization code of the script gets executed twice
|
||||
# resulting in 2 load messages if the script outputs one of those.
|
||||
@property
|
||||
def units(self):
|
||||
"""Get the units used by the post processor."""
|
||||
return self._units
|
||||
|
||||
exec("reload(%s)" % "current_post")
|
||||
def _buildPostList(self):
|
||||
"""
|
||||
determines the specific objects and order to
|
||||
postprocess Returns a list of objects which can be passed to
|
||||
exportObjectsWith() for final posting."""
|
||||
|
||||
sys.path = syspath
|
||||
def __fixtureSetup(order, fixture, job):
|
||||
"""Convert a Fixure setting to _TempObject instance with a G0 move to a
|
||||
safe height every time the fixture coordinate system change. Skip
|
||||
the move for first fixture, to avoid moving before tool and tool
|
||||
height compensation is enabled.
|
||||
|
||||
instance = PostProcessor(current_post)
|
||||
if hasattr(current_post, "UNITS"):
|
||||
if current_post.UNITS == "G21":
|
||||
instance.units = "Metric"
|
||||
else:
|
||||
instance.units = "Inch"
|
||||
"""
|
||||
|
||||
if hasattr(current_post, "TOOLTIP"):
|
||||
instance.tooltip = current_post.TOOLTIP
|
||||
if hasattr(current_post, "TOOLTIP_ARGS"):
|
||||
instance.tooltipArgs = current_post.TOOLTIP_ARGS
|
||||
return instance
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(fixture)
|
||||
fobj.Path = Path.Path([c1])
|
||||
# Avoid any tool move after G49 in preamble and before tool change
|
||||
# and G43 in case tool height compensation is in use, to avoid
|
||||
# dangerous move without toolgg compesation.
|
||||
if order != 0:
|
||||
c2 = Path.Command(
|
||||
"G0 Z"
|
||||
+ str(
|
||||
job.Stock.Shape.BoundBox.ZMax
|
||||
+ job.SetupSheet.ClearanceHeightOffset.Value
|
||||
)
|
||||
)
|
||||
fobj.Path.addCommands(c2)
|
||||
fobj.InList.append(job)
|
||||
return fobj
|
||||
|
||||
def __init__(self, script):
|
||||
self.script = script
|
||||
self.tooltip = None
|
||||
self.tooltipArgs = None
|
||||
self.units = None
|
||||
self.machineName = None
|
||||
wcslist = self._job.Fixtures
|
||||
orderby = self._job.OrderOutputBy
|
||||
Path.Log.debug(f"Ordering by {orderby}")
|
||||
|
||||
def export(self, obj, filename, args):
|
||||
return self.script.export(obj, filename, args)
|
||||
postlist = []
|
||||
|
||||
if orderby == "Fixture":
|
||||
Path.Log.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
|
||||
sublist = [__fixtureSetup(index, f, self._job)]
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in self._job.Operations.Group:
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None and PathUtil.opProperty(obj, "Active"):
|
||||
if tc.ToolNumber != currTool:
|
||||
sublist.append(tc)
|
||||
Path.Log.debug(f"Appending TC: {tc.Name}")
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append((f, sublist))
|
||||
|
||||
elif orderby == "Tool":
|
||||
Path.Log.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.
|
||||
|
||||
toolstring = "None"
|
||||
currTool = None
|
||||
|
||||
# Build the fixture list
|
||||
fixturelist = []
|
||||
for index, f in enumerate(wcslist):
|
||||
# create an object to serve as the fixture path
|
||||
fixturelist.append(__fixtureSetup(index, f, self._job))
|
||||
|
||||
# Now generate the gcode
|
||||
curlist = [] # list of ops for tool, will repeat for each fixture
|
||||
sublist = [] # list of ops for output splitting
|
||||
|
||||
Path.Log.track(self._job.PostProcessorOutputFile)
|
||||
for idx, obj in enumerate(self._job.Operations.Group):
|
||||
Path.Log.track(obj.Label)
|
||||
|
||||
# check if the operation is active
|
||||
if not getattr(obj, "Active", True):
|
||||
Path.Log.track()
|
||||
continue
|
||||
|
||||
# Determine the proper string for the Op's TC
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is None:
|
||||
tcstring = "None"
|
||||
elif "%T" in self._job.PostProcessorOutputFile:
|
||||
tcstring = f"{tc.ToolNumber}"
|
||||
else:
|
||||
tcstring = re.sub(r"[^\w\d-]", "_", tc.Label)
|
||||
Path.Log.track(toolstring)
|
||||
|
||||
if tc is None or tc.ToolNumber == currTool:
|
||||
curlist.append(obj)
|
||||
elif tc.ToolNumber != currTool and currTool is None: # first TC
|
||||
sublist.append(tc)
|
||||
curlist.append(obj)
|
||||
currTool = tc.ToolNumber
|
||||
toolstring = tcstring
|
||||
|
||||
elif tc.ToolNumber != currTool and currTool is not None: # TC
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
postlist.append((toolstring, sublist))
|
||||
sublist = [tc]
|
||||
curlist = [obj]
|
||||
currTool = tc.ToolNumber
|
||||
toolstring = tcstring
|
||||
|
||||
if idx == len(self._job.Operations.Group) - 1: # Last operation.
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
|
||||
postlist.append((toolstring, sublist))
|
||||
|
||||
elif orderby == "Operation":
|
||||
Path.Log.debug("Ordering by Operation")
|
||||
# Order by operation means ops are done in each fixture in
|
||||
# sequence.
|
||||
currTool = None
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in self._job.Operations.Group:
|
||||
|
||||
# check if the operation is active
|
||||
if not getattr(obj, "Active", True):
|
||||
continue
|
||||
|
||||
sublist = []
|
||||
Path.Log.debug(f"obj: {obj.Name}")
|
||||
|
||||
for index, f in enumerate(wcslist):
|
||||
sublist.append(__fixtureSetup(index, f, self._job))
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None:
|
||||
if self._job.SplitOutput or (tc.ToolNumber != currTool):
|
||||
sublist.append(tc)
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append((obj.Label, sublist))
|
||||
|
||||
Path.Log.debug(f"Postlist: {postlist}")
|
||||
|
||||
if self._job.SplitOutput:
|
||||
Path.Log.track()
|
||||
return postlist
|
||||
|
||||
Path.Log.track()
|
||||
finalpostlist = [
|
||||
("allitems", [item for slist in postlist for item in slist[1]])
|
||||
]
|
||||
Path.Log.debug(f"Postlist: {postlist}")
|
||||
return finalpostlist
|
||||
|
||||
|
||||
class WrapperPost(PostProcessor):
|
||||
"""Wrapper class for old post processors that are scripts."""
|
||||
|
||||
def __init__(self, job, script_path, *args, **kwargs):
|
||||
super().__init__(
|
||||
job, tooltip=None, tooltipargs=None, units=None, *args, **kwargs
|
||||
)
|
||||
self.script_path = script_path
|
||||
Path.Log.debug(f"WrapperPost.__init__({script_path})")
|
||||
self.load_script()
|
||||
|
||||
def load_script(self):
|
||||
# Dynamically load the script as a module
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"script_module", self.script_path
|
||||
)
|
||||
self.script_module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(self.script_module)
|
||||
except Exception as e:
|
||||
raise ImportError(f"Failed to load script: {e}")
|
||||
|
||||
if not hasattr(self.script_module, "export"):
|
||||
raise AttributeError("The script does not have an 'export' function.")
|
||||
|
||||
# Set properties based on attributes of the module
|
||||
self._units = (
|
||||
"Metric" if getattr(self.script_module, "UNITS", "G21") == "G21" else "Inch"
|
||||
)
|
||||
self._tooltip = getattr(self.script_module, "TOOLTIP", "No tooltip provided")
|
||||
self._tooltipargs = getattr(self.script_module, "TOOLTIP_ARGS", [])
|
||||
|
||||
def export(self):
|
||||
# Dynamically reload the module for the export to ensure up-to-date usage
|
||||
|
||||
postables = self._buildPostList()
|
||||
Path.Log.debug(f"postables count: {len(postables)}")
|
||||
|
||||
g_code_sections = []
|
||||
for idx, section in enumerate(postables):
|
||||
partname, sublist = section
|
||||
|
||||
gcode = self.script_module.export(sublist, "-", self._job.PostProcessorArgs)
|
||||
Path.Log.debug(f"Exported {partname}")
|
||||
g_code_sections.append((partname, gcode))
|
||||
return g_code_sections
|
||||
|
||||
@property
|
||||
def tooltip(self):
|
||||
return self._tooltip
|
||||
|
||||
@property
|
||||
def tooltipArgs(self):
|
||||
return self._tooltipargs
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self._units
|
||||
|
||||
@@ -27,21 +27,154 @@
|
||||
These are common functions and classes for creating custom post processors.
|
||||
"""
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
import FreeCAD
|
||||
|
||||
import Path
|
||||
import Part
|
||||
|
||||
from Path.Base.MachineState import MachineState
|
||||
from PySide import QtCore, QtGui
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Path
|
||||
import os
|
||||
import re
|
||||
|
||||
debug = False
|
||||
if debug:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
FreeCADGui = None
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
|
||||
class FilenameGenerator:
|
||||
def __init__(self, job):
|
||||
self.job = job
|
||||
self.subpartname = ""
|
||||
self.sequencenumber = 0
|
||||
path, filename, ext = self.get_path_and_filename_default()
|
||||
|
||||
self.qualified_path = self._apply_path_substitutions(path)
|
||||
self.qualified_filename = self._apply_filename_substitutions(filename)
|
||||
self.extension = ext
|
||||
|
||||
def get_path_and_filename_default(self):
|
||||
outputpath = ""
|
||||
filename = ""
|
||||
ext = ".nc"
|
||||
|
||||
validPathSubstitutions = ["D", "d", "M", "j"]
|
||||
validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"]
|
||||
|
||||
if self.job.PostProcessorOutputFile:
|
||||
candidateOutputPath, candidateFilename = os.path.split(
|
||||
self.job.PostProcessorOutputFile
|
||||
)
|
||||
|
||||
if candidateOutputPath:
|
||||
outputpath = candidateOutputPath
|
||||
|
||||
if candidateFilename:
|
||||
filename, ext = os.path.splitext(candidateFilename)
|
||||
else:
|
||||
outputpath, filename = os.path.split(Path.Preferences.defaultOutputFile())
|
||||
filename, ext = os.path.splitext(filename)
|
||||
|
||||
# Make sure we have something to work with
|
||||
if not filename:
|
||||
filename = FreeCAD.ActiveDocument.Label
|
||||
|
||||
if not outputpath:
|
||||
outputpath = os.getcwd()
|
||||
|
||||
if not ext:
|
||||
ext = ".nc"
|
||||
|
||||
# Check for invalid matches
|
||||
for match in re.findall("%(.)", outputpath):
|
||||
Path.Log.debug(f"match: {match}")
|
||||
if match not in validPathSubstitutions:
|
||||
outputpath = outputpath.replace(f"%{match}", "")
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Invalid substitution strings will be ignored in output path: %s\n"
|
||||
% match
|
||||
)
|
||||
|
||||
for match in re.findall("%(.)", filename):
|
||||
Path.Log.debug(f"match: {match}")
|
||||
if match not in validFilenameSubstitutions:
|
||||
filename = filename.replace(f"%{match}", "")
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Invalid substitution strings will be ignored in file path: %s\n"
|
||||
% match
|
||||
)
|
||||
|
||||
Path.Log.debug(f"outputpath: {outputpath} filename: {filename} ext: {ext}")
|
||||
return outputpath, filename, ext
|
||||
|
||||
def set_subpartname(self, subpartname):
|
||||
self.subpartname = subpartname
|
||||
|
||||
def _apply_path_substitutions(self, file_path):
|
||||
"""Apply substitutions based on job settings and other parameters."""
|
||||
substitutions = {
|
||||
"%D": os.path.dirname(self.job.Document.FileName or "."),
|
||||
"%d": self.job.Document.Label,
|
||||
"%j": self.job.Label,
|
||||
"%M": os.path.dirname(FreeCAD.getUserMacroDir()),
|
||||
}
|
||||
for key, value in substitutions.items():
|
||||
file_path = file_path.replace(key, value)
|
||||
|
||||
Path.Log.debug(f"file_path: {file_path}")
|
||||
return file_path
|
||||
|
||||
def _apply_filename_substitutions(self, file_name):
|
||||
Path.Log.debug(f"file_name: {file_name}")
|
||||
"""Apply substitutions based on job settings and other parameters."""
|
||||
substitutions = {
|
||||
"%d": self.job.Document.Label,
|
||||
"%j": self.job.Label,
|
||||
"%T": self.subpartname, # Tool
|
||||
"%t": self.subpartname, # Tool
|
||||
"%W": self.subpartname, # Fixture
|
||||
"%O": self.subpartname, # Operation
|
||||
}
|
||||
for key, value in substitutions.items():
|
||||
file_name = file_name.replace(key, value)
|
||||
|
||||
Path.Log.debug(f"file_name: {file_name}")
|
||||
return file_name
|
||||
|
||||
def generate_filenames(self):
|
||||
"""Yield filenames indefinitely with proper substitutions."""
|
||||
while True:
|
||||
temp_filename = self.qualified_filename
|
||||
Path.Log.debug(f"temp_filename: {temp_filename}")
|
||||
explicit_sequence = False
|
||||
matches = re.findall("%S", temp_filename)
|
||||
if matches:
|
||||
Path.Log.debug(f"matches: {matches}")
|
||||
temp_filename = re.sub("%S", str(self.sequencenumber), temp_filename)
|
||||
explicit_sequence = True
|
||||
|
||||
subpart = f"-{self.subpartname}" if self.subpartname else ""
|
||||
sequence = (
|
||||
f"-{self.sequencenumber}"
|
||||
if not explicit_sequence and self.sequencenumber
|
||||
else ""
|
||||
)
|
||||
filename = f"{temp_filename}{subpart}{sequence}{self.extension}"
|
||||
full_path = os.path.join(self.qualified_path, filename)
|
||||
|
||||
self.sequencenumber += 1
|
||||
Path.Log.debug(f"yielding filename: {full_path}")
|
||||
yield os.path.normpath(full_path)
|
||||
|
||||
|
||||
class GCodeHighlighter(QtGui.QSyntaxHighlighter):
|
||||
def __init__(self, parent=None):
|
||||
super(GCodeHighlighter, self).__init__(parent)
|
||||
|
||||
@@ -27,7 +27,7 @@ import PathScripts.PathUtils as PathUtils
|
||||
from builtins import open as pyopen
|
||||
|
||||
TOOLTIP = """
|
||||
Dumper is an extremely simple postprocessor file for the Path workbench. It is used
|
||||
Dumper is an extremely simple postprocessor file for the CAM workbench. It is used
|
||||
to dump the command list from one or more Path objects for simple inspection. This post
|
||||
doesn't do any manipulation of the path and doesn't write anything to disk. It just
|
||||
shows the dialog so you can see it. Useful for debugging, but not much else.
|
||||
|
||||
87
src/Mod/CAM/Path/Post/scripts/generic_post.py
Normal file
87
src/Mod/CAM/Path/Post/scripts/generic_post.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2024 Ondsel <development@ondsel.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import os
|
||||
from Path.Post.Processor import PostProcessor
|
||||
import Path
|
||||
import FreeCAD
|
||||
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
debug = True
|
||||
if debug:
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
class Generic(PostProcessor):
|
||||
def __init__(self, job):
|
||||
super().__init__(
|
||||
job,
|
||||
tooltip=translate("CAM", "Generic post processor"),
|
||||
tooltipargs=["arg1", "arg2"],
|
||||
units="kg",
|
||||
)
|
||||
Path.Log.debug("Generic post processor initialized")
|
||||
|
||||
def export(self):
|
||||
Path.Log.debug("Exporting the job")
|
||||
|
||||
postables = self._buildPostList()
|
||||
Path.Log.debug(f"postables count: {len(postables)}")
|
||||
|
||||
g_code_sections = []
|
||||
for idx, section in enumerate(postables):
|
||||
partname, sublist = section
|
||||
|
||||
# here is where the sections are converted to gcode.
|
||||
g_code_sections.append((idx, partname))
|
||||
|
||||
return g_code_sections
|
||||
|
||||
@property
|
||||
def tooltip(self):
|
||||
|
||||
tooltip = """
|
||||
This is a generic post processor.
|
||||
It doesn't do anything yet because we haven't immplemented it.
|
||||
|
||||
Implementing it would be a good idea
|
||||
"""
|
||||
return tooltip
|
||||
|
||||
@property
|
||||
def tooltipArgs(self):
|
||||
argtooltip = """
|
||||
--arg1: This is the first argument
|
||||
--arg2: This is the second argument
|
||||
|
||||
"""
|
||||
return argtooltip
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self._units
|
||||
@@ -401,6 +401,9 @@ def findToolController(obj, proxy, name=None):
|
||||
def findParentJob(obj):
|
||||
"""retrieves a parent job object for an operation or other Path object"""
|
||||
Path.Log.track()
|
||||
if hasattr(obj, "Proxy") and isinstance(obj.Proxy, PathJob.ObjectJob):
|
||||
return obj
|
||||
|
||||
for i in obj.InList:
|
||||
if hasattr(i, "Proxy") and isinstance(i.Proxy, PathJob.ObjectJob):
|
||||
return i
|
||||
|
||||
@@ -44,10 +44,13 @@ from Tests.TestPathHelixGenerator import TestPathHelixGenerator
|
||||
from Tests.TestPathLog import TestPathLog
|
||||
from Tests.TestPathOpUtil import TestPathOpUtil
|
||||
|
||||
# from Tests.TestPathPost import TestPathPost
|
||||
#from Tests.TestPathPost import TestPathPost
|
||||
from Tests.TestPathPost import TestPathPostUtils
|
||||
from Tests.TestPathPost import TestBuildPostList
|
||||
from Tests.TestPathPost import TestOutputNameSubstitution
|
||||
# from Tests.TestPathPost import TestOutputNameSubstitution
|
||||
from Tests.TestPathPost import TestPostProcessorFactory
|
||||
from Tests.TestPathPost import TestResolvingPostProcessorName
|
||||
from Tests.TestPathPost import TestFileNameGenerator
|
||||
|
||||
from Tests.TestPathPreferences import TestPathPreferences
|
||||
from Tests.TestPathProfile import TestPathProfile
|
||||
@@ -83,10 +86,11 @@ False if TestApp.__name__ else True
|
||||
False if TestBuildPostList.__name__ else True
|
||||
False if TestDressupDogbone.__name__ else True
|
||||
False if TestDressupDogboneII.__name__ else True
|
||||
False if TestFileNameGenerator.__name__ else True
|
||||
False if TestGeneratorDogboneII.__name__ else True
|
||||
False if TestHoldingTags.__name__ else True
|
||||
False if TestPathLanguage.__name__ else True
|
||||
False if TestOutputNameSubstitution.__name__ else True
|
||||
# False if TestOutputNameSubstitution.__name__ else True
|
||||
False if TestPathAdaptive.__name__ else True
|
||||
False if TestPathCore.__name__ else True
|
||||
False if TestPathOpDeburr.__name__ else True
|
||||
@@ -96,7 +100,9 @@ False if TestPathHelpers.__name__ else True
|
||||
# False if TestPathHelix.__name__ else True
|
||||
False if TestPathLog.__name__ else True
|
||||
False if TestPathOpUtil.__name__ else True
|
||||
# False if TestPathPost.__name__ else True
|
||||
#False if TestPathPost.__name__ else True
|
||||
False if TestPostProcessorFactory.__name__ else True
|
||||
False if TestResolvingPostProcessorName.__name__ else True
|
||||
False if TestPathPostUtils.__name__ else True
|
||||
False if TestPathPreferences.__name__ else True
|
||||
False if TestPathProfile.__name__ else True
|
||||
|
||||
@@ -34,6 +34,7 @@ Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
DOC = FreeCAD.getHomePath() + "Mod/CAM/Tests/test_geomop.fcstd"
|
||||
|
||||
|
||||
def getWire(obj, nr=0):
|
||||
return obj.Tip.Profile[0].Shape.Wires[nr]
|
||||
|
||||
@@ -81,6 +82,13 @@ def wireMarkers(wire):
|
||||
|
||||
|
||||
class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.doc = FreeCAD.openDocument(DOC)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
FreeCAD.closeDocument(cls.doc.Name)
|
||||
|
||||
def test00(self):
|
||||
"""Verify isWireClockwise for polygon wires."""
|
||||
@@ -137,8 +145,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
def test11(self):
|
||||
"""Check offsetting a circular hole."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-circle")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-circle")[0]
|
||||
|
||||
small = getWireInside(obj)
|
||||
self.assertRoughly(10, small.Edges[0].Curve.Radius)
|
||||
@@ -154,12 +161,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
self.assertRoughly(0.1, wire.Edges[0].Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, 1), wire.Edges[0].Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test12(self):
|
||||
"""Check offsetting a circular hole by the radius or more makes the hole vanish."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-circle")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-circle")[0]
|
||||
|
||||
small = getWireInside(obj)
|
||||
self.assertRoughly(10, small.Edges[0].Curve.Radius)
|
||||
@@ -168,12 +173,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
wire = PathOpUtil.offsetWire(small, obj.Shape, 15, True)
|
||||
self.assertIsNone(wire)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test13(self):
|
||||
"""Check offsetting a cylinder succeeds."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-circle")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-circle")[0]
|
||||
|
||||
big = getWireOutside(obj)
|
||||
self.assertRoughly(20, big.Edges[0].Curve.Radius)
|
||||
@@ -189,12 +192,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
self.assertRoughly(40, wire.Edges[0].Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test14(self):
|
||||
"""Check offsetting a hole with Placement."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-placement")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-placement")[0]
|
||||
|
||||
wires = [
|
||||
w
|
||||
@@ -215,12 +216,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertRoughly(8, wire.Edges[0].Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, 0), wire.Edges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, 1), wire.Edges[0].Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test15(self):
|
||||
"""Check offsetting a cylinder with Placement."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-placement")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-placement")[0]
|
||||
|
||||
wires = [
|
||||
w
|
||||
@@ -241,12 +240,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertRoughly(22, wire.Edges[0].Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, 0), wire.Edges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test20(self):
|
||||
"""Check offsetting hole wire succeeds."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
small = getWireInside(obj)
|
||||
# sanity check
|
||||
@@ -276,12 +273,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
False,
|
||||
[Vector(0, 4, 0), Vector(-x, -2, 0), Vector(x, -2, 0), Vector(0, 4, 0)],
|
||||
)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test21(self):
|
||||
"""Check offsetting hole wire for more than it's size makes hole vanish."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
small = getWireInside(obj)
|
||||
# sanity check
|
||||
@@ -299,12 +294,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
)
|
||||
wire = PathOpUtil.offsetWire(small, obj.Shape, 5, True)
|
||||
self.assertIsNone(wire)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test22(self):
|
||||
"""Check offsetting a body wire succeeds."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
big = getWireOutside(obj)
|
||||
# sanity check
|
||||
@@ -351,12 +344,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertIsNone("%s: angle=%s" % (type(e.Curve), angle))
|
||||
lastAngle = angle
|
||||
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test31(self):
|
||||
"""Check offsetting a cylinder."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("circle-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("circle-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
@@ -372,12 +363,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertCoincide(Vector(), edge.Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, +1), edge.Curve.Axis)
|
||||
self.assertRoughly(33, edge.Curve.Radius)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test32(self):
|
||||
"""Check offsetting a box."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("square-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("square-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True)
|
||||
self.assertEqual(8, len(wire.Edges))
|
||||
@@ -413,12 +402,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertRoughly(3, e.Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
||||
self.assertFalse(PathOpUtil.isWireClockwise(wire))
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test33(self):
|
||||
"""Check offsetting a triangle."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("triangle-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("triangle-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True)
|
||||
self.assertEqual(6, len(wire.Edges))
|
||||
@@ -447,12 +434,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
if Part.Circle == type(e.Curve):
|
||||
self.assertRoughly(3, e.Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test34(self):
|
||||
"""Check offsetting a shape."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("shape-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("shape-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True)
|
||||
self.assertEqual(6, len(wire.Edges))
|
||||
@@ -482,12 +467,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
if Part.Circle == type(e.Curve):
|
||||
self.assertRoughly(radius, e.Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test35(self):
|
||||
"""Check offsetting a cylindrical hole."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("circle-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("circle-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
@@ -503,12 +486,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertCoincide(Vector(), edge.Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, -1), edge.Curve.Axis)
|
||||
self.assertRoughly(27, edge.Curve.Radius)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test36(self):
|
||||
"""Check offsetting a square hole."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("square-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("square-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
||||
self.assertEqual(4, len(wire.Edges))
|
||||
@@ -530,12 +511,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y):
|
||||
self.assertRoughly(54, e.Length)
|
||||
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test37(self):
|
||||
"""Check offsetting a triangular holee."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("triangle-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("triangle-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
||||
self.assertEqual(3, len(wire.Edges))
|
||||
@@ -552,12 +531,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
for e in wire.Edges:
|
||||
self.assertRoughly(length, e.Length)
|
||||
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test38(self):
|
||||
"""Check offsetting a shape hole."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("shape-cut")[0]
|
||||
obj = self.doc.getObjectsByLabel("shape-cut")[0]
|
||||
|
||||
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
||||
self.assertEqual(6, len(wire.Edges))
|
||||
@@ -587,12 +564,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
if Part.Circle == type(e.Curve):
|
||||
self.assertRoughly(radius, e.Curve.Radius)
|
||||
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test40(self):
|
||||
"""Check offsetting a single outside edge forward."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi / 6)
|
||||
@@ -628,12 +603,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test41(self):
|
||||
"""Check offsetting a single outside edge not forward."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi / 6)
|
||||
@@ -668,14 +641,12 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test42(self):
|
||||
"""Check offsetting multiple outside edges."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj.Shape.tessellate(0.01)
|
||||
doc.recompute()
|
||||
self.doc.recompute()
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi / 6)
|
||||
@@ -713,14 +684,12 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertEqual(1, len(rEdges))
|
||||
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, +1), rEdges[0].Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test43(self):
|
||||
"""Check offsetting multiple backwards outside edges."""
|
||||
# This is exactly the same as test32, except that the wire is flipped to make
|
||||
# sure the input orientation doesn't matter
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi / 6)
|
||||
@@ -759,12 +728,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
self.assertEqual(1, len(rEdges))
|
||||
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, +1), rEdges[0].Curve.Axis)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test44(self):
|
||||
"""Check offsetting a single inside edge forward."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi / 6)
|
||||
@@ -800,12 +767,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test45(self):
|
||||
"""Check offsetting a single inside edge not forward."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi / 6)
|
||||
@@ -841,12 +806,10 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test46(self):
|
||||
"""Check offsetting multiple inside edges."""
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi / 6)
|
||||
@@ -878,14 +841,12 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
self.assertEqual(0, len(rEdges))
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test47(self):
|
||||
"""Check offsetting multiple backwards inside edges."""
|
||||
# This is exactly the same as test36 except that the wire is flipped to make
|
||||
# sure it's orientation doesn't matter
|
||||
doc = FreeCAD.openDocument(DOC)
|
||||
obj = doc.getObjectsByLabel("offset-edge")[0]
|
||||
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi / 6)
|
||||
@@ -918,7 +879,6 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
self.assertEqual(0, len(rEdges))
|
||||
FreeCAD.closeDocument("test_geomop")
|
||||
|
||||
def test50(self):
|
||||
"""Orient an already oriented wire"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user