From c57f843092264382fb3862f77f899b9167ae4972 Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Wed, 6 May 2026 17:45:57 +0300 Subject: [PATCH] buffer non planar, merge paths with zip --- __init__.py | 69 +++++++++++++++++++++++++--- infra/blender_slice_nodes_generic.py | 6 ++- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/__init__.py b/__init__.py index 377d42a..791fc02 100644 --- a/__init__.py +++ b/__init__.py @@ -13,10 +13,12 @@ bl_info = { } from math import floor +from itertools import zip_longest from typing import TYPE_CHECKING, Any import bpy import os import numpy as np +from mathutils import Euler, Matrix from bpy.types import NodeTree, Operator, Menu from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty @@ -110,6 +112,13 @@ class SLICESOCK_String(InputNodeSocket): value: StringProperty(name="String", default="") # pyright: ignore[reportInvalidTypeForm] color = (0.4, 0.7, 1.0, 1.0) +@register_class +class SLICESOCK_Vector(InputNodeSocket): + bl_label = "Vector" + + value: bpy.props.FloatVectorProperty(name="Vector", size=3, default=(0.0, 0.0, 0.0), subtype='EULER') # pyright: ignore[reportInvalidTypeForm] + color = (0.39, 0.39, 0.78, 1.0) + @register_class class SLICESOCK_Mesh(LabelNodeSocket): bl_label = "Mesh" @@ -310,6 +319,7 @@ class SLICENODE_BufferShells(SliceNode): def init(self, context): self.add_input("SLICESOCK_Slices") self.add_input("SLICESOCK_Float", "Distance").value = 0 + self.add_input("SLICESOCK_Vector", "Rotation") self.add_output("SLICESOCK_Slices") def draw_buttons(self, context, layout): @@ -320,7 +330,25 @@ class SLICENODE_BufferShells(SliceNode): if socket_name != "Slices": return [] slices: list[np.ndarray] = resolve_input(self.inputs.get("Slices")) - distance: float = resolve_input(self.inputs.get("Distance")) + distance: float = resolve_input(self.inputs.get("Distance")) or 0.0 + rotation: Any = resolve_input(self.inputs.get("Rotation")) or (0.0, 0.0, 0.0) + + if not slices: return [] + + if any(rotation): + # De-rotate shells to Z-perpendicular plane + mat = Euler(rotation).to_matrix().inverted() + mat_np = np.array(mat) + de_rotated_slices = [s @ mat_np.T for s in slices] + + # Buffer + buffered_slices = slice.buffer_slice(de_rotated_slices, distance, miter=self.shape_option) + + # Re-rotate shells back + mat_inv = Euler(rotation).to_matrix() + mat_inv_np = np.array(mat_inv) + re_rotated_slices = [s @ mat_inv_np.T for s in buffered_slices] + return re_rotated_slices buffered_slices = slice.buffer_slice(slices, distance, miter=self.shape_option) @@ -466,15 +494,44 @@ class SLICENODE_JoinPaths(SliceNodeJoiner): bl_label = "Join Paths" socket_type = "SLICESOCK_Paths" - def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]: - merged_paths = [] + mode: bpy.props.EnumProperty( + name="Mode", + items=[ + ('EXTEND', "Extend", "Append paths sequentially"), + ('ZIP', "Zip", "Interleave paths from all inputs"), + ], + default='EXTEND' + ) # pyright: ignore[reportInvalidTypeForm] + def draw_buttons(self, context, layout): + assert layout + layout.prop(self, "mode", text="") + + def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]: + inputs_data: list[list[np.ndarray]] = [] for input in self.inputs: paths = resolve_input(input) - if paths: - merged_paths.extend(paths) + if paths: + inputs_data.append(paths) - return merged_paths + if not inputs_data: + return [] + + if self.mode == 'EXTEND': + merged_paths = [] + for paths in inputs_data: + merged_paths.extend(paths) + return merged_paths + + elif self.mode == 'ZIP': + merged_paths = [] + for group in zip_longest(*inputs_data): + for path in group: + if path is not None: + merged_paths.append(path) + return merged_paths + + return [] ### I/O diff --git a/infra/blender_slice_nodes_generic.py b/infra/blender_slice_nodes_generic.py index bafc8c9..82cb147 100644 --- a/infra/blender_slice_nodes_generic.py +++ b/infra/blender_slice_nodes_generic.py @@ -68,8 +68,10 @@ def resolve_input( assert socket.node if not socket.is_linked: - if v := getattr(socket, "value", None): return v - else: return socket.default + val = getattr(socket, "value", None) + if val is not None: + return val + return socket.default assert socket.is_linked assert socket.links