variable_f_rate

This commit is contained in:
Nicolas Predella 2026-05-22 19:36:43 +03:00
parent 3080f65695
commit 1d0147ce73
4 changed files with 46 additions and 17 deletions

View file

@ -345,7 +345,8 @@ class SLICENODE_GetMesh(SliceNode):
class SLICENODE_GetPaths(SliceNode): class SLICENODE_GetPaths(SliceNode):
bl_label = "Get Paths" bl_label = "Get Paths"
attr_name: NfStringProperty(name="Extrusion Multiplier", default="e_mult") # pyright: ignore[reportInvalidTypeForm] attr_name: NfStringProperty(name="Extrusion Attr", default="e_mult") # pyright: ignore[reportInvalidTypeForm]
f_attr_name: NfStringProperty(name="Feedrate Attr", default="f_rate") # pyright: ignore[reportInvalidTypeForm]
def init(self, context): def init(self, context):
self.add_input("SLICESOCK_Object") self.add_input("SLICESOCK_Object")
@ -354,15 +355,17 @@ class SLICENODE_GetPaths(SliceNode):
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
assert layout assert layout
layout.prop(self, "attr_name") layout.prop(self, "attr_name")
layout.prop(self, "f_attr_name")
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths: def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
obj: Any = self.resolve("Object") obj: Any = self.resolve("Object")
attr_name: str = self.attr_name attr_name: str = self.attr_name
f_attr_name: str = self.f_attr_name
from ...infra.blender.blender_mesh_capture import capture_mesh_edges from ...infra.blender.blender_mesh_capture import capture_mesh_edges
so, e_rates = capture_mesh_edges(obj, attr_name=attr_name) so, e_rates, f_rates = capture_mesh_edges(obj, attr_name=attr_name, f_attr_name=f_attr_name)
return SlicePaths(so, extrusion_rate=e_rates) return SlicePaths(so, extrusion_rate=e_rates, feedrate=f_rates)
@register_class @register_class
class SLICENODE_SliceShells(SliceNode): class SLICENODE_SliceShells(SliceNode):

View file

