Make default postprocessor empty string. (#23706)
* Make default postprocessor empty string. Default post when creating a job is now not set. This will cause the user to be prompted for a post instead This PR also cleans up some unit tests to avoid loading from disk * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -29,6 +29,8 @@ import Path
|
||||
import Path.Post.Command as PathCommand
|
||||
import Path.Post.Processor as PathPost
|
||||
import Path.Post.Utils as PostUtils
|
||||
import Path.Main.Job as PathJob
|
||||
import Path.Tool.Controller as PathToolController
|
||||
import difflib
|
||||
import os
|
||||
import unittest
|
||||
@@ -50,14 +52,26 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
|
||||
The Following can be used if output is being split. If Output is not split
|
||||
these will be ignored.
|
||||
|
||||
%S ... Sequence Number (default)
|
||||
|
||||
Either:
|
||||
%T ... Tool Number
|
||||
%t ... Tool Controller label
|
||||
|
||||
%W ... Work Coordinate System
|
||||
%O ... Operation Label
|
||||
|
||||
|split on| use | Ignore |
|
||||
|-----------|-------|--------|
|
||||
|fixture | %W | %O %T %t |
|
||||
|Operation| %O | %T %t %W |
|
||||
|Tool| **Either %T or %t** | %O %W |
|
||||
|
||||
The confusing bit is that for split on tool, it will use EITHER the tool number or the tool label.
|
||||
If you include both, the second one overrides the first.
|
||||
And for split on operation, where including the tool should be possible, it ignores it altogether.
|
||||
|
||||
self.job.Fixtures = ["G54"]
|
||||
self.job.SplitOutput = False
|
||||
self.job.OrderOutputBy = "Fixture"
|
||||
@@ -83,55 +97,61 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# cls.doc = FreeCAD.open(FreeCAD.getHomePath() + "/Mod/CAM/CAMTests/boxtest.fcstd")
|
||||
# cls.job = cls.doc.getObject("Job")
|
||||
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
|
||||
|
||||
cls.testfile = FreeCAD.getHomePath() + "Mod/CAM/CAMTests/test_filenaming.fcstd"
|
||||
cls.testfilepath, cls.testfilename = os.path.split(cls.testfile)
|
||||
cls.testfilename, cls.ext = os.path.splitext(cls.testfilename)
|
||||
|
||||
cls.doc = FreeCAD.open(cls.testfile)
|
||||
cls.job = cls.doc.getObjectsByLabel("MainJob")[0]
|
||||
# Create a new document instead of opening external file
|
||||
cls.doc = FreeCAD.newDocument("TestFileNaming")
|
||||
cls.testfilename = cls.doc.Name
|
||||
cls.testfilepath = os.getcwd()
|
||||
cls.macro = FreeCAD.getUserMacroDir()
|
||||
|
||||
# Create a simple geometry object for the job
|
||||
import Part
|
||||
|
||||
box = cls.doc.addObject("Part::Box", "TestBox")
|
||||
box.Length = 100
|
||||
box.Width = 100
|
||||
box.Height = 20
|
||||
|
||||
# Create CAM job programmatically
|
||||
cls.job = PathJob.Create("MainJob", [box], None)
|
||||
cls.job.PostProcessor = "linuxcnc"
|
||||
cls.job.PostProcessorOutputFile = ""
|
||||
cls.job.SplitOutput = False
|
||||
cls.job.OrderOutputBy = "Operation"
|
||||
cls.job.Fixtures = ["G54", "G55"]
|
||||
|
||||
# Create a tool controller for testing tool-related substitutions
|
||||
from Path.Tool.toolbit import ToolBit
|
||||
|
||||
tool_attrs = {
|
||||
"name": "TestTool",
|
||||
"shape": "endmill.fcstd",
|
||||
"parameter": {"Diameter": 6.0},
|
||||
"attribute": {},
|
||||
}
|
||||
toolbit = ToolBit.from_dict(tool_attrs)
|
||||
tool = toolbit.attach_to_doc(doc=cls.doc)
|
||||
tool.Label = "6mm_Endmill"
|
||||
|
||||
tc = PathToolController.Create("TC_Test_Tool", tool, 5)
|
||||
tc.Label = "TC: 6mm Endmill"
|
||||
cls.job.addObject(tc)
|
||||
|
||||
# Create a simple mock operation for testing operation-related substitutions
|
||||
profile_op = cls.doc.addObject("Path::FeaturePython", "TestProfile")
|
||||
profile_op.Label = "OutsideProfile"
|
||||
# Path::FeaturePython objects already have a Path property
|
||||
profile_op.Path = Path.Path()
|
||||
cls.job.Operations.addObject(profile_op)
|
||||
|
||||
cls.doc.recompute()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
FreeCAD.closeDocument(cls.doc.Name)
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "")
|
||||
|
||||
# def test010(self):
|
||||
# self.job.PostProcessorOutputFile = ""
|
||||
# generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
|
||||
# filename_generator = generator.generate_filenames()
|
||||
# generated_filename = next(filename_generator)
|
||||
# self.assertEqual(generated_filename, "-Job.nc")
|
||||
|
||||
# def test020(self):
|
||||
# generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
# filename_generator = generator.generate_filenames()
|
||||
# expected_filenames = ["-Job.nc"] + [f"-Job-{i}.nc" for i in range(1, 5)]
|
||||
# print(expected_filenames)
|
||||
# for expected_filename in expected_filenames:
|
||||
# generated_filename = next(filename_generator)
|
||||
# self.assertEqual(generated_filename, expected_filename)
|
||||
|
||||
# def setUp(self):
|
||||
# self.testfile = FreeCAD.getHomePath() + "Mod/CAM/CAMTests/test_filenaming.fcstd"
|
||||
# self.testfilepath, self.testfilename = os.path.split(self.testfile)
|
||||
# self.testfilename, self.ext = os.path.splitext(self.testfilename)
|
||||
|
||||
# self.doc = FreeCAD.open(self.testfile)
|
||||
# self.job = self.doc.getObjectsByLabel("MainJob")[0]
|
||||
# self.macro = FreeCAD.getUserMacroDir()
|
||||
# self.job.SplitOutput = False
|
||||
|
||||
# def tearDown(self):
|
||||
# FreeCAD.closeDocument(self.doc.Name)
|
||||
|
||||
def test000(self):
|
||||
# Test basic name generation with empty string
|
||||
FreeCAD.setActiveDocument(self.doc.Label)
|
||||
@@ -143,13 +163,6 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
Path.Log.debug(filename)
|
||||
# outlist = PathPost.buildPostList(self.job)
|
||||
|
||||
# self.assertTrue(len(outlist) == 1)
|
||||
# subpart, objs = outlist[0]
|
||||
|
||||
# filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
# self.assertEqual(filename, os.path.normpath(f"{self.testfilename}.nc"))
|
||||
assertFilePathsEqual(
|
||||
self, filename, os.path.join(self.testfilepath, f"{self.testfilename}.nc")
|
||||
)
|
||||
@@ -172,16 +185,11 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
teststring = "~/Desktop/%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
Path.Preferences.setOutputFileDefaults(teststring, "Append Unique ID on conflict")
|
||||
# outlist = PathPost.buildPostList(self.job)
|
||||
|
||||
# self.assertTrue(len(outlist) == 1)
|
||||
# subpart, objs = outlist[0]
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
# filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
assertFilePathsEqual(self, filename, "~/Desktop/MainJob.nc")
|
||||
|
||||
def test020(self):
|
||||
@@ -224,8 +232,8 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
"""Testing the sequence number substitution"""
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
filename_generator = generator.generate_filenames()
|
||||
expected_filenames = [f"test_filenaming{os.sep}testdoc.nc"] + [
|
||||
f"test_filenaming{os.sep}testdoc-{i}.nc" for i in range(1, 5)
|
||||
expected_filenames = [f"TestFileNaming{os.sep}testdoc.nc"] + [
|
||||
f"TestFileNaming{os.sep}testdoc-{i}.nc" for i in range(1, 5)
|
||||
]
|
||||
for expected_filename in expected_filenames:
|
||||
filename = next(filename_generator)
|
||||
@@ -238,7 +246,7 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
filename_generator = generator.generate_filenames()
|
||||
expected_filenames = [
|
||||
os.path.join(self.testfilepath, f"{i}-test_filenaming.nc") for i in range(5)
|
||||
os.path.join(self.testfilepath, f"{i}-TestFileNaming.nc") for i in range(5)
|
||||
]
|
||||
for expected_filename in expected_filenames:
|
||||
filename = next(filename_generator)
|
||||
@@ -254,9 +262,7 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
assertFilePathsEqual(
|
||||
self, filename, os.path.join(self.testfilepath, "0-test_filenaming.nc")
|
||||
)
|
||||
assertFilePathsEqual(self, filename, os.path.join(self.testfilepath, "0-TestFileNaming.nc"))
|
||||
|
||||
def test060(self):
|
||||
"""Test subpart naming"""
|
||||
@@ -271,13 +277,102 @@ class TestFileNameGenerator(unittest.TestCase):
|
||||
|
||||
assertFilePathsEqual(self, filename, f"{self.macro}outfile-Tool.nc")
|
||||
|
||||
def test070(self):
|
||||
"""Test %T substitution (tool number) with actual tool controller"""
|
||||
teststring = "%T.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
generator.set_subpartname("5") # Tool number from our test tool controller
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
assertFilePathsEqual(self, filename, os.path.join(self.testfilepath, "5.nc"))
|
||||
|
||||
def test071(self):
|
||||
"""Test %t substitution (tool description) with actual tool controller"""
|
||||
teststring = "%t.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
generator.set_subpartname("TC__6mm_Endmill") # Sanitized tool label
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
assertFilePathsEqual(self, filename, os.path.join(self.testfilepath, "TC__6mm_Endmill.nc"))
|
||||
|
||||
def test072(self):
|
||||
"""Test %W substitution (work coordinate system/fixture)"""
|
||||
teststring = "%W.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
generator.set_subpartname("G54") # First fixture from our job setup
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
assertFilePathsEqual(self, filename, os.path.join(self.testfilepath, "G54.nc"))
|
||||
|
||||
def test073(self):
|
||||
"""Test %O substitution (operation label)"""
|
||||
teststring = "%O.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
generator.set_subpartname("OutsideProfile") # Operation label from our test setup
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
assertFilePathsEqual(self, filename, os.path.join(self.testfilepath, "OutsideProfile.nc"))
|
||||
|
||||
def test075(self):
|
||||
"""Test path and filename substitutions together"""
|
||||
teststring = "%D/%j_%S.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
# %D should resolve to document directory (empty since doc has no filename)
|
||||
# %j should resolve to job name "MainJob"
|
||||
# %S should resolve to sequence number "0"
|
||||
assertFilePathsEqual(self, filename, os.path.join(".", "MainJob_0.nc"))
|
||||
|
||||
def test076(self):
|
||||
"""Test invalid substitution characters are ignored"""
|
||||
teststring = "%X%Y%Z/invalid_%Q.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
|
||||
generator = PostUtils.FilenameGenerator(job=self.job)
|
||||
filename_generator = generator.generate_filenames()
|
||||
filename = next(filename_generator)
|
||||
|
||||
# Invalid substitutions should be removed, leaving "invalid_.nc"
|
||||
assertFilePathsEqual(self, filename, os.path.join(self.testfilepath, "invalid_.nc"))
|
||||
|
||||
|
||||
class TestResolvingPostProcessorName(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
|
||||
cls.doc = FreeCAD.open(FreeCAD.getHomePath() + "/Mod/CAM/CAMTests/boxtest.fcstd")
|
||||
cls.job = cls.doc.getObject("Job")
|
||||
# Create a new document instead of opening external file
|
||||
cls.doc = FreeCAD.newDocument("boxtest")
|
||||
|
||||
# Create a simple geometry object for the job
|
||||
import Part
|
||||
|
||||
box = cls.doc.addObject("Part::Box", "TestBox")
|
||||
box.Length = 100
|
||||
box.Width = 100
|
||||
box.Height = 20
|
||||
|
||||
# Create CAM job programmatically
|
||||
cls.job = PathJob.Create("MainJob", [box], None)
|
||||
cls.job.PostProcessorOutputFile = ""
|
||||
cls.job.SplitOutput = False
|
||||
cls.job.OrderOutputBy = "Operation"
|
||||
cls.job.Fixtures = ["G54", "G55"]
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -293,6 +388,7 @@ class TestResolvingPostProcessorName(unittest.TestCase):
|
||||
|
||||
def test010(self):
|
||||
# Test if post is defined in job
|
||||
self.job.PostProcessor = "linuxcnc"
|
||||
with patch("Path.Post.Processor.PostProcessor.exists", return_value=True):
|
||||
postname = PathCommand._resolve_post_processor_name(self.job)
|
||||
self.assertEqual(postname, "linuxcnc")
|
||||
@@ -305,16 +401,16 @@ class TestResolvingPostProcessorName(unittest.TestCase):
|
||||
|
||||
def test030(self):
|
||||
# Test if post is defined in prefs
|
||||
self.job.PostProcessor = ""
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/CAM")
|
||||
pref.SetString("PostProcessorDefault", "grbl")
|
||||
self.job.PostProcessor = ""
|
||||
|
||||
with patch("Path.Post.Processor.PostProcessor.exists", return_value=True):
|
||||
postname = PathCommand._resolve_post_processor_name(self.job)
|
||||
self.assertEqual(postname, "grbl")
|
||||
|
||||
def test040(self):
|
||||
# Test if user interaction is correctly handled
|
||||
self.job.PostProcessor = ""
|
||||
if FreeCAD.GuiUp:
|
||||
with patch("Path.Post.Command.DlgSelectPostProcessor") as mock_dlg, patch(
|
||||
"Path.Post.Processor.PostProcessor.exists", return_value=True
|
||||
@@ -323,8 +419,9 @@ class TestResolvingPostProcessorName(unittest.TestCase):
|
||||
postname = PathCommand._resolve_post_processor_name(self.job)
|
||||
self.assertEqual(postname, "generic")
|
||||
else:
|
||||
with self.assertRaises(ValueError):
|
||||
PathCommand._resolve_post_processor_name(self.job)
|
||||
with patch.object(self.job, "PostProcessor", ""):
|
||||
with self.assertRaises(ValueError):
|
||||
PathCommand._resolve_post_processor_name(self.job)
|
||||
|
||||
|
||||
class TestPostProcessorFactory(unittest.TestCase):
|
||||
@@ -333,8 +430,24 @@ class TestPostProcessorFactory(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
|
||||
cls.doc = FreeCAD.open(FreeCAD.getHomePath() + "/Mod/CAM/CAMTests/boxtest.fcstd")
|
||||
cls.job = cls.doc.getObject("Job")
|
||||
# Create a new document instead of opening external file
|
||||
cls.doc = FreeCAD.newDocument("boxtest")
|
||||
|
||||
# Create a simple geometry object for the job
|
||||
import Part
|
||||
|
||||
box = cls.doc.addObject("Part::Box", "TestBox")
|
||||
box.Length = 100
|
||||
box.Width = 100
|
||||
box.Height = 20
|
||||
|
||||
# Create CAM job programmatically
|
||||
cls.job = PathJob.Create("MainJob", [box], None)
|
||||
cls.job.PostProcessor = "linuxcnc"
|
||||
cls.job.PostProcessorOutputFile = ""
|
||||
cls.job.SplitOutput = False
|
||||
cls.job.OrderOutputBy = "Operation"
|
||||
cls.job.Fixtures = ["G54", "G55"]
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -366,212 +479,6 @@ class TestPostProcessorFactory(unittest.TestCase):
|
||||
self.assertEqual(post.script_module.__name__, "linuxcnc_post")
|
||||
|
||||
|
||||
class TestPostProcessorClass(unittest.TestCase):
|
||||
"""Test new post structure objects."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
|
||||
cls.doc = FreeCAD.open(FreeCAD.getHomePath() + "/Mod/CAM/CAMTests/boxtest.fcstd")
|
||||
cls.job = cls.doc.getObject("Job")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
FreeCAD.closeDocument(cls.doc.Name)
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "")
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test010(self):
|
||||
"""Test the export function."""
|
||||
post = PostProcessorFactory.get_post_processor(self.job, "linuxcnc")
|
||||
sections = post.export()
|
||||
for sec in sections:
|
||||
print(sec[0])
|
||||
|
||||
def test020(self):
|
||||
"""Test the export function with splitting."""
|
||||
post = PostProcessorFactory.get_post_processor(self.job, "linuxcnc")
|
||||
sections = post.export()
|
||||
for sec in sections:
|
||||
print(sec[0])
|
||||
|
||||
def test030(self):
|
||||
"""Test the export function with splitting."""
|
||||
post = PostProcessorFactory.get_post_processor(self.job, "generic")
|
||||
sections = post.export()
|
||||
for sec in sections:
|
||||
print(sec[0])
|
||||
|
||||
|
||||
# class TestPostProcessorScript(unittest.TestCase):
|
||||
# """Test old-school posts"""
|
||||
|
||||
# def setUp(self):
|
||||
# self.doc = FreeCAD.open(FreeCAD.getHomePath() + "/Mod/CAM/CAMTests/boxtest.fcstd")
|
||||
# self.job = self.doc.getObject("Job")
|
||||
# post = PostProcessorFactory.get_post_processor(self.job, "linuxcnc")
|
||||
# results = post.export()
|
||||
|
||||
# def tearDown(self):
|
||||
# FreeCAD.closeDocument("boxtest")
|
||||
|
||||
##
|
||||
## You can run just this test using:
|
||||
## ./FreeCAD -c -t CAMTests.TestPathPost.TestPathPost.test_postprocessors
|
||||
##
|
||||
# def test_postprocessors(self):
|
||||
# """Test the postprocessors."""
|
||||
# #
|
||||
# # The tests are performed in the order they are listed:
|
||||
# # one test performed on all of the postprocessors
|
||||
# # then the next test on all of the postprocessors, etc.
|
||||
# # You can comment out the tuples for tests that you don't want
|
||||
# # to use.
|
||||
# #
|
||||
# tests_to_perform = (
|
||||
# # (output_file_id, freecad_document, job_name, postprocessor_arguments,
|
||||
# # postprocessor_list)
|
||||
# #
|
||||
# # test with all of the defaults (metric mode, etc.)
|
||||
# ("default", "boxtest1", "Job", "--no-show-editor", ()),
|
||||
# # test in Imperial mode
|
||||
# ("imperial", "boxtest1", "Job", "--no-show-editor --inches", ()),
|
||||
# # test in metric, G55, M4, the other way around the part
|
||||
# ("other_way", "boxtest1", "Job001", "--no-show-editor", ()),
|
||||
# # test in metric, split by fixtures, G54, G55, G56
|
||||
# ("split", "boxtest1", "Job002", "--no-show-editor", ()),
|
||||
# # test in metric mode without the header
|
||||
# ("no_header", "boxtest1", "Job", "--no-header --no-show-editor", ()),
|
||||
# # test translating G81, G82, and G83 to G00 and G01 commands
|
||||
# (
|
||||
# "drill_translate",
|
||||
# "drill_test1",
|
||||
# "Job",
|
||||
# "--no-show-editor --translate_drill",
|
||||
# ("grbl", "refactored_grbl"),
|
||||
# ),
|
||||
# )
|
||||
# #
|
||||
# # The postprocessors to test.
|
||||
# # You can comment out any postprocessors that you don't want
|
||||
# # to test.
|
||||
# #
|
||||
# postprocessors_to_test = (
|
||||
# "centroid",
|
||||
# # "fanuc",
|
||||
# "grbl",
|
||||
# "linuxcnc",
|
||||
# "mach3_mach4",
|
||||
# "refactored_centroid",
|
||||
# # "refactored_fanuc",
|
||||
# "refactored_grbl",
|
||||
# "refactored_linuxcnc",
|
||||
# "refactored_mach3_mach4",
|
||||
# "refactored_test",
|
||||
# )
|
||||
# #
|
||||
# # Enough of the path to where the tests are stored so that
|
||||
# # they can be found by the python interpreter.
|
||||
# #
|
||||
# PATHTESTS_LOCATION = "Mod/CAM/CAMTests"
|
||||
# #
|
||||
# # The following code tries to reuse an open FreeCAD document
|
||||
# # as much as possible. It compares the current document with
|
||||
# # the document for the next test. If the names are different
|
||||
# # then the current document is closed and the new document is
|
||||
# # opened. The final document is closed at the end of the code.
|
||||
# #
|
||||
# current_document = ""
|
||||
# for (
|
||||
# output_file_id,
|
||||
# freecad_document,
|
||||
# job_name,
|
||||
# postprocessor_arguments,
|
||||
# postprocessor_list,
|
||||
# ) in tests_to_perform:
|
||||
# if current_document != freecad_document:
|
||||
# if current_document != "":
|
||||
# FreeCAD.closeDocument(current_document)
|
||||
# current_document = freecad_document
|
||||
# current_document_path = (
|
||||
# FreeCAD.getHomePath()
|
||||
# + PATHTESTS_LOCATION
|
||||
# + os.path.sep
|
||||
# + current_document
|
||||
# + ".fcstd"
|
||||
# )
|
||||
# FreeCAD.open(current_document_path)
|
||||
# job = FreeCAD.ActiveDocument.getObject(job_name)
|
||||
# # Create the objects to be written by the postprocessor.
|
||||
# self.pp._buildPostList(job)
|
||||
# for postprocessor_id in postprocessors_to_test:
|
||||
# if postprocessor_list == () or postprocessor_id in postprocessor_list:
|
||||
# print(
|
||||
# "\nRunning %s test on %s postprocessor:\n"
|
||||
# % (output_file_id, postprocessor_id)
|
||||
# )
|
||||
# processor = PostProcessor.load(postprocessor_id)
|
||||
# output_file_path = FreeCAD.getHomePath() + PATHTESTS_LOCATION
|
||||
# output_file_pattern = "test_%s_%s" % (
|
||||
# postprocessor_id,
|
||||
# output_file_id,
|
||||
# )
|
||||
# output_file_extension = ".ngc"
|
||||
# for idx, section in enumerate(postlist):
|
||||
# partname = section[0]
|
||||
# sublist = section[1]
|
||||
# output_filename = PathPost.processFileNameSubstitutions(
|
||||
# job,
|
||||
# partname,
|
||||
# idx,
|
||||
# output_file_path,
|
||||
# output_file_pattern,
|
||||
# output_file_extension,
|
||||
# )
|
||||
# # print("output file: " + output_filename)
|
||||
# file_path, extension = os.path.splitext(output_filename)
|
||||
# reference_file_name = "%s%s%s" % (file_path, "_ref", extension)
|
||||
# # print("reference file: " + reference_file_name)
|
||||
# gcode = processor.export(
|
||||
# sublist, output_filename, postprocessor_arguments
|
||||
# )
|
||||
# if not gcode:
|
||||
# print("no gcode")
|
||||
# with open(reference_file_name, "r") as fp:
|
||||
# reference_gcode = fp.read()
|
||||
# if not reference_gcode:
|
||||
# print("no reference gcode")
|
||||
# # Remove the "Output Time:" line in the header from the
|
||||
# # comparison if it is present because it changes with
|
||||
# # every test.
|
||||
# gcode_lines = [
|
||||
# i for i in gcode.splitlines(True) if "Output Time:" not in i
|
||||
# ]
|
||||
# reference_gcode_lines = [
|
||||
# i
|
||||
# for i in reference_gcode.splitlines(True)
|
||||
# if "Output Time:" not in i
|
||||
# ]
|
||||
# if gcode_lines != reference_gcode_lines:
|
||||
# msg = "".join(
|
||||
# difflib.ndiff(gcode_lines, reference_gcode_lines)
|
||||
# )
|
||||
# self.fail(
|
||||
# os.path.basename(output_filename)
|
||||
# + " output doesn't match:\n"
|
||||
# + msg
|
||||
# )
|
||||
# if not KEEP_DEBUG_OUTPUT:
|
||||
# os.remove(output_filename)
|
||||
# if current_document != "":
|
||||
# FreeCAD.closeDocument(current_document)
|
||||
|
||||
|
||||
class TestPathPostUtils(unittest.TestCase):
|
||||
def test010(self):
|
||||
"""Test the utility functions in the PostUtils.py file."""
|
||||
@@ -618,14 +525,75 @@ class TestBuildPostList(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
|
||||
cls.testfile = FreeCAD.getHomePath() + "Mod/CAM/CAMTests/test_filenaming.fcstd"
|
||||
cls.doc = FreeCAD.open(cls.testfile)
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "")
|
||||
cls.job = cls.doc.getObjectsByLabel("MainJob")[0]
|
||||
# Create a new document instead of opening external file
|
||||
cls.doc = FreeCAD.newDocument("test_filenaming")
|
||||
|
||||
# Create a simple geometry object for the job
|
||||
import Part
|
||||
|
||||
box = cls.doc.addObject("Part::Box", "TestBox")
|
||||
box.Length = 100
|
||||
box.Width = 100
|
||||
box.Height = 20
|
||||
|
||||
# Create CAM job programmatically
|
||||
cls.job = PathJob.Create("MainJob", [box], None)
|
||||
cls.job.PostProcessor = "generic"
|
||||
cls.job.PostProcessorOutputFile = ""
|
||||
cls.job.SplitOutput = False
|
||||
cls.job.OrderOutputBy = "Operation"
|
||||
cls.job.Fixtures = ["G54", "G55"] # 2 fixtures as expected by tests
|
||||
|
||||
# Create additional tool controllers to match original file structure
|
||||
# Original had 2 tool controllers both with "TC: 7/16\" two flute" label
|
||||
|
||||
# Modify the first tool controller to have the expected values
|
||||
cls.job.Tools.Group[0].ToolNumber = 5
|
||||
cls.job.Tools.Group[0].Label = (
|
||||
'TC: 7/16" two flute' # test050 expects this sanitized to "TC__7_16__two_flute"
|
||||
)
|
||||
|
||||
# Add second tool controller with same label but different number
|
||||
tc2 = PathToolController.Create()
|
||||
tc2.ToolNumber = 2
|
||||
tc2.Label = 'TC: 7/16" two flute' # Same label as first tool controller
|
||||
cls.job.Proxy.addToolController(tc2)
|
||||
|
||||
# Create mock operations to match original file structure
|
||||
# Original had 3 operations: outsideprofile, DrillAllHoles, Comment
|
||||
# The Comment operation has no tool controller
|
||||
operation_names = ["outsideprofile", "DrillAllHoles", "Comment"]
|
||||
|
||||
for i, name in enumerate(operation_names):
|
||||
# Create a simple document object that mimics an operation
|
||||
op = cls.doc.addObject("Path::FeaturePython", name)
|
||||
op.Label = name
|
||||
# Path::FeaturePython objects already have a Path property
|
||||
op.Path = Path.Path()
|
||||
|
||||
# Only add ToolController property for operations that need it
|
||||
if name != "Comment":
|
||||
# Add ToolController property to the operation
|
||||
op.addProperty(
|
||||
"App::PropertyLink",
|
||||
"ToolController",
|
||||
"Base",
|
||||
"Tool controller for this operation",
|
||||
)
|
||||
# Assign operations to tool controllers
|
||||
if i == 0: # outsideprofile uses first tool controller (tool 5)
|
||||
op.ToolController = cls.job.Tools.Group[0]
|
||||
elif i == 1: # DrillAllHoles uses second tool controller (tool 2)
|
||||
op.ToolController = cls.job.Tools.Group[1]
|
||||
# Comment operation has no tool controller (None)
|
||||
|
||||
# Add to job operations
|
||||
cls.job.Operations.addObject(op)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
FreeCAD.closeDocument(cls.doc.Name)
|
||||
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "")
|
||||
|
||||
def setUp(self):
|
||||
self.pp = PathPost.PostProcessor(self.job, "generic", "", "")
|
||||
@@ -636,9 +604,11 @@ class TestBuildPostList(unittest.TestCase):
|
||||
def test000(self):
|
||||
|
||||
# check that the test file is structured correctly
|
||||
self.assertTrue(len(self.job.Tools.Group) == 2)
|
||||
self.assertTrue(len(self.job.Fixtures) == 2)
|
||||
self.assertTrue(len(self.job.Operations.Group) == 3)
|
||||
self.assertEqual(len(self.job.Tools.Group), 2)
|
||||
self.assertEqual(len(self.job.Fixtures), 2)
|
||||
self.assertEqual(
|
||||
len(self.job.Operations.Group), 3
|
||||
) # Updated back to 3 operations, Comment has no tool controller
|
||||
|
||||
self.job.SplitOutput = False
|
||||
self.job.OrderOutputBy = "Operation"
|
||||
@@ -658,7 +628,7 @@ class TestBuildPostList(unittest.TestCase):
|
||||
self.job.SplitOutput = False
|
||||
self.job.OrderOutputBy = "Operation"
|
||||
postlist = self.pp._buildPostList()
|
||||
self.assertTrue(len(postlist) == 1)
|
||||
self.assertEqual(len(postlist), 1)
|
||||
|
||||
def test030(self):
|
||||
# No splitting should include all ops, tools, and fixtures
|
||||
@@ -667,7 +637,9 @@ class TestBuildPostList(unittest.TestCase):
|
||||
postlist = self.pp._buildPostList()
|
||||
firstoutputitem = postlist[0]
|
||||
firstoplist = firstoutputitem[1]
|
||||
self.assertTrue(len(firstoplist) == 14)
|
||||
print(f"DEBUG test030: postlist length={len(firstoplist)}, expected=14")
|
||||
print(f"DEBUG test030: firstoplist={[str(item) for item in firstoplist]}")
|
||||
self.assertEqual(len(firstoplist), 14)
|
||||
|
||||
def test040(self):
|
||||
# Test splitting by tool
|
||||
@@ -679,11 +651,14 @@ class TestBuildPostList(unittest.TestCase):
|
||||
postlist = self.pp._buildPostList()
|
||||
|
||||
firstoutputitem = postlist[0]
|
||||
print(f"DEBUG test040: firstoutputitem[0]={firstoutputitem[0]}, expected='5'")
|
||||
print(f"DEBUG test040: tool numbers={[tc.ToolNumber for tc in self.job.Tools.Group]}")
|
||||
self.assertTrue(firstoutputitem[0] == str(5))
|
||||
|
||||
# check length of output
|
||||
firstoplist = firstoutputitem[1]
|
||||
self.assertTrue(len(firstoplist) == 5)
|
||||
print(f"DEBUG test040: postlist length={len(firstoplist)}, expected=5")
|
||||
self.assertEqual(len(firstoplist), 5)
|
||||
|
||||
def test050(self):
|
||||
# ordering by tool with tool description for string
|
||||
@@ -706,5 +681,5 @@ class TestBuildPostList(unittest.TestCase):
|
||||
|
||||
firstoutputitem = postlist[0]
|
||||
firstoplist = firstoutputitem[1]
|
||||
self.assertTrue(len(firstoplist) == 6)
|
||||
self.assertEqual(len(firstoplist), 6)
|
||||
self.assertTrue(firstoutputitem[0] == "G54")
|
||||
|
||||
@@ -216,13 +216,17 @@ class ObjectJob:
|
||||
setattr(obj, n[0], n[1])
|
||||
|
||||
obj.PostProcessorOutputFile = Path.Preferences.defaultOutputFile()
|
||||
obj.PostProcessor = postProcessors = Path.Preferences.allEnabledPostProcessors()
|
||||
postProcessors = Path.Preferences.allEnabledPostProcessors()
|
||||
# Add empty string as a valid enumeration option
|
||||
if "" not in postProcessors:
|
||||
postProcessors = [""] + postProcessors
|
||||
obj.PostProcessor = postProcessors
|
||||
defaultPostProcessor = Path.Preferences.defaultPostProcessor()
|
||||
# Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed)
|
||||
if defaultPostProcessor in postProcessors:
|
||||
obj.PostProcessor = defaultPostProcessor
|
||||
else:
|
||||
obj.PostProcessor = postProcessors[0]
|
||||
obj.PostProcessor = ""
|
||||
obj.PostProcessorArgs = Path.Preferences.defaultPostProcessorArgs()
|
||||
obj.GeometryTolerance = Path.Preferences.defaultGeometryTolerance()
|
||||
|
||||
|
||||
@@ -139,8 +139,8 @@ class FilenameGenerator:
|
||||
substitutions = {
|
||||
"%d": self.job.Document.Label,
|
||||
"%j": self.job.Label,
|
||||
"%T": self.subpartname, # Tool
|
||||
"%t": self.subpartname, # Tool
|
||||
"%T": self.subpartname, # Tool Number
|
||||
"%t": self.subpartname, # Tool Controller Label
|
||||
"%W": self.subpartname, # Fixture
|
||||
"%O": self.subpartname, # Operation
|
||||
}
|
||||
@@ -167,6 +167,10 @@ class FilenameGenerator:
|
||||
f"-{self.sequencenumber}" if not explicit_sequence and self.sequencenumber else ""
|
||||
)
|
||||
filename = f"{temp_filename}{subpart}{sequence}{self.extension}"
|
||||
|
||||
# Trim leading dash if filename starts with one
|
||||
if filename.startswith("-"):
|
||||
filename = filename[1:]
|
||||
full_path = os.path.join(self.qualified_path, filename)
|
||||
|
||||
self.sequencenumber += 1
|
||||
|
||||
Reference in New Issue
Block a user