buffer non planar, merge paths with zip

This commit is contained in:
Nicolas Predella 2026-05-06 17:45:57 +03:00
parent af96629b76
commit c57f843092
2 changed files with 67 additions and 8 deletions

View file

@ -13,10 +13,12 @@ bl_info = {
} }
from math import floor from math import floor
from itertools import zip_longest
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
import bpy import bpy
import os import os
import numpy as np import numpy as np
from mathutils import Euler, Matrix
from bpy.types import NodeTree, Operator, Menu from bpy.types import NodeTree, Operator, Menu
from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty
@ -110,6 +112,13 @@ class SLICESOCK_String(InputNodeSocket):
value: StringProperty(name="String", default="") # pyright: ignore[reportInvalidTypeForm] value: StringProperty(name="String", default="") # pyright: ignore[reportInvalidTypeForm]
color = (0.4, 0.7, 1.0, 1.0) 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 @register_class
class SLICESOCK_Mesh(LabelNodeSocket): class SLICESOCK_Mesh(LabelNodeSocket):
bl_label = "Mesh" bl_label = "Mesh"
@ -310,6 +319,7 @@ class SLICENODE_BufferShells(SliceNode):
def init(self, context): def init(self, context):
self.add_input("SLICESOCK_Slices") self.add_input("SLICESOCK_Slices")
self.add_input("SLICESOCK_Float", "Distance").value = 0 self.add_input("SLICESOCK_Float", "Distance").value = 0
self.add_input("SLICESOCK_Vector", "Rotation")
self.add_output("SLICESOCK_Slices") self.add_output("SLICESOCK_Slices")
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
@ -320,7 +330,25 @@ class SLICENODE_BufferShells(SliceNode):
if socket_name != "Slices": return [] if socket_name != "Slices": return []
slices: list[np.ndarray] = resolve_input(self.inputs.get("Slices")) 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) buffered_slices = slice.buffer_slice(slices, distance, miter=self.shape_option)
@ -466,15 +494,44 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
bl_label = "Join Paths" bl_label = "Join Paths"
socket_type = "SLICESOCK_Paths" socket_type = "SLICESOCK_Paths"
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]: mode: bpy.props.EnumProperty(
merged_paths = [] 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: for input in self.inputs:
paths = resolve_input(input) paths = resolve_input(input)
if paths: if paths:
merged_paths.extend(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 ### I/O

View file

@ -68,8 +68,10 @@ def resolve_input(
assert socket.node assert socket.node
if not socket.is_linked: if not socket.is_linked:
if v := getattr(socket, "value", None): return v val = getattr(socket, "value", None)
else: return socket.default if val is not None:
return val
return socket.default
assert socket.is_linked assert socket.is_linked
assert socket.links assert socket.links