From 9298ad8ad0f37bdb7651ba5ac2919873e2020101 Mon Sep 17 00:00:00 2001 From: jalapenopuzzle <8386278+jalapenopuzzle@users.noreply.github.com> Date: Sat, 29 Mar 2025 13:10:37 +1100 Subject: [PATCH] CAM: snapmaker add --quick-swap and --bracing-kit options --- src/Mod/CAM/CAMTests/TestSnapmakerPost.py | 124 ++++++++++++++++++ .../CAM/Path/Post/scripts/snapmaker_post.py | 56 +++++++- 2 files changed, 179 insertions(+), 1 deletion(-) diff --git a/src/Mod/CAM/CAMTests/TestSnapmakerPost.py b/src/Mod/CAM/CAMTests/TestSnapmakerPost.py index ac0cb9f3e2..313b871387 100644 --- a/src/Mod/CAM/CAMTests/TestSnapmakerPost.py +++ b/src/Mod/CAM/CAMTests/TestSnapmakerPost.py @@ -16,6 +16,7 @@ # * implied. See the Licence for the specific language governing * # * permissions and limitations under the Licence. * # *************************************************************************** +import argparse import re from typing import List @@ -317,6 +318,129 @@ M5 result = gcode.splitlines()[18] self.assertEqual(result, expected) + def test_mod_kits(self): + """Test the various mod kits against various models.""" + + command = Path.Command("G0 X10 Y20 Z30") + expected = "G0 X10.000 Y20.000 Z30.000" + + gcode = self.get_gcode( + [command], + "--machine=Original --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],[]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=90, Y=90, Z=50)) + + gcode = self.get_gcode( + [command], + "--machine=Original --quick-swap --no-header", + ) + # I don't understand why export returns the arguments + # if snapmaker_process_arguments fails. + self.assertTrue(isinstance(gcode,argparse.Namespace)) + self.assertFalse(isinstance(gcode,str)) + + gcode = self.get_gcode( + [command], + "--machine=Original --bracing-kit --no-header", + ) + # I don't understand why export returns the arguments + # if snapmaker_process_arguments fails. + self.assertTrue(isinstance(gcode,argparse.Namespace)) + self.assertFalse(isinstance(gcode,str)) + + gcode = self.get_gcode( + [command], + "--machine=Artisan --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],[]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=400, Y=400, Z=400)) + + gcode = self.get_gcode( + [command], + "--machine=Artisan --quick-swap --no-header", + ) + # I don't understand why export returns the arguments + # if snapmaker_process_arguments fails. + self.assertTrue(isinstance(gcode,argparse.Namespace)) + self.assertFalse(isinstance(gcode,str)) + + gcode = self.get_gcode( + [command], + "--machine=Artisan --bracing-kit --no-header", + ) + # I don't understand why export returns the arguments + # if snapmaker_process_arguments fails. + self.assertTrue(isinstance(gcode,argparse.Namespace)) + self.assertFalse(isinstance(gcode,str)) + + gcode = self.get_gcode( + [command], + "--machine=A150 --toolhead=50W_CNC --bracing-kit --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["BK"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=160, Y=148, Z=84)) + + gcode = self.get_gcode( + [command], + "--machine=A150 --toolhead=50W_CNC --quick-swap --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["QS"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=160, Y=145, Z=75)) + + gcode = self.get_gcode( + [command], + "--machine=A250 --toolhead=50W_CNC --bracing-kit --quick-swap --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["QS","BK"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=230, Y=223, Z=159)) + + gcode = self.get_gcode( + [command], + "--machine=A250T --toolhead=50W_CNC --quick-swap --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["QS"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=230, Y=235, Z=165)) + + gcode = self.get_gcode( + [command], + "--machine=A350 --toolhead=50W_CNC --bracing-kit --quick-swap --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["QS","BK"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=320, Y=323, Z=254)) + + gcode = self.get_gcode( + [command], + "--machine=A350T --toolhead=50W_CNC --bracing-kit --quick-swap --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["QS","BK"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=320, Y=323, Z=254)) + + gcode = self.get_gcode( + [command], + "--machine=A350T --toolhead=200W_CNC --bracing-kit --quick-swap --no-header", + ) + result = gcode.splitlines()[18] + self.assertEqual(result, expected) + self.assertEqual(self.post.values["MOD_KITS_INSTALLED"],["QS","BK"]) + self.assertEqual(self.post.values["BOUNDARIES"],dict(X=320, Y=310, Z=254)) + def test_toolhead_selection(self): """Test automatic selection of toolhead where appropriate""" diff --git a/src/Mod/CAM/Path/Post/scripts/snapmaker_post.py b/src/Mod/CAM/Path/Post/scripts/snapmaker_post.py index 613ffcbf01..db0b4c941b 100644 --- a/src/Mod/CAM/Path/Post/scripts/snapmaker_post.py +++ b/src/Mod/CAM/Path/Post/scripts/snapmaker_post.py @@ -46,6 +46,15 @@ if DEBUG := False: else: Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +def convert_option_to_attr(option_name): + # transforms argparse options into identifiers + if option_name.startswith('--'): + option_name = option_name[2:] + elif option_name.startswith('-'): + option_name = option_name[1:] + + return option_name.replace('-','_') + SNAPMAKER_MACHINES = dict( Original=dict( key="Original", @@ -105,6 +114,27 @@ SNAPMAKER_MACHINES = dict( ), ) +# These modifications were released to upgrade the Snapmaker 2.0 machines +# which started on Kickstarter. +SNAPMAKER_MOD_KITS = { + "QS": dict( + key="QS", + name="Quick Swap Kit", + option_name="--quick-swap", + option_help_text="Indicates that the quick swap kit is installed. Only compatible with Snapmaker 2 machines.", + compatible_machines={"A150","A250","A250T","A350","A350T"}, + boundaries_delta=dict(X=0, Y=-15, Z=-15), + ), + "BK": dict( + key="BK", + name="Bracing Kit", + option_name="--bracing-kit", + option_help_text="Indicates that the bracing kit is installed. Only compatible with Snapmaker 2 machines.", + compatible_machines={"A150","A250","A250T","A350","A350T"}, + boundaries_delta=dict(X=0, Y=-12, Z=-6), + ), +} + # Could support other types of toolheads (laser, drag knife, 3DP, ...) in the future # https://wiki.snapmaker.com/en/Snapmaker_Luban/manual/2_supported_gcode_references#m3m4-modified-cnclaser-on SNAPMAKER_TOOLHEADS = { @@ -262,6 +292,7 @@ class Snapmaker(Path.Post.Processor.PostProcessor): self.values["BOUNDARIES"] = None self.values["BOUNDARIES_CHECK"] = False self.values["MACHINES"] = SNAPMAKER_MACHINES + self.values["MOD_KITS_ALL"] = SNAPMAKER_MOD_KITS self.values["TOOLHEADS"] = SNAPMAKER_TOOLHEADS self.values["TOOLHEAD_NAME"] = None self.values["SPINDLE_SPEEDS"] = dict() @@ -353,7 +384,7 @@ class Snapmaker(Path.Post.Processor.PostProcessor): "--boundaries", action=CoordinatesAction, default=None, - help='Custom boundaries (e.g. "100, 200, 300"). Overrides --machine', + help='Custom boundaries (e.g. "100, 200, 300"). Overrides boundaries from --machine', ) group.add_argument( @@ -364,6 +395,14 @@ class Snapmaker(Path.Post.Processor.PostProcessor): help=f"Snapmaker machine. Choose from [{self.values['MACHINES'].keys()}].", ) + for key, value in SNAPMAKER_MOD_KITS.items(): + group.add_argument( + value["option_name"], + default=False, + action="store_true", + help=value["option_help_text"], + ) + group.add_argument( "--toolhead", default=None, @@ -465,10 +504,25 @@ class Snapmaker(Path.Post.Processor.PostProcessor): "Spindle speed will be controlled using using RPM.\n" ) + self.values["MOD_KITS_INSTALLED"] = [] if args.boundaries: # may override machine boundaries, which is expected self.values["BOUNDARIES"] = args.boundaries self.values["MACHINE_NAME"] += " Boundaries overide=" + str(args.boundaries) else: + # Update machine dimensions based on installed kits + for mod_kit in self.values["MOD_KITS_ALL"].values(): + if getattr(args, convert_option_to_attr(mod_kit["option_name"])): + if self.values["MACHINE_KEY"] not in mod_kit["compatible_machines"]: + FreeCAD.Console.PrintError( + f"Machine {machine['name']} is not compatible with {mod_kit["option_name"]}.\n" + ) + flag = False + return (flag, args) + self.values["MACHINE_NAME"] += " " + mod_kit["name"] + self.values["MOD_KITS_INSTALLED"].append(mod_kit["key"]) + for axis in mod_kit["boundaries_delta"].keys(): + self.values["BOUNDARIES"][axis] += mod_kit["boundaries_delta"][axis] + # Update machine dimensions based on installed toolhead for axis in toolhead["boundaries_delta"].keys(): self.values["BOUNDARIES"][axis] += toolhead["boundaries_delta"][axis]