CAM: snapmaker toolheads and spindle speeds

* Removed generic snapmaker machine and toolhead configuration. The idea of running a CNC code that is not matched to the machine's abilities and work area seems dangerous.
* --machine argument is required
* --toolhead argument is required when the selected machine is compatible with more than one toolhead. When the selected machine only supports one toolhead, it is selected as the default.
* --spindle-percent defaults according to the selected toolhead capabilities. If the toolhead can do RPM, then RPM is used, otherwise falls back to percent. This option now functions as an override.
* fixed a bug in convert_spindle() when RPM is selected. The gcode was not returned.
This commit is contained in:
jalapenopuzzle
2025-04-07 00:03:57 +10:00
parent 699a25e243
commit 5eaa6326a4
2 changed files with 186 additions and 70 deletions

View File

@@ -51,47 +51,79 @@ SNAPMAKER_MACHINES = dict(
key="Original",
name="Snapmaker Original",
boundaries=dict(X=90, Y=90, Z=50),
compatible_toolheads={"Original_CNC"},
),
Original_Z_Extension=dict(
key="Original_Z_Extension",
name="Snapmaker Original with Z extension",
boundaries=dict(X=90, Y=90, Z=146),
compatible_toolheads={"Original_CNC"},
),
A150=dict(
key="A150",
name="Snapmaker 2 A150",
boundaries=dict(X=160, Y=160, Z=90),
compatible_toolheads={"50W_CNC"},
),
A250=dict(
key="A250",
name="Snapmaker 2 A250",
boundaries=dict(X=230, Y=250, Z=180),
compatible_toolheads={"50W_CNC", "200W_CNC"},
),
A250T=dict(
key="A250T",
name="Snapmaker 2 A250T",
boundaries=dict(X=230, Y=250, Z=180),
compatible_toolheads={"50W_CNC", "200W_CNC"},
),
A350=dict(
key="A350",
name="Snapmaker 2 A350",
boundaries=dict(X=320, Y=350, Z=275),
compatible_toolheads={"50W_CNC", "200W_CNC"},
),
A350T=dict(
key="A350T",
name="Snapmaker 2 A350T",
boundaries=dict(X=320, Y=350, Z=275),
compatible_toolheads={"50W_CNC", "200W_CNC"},
),
Artisan=dict(
key="Artisan",
name="Snapmaker Artisan",
boundaries=dict(X=400, Y=400, Z=400),
boundaries=dict(X=400, Y=413, Z=400),
compatible_toolheads={"200W_CNC"},
),
)
# 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 = {
"50W": dict(name="50W CNC module", min=0, max=12000, percent=True),
"200W": dict(name="200W CNC module", min=8000, max=18000, percent=False),
"Original_CNC": dict(
key="Original_CNC",
name="Original CNC module",
speed_rpm=dict(min=0, max=7000),
boundaries_delta=dict(X=0, Y=0, Z=0),
has_percent=True,
has_speed_s=False,
),
"50W_CNC": dict(
key="50W_CNC",
name="50W CNC module",
speed_rpm=dict(min=0, max=12000),
boundaries_delta=dict(X=0, Y=0, Z=0),
has_percent=True,
has_speed_s=False,
),
"200W_CNC": dict(
key="200W_CNC",
name="200W CNC module",
speed_rpm=dict(min=0, max=18000),
boundaries_delta=dict(X=0, Y=-13, Z=0),
has_percent=True,
has_speed_s=True,
),
}
@@ -223,16 +255,9 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
self.values["BOUNDARIES_CHECK"] = False
self.values["MACHINES"] = SNAPMAKER_MACHINES
self.values["TOOLHEADS"] = SNAPMAKER_TOOLHEADS
# default toolhead is 50W (the weakest one)
self.values["DEFAULT_TOOLHEAD"] = "50W"
self.values["TOOLHEAD_NAME"] = SNAPMAKER_TOOLHEADS[self.values["DEFAULT_TOOLHEAD"]]["name"]
self.values["SPINDLE_SPEEDS"] = dict(
min=SNAPMAKER_TOOLHEADS[self.values["DEFAULT_TOOLHEAD"]]["min"],
max=SNAPMAKER_TOOLHEADS[self.values["DEFAULT_TOOLHEAD"]]["max"],
)
self.values["SPINDLE_PERCENT"] = SNAPMAKER_TOOLHEADS[self.values["DEFAULT_TOOLHEAD"]][
"percent"
]
self.values["TOOLHEAD_NAME"] = None
self.values["SPINDLE_SPEEDS"] = dict()
self.values["SPINDLE_PERCENT"] = None
def snapmaker_init_argument_defaults(self) -> None:
"""Initialize which arguments (in a pair) are shown as the default argument."""
@@ -245,7 +270,6 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
self.argument_defaults["thumbnail"] = True
self.argument_defaults["gui"] = True
self.argument_defaults["boundaries-check"] = True
self.argument_defaults["spindle-percent"] = True
def snapmaker_init_arguments_visible(self) -> None:
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
@@ -349,14 +373,8 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
group.add_argument(
"--spindle-percent",
action="store_true",
default=argument_defaults["spindle-percent"],
help="use percent as toolhead spindle speed unit",
)
group.add_argument(
"--spindle-rpm",
action="store_false",
dest="spindle_percent",
help="Use RPM as toolhead spindle speed unit",
default=None,
help="use percent as toolhead spindle speed unit (default: use RPM if supported by toolhead, otherwise percent)",
)
group.add_argument(
@@ -390,33 +408,63 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
# The deepcopy is necessary to avoid modifying the boundaries in the MACHINES dict.
self.values["BOUNDARIES"] = copy.deepcopy(machine["boundaries"])
if args.boundaries: # may override machine boundaries, which is expected
self.values["BOUNDARIES"] = args.boundaries
if args.toolhead:
if args.toolhead not in machine["compatible_toolheads"]:
FreeCAD.Console.PrintError(
f"Selected --toolhead={args.toolhead} is not compatible with machine {machine['name']}."
+f" Choose from [{machine['compatible_toolheads']}]\n")
flag = False
return (flag, args)
toolhead = self.values["TOOLHEADS"][args.toolhead]
self.values["TOOLHEAD_NAME"] = toolhead["name"]
elif len(machine["compatible_toolheads"]) == 1:
toolhead_key = next(iter(machine["compatible_toolheads"]))
toolhead = self.values["TOOLHEADS"][toolhead_key]
else:
FreeCAD.Console.PrintWarning(
f'No toolhead selected, using default ({self.values["TOOLHEAD_NAME"]}). '
f"Consider adding --toolhead\n"
FreeCAD.Console.PrintError(
f"Machine {machine['name']} has multiple compatible toolheads:\n"
f"{machine['compatible_toolheads']}\n"
"Please add --toolhead argument.\n"
)
toolhead = self.values["TOOLHEADS"][self.values["DEFAULT_TOOLHEAD"]]
flag = False
return (flag, args)
self.values["TOOLHEAD_KEY"] = toolhead["key"]
self.values["TOOLHEAD_NAME"] = toolhead["name"]
self.values["SPINDLE_SPEEDS"] = {key: toolhead[key] for key in ("min", "max")}
self.values["SPINDLE_SPEEDS"] = toolhead["speed_rpm"]
if args.spindle_speeds: # may override toolhead value, which is expected
self.values["SPINDLE_SPEEDS"] = args.spindle_speeds
if args.spindle_percent is not None:
if toolhead["percent"] is True:
if toolhead["has_percent"]:
self.values["SPINDLE_PERCENT"] = True
if args.spindle_percent is False:
FreeCAD.Console.PrintWarning(
"Toolhead does not handle RPM spindle speed, using percents instead.\n"
)
else:
self.values["SPINDLE_PERCENT"] = args.spindle_percent
FreeCAD.Console.PrintError(
f"Requested spindle speed in percent, but toolhead {toolhead['name']}"
+" does not support speed as percent.\n"
)
flag = False
return (flag, args)
else:
# Prefer speed S over percent P
self.values["SPINDLE_PERCENT"] = toolhead['has_percent'] and not toolhead['has_speed_s']
if self.values["SPINDLE_PERCENT"]:
FreeCAD.Console.PrintWarning(
"Spindle speed will be controlled using using percentages.\n"
)
else:
FreeCAD.Console.PrintWarning(
"Spindle speed will be controlled using using RPM.\n"
)
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 toolhead
for axis in toolhead["boundaries_delta"].keys():
self.values["BOUNDARIES"][axis] += toolhead["boundaries_delta"][axis]
self.values["MACHINE_NAME"] += " " + toolhead["name"]
self.values["THUMBNAIL"] = args.thumbnail
self.values["ALLOW_GUI"] = args.gui
@@ -539,9 +587,10 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
def convert_spindle(self, gcode: List[str]) -> List[str]:
"""convert spindle speed values from RPM to percent (%) (M3/M4 commands)"""
if self.values["SPINDLE_PERCENT"] is False:
return
return gcode
# TODO: check if percentage covers range 0-max (most probable) or min-max (200W has a documented min speed)
# https://wiki.snapmaker.com/en/Snapmaker_Luban/manual/2_supported_gcode_references#m3m4-modified-cnclaser-on
# Speed as percentage in [0,100]% range
for index, commandline in enumerate(
gcode
): # .split(self.values["END_OF_LINE_CHARACTERS"]):