@ -11,8 +11,9 @@ SCALE = 1_000_000
class SlicePaths(list[np.ndarray]): class SlicePaths(list[np.ndarray]):
groups: list[int] groups: list[int]
extrusion_rate: list[np.ndarray] extrusion_rate: list[np.ndarray]
feedrate: list[np.ndarray]
def __init__(self, paths: list[np.ndarray] = [], groups: list[int] = [], extrusion_rate: list[np.ndarray] | None = None): def __init__(self, paths: list[np.ndarray] = [], groups: list[int] = [], extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None):
super().__init__(paths) super().__init__(paths)
self.groups = [] self.groups = []
@ -25,17 +26,27 @@ class SlicePaths(list[np.ndarray]):
else: else:
self.extrusion_rate = [np.ones(len(p)) for p in paths] self.extrusion_rate = [np.ones(len(p)) for p in paths]
def copy_with(self, paths: list[np.ndarray], groups: list[int] | None = None, extrusion_rate: list[np.ndarray] | None = None) -> SlicePaths: self.feedrate = []
if feedrate:
self.feedrate = list(feedrate)
else:
self.feedrate = [np.zeros(len(p)) for p in paths]
def copy_with(self, paths: list[np.ndarray], groups: list[int] | None = None, extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None) -> SlicePaths:
"""Returns a new SlicePaths object with new paths/groups but preserving all other metadata.""" """Returns a new SlicePaths object with new paths/groups but preserving all other metadata."""
if extrusion_rate is None: if extrusion_rate is None:
# Only reuse extrusion_rate if path lengths match exactly # Only reuse extrusion_rate if path lengths match exactly
if len(paths) == len(self) and all(len(p) == len(op) for p, op in zip(paths, self)): if len(paths) == len(self) and all(len(p) == len(op) for p, op in zip(paths, self)):
extrusion_rate = self.extrusion_rate extrusion_rate = self.extrusion_rate
new_obj = SlicePaths(paths, groups if groups is not None else self.groups, extrusion_rate) if feedrate is None:
if len(paths) == len(self) and all(len(p) == len(op) for p, op in zip(paths, self)):
feedrate = self.feedrate
new_obj = SlicePaths(paths, groups if groups is not None else self.groups, extrusion_rate, feedrate)
# Copy any other attributes that might have been added # Copy any other attributes that might have been added
for k, v in self.__dict__.items(): for k, v in self.__dict__.items():
if k not in ["groups", "extrusion_rate"]: if k not in ["groups", "extrusion_rate", "feedrate"]:
setattr(new_obj, k, v) setattr(new_obj, k, v)
return new_obj return new_obj
@ -522,6 +533,7 @@ class Pather(object):
def __init__(self, paths: list[np.ndarray], **kwargs) -> None: def __init__(self, paths: list[np.ndarray], **kwargs) -> None:
self.paths = paths self.paths = paths
self.extrusion_rates = getattr(paths, "extrusion_rate", [np.ones(len(p)) for p in paths]) self.extrusion_rates = getattr(paths, "extrusion_rate", [np.ones(len(p)) for p in paths])
self.feedrates = getattr(paths, "feedrate", [np.zeros(len(p)) for p in paths])
self.bed_center_x: float = 250. self.bed_center_x: float = 250.
self.bed_center_y: float = 250. self.bed_center_y: float = 250.
self.feedrate: float = 1200. self.feedrate: float = 1200.
@ -580,6 +592,7 @@ class Pather(object):
continue continue
e_rates = self.extrusion_rates[i] e_rates = self.extrusion_rates[i]
f_rates = self.feedrates[i]
x0, y0, z0 = coords[0] x0, y0, z0 = coords[0]
@ -592,12 +605,14 @@ class Pather(object):
gcode.append(f"G0 X{x0:.3f} Y{y0:.3f} Z{z0:.3f}") gcode.append(f"G0 X{x0:.3f} Y{y0:.3f} Z{z0:.3f}")
old_x, old_y, old_z = x0, y0, z0 old_x, old_y, old_z = x0, y0, z0
fr = self.feedrate
old_fr = 0 old_fr = 0
for j, (p1, p2) in enumerate(zip(coords, coords[1:])): for j, (p1, p2) in enumerate(zip(coords, coords[1:])):
seg_len: float = segment_length_3d(p1, p2) seg_len: float = segment_length_3d(p1, p2)
e_segment = seg_len * self.extrusion_rate * e_rates[j] e_segment = seg_len * self.extrusion_rate * e_rates[j]
# Per-segment feedrate value (not multiplier)
fr = f_rates[j] if f_rates[j] > 0 else self.feedrate
old_x, old_y, old_z = p1 old_x, old_y, old_z = p1
x, y, z = p2 x, y, z = p2

View file

