From 77bab165afb3b31b0daa468615403d013aa3c791 Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Wed, 6 May 2026 18:17:30 +0300 Subject: [PATCH] intrinsics implementation --- __init__.py | 82 +++++++++++++++++++++++++++++++++-- core/slice.py | 21 ++++++++- infra/blender_mesh_capture.py | 2 + 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index 791fc02..bce2c52 100644 --- a/__init__.py +++ b/__init__.py @@ -32,6 +32,18 @@ from .infra.blender_slice_nodes_generic import SliceNode, SliceNodeSocket, resol if TYPE_CHECKING: from bpy.stub_internal.rna_enums import OperatorReturnItems +class SliceIntrinsics: + bed_x: float + bed_y: float + feedrate: float + extrusion_rate: float + + def __init__(self, bed_x=250.0, bed_y=250.0, feedrate=1200.0, extrusion_rate=0.017): + self.bed_x = bed_x + self.bed_y = bed_y + self.feedrate = feedrate + self.extrusion_rate = extrusion_rate + # ----------------------------------------------------------------------------- # Node tree # ----------------------------------------------------------------------------- @@ -143,6 +155,12 @@ class SLICESOCK_Gcode(LabelNodeSocket): default = "" color = (1., 1., 1., 1.) +@register_class +class SLICESOCK_Intrinsics(LabelNodeSocket): + bl_label = "Intrinsics" + default = None + color = (0.2, 0.2, 0.2, 1.0) + # ----------------------------------------------------------------------------- # Nodes # ----------------------------------------------------------------------------- @@ -179,6 +197,33 @@ class SLICENODE_InputObject(InputNode): input_type = "Object" bl_icon = "OBJECT_DATA" +@register_class +class SLICENODE_InputIntrinsics(SliceNode): + bl_label = "Printer Intrinsics" + + bed_x: FloatProperty(name="Bed X", default=250.0) # pyright: ignore[reportInvalidTypeForm] + bed_y: FloatProperty(name="Bed Y", default=250.0) # pyright: ignore[reportInvalidTypeForm] + feedrate: FloatProperty(name="Feedrate", default=1200.0) # pyright: ignore[reportInvalidTypeForm] + extrusion_rate: FloatProperty(name="Extrusion Rate", default=0.017, precision=4) # pyright: ignore[reportInvalidTypeForm] + + def init(self, context): + self.add_output("SLICESOCK_Intrinsics") + + def draw_buttons(self, context, layout): + assert layout + layout.prop(self, "bed_x") + layout.prop(self, "bed_y") + layout.prop(self, "feedrate") + layout.prop(self, "extrusion_rate") + + def evaluate_output(self, socket_name: str, **_kwargs) -> SliceIntrinsics: + return SliceIntrinsics( + bed_x=self.bed_x, + bed_y=self.bed_y, + feedrate=self.feedrate, + extrusion_rate=self.extrusion_rate + ) + # Math @register_class @@ -372,11 +417,22 @@ class SLICENODE_PathsToGcode(SliceNode): def init(self, context): self.add_input("SLICESOCK_Paths") + self.add_input("SLICESOCK_Intrinsics") self.add_output("SLICESOCK_Gcode") def evaluate_output(self, socket_name: str, **_kwargs) -> str: paths: list[np.ndarray] = resolve_input(self.inputs.get("Paths")) + intrinsics: SliceIntrinsics | None = resolve_input(self.inputs.get("Intrinsics")) + pather: Pather = Pather(paths) + if intrinsics: + # Note: Pather currently uses bed_center_x/y as its internal attributes. + # We map the bed size center to these attributes if provided. + pather.bed_center_x = intrinsics.bed_x / 2.0 + pather.bed_center_y = intrinsics.bed_y / 2.0 + pather.feedrate = intrinsics.feedrate + pather.extrusion_rate = intrinsics.extrusion_rate + gcode: str = pather.to_gcode() return gcode @@ -598,12 +654,24 @@ class SLICENODE_GcodeToMesh(ExecSliceNode): segments = [] last_x, last_y, last_z = 0.0, 0.0, 0.0 last_e = 0.0 + + offset_x, offset_y = 0.0, 0.0 for line in gcode.splitlines(): - line = line.split(';')[0].strip() - if not line: continue + # Parse metadata from comments + if ';' in line: + comment = line.split(';', 1)[1].strip() + if comment.startswith("OFFSET_X="): + try: offset_x = float(comment.split('=')[1]) + except ValueError: pass + elif comment.startswith("OFFSET_Y="): + try: offset_y = float(comment.split('=')[1]) + except ValueError: pass + + line_no_comment = line.split(';')[0].strip() + if not line_no_comment: continue - parts = line.split() + parts = line_no_comment.split() cmd = parts[0].upper() params = {} @@ -626,7 +694,12 @@ class SLICENODE_GcodeToMesh(ExecSliceNode): is_extruding = new_e > 0 if is_extruding: - segments.append(((last_x, last_y, last_z), (new_x, new_y, new_z))) + # Subtract offsets (scaled to Blender units) to return to original space + ox, oy = offset_x * 0.001, offset_y * 0.001 + segments.append(( + (last_x - ox, last_y - oy, last_z), + (new_x - ox, new_y - oy, new_z) + )) last_x, last_y, last_z = new_x, new_y, new_z last_e = new_e @@ -707,6 +780,7 @@ class SLICENODE_MT_Menu(Menu): layout.operator("node.add_node", text="Integer").type = "SLICENODE_InputInt" layout.operator("node.add_node", text="Integer Math").type = "SLICENODE_IntMath" layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject" + layout.operator("node.add_node", text="Printer Intrinsics").type = "SLICENODE_InputIntrinsics" layout.operator("node.add_node", text="Test").type = "SLICENODE_Test" layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh" layout.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths" diff --git a/core/slice.py b/core/slice.py index a3f442e..8c18476 100644 --- a/core/slice.py +++ b/core/slice.py @@ -313,6 +313,23 @@ class Pather(object): if not self.paths: return "" + # Centering logic using NumPy + all_pts = np.concatenate(self.paths) + min_xy = all_pts[:, :2].min(axis=0) + max_xy = all_pts[:, :2].max(axis=0) + center_xy = (min_xy + max_xy) / 2.0 + + offset_x = self.bed_center_x - center_xy[0] + offset_y = self.bed_center_y - center_xy[1] + + # Apply offset to all paths + centered_paths = [] + for path in self.paths: + new_path = path.copy() + new_path[:, 0] += offset_x + new_path[:, 1] += offset_y + centered_paths.append(new_path) + def segment_length_3d(p1, p2) -> float: dx = p2[0] - p1[0] dy = p2[1] - p1[1] @@ -324,6 +341,8 @@ class Pather(object): f";TYPE:Custom", f";FEEDRATE={self.feedrate}", f";EXTRUSION_RATE={self.extrusion_rate}", + f";OFFSET_X={offset_x:.6f}", + f";OFFSET_Y={offset_y:.6f}", "G21 ; set units to mm", "G90 ; absolute XYZ positioning", "M83 ; relative extrusion mode", @@ -333,7 +352,7 @@ class Pather(object): last_z = -1.0 max_z = 0 - for line in self.paths: + for line in centered_paths: coords = line.tolist() if len(coords) < 2: continue diff --git a/infra/blender_mesh_capture.py b/infra/blender_mesh_capture.py index 6074957..5157f5c 100644 --- a/infra/blender_mesh_capture.py +++ b/infra/blender_mesh_capture.py @@ -364,6 +364,8 @@ def capture_mesh_edges(obj: Object) -> list[NDArray[float64]]: return [] # 1. Scaling & Matrix Setup + assert bpy.context.scene + assert bpy.context.scene.unit_settings scale = 1000.0 * bpy.context.scene.unit_settings.scale_length world_mat = np.array(obj.matrix_world)