intrinsics implementation

This commit is contained in:
Nicolas Predella 2026-05-06 18:17:30 +03:00
parent c57f843092
commit 77bab165af
3 changed files with 100 additions and 5 deletions

View file

@ -32,6 +32,18 @@ from .infra.blender_slice_nodes_generic import SliceNode, SliceNodeSocket, resol
if TYPE_CHECKING: if TYPE_CHECKING:
from bpy.stub_internal.rna_enums import OperatorReturnItems 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 # Node tree
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -143,6 +155,12 @@ class SLICESOCK_Gcode(LabelNodeSocket):
default = "" default = ""
color = (1., 1., 1., 1.) 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 # Nodes
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -179,6 +197,33 @@ class SLICENODE_InputObject(InputNode):
input_type = "Object" input_type = "Object"
bl_icon = "OBJECT_DATA" 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 # Math
@register_class @register_class
@ -372,11 +417,22 @@ class SLICENODE_PathsToGcode(SliceNode):
def init(self, context): def init(self, context):
self.add_input("SLICESOCK_Paths") self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Intrinsics")
self.add_output("SLICESOCK_Gcode") self.add_output("SLICESOCK_Gcode")
def evaluate_output(self, socket_name: str, **_kwargs) -> str: def evaluate_output(self, socket_name: str, **_kwargs) -> str:
paths: list[np.ndarray] = resolve_input(self.inputs.get("Paths")) paths: list[np.ndarray] = resolve_input(self.inputs.get("Paths"))
intrinsics: SliceIntrinsics | None = resolve_input(self.inputs.get("Intrinsics"))
pather: Pather = Pather(paths) 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() gcode: str = pather.to_gcode()
return gcode return gcode
@ -599,11 +655,23 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
last_x, last_y, last_z = 0.0, 0.0, 0.0 last_x, last_y, last_z = 0.0, 0.0, 0.0
last_e = 0.0 last_e = 0.0
for line in gcode.splitlines(): offset_x, offset_y = 0.0, 0.0
line = line.split(';')[0].strip()
if not line: continue
parts = line.split() for line in gcode.splitlines():
# 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_no_comment.split()
cmd = parts[0].upper() cmd = parts[0].upper()
params = {} params = {}
@ -626,7 +694,12 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
is_extruding = new_e > 0 is_extruding = new_e > 0
if is_extruding: 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_x, last_y, last_z = new_x, new_y, new_z
last_e = new_e 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").type = "SLICENODE_InputInt"
layout.operator("node.add_node", text="Integer Math").type = "SLICENODE_IntMath" 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="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="Test").type = "SLICENODE_Test"
layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh" layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh"
layout.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths" layout.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths"

View file

@ -313,6 +313,23 @@ class Pather(object):
if not self.paths: if not self.paths:
return "" 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: def segment_length_3d(p1, p2) -> float:
dx = p2[0] - p1[0] dx = p2[0] - p1[0]
dy = p2[1] - p1[1] dy = p2[1] - p1[1]
@ -324,6 +341,8 @@ class Pather(object):
f";TYPE:Custom", f";TYPE:Custom",
f";FEEDRATE={self.feedrate}", f";FEEDRATE={self.feedrate}",
f";EXTRUSION_RATE={self.extrusion_rate}", f";EXTRUSION_RATE={self.extrusion_rate}",
f";OFFSET_X={offset_x:.6f}",
f";OFFSET_Y={offset_y:.6f}",
"G21 ; set units to mm", "G21 ; set units to mm",
"G90 ; absolute XYZ positioning", "G90 ; absolute XYZ positioning",
"M83 ; relative extrusion mode", "M83 ; relative extrusion mode",
@ -333,7 +352,7 @@ class Pather(object):
last_z = -1.0 last_z = -1.0
max_z = 0 max_z = 0
for line in self.paths: for line in centered_paths:
coords = line.tolist() coords = line.tolist()
if len(coords) < 2: if len(coords) < 2:
continue continue

View file

@ -364,6 +364,8 @@ def capture_mesh_edges(obj: Object) -> list[NDArray[float64]]:
return [] return []
# 1. Scaling & Matrix Setup # 1. Scaling & Matrix Setup
assert bpy.context.scene
assert bpy.context.scene.unit_settings
scale = 1000.0 * bpy.context.scene.unit_settings.scale_length scale = 1000.0 * bpy.context.scene.unit_settings.scale_length
world_mat = np.array(obj.matrix_world) world_mat = np.array(obj.matrix_world)