@ -410,11 +410,12 @@ def _cached_capture_mesh_edges(
mesh_name: str, mesh_name: str,
matrix_tuple: tuple[float, ...], matrix_tuple: tuple[float, ...],
scene_scale: float, scene_scale: float,
attr_name: str = "e_mult" attr_name: str = "e_mult",
) -> tuple[list[NDArray[float64]], list[NDArray[float64]]]: f_attr_name: str = "f_rate"
) -> tuple[list[NDArray[float64]], list[NDArray[float64]], list[NDArray[float64]]]:
# This helper expects that the object and mesh exist and are accessible via bpy.data # This helper expects that the object and mesh exist and are accessible via bpy.data
obj = bpy.data.objects.get(obj_name) obj = bpy.data.objects.get(obj_name)
if not obj: return [], [] if not obj: return [], [], []
# We don't use obj_eval here directly as it's not hashable, # We don't use obj_eval here directly as it's not hashable,
# but we assume this is called when we know we need to re-capture. # but we assume this is called when we know we need to re-capture.
@ -424,10 +425,10 @@ def _cached_capture_mesh_edges(
obj_eval = obj.evaluated_get(depsgraph) obj_eval = obj.evaluated_get(depsgraph)
mesh = obj_eval.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph) mesh = obj_eval.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph)
except Exception: except Exception:
return [], [] return [], [], []
if not mesh or not mesh.vertices: if not mesh or not mesh.vertices:
return [], [] return [], [], []
try: try:
world_mat = np.array(matrix_tuple).reshape(4, 4) world_mat = np.array(matrix_tuple).reshape(4, 4)
@ -439,6 +440,10 @@ def _cached_capture_mesh_edges(
if attr_name in mesh.attributes: if attr_name in mesh.attributes:
mesh.attributes[attr_name].data.foreach_get("value", e_mults) mesh.attributes[attr_name].data.foreach_get("value", e_mults)
f_rates = np.zeros(num_verts, dtype=np.float64)
if f_attr_name in mesh.attributes:
mesh.attributes[f_attr_name].data.foreach_get("value", f_rates)
tx_verts = (verts @ world_mat[:3, :3].T + world_mat[:3, 3]) * scene_scale tx_verts = (verts @ world_mat[:3, :3].T + world_mat[:3, 3]) * scene_scale
edges_set = {tuple(sorted(e.vertices)) for e in mesh.edges} edges_set = {tuple(sorted(e.vertices)) for e in mesh.edges}
@ -447,29 +452,34 @@ def _cached_capture_mesh_edges(
split_at = np.where(~connected)[0] + 1 split_at = np.where(~connected)[0] + 1
raw_paths = np.split(tx_verts, split_at) raw_paths = np.split(tx_verts, split_at)
raw_e_mults = np.split(e_mults, split_at) raw_e_mults = np.split(e_mults, split_at)
raw_f_rates = np.split(f_rates, split_at)
indices_split = np.split(np.arange(num_verts), split_at) indices_split = np.split(np.arange(num_verts), split_at)
final_paths = [] final_paths = []
final_e_mults = [] final_e_mults = []
final_f_rates = []
for i, path_coords in enumerate(raw_paths): for i, path_coords in enumerate(raw_paths):
if path_coords.shape[0] == 0: if path_coords.shape[0] == 0:
continue continue
idx = indices_split[i] idx = indices_split[i]
e_vals = raw_e_mults[i] e_vals = raw_e_mults[i]
f_vals = raw_f_rates[i]
if len(idx) > 2 and tuple(sorted((idx[0], idx[-1]))) in edges_set: if len(idx) > 2 and tuple(sorted((idx[0], idx[-1]))) in edges_set:
path_coords = np.vstack((path_coords, path_coords[0])) path_coords = np.vstack((path_coords, path_coords[0]))
e_vals = np.concatenate((e_vals, [e_vals[0]])) e_vals = np.concatenate((e_vals, [e_vals[0]]))
f_vals = np.concatenate((f_vals, [f_vals[0]]))
final_paths.append(path_coords) final_paths.append(path_coords)
final_e_mults.append(e_vals) final_e_mults.append(e_vals)
final_f_rates.append(f_vals)
return final_paths, final_e_mults return final_paths, final_e_mults, final_f_rates
finally: finally:
if hasattr(obj_eval, "to_mesh_clear"): if hasattr(obj_eval, "to_mesh_clear"):
obj_eval.to_mesh_clear() obj_eval.to_mesh_clear()
def capture_mesh_edges(obj: Object, attr_name: str = "e_mult") -> tuple[list[NDArray[float64]], list[NDArray[float64]]]: def capture_mesh_edges(obj: Object, attr_name: str = "e_mult", f_attr_name: str = "f_rate") -> tuple[list[NDArray[float64]], list[NDArray[float64]], list[NDArray[float64]]]:
if not obj or not obj.data: return [], [] if not obj or not obj.data: return [], [], []
if obj.mode == 'EDIT': if obj.mode == 'EDIT':
obj.update_from_editmode() obj.update_from_editmode()
@ -485,5 +495,6 @@ def capture_mesh_edges(obj: Object, attr_name: str = "e_mult") -> tuple[list[NDA
obj.data.name, obj.data.name,
matrix_tuple, matrix_tuple,
scene_scale, scene_scale,
attr_name attr_name,
f_attr_name
) )

Binary file not shown.