typing
This commit is contained in:
parent
39d2530f14
commit
7535bd7a85
28 changed files with 451 additions and 324 deletions
|
|
@ -25,7 +25,7 @@ def register():
|
|||
bpy.app.handlers.depsgraph_update_post.append(tree.depsgraph_update_handler)
|
||||
|
||||
if not hasattr(bpy.types.Object, "nodeslicer"):
|
||||
bpy.types.Object.nodeslicer = bpy.props.PointerProperty(type=SlicerObjectPropertyGroup, name="nodeslicer") #type: ignore
|
||||
setattr(bpy.types.Object, "nodeslicer", bpy.props.PointerProperty(type=SlicerObjectPropertyGroup, name="nodeslicer"))
|
||||
_registered_nodeslicer = True
|
||||
|
||||
# Register Keymaps
|
||||
|
|
@ -70,7 +70,7 @@ def unregister():
|
|||
if _registered_nodeslicer:
|
||||
try:
|
||||
if hasattr(bpy.types.Object, "nodeslicer"):
|
||||
del bpy.types.Object.nodeslicer #type: ignore
|
||||
delattr(bpy.types.Object, "nodeslicer")
|
||||
except Exception as e:
|
||||
print(f"Error removing property: {e}")
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import numpy as np
|
||||
import zlib
|
||||
|
||||
import numpy as np
|
||||
import zlib
|
||||
import struct
|
||||
|
|
@ -19,8 +16,8 @@ def crc32_list(l: list[np.ndarray]) -> int:
|
|||
h = zlib.adler32(struct.pack(">I", crc32_array(a)), h)
|
||||
return h
|
||||
|
||||
def ndarray_lru_cache(maxsize=128):
|
||||
def decorator(func: Callable):
|
||||
def ndarray_lru_cache(maxsize: int = 128) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
cache = {}
|
||||
order = []
|
||||
def wrapper(*args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any, Callable
|
||||
import bpy
|
||||
from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty, EnumProperty, BoolProperty, FloatVectorProperty, CollectionProperty
|
||||
|
||||
|
|
@ -52,16 +54,33 @@ def notify_tree_changed(self, context):
|
|||
except Exception as e:
|
||||
print(f"Error updating node {node.name}: {e}")
|
||||
|
||||
def NfProperty(prop_func):
|
||||
def wrapper(**kwargs):
|
||||
class MockPropertyDescriptor:
|
||||
def __init__(self, default: Any = None):
|
||||
self.default = default
|
||||
self._values: dict[int, Any] = {}
|
||||
|
||||
def __get__(self, instance: Any, owner: Any = None) -> Any:
|
||||
if instance is None:
|
||||
return self
|
||||
return self._values.get(id(instance), self.default)
|
||||
|
||||
def __set__(self, instance: Any, value: Any) -> None:
|
||||
self._values[id(instance)] = value
|
||||
|
||||
def NfProperty(prop_func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
def wrapper(**kwargs: Any) -> Any:
|
||||
kwargs.setdefault("update", notify_tree_changed)
|
||||
return prop_func(**kwargs)
|
||||
res = prop_func(**kwargs)
|
||||
if "Mock" in type(res).__name__:
|
||||
default = kwargs.get("default", None)
|
||||
return MockPropertyDescriptor(default)
|
||||
return res
|
||||
return wrapper
|
||||
|
||||
NfStringProperty = NfProperty(StringProperty)
|
||||
NfIntProperty = NfProperty(IntProperty)
|
||||
NfFloatProperty = NfProperty(FloatProperty)
|
||||
NfEnumProperty = NfProperty(EnumProperty)
|
||||
NfBoolProperty = NfProperty(BoolProperty)
|
||||
NfPointerProperty = NfProperty(PointerProperty)
|
||||
NfFloatVectorProperty = NfProperty(FloatVectorProperty)
|
||||
NfStringProperty: Callable[..., Any] = NfProperty(StringProperty)
|
||||
NfIntProperty: Callable[..., Any] = NfProperty(IntProperty)
|
||||
NfFloatProperty: Callable[..., Any] = NfProperty(FloatProperty)
|
||||
NfEnumProperty: Callable[..., Any] = NfProperty(EnumProperty)
|
||||
NfBoolProperty: Callable[..., Any] = NfProperty(BoolProperty)
|
||||
NfPointerProperty: Callable[..., Any] = NfProperty(PointerProperty)
|
||||
NfFloatVectorProperty: Callable[..., Any] = NfProperty(FloatVectorProperty)
|
||||
|
|
@ -61,7 +61,7 @@ class InputNode(SliceNode):
|
|||
class MathNode(SliceNode):
|
||||
bl_label = "Float Math"
|
||||
|
||||
operation: NfEnumProperty(
|
||||
operation: str = NfEnumProperty(
|
||||
name="Operation",
|
||||
items=[
|
||||
('ADD', "Add", "Add the two values"),
|
||||
|
|
@ -81,7 +81,7 @@ class MathNode(SliceNode):
|
|||
('MODULO_TRUNCATED', "Truncated Modulo", "Modulo using truncated division"),
|
||||
],
|
||||
default='ADD'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
math_type: TypeAlias = float
|
||||
socket_label: str = "Float"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from .base import SliceNode, SliceIntrinsics, Pather, SliceNodeJoiner, SlicePath
|
|||
class SLICENODE_PathsToGcode(SliceNode):
|
||||
bl_label = "Paths to Gcode"
|
||||
|
||||
use_header: NfBoolProperty(name="Header", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||
use_header: bool = NfBoolProperty(name="Header", default=True)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Paths")
|
||||
|
|
@ -119,7 +119,7 @@ class SLICENODE_GcodeRegexReplace(SliceNode):
|
|||
class SLICENODE_GcodeHelper(SliceNode):
|
||||
bl_label = "Gcode Helper"
|
||||
|
||||
command: NfEnumProperty(
|
||||
command: str = NfEnumProperty(
|
||||
name="Command",
|
||||
items=[
|
||||
('G0', "G0: Rapid Move", "Fast movement without extrusion"),
|
||||
|
|
@ -149,9 +149,9 @@ class SLICENODE_GcodeHelper(SliceNode):
|
|||
('T', "T: Change Extruder", "Switch to a different extruder (e.g. T0, T1)"),
|
||||
],
|
||||
default='G1'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
parameters: NfStringProperty(name="Params", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
parameters: str = NfStringProperty(name="Params", default="")
|
||||
|
||||
def init(self, context):
|
||||
self.add_output("NodeSocketString")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
import os
|
||||
import numpy as np
|
||||
|
||||
from ..bpy_ns_types import NfStringProperty, notify_tree_changed, NfEnumProperty
|
||||
from ..bpy_ns_types import NfStringProperty, notify_tree_changed, NfEnumProperty, NfPointerProperty
|
||||
from ....registry import register_class
|
||||
from .base import InputNode, SliceNode, SliceIntrinsics
|
||||
|
||||
|
|
@ -43,11 +43,11 @@ class SLICENODE_InputObject(InputNode):
|
|||
class SLICENODE_CollectionObjects(SliceNode):
|
||||
bl_label = "Collection Objects"
|
||||
|
||||
collection: bpy.props.PointerProperty(
|
||||
collection: bpy.types.Collection | None = NfPointerProperty(
|
||||
name="Collection",
|
||||
type=bpy.types.Collection,
|
||||
update=notify_tree_changed
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self._update_outputs()
|
||||
|
|
@ -61,11 +61,12 @@ class SLICENODE_CollectionObjects(SliceNode):
|
|||
layout.prop(self, "collection", text="")
|
||||
|
||||
def _update_outputs(self):
|
||||
if not self.collection:
|
||||
col = getattr(self, "collection", None)
|
||||
if not col or not hasattr(col, "objects"):
|
||||
self.outputs.clear()
|
||||
return
|
||||
|
||||
objs = [obj for obj in self.collection.objects if obj.type == 'MESH']
|
||||
objs = [obj for obj in col.objects if getattr(obj, "type", "") == 'MESH']
|
||||
expected_names = [obj.name for obj in objs]
|
||||
|
||||
# Remove outputs that are no longer in the collection
|
||||
|
|
@ -96,11 +97,12 @@ class SLICENODE_WipeTower(SliceNode):
|
|||
if socket_name != "Settings":
|
||||
return {}
|
||||
|
||||
raw_x = self.resolve("X Position")
|
||||
raw_y = self.resolve("Y Position")
|
||||
raw_x: Any = self.resolve("X Position")
|
||||
raw_y: Any = self.resolve("Y Position")
|
||||
|
||||
if isinstance(raw_x, (list, tuple, np.ndarray)) and len(raw_x) >= 2:
|
||||
pos_x, pos_y = float(raw_x[0]), float(raw_x[1])
|
||||
if isinstance(raw_x, (list, tuple, np.ndarray)):
|
||||
pos_x = float(raw_x[0]) if len(raw_x) >= 1 else 0.0
|
||||
pos_y = float(raw_x[1]) if len(raw_x) >= 2 else (float(raw_y) if raw_y is not None else 0.0)
|
||||
else:
|
||||
pos_x = float(raw_x) if raw_x is not None else 0.0
|
||||
pos_y = float(raw_y) if raw_y is not None else 0.0
|
||||
|
|
@ -123,13 +125,13 @@ class SLICENODE_OffsetObject(SliceNode):
|
|||
if socket_name != "Settings":
|
||||
return {}
|
||||
|
||||
raw_x = self.resolve("X Offset")
|
||||
raw_y = self.resolve("Y Offset")
|
||||
raw_z = self.resolve("Z Offset")
|
||||
raw_x: Any = self.resolve("X Offset")
|
||||
raw_y: Any = self.resolve("Y Offset")
|
||||
raw_z: Any = self.resolve("Z Offset")
|
||||
|
||||
if isinstance(raw_x, (list, tuple, np.ndarray)) and len(raw_x) >= 2:
|
||||
ox = float(raw_x[0])
|
||||
oy = float(raw_x[1])
|
||||
if isinstance(raw_x, (list, tuple, np.ndarray)):
|
||||
ox = float(raw_x[0]) if len(raw_x) >= 1 else 0.0
|
||||
oy = float(raw_x[1]) if len(raw_x) >= 2 else (float(raw_y) if raw_y is not None else 0.0)
|
||||
oz = float(raw_x[2]) if len(raw_x) >= 3 else (float(raw_z) if raw_z is not None else 0.0)
|
||||
else:
|
||||
ox = float(raw_x) if raw_x is not None else 0.0
|
||||
|
|
@ -205,9 +207,9 @@ class SLICENODE_PrusaslicerConf(SliceNode):
|
|||
def search_prints(self, _context, edit_text) -> list[str]:
|
||||
return self.search_conf(edit_text, conf_type="prints")
|
||||
|
||||
printer_id: NfStringProperty(name="Printer", default='', update=update_profiles, search=search_printers) # pyright: ignore[reportInvalidTypeForm]
|
||||
filament_id: NfStringProperty(name="Filament", default='', search=search_filaments) # pyright: ignore[reportInvalidTypeForm]
|
||||
print_id: NfStringProperty(name="Print", default='', search=search_prints) # pyright: ignore[reportInvalidTypeForm]
|
||||
printer_id: str = NfStringProperty(name="Printer", default='', update=update_profiles, search=search_printers)
|
||||
filament_id: str = NfStringProperty(name="Filament", default='', search=search_filaments)
|
||||
print_id: str = NfStringProperty(name="Print", default='', search=search_prints)
|
||||
|
||||
def init(self, context):
|
||||
self.add_output("SLICESOCK_PrusaslicerConf", "Config")
|
||||
|
|
@ -242,9 +244,9 @@ class SLICENODE_InputPrinter(SliceNode):
|
|||
self.label = self.printer_label
|
||||
notify_tree_changed(self, context)
|
||||
|
||||
printer_label: NfStringProperty(name="Label", default="Printer", update=update_label) # pyright: ignore[reportInvalidTypeForm]
|
||||
printer_label: str = NfStringProperty(name="Label", default="Printer", update=update_label)
|
||||
|
||||
host_type: NfEnumProperty(
|
||||
host_type: str = NfEnumProperty(
|
||||
name="Type",
|
||||
items=[
|
||||
('PRUSALINK', "PrusaLink", "PrusaLink API"),
|
||||
|
|
@ -252,7 +254,7 @@ class SLICENODE_InputPrinter(SliceNode):
|
|||
('MOONRAKER', "Moonraker", "Moonraker (Klipper) API"),
|
||||
],
|
||||
default='PRUSALINK'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_output("SLICESOCK_Printer")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from ....infra.blender.blender_mesh_capture import PdfProperties
|
|||
class SLICENODE_GetMesh(SliceNode):
|
||||
bl_label = "Get Mesh"
|
||||
|
||||
include_children: NfBoolProperty(name="Include Children", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||
include_children: bool = NfBoolProperty(name="Include Children", default=True)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("NodeSocketObject")
|
||||
|
|
@ -73,7 +73,7 @@ class SLICENODE_JoinMeshGroups(SliceNodeJoiner):
|
|||
class SLICENODE_SeparateMesh(SliceNode):
|
||||
bl_label = "Separate Mesh"
|
||||
|
||||
comparison: NfEnumProperty(
|
||||
comparison: str = NfEnumProperty(
|
||||
name="Comparison",
|
||||
items=[
|
||||
('EQUAL', "Equal", "=="),
|
||||
|
|
@ -85,7 +85,7 @@ class SLICENODE_SeparateMesh(SliceNode):
|
|||
],
|
||||
default='EQUAL',
|
||||
update=notify_tree_changed
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh")
|
||||
|
|
@ -167,8 +167,8 @@ class SLICENODE_SeparateMesh(SliceNode):
|
|||
class SLICENODE_SeparateMeshByRange(SliceNode):
|
||||
bl_label = "Separate Mesh by Range"
|
||||
|
||||
start_val: NfIntProperty(name="Start", default=0, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
|
||||
end_val: NfIntProperty(name="End", default=1, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
|
||||
start_val: int = NfIntProperty(name="Start", default=0, update=notify_tree_changed)
|
||||
end_val: int = NfIntProperty(name="End", default=1, update=notify_tree_changed)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh")
|
||||
|
|
@ -181,8 +181,9 @@ class SLICENODE_SeparateMeshByRange(SliceNode):
|
|||
|
||||
def _update_outputs(self):
|
||||
# Ensure end >= start
|
||||
start = self.start_val
|
||||
end = max(start, self.end_val)
|
||||
start = int(self.start_val) if isinstance(getattr(self, "start_val", None), (int, float)) else 0
|
||||
end_val = int(self.end_val) if isinstance(getattr(self, "end_val", None), (int, float)) else 1
|
||||
end = max(start, end_val)
|
||||
expected_ids = [str(i) for i in range(start, end + 1)]
|
||||
|
||||
# Remove outputs that are no longer in the range
|
||||
|
|
@ -222,6 +223,12 @@ class SLICENODE_SeparateMeshByRange(SliceNode):
|
|||
|
||||
res = SlicingCollection([], f"{coll.name}_{target_val}")
|
||||
|
||||
start = self.start_val
|
||||
end = max(start, self.end_val)
|
||||
total_parts = end - start + 1
|
||||
num_digits = max(2, len(str(total_parts)))
|
||||
i = target_val - start
|
||||
|
||||
for so in coll.objects:
|
||||
if attr_name not in so.attributes:
|
||||
continue
|
||||
|
|
@ -231,19 +238,20 @@ class SLICENODE_SeparateMeshByRange(SliceNode):
|
|||
|
||||
# Attribute values might be floats or ints, we check for equality
|
||||
mask = vals == target_val
|
||||
|
||||
num_digits = max(2, len(str(self.end_val)))
|
||||
|
||||
if np.all(mask):
|
||||
res.objects.append(so)
|
||||
elif np.any(mask):
|
||||
if np.any(mask):
|
||||
# Split the object
|
||||
so_new = SlicingObject()
|
||||
so_new.name = f"{so.name}{target_val:0{num_digits}d}"
|
||||
base_mesh_name = so.name
|
||||
so_new.name = f"{base_mesh_name}{i:0{num_digits}d}"
|
||||
so_new.parent = so.parent
|
||||
so_new.object_type = so.object_type
|
||||
so_new.extruder = so.extruder
|
||||
so_new.mesh = so.mesh[mask]
|
||||
so_new.transforms = [dict(t) for t in getattr(so, "transforms", [])]
|
||||
so_new.pdf_properties = copy.deepcopy(so.pdf_properties) if hasattr(so, "pdf_properties") and so.pdf_properties else PdfProperties()
|
||||
so_new.object_properties = list(getattr(so, "object_properties", []))
|
||||
so_new.modifiers = so.modifiers.copy() if so.modifiers else []
|
||||
for k, v in so.attributes.items():
|
||||
so_new.attributes[k] = {
|
||||
"domain": v["domain"],
|
||||
|
|
@ -258,7 +266,7 @@ class SLICENODE_SeparateMeshByRange(SliceNode):
|
|||
class SLICENODE_ArrangeMesh(SliceNode):
|
||||
bl_label = "Arrange Slicing Group"
|
||||
|
||||
method: NfEnumProperty(
|
||||
method: str = NfEnumProperty(
|
||||
name="Method",
|
||||
items=[
|
||||
('NFP', "NFP (Default)", "Nest using No-Fit Polygon placer + First-Fit selection"),
|
||||
|
|
@ -267,13 +275,13 @@ class SLICENODE_ArrangeMesh(SliceNode):
|
|||
],
|
||||
default='NFP',
|
||||
update=notify_tree_changed
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
allow_rotations: NfBoolProperty(
|
||||
allow_rotations: bool = NfBoolProperty(
|
||||
name="Allow Rotations",
|
||||
default=True,
|
||||
update=notify_tree_changed
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh", "Slicing Group", display_shape='LIST')
|
||||
|
|
@ -455,7 +463,7 @@ def _transform_slice_paths(sp: SlicePaths, t_arr: np.ndarray, r_arr: np.ndarray,
|
|||
if not (has_scale or has_rot or has_trans):
|
||||
return sp.copy_with(paths=list(sp))
|
||||
|
||||
R = None
|
||||
rot_mat: np.ndarray | None = None
|
||||
if has_rot:
|
||||
rx, ry, rz = r_arr
|
||||
cx, sx = np.cos(rx), np.sin(rx)
|
||||
|
|
@ -477,7 +485,7 @@ def _transform_slice_paths(sp: SlicePaths, t_arr: np.ndarray, r_arr: np.ndarray,
|
|||
[sz, cz, 0.0],
|
||||
[0.0, 0.0, 1.0]
|
||||
], dtype=np.float64)
|
||||
R = Rz @ (Ry @ Rx)
|
||||
rot_mat = Rz @ (Ry @ Rx)
|
||||
|
||||
for p in sp:
|
||||
if len(p) == 0:
|
||||
|
|
@ -493,11 +501,11 @@ def _transform_slice_paths(sp: SlicePaths, t_arr: np.ndarray, r_arr: np.ndarray,
|
|||
else:
|
||||
new_p[:, :dim] *= s_arr[:dim]
|
||||
|
||||
if has_rot and R is not None and dim > 0:
|
||||
if has_rot and rot_mat is not None and dim > 0:
|
||||
if dim >= 3:
|
||||
new_p[:, :3] = new_p[:, :3] @ R.T
|
||||
new_p[:, :3] = new_p[:, :3] @ rot_mat.T
|
||||
elif dim == 2:
|
||||
rot_2d = R[:2, :2]
|
||||
rot_2d = rot_mat[:2, :2]
|
||||
new_p[:, :2] = new_p[:, :2] @ rot_2d.T
|
||||
|
||||
if has_trans and dim > 0:
|
||||
|
|
@ -511,7 +519,7 @@ def _transform_slice_paths(sp: SlicePaths, t_arr: np.ndarray, r_arr: np.ndarray,
|
|||
return sp.copy_with(paths=new_paths)
|
||||
|
||||
|
||||
def _transform_geometry_item(geom: Any, t_arr: np.ndarray, r_arr: np.ndarray, s_arr: np.ndarray, t_rec: dict) -> Any:
|
||||
def _transform_geometry_item(geom: Any, t_arr: np.ndarray, r_arr: np.ndarray, s_arr: np.ndarray, t_rec: dict[str, Any]) -> Any:
|
||||
if geom is None:
|
||||
return None
|
||||
elif isinstance(geom, SlicePaths):
|
||||
|
|
@ -590,10 +598,12 @@ class SLICENODE_TransformGeometry(SliceNode):
|
|||
self.add_output("SLICESOCK_Geometry", "Geometry")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||
if socket_name not in ("Geometry"):
|
||||
if socket_name not in ("Geometry", "Mesh"):
|
||||
return None
|
||||
|
||||
geom_data: Any = self.resolve("Geometry")
|
||||
if geom_data is None:
|
||||
geom_data = self.resolve("Mesh")
|
||||
if geom_data is None:
|
||||
return None
|
||||
|
||||
|
|
@ -641,7 +651,7 @@ class SLICENODE_DropGeometry(SliceNode):
|
|||
if socket_name != "Mesh":
|
||||
return None
|
||||
|
||||
mesh_data: SlicingCollection | SlicingGroup | None = self.resolve("Mesh")
|
||||
mesh_data = self.resolve("Mesh")
|
||||
if not mesh_data:
|
||||
return None
|
||||
|
||||
|
|
@ -682,7 +692,7 @@ def format_name_template(template: str, so: Any, default_old_name: str = "") ->
|
|||
import re
|
||||
from ....core.pdf_exporter.metadata import get_first_attribute_value
|
||||
|
||||
def replace_match(match: re.Match) -> str:
|
||||
def replace_match(match: re.Match[str]) -> str:
|
||||
attr_name = match.group(1).strip()
|
||||
val = get_first_attribute_value(so, attr_name)
|
||||
if val is None and attr_name in ("Name", "name"):
|
||||
|
|
@ -699,6 +709,11 @@ def format_name_template(template: str, so: Any, default_old_name: str = "") ->
|
|||
def _rename_slicing_item(geom: Any, new_name: str) -> Any:
|
||||
if geom is None or not new_name:
|
||||
return geom
|
||||
elif isinstance(geom, SlicePaths):
|
||||
copied = geom.copy_with(paths=list(geom))
|
||||
target_name = format_name_template(new_name, copied, default_old_name=getattr(copied, "name", ""))
|
||||
copied.name = target_name
|
||||
return copied
|
||||
elif isinstance(geom, (list, tuple)):
|
||||
return [_rename_slicing_item(item, f"{new_name}_{i}" if (len(geom) > 1 and "{" not in new_name) else new_name) for i, item in enumerate(geom)]
|
||||
elif isinstance(geom, SlicingGroup):
|
||||
|
|
@ -706,40 +721,26 @@ def _rename_slicing_item(geom: Any, new_name: str) -> Any:
|
|||
old_colls = dict(copied.collections)
|
||||
new_colls = {}
|
||||
for i, (old_key, coll) in enumerate(old_colls.items()):
|
||||
if coll.objects:
|
||||
coll_name = format_name_template(new_name, coll.objects[0], default_old_name=old_key)
|
||||
else:
|
||||
coll_name = format_name_template(new_name, None, default_old_name=old_key)
|
||||
if len(old_colls) > 1 and "{" not in new_name:
|
||||
coll_name = f"{coll_name}_{i}"
|
||||
coll.name = coll_name
|
||||
for j, obj in enumerate(coll.objects):
|
||||
obj_name = format_name_template(new_name, obj, default_old_name=obj.name)
|
||||
if (len(coll.objects) > 1 or len(old_colls) > 1) and "{" not in new_name:
|
||||
obj_name = f"{obj_name}_{j}"
|
||||
obj.name = obj_name
|
||||
obj.parent = coll_name
|
||||
new_colls[coll_name] = coll
|
||||
coll_old_name = getattr(coll, "name", old_key)
|
||||
target_name = format_name_template(new_name, coll, default_old_name=coll_old_name)
|
||||
coll_new_name = f"{target_name}_{i}" if (len(old_colls) > 1 and "{" not in new_name and target_name == new_name) else target_name
|
||||
new_colls[coll_new_name] = _rename_slicing_item(coll, coll_new_name)
|
||||
copied.collections = new_colls
|
||||
return copied
|
||||
elif isinstance(geom, SlicingCollection):
|
||||
copied = _copy_slicing_collection(geom)
|
||||
old_coll_name = geom.name
|
||||
if copied.objects:
|
||||
coll_name = format_name_template(new_name, copied.objects[0], default_old_name=old_coll_name)
|
||||
else:
|
||||
coll_name = format_name_template(new_name, None, default_old_name=old_coll_name)
|
||||
copied.name = coll_name
|
||||
for i, obj in enumerate(copied.objects):
|
||||
obj_name = format_name_template(new_name, obj, default_old_name=obj.name)
|
||||
if len(copied.objects) > 1 and "{" not in new_name:
|
||||
obj_name = f"{obj_name}_{i}"
|
||||
obj.name = obj_name
|
||||
obj.parent = coll_name
|
||||
target_name = format_name_template(new_name, copied, default_old_name=getattr(copied, "name", ""))
|
||||
copied.name = target_name
|
||||
for i, so in enumerate(copied.objects):
|
||||
so.parent = target_name
|
||||
so_old_name = getattr(so, "name", "")
|
||||
so_target = format_name_template(new_name, so, default_old_name=so_old_name)
|
||||
so.name = f"{so_target}_{i}" if (len(copied.objects) > 1 and "{" not in new_name and so_target == new_name) else so_target
|
||||
return copied
|
||||
elif isinstance(geom, SlicingObject):
|
||||
copied = _copy_slicing_object(geom)
|
||||
copied.name = format_name_template(new_name, copied, default_old_name=geom.name)
|
||||
target_name = format_name_template(new_name, copied, default_old_name=getattr(copied, "name", ""))
|
||||
copied.name = target_name
|
||||
return copied
|
||||
return geom
|
||||
|
||||
|
|
@ -758,7 +759,7 @@ class SLICENODE_RenameMesh(SliceNode):
|
|||
return None
|
||||
|
||||
geom: Any = self.resolve("Geometry")
|
||||
if not geom:
|
||||
if geom is None:
|
||||
return None
|
||||
|
||||
name_socket: str | None = self.resolve("Name")
|
||||
|
|
@ -769,7 +770,7 @@ class SLICENODE_RenameMesh(SliceNode):
|
|||
return _rename_slicing_item(geom, target_name)
|
||||
|
||||
|
||||
def _assign_pdf_properties_to_item(geom: Any, line_width: float, line_color: tuple[float, float, float], show_name: bool = False) -> Any:
|
||||
def _assign_pdf_properties_to_item(geom: Any, line_width: float, line_color: tuple[float, float, float] | None, show_name: bool) -> Any:
|
||||
if geom is None:
|
||||
return None
|
||||
elif isinstance(geom, SlicePaths):
|
||||
|
|
@ -780,14 +781,14 @@ def _assign_pdf_properties_to_item(geom: Any, line_width: float, line_color: tup
|
|||
return [_assign_pdf_properties_to_item(item, line_width, line_color, show_name) for item in geom]
|
||||
elif isinstance(geom, SlicingGroup):
|
||||
copied = _copy_slicing_group(geom)
|
||||
for coll in copied.collections.values():
|
||||
for obj in coll.objects:
|
||||
obj.pdf_properties = PdfProperties(line_width=line_width, line_color=line_color, show_name=show_name)
|
||||
new_colls = {}
|
||||
for k, coll in copied.collections.items():
|
||||
new_colls[k] = _assign_pdf_properties_to_item(coll, line_width, line_color, show_name)
|
||||
copied.collections = new_colls
|
||||
return copied
|
||||
elif isinstance(geom, SlicingCollection):
|
||||
copied = _copy_slicing_collection(geom)
|
||||
for obj in copied.objects:
|
||||
obj.pdf_properties = PdfProperties(line_width=line_width, line_color=line_color, show_name=show_name)
|
||||
copied.objects = [_assign_pdf_properties_to_item(so, line_width, line_color, show_name) for so in copied.objects]
|
||||
return copied
|
||||
elif isinstance(geom, SlicingObject):
|
||||
copied = _copy_slicing_object(geom)
|
||||
|
|
@ -800,16 +801,16 @@ def _assign_pdf_properties_to_item(geom: Any, line_width: float, line_color: tup
|
|||
class SLICENODE_AssignPdfProperties(SliceNode):
|
||||
bl_label = "Assign PDF Properties"
|
||||
|
||||
line_width: NfFloatProperty(name="Line Width (mm)", default=0.0, min=0.0, max=50.0) # pyright: ignore[reportInvalidTypeForm]
|
||||
line_color: NfFloatVectorProperty(
|
||||
line_width: float = NfFloatProperty(name="Line Width (mm)", default=0.0, min=0.0, max=50.0)
|
||||
line_color: tuple[float, float, float] = NfFloatVectorProperty(
|
||||
name="Line Color",
|
||||
subtype='COLOR',
|
||||
default=(0.0, 0.0, 0.0),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=3
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
show_name: NfBoolProperty(name="Show Name", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
show_name: bool = NfBoolProperty(name="Show Name", default=False)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh", "Geometry")
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class SLICENODE_GcodeToTextBlock(ExecSliceNode):
|
|||
bl_label = "Gcode to Text Block"
|
||||
button_label = "Update Text Block"
|
||||
|
||||
text_block_name: NfStringProperty(name="Text Block Name", default="NodeSlicer_Gcode.gcode") # pyright: ignore[reportInvalidTypeForm]
|
||||
text_block_name: str = NfStringProperty(name="Text Block Name", default="NodeSlicer_Gcode.gcode")
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
|
|
@ -171,14 +171,15 @@ class SLICENODE_StartPrint(ExecSliceNode):
|
|||
return 'ok'
|
||||
|
||||
class ExecSliceNode_DatabaseSubmit(ExecSliceNode):
|
||||
record_id: NfStringProperty(name="Record ID", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
record_id: str = NfStringProperty(name="Record ID", default="")
|
||||
|
||||
_db_name = "Database"
|
||||
_token_prop_name = "token_path"
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
self.add_multi_socket_group("NodeSocketString", name="Gcode")
|
||||
self.add_input("NodeSocketString", "Gcode")
|
||||
self.add_input("SLICESOCK_Settings", "Metadata")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
super().draw_buttons(context, layout)
|
||||
|
|
@ -275,7 +276,7 @@ class ExecSliceNode_DatabaseSubmit(ExecSliceNode):
|
|||
def _check_config(self, token: str) -> bool:
|
||||
return False
|
||||
|
||||
def _submit_to_db(self, token: str, fields: dict, temp_path: Path) -> str:
|
||||
def _submit_to_db(self, token: str, fields: dict[str, Any], temp_path: Path) -> str:
|
||||
return ""
|
||||
|
||||
@register_class
|
||||
|
|
@ -283,9 +284,9 @@ class SLICENODE_BaserowSubmit(ExecSliceNode_DatabaseSubmit):
|
|||
bl_label = "Baserow Submit"
|
||||
button_label = "Submit to Baserow"
|
||||
|
||||
host: NfStringProperty(name="Host", default="https://baserow.io") # pyright: ignore[reportInvalidTypeForm]
|
||||
token_path: NfStringProperty(name="Token File", subtype='FILE_PATH', default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
table_id: NfStringProperty(name="Table ID", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
host: str = NfStringProperty(name="Host", default="https://baserow.io")
|
||||
token_path: str = NfStringProperty(name="Token File", subtype='FILE_PATH', default="")
|
||||
table_id: str = NfStringProperty(name="Table ID", default="")
|
||||
|
||||
_db_name = "Baserow"
|
||||
_token_prop_name = "token_path"
|
||||
|
|
@ -321,9 +322,9 @@ class SLICENODE_AirtableSubmit(ExecSliceNode_DatabaseSubmit):
|
|||
bl_label = "Airtable Submit"
|
||||
button_label = "Submit to Airtable"
|
||||
|
||||
pat_path: NfStringProperty(name="PAT File", subtype='FILE_PATH', default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
base_id: NfStringProperty(name="Base ID", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
table_id: NfStringProperty(name="Table ID", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
pat_path: str = NfStringProperty(name="PAT File", subtype='FILE_PATH', default="")
|
||||
base_id: str = NfStringProperty(name="Base ID", default="")
|
||||
table_id: str = NfStringProperty(name="Table ID", default="")
|
||||
|
||||
_db_name = "Airtable"
|
||||
_token_prop_name = "pat_path"
|
||||
|
|
@ -358,59 +359,59 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
|||
bl_label = "Mesh to PDF"
|
||||
button_label = "Generate PDF"
|
||||
|
||||
draw_wireframe: NfBoolProperty(name="Wireframe", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_boundary: NfBoolProperty(name="Boundary (1 Face)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_contour: NfBoolProperty(name="Contour", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_crease: NfBoolProperty(name="Crease Angle", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
crease_angle: NfFloatProperty(name="Angle (°)", default=30.0, min=0.0, max=180.0) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_bbox: NfBoolProperty(name="Bounding Box", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_sa2_holes: NfBoolProperty(name="Holes (Ø)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
show_lineart: NfBoolProperty(name="Lineart Visibility", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
show_sa2: NfBoolProperty(name="SA2 Settings", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_grid_xy: NfBoolProperty(name="XY Grid (Top)", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_grid_xz: NfBoolProperty(name="XZ Grid (Front)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_grid_yz: NfBoolProperty(name="YZ Grid (Side)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
show_grids: NfBoolProperty(name="Grids", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_wireframe: bool = NfBoolProperty(name="Wireframe", default=True)
|
||||
draw_boundary: bool = NfBoolProperty(name="Boundary (1 Face)", default=False)
|
||||
draw_contour: bool = NfBoolProperty(name="Contour", default=False)
|
||||
draw_crease: bool = NfBoolProperty(name="Crease Angle", default=False)
|
||||
crease_angle: float = NfFloatProperty(name="Angle (°)", default=30.0, min=0.0, max=180.0)
|
||||
draw_bbox: bool = NfBoolProperty(name="Bounding Box", default=False)
|
||||
draw_sa2_holes: bool = NfBoolProperty(name="Holes (Ø)", default=False)
|
||||
show_lineart: bool = NfBoolProperty(name="Lineart Visibility", default=False)
|
||||
show_sa2: bool = NfBoolProperty(name="SA2 Settings", default=False)
|
||||
draw_grid_xy: bool = NfBoolProperty(name="XY Grid (Top)", default=True)
|
||||
draw_grid_xz: bool = NfBoolProperty(name="XZ Grid (Front)", default=False)
|
||||
draw_grid_yz: bool = NfBoolProperty(name="YZ Grid (Side)", default=False)
|
||||
show_grids: bool = NfBoolProperty(name="Grids", default=False)
|
||||
|
||||
draw_header: NfBoolProperty(name="Technical Header", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||
show_header: NfBoolProperty(name="Technical Drawing Header", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
header_company: NfStringProperty(name="Company", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
header_part_name: NfStringProperty(name="Part Name", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
header_drawing_number: NfStringProperty(name="Drawing No.", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
header_author: NfStringProperty(name="Author", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
header_revision: NfStringProperty(name="Revision", default="A") # pyright: ignore[reportInvalidTypeForm]
|
||||
header_material: NfStringProperty(name="Material", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
header_tolerance: NfStringProperty(name="Tolerance", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
draw_header: bool = NfBoolProperty(name="Technical Header", default=True)
|
||||
show_header: bool = NfBoolProperty(name="Technical Drawing Header", default=False)
|
||||
header_company: str = NfStringProperty(name="Company", default="")
|
||||
header_part_name: str = NfStringProperty(name="Part Name", default="")
|
||||
header_drawing_number: str = NfStringProperty(name="Drawing No.", default="")
|
||||
header_author: str = NfStringProperty(name="Author", default="")
|
||||
header_revision: str = NfStringProperty(name="Revision", default="A")
|
||||
header_material: str = NfStringProperty(name="Material", default="")
|
||||
header_tolerance: str = NfStringProperty(name="Tolerance", default="")
|
||||
|
||||
font_size: NfFloatProperty(name="Font Size (mm)", default=0.0, min=0.0, max=100.0) # pyright: ignore[reportInvalidTypeForm]
|
||||
margin: NfFloatProperty(name="Margin (mm)", default=10.0, min=0.0, max=500.0) # pyright: ignore[reportInvalidTypeForm]
|
||||
font_size: float = NfFloatProperty(name="Font Size (mm)", default=0.0, min=0.0, max=100.0)
|
||||
margin: float = NfFloatProperty(name="Margin (mm)", default=10.0, min=0.0, max=500.0)
|
||||
|
||||
bg_color: NfFloatVectorProperty(
|
||||
bg_color: tuple[float, float, float] = NfFloatVectorProperty(
|
||||
name="Background",
|
||||
subtype='COLOR',
|
||||
default=(1.0, 1.0, 1.0),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=3
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
wire_color: NfFloatVectorProperty(
|
||||
wire_color: tuple[float, float, float] = NfFloatVectorProperty(
|
||||
name="Wireframe",
|
||||
subtype='COLOR',
|
||||
default=(0.0, 0.0, 0.0),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=3
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
bbox_color: NfFloatVectorProperty(
|
||||
bbox_color: tuple[float, float, float] = NfFloatVectorProperty(
|
||||
name="BBox Color",
|
||||
subtype='COLOR',
|
||||
default=(0.0, 0.0, 1.0),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=3
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any
|
||||
from typing import Any, cast, Literal
|
||||
import numpy as np
|
||||
|
||||
from ..bpy_ns_types import NfEnumProperty, NfBoolProperty, notify_tree_changed
|
||||
|
|
@ -149,14 +148,14 @@ class SLICENODE_SliceShells(SliceNode):
|
|||
class SLICENODE_BufferShells(SliceNode):
|
||||
bl_label = "Buffer Shells"
|
||||
|
||||
shape_option: NfEnumProperty(
|
||||
shape_option: str = NfEnumProperty(
|
||||
name="Miter",
|
||||
items=[
|
||||
('round', "Round", ""),
|
||||
('square', "Square", ""),
|
||||
('flat', "Flat", "")
|
||||
]
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Paths")
|
||||
|
|
@ -183,7 +182,7 @@ class SLICENODE_BufferShells(SliceNode):
|
|||
class SLICENODE_BufferCurve(SliceNode):
|
||||
bl_label = "Buffer Curve"
|
||||
|
||||
cap_style: NfEnumProperty(
|
||||
cap_style: str = NfEnumProperty(
|
||||
name="Cap Style",
|
||||
items=[
|
||||
('round', "Round", ""),
|
||||
|
|
@ -191,9 +190,9 @@ class SLICENODE_BufferCurve(SliceNode):
|
|||
('square', "Square", "")
|
||||
],
|
||||
default='round'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
join_style: NfEnumProperty(
|
||||
join_style: str = NfEnumProperty(
|
||||
name="Join Style",
|
||||
items=[
|
||||
('round', "Round", ""),
|
||||
|
|
@ -201,13 +200,13 @@ class SLICENODE_BufferCurve(SliceNode):
|
|||
('bevel', "Bevel", "")
|
||||
],
|
||||
default='round'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
single_sided: NfBoolProperty(
|
||||
single_sided: bool = NfBoolProperty(
|
||||
name="Single Sided",
|
||||
description="Buffer only on one side of the curve",
|
||||
default=False
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Paths")
|
||||
|
|
@ -242,7 +241,7 @@ class SLICENODE_BufferCurve(SliceNode):
|
|||
class SLICENODE_OffsetCurve(SliceNode):
|
||||
bl_label = "Offset Curve"
|
||||
|
||||
join_style: NfEnumProperty(
|
||||
join_style: str = NfEnumProperty(
|
||||
name="Join Style",
|
||||
items=[
|
||||
('round', "Round", ""),
|
||||
|
|
@ -250,7 +249,7 @@ class SLICENODE_OffsetCurve(SliceNode):
|
|||
('bevel', "Bevel", "")
|
||||
],
|
||||
default='round'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Paths")
|
||||
|
|
@ -273,7 +272,7 @@ class SLICENODE_OffsetCurve(SliceNode):
|
|||
|
||||
return paths.offset_curve(
|
||||
distance,
|
||||
join_style=self.join_style,
|
||||
join_style=cast(Literal['round', 'mitre'], self.join_style),
|
||||
rotation=rotation
|
||||
)
|
||||
|
||||
|
|
@ -319,14 +318,14 @@ class SLICENODE_RotatePath(SliceNode):
|
|||
class SLICENODE_BufferShellsMultiple(SliceNode):
|
||||
bl_label = "Buffer Shells Multiple"
|
||||
|
||||
shape_option: NfEnumProperty(
|
||||
shape_option: str = NfEnumProperty(
|
||||
name="Miter",
|
||||
items=[
|
||||
('round', "Round", ""),
|
||||
('square', "Square", ""),
|
||||
('flat', "Flat", "")
|
||||
]
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Paths")
|
||||
|
|
@ -357,7 +356,7 @@ class SLICENODE_BufferShellsMultiple(SliceNode):
|
|||
class SLICENODE_Infill(SliceNode):
|
||||
bl_label = "Infill"
|
||||
|
||||
infill_type: NfEnumProperty(
|
||||
infill_type: str = NfEnumProperty(
|
||||
name="Type",
|
||||
items=[
|
||||
('GRID', "Grid", "Cross-hatch grid"),
|
||||
|
|
@ -366,7 +365,7 @@ class SLICENODE_Infill(SliceNode):
|
|||
],
|
||||
default='GRID',
|
||||
update=notify_tree_changed
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Paths")
|
||||
|
|
@ -443,7 +442,7 @@ class SLICENODE_JoinSlices(SliceNodeJoiner):
|
|||
bl_label = "Boolean Paths"
|
||||
socket_type = "SLICESOCK_Paths"
|
||||
|
||||
operation: NfEnumProperty(
|
||||
operation: str = NfEnumProperty(
|
||||
name="Operation",
|
||||
items=[
|
||||
('UNION', "Union", "Union of the slices"),
|
||||
|
|
@ -452,13 +451,13 @@ class SLICENODE_JoinSlices(SliceNodeJoiner):
|
|||
('XOR', "Exclusive or", "Exclusive or of the slices"),
|
||||
],
|
||||
default='UNION'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
connect_along_boundary: NfBoolProperty(
|
||||
connect_along_boundary: bool = NfBoolProperty(
|
||||
name="Connect along boundary",
|
||||
description="Connect clipped segments along the boundary of the clipping polygon",
|
||||
default=False
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
assert layout
|
||||
|
|
@ -489,14 +488,14 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
|||
bl_label = "Join Paths"
|
||||
socket_type = "SLICESOCK_Paths"
|
||||
|
||||
mode: NfEnumProperty(
|
||||
mode: str = NfEnumProperty(
|
||||
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
|
||||
|
|
@ -507,7 +506,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
|||
|
||||
inputs_data: list[SlicePaths] = []
|
||||
for input in self.inputs:
|
||||
paths: SlicePaths = resolve_input(input)
|
||||
paths: SlicePaths | None = resolve_input(input)
|
||||
if paths:
|
||||
inputs_data.append(paths)
|
||||
|
||||
|
|
@ -526,7 +525,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
|||
class SLICENODE_SimplifyPaths(SliceNode):
|
||||
bl_label = "Simplify Paths"
|
||||
|
||||
preserve_topology: NfBoolProperty( # pyright: ignore[reportInvalidTypeForm]
|
||||
preserve_topology: bool = NfBoolProperty(
|
||||
name="Preserve Topology",
|
||||
description="Prevents swapping of contained/containing geometry during simplification",
|
||||
default=True
|
||||
|
|
@ -547,7 +546,6 @@ class SLICENODE_SimplifyPaths(SliceNode):
|
|||
|
||||
paths: SlicePaths = self.resolve("Paths")
|
||||
tolerance: float = self.resolve("Tolerance") or 0.0
|
||||
|
||||
if not paths or tolerance <= 0:
|
||||
return paths
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any
|
||||
import bpy
|
||||
import os
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
|
|||
bl_label = "Gcode to Preview Mesh"
|
||||
|
||||
button_label = "Update Preview"
|
||||
show_e_0: NfBoolProperty(name="Show Zero Extrusions", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
show_e_0: bool = NfBoolProperty(name="Show Zero Extrusions", default=False)
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
|
|
@ -201,15 +202,14 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
|
|||
ng = bpy.data.node_groups.get(ng_name)
|
||||
|
||||
# Get existing 'Preview' modifier or create it
|
||||
mod: Any = None
|
||||
if hasattr(obj.modifiers, "get"):
|
||||
mod = obj.modifiers.get("Preview")
|
||||
elif isinstance(obj.modifiers, list):
|
||||
mod = next((m for m in obj.modifiers if (isinstance(m, dict) and m.get("name") == "Preview") or getattr(m, "name", "Preview") == "Preview"), None)
|
||||
else:
|
||||
mod = None
|
||||
|
||||
if (not mod or (hasattr(mod, "type") and mod.type != 'NODES')) and hasattr(obj.modifiers, "new"):
|
||||
mod = obj.modifiers.new(name="Preview", type='NODES')
|
||||
if (not mod or (hasattr(mod, "type") and getattr(mod, "type", None) != 'NODES')) and hasattr(obj.modifiers, "new"):
|
||||
mod = getattr(obj.modifiers, "new")(name="Preview", type='NODES')
|
||||
elif not mod and isinstance(obj.modifiers, list):
|
||||
mod = {"name": "Preview", "type": "NODES"}
|
||||
obj.modifiers.append(mod)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class SLICENODE_PrinterList(ExecSliceNode):
|
|||
def update_config(self, context):
|
||||
self._refresh()
|
||||
|
||||
printers_json_path: NfStringProperty(name="Config File", default="", subtype='FILE_PATH', update=update_config) # pyright: ignore[reportInvalidTypeForm]
|
||||
printers_json_path: str = NfStringProperty(name="Config File", default="", subtype='FILE_PATH', update=update_config)
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
|
|
@ -157,7 +157,7 @@ class SLICENODE_PrinterList(ExecSliceNode):
|
|||
class SLICENODE_PrusaslicerSlice(SliceNode):
|
||||
bl_label = "PrusaSlicer Slice"
|
||||
|
||||
prusaslicer_executable: NfStringProperty(name="PrusaSlicer Path", subtype='FILE_PATH', default='flatpak run com.prusa3d.PrusaSlicer') # pyright: ignore[reportInvalidTypeForm]
|
||||
prusaslicer_executable: str = NfStringProperty(name="PrusaSlicer Path", subtype='FILE_PATH', default='flatpak run com.prusa3d.PrusaSlicer')
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_PrusaslicerConf", "Config")
|
||||
|
|
@ -309,11 +309,11 @@ class SLICENODE_PrusaslicerPreview(ExecSliceNode):
|
|||
bl_label = "PrusaSlicer Preview"
|
||||
button_label = "Open Preview"
|
||||
|
||||
prusaslicer_executable: NfStringProperty(
|
||||
prusaslicer_executable: str = NfStringProperty(
|
||||
name="PrusaSlicer Path",
|
||||
subtype='FILE_PATH',
|
||||
default='flatpak run com.prusa3d.PrusaSlicer'
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
|
|
@ -396,7 +396,7 @@ class SLICENODE_Executor(ExecSliceNode, SliceNodeJoiner):
|
|||
button_label = "Execute All"
|
||||
socket_type = "SLICESOCK_ExecutionLink"
|
||||
|
||||
auto_refresh: NfBoolProperty(name="Auto-refresh", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||
auto_refresh: bool = NfBoolProperty(name="Auto-refresh", default=False)
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ class SLICESOCK_PrusaslicerConf(LabelNodeSocket):
|
|||
|
||||
@register_class
|
||||
class SettingItem(bpy.types.PropertyGroup):
|
||||
field: NfStringProperty(name="Field", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
value_str: NfStringProperty(name="Value", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
field: str = NfStringProperty(name="Field", default="")
|
||||
value_str: str = NfStringProperty(name="Value", default="")
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
|
|
@ -137,14 +137,14 @@ class SLICESOCK_ExecutionLink(LabelNodeSocket):
|
|||
class SLICESOCK_Printer(InputNodeSocket):
|
||||
bl_label = "Printer"
|
||||
|
||||
host: NfStringProperty(name="Host", default="localhost") # pyright: ignore[reportInvalidTypeForm]
|
||||
port: NfIntProperty(name="Port", default=80, min=1, max=65535) # pyright: ignore[reportInvalidTypeForm]
|
||||
prefix: NfStringProperty(name="Prefix", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
label: NfStringProperty(name="Label", default="Printer") # pyright: ignore[reportInvalidTypeForm]
|
||||
username: NfStringProperty(name="Username", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
password: NfStringProperty(name="Password", default="", subtype='PASSWORD') # pyright: ignore[reportInvalidTypeForm]
|
||||
host: str = NfStringProperty(name="Host", default="localhost")
|
||||
port: int = NfIntProperty(name="Port", default=80, min=1, max=65535)
|
||||
prefix: str = NfStringProperty(name="Prefix", default="")
|
||||
label: str = NfStringProperty(name="Label", default="Printer")
|
||||
username: str = NfStringProperty(name="Username", default="")
|
||||
password: str = NfStringProperty(name="Password", default="", subtype='PASSWORD')
|
||||
|
||||
host_type: NfEnumProperty(
|
||||
host_type: str = NfEnumProperty(
|
||||
name="Type",
|
||||
items=[
|
||||
('PRUSALINK', "PrusaLink", "PrusaLink API"),
|
||||
|
|
@ -153,7 +153,7 @@ class SLICESOCK_Printer(InputNodeSocket):
|
|||
],
|
||||
default='PRUSALINK',
|
||||
update=notify_tree_changed
|
||||
) # pyright: ignore[reportInvalidTypeForm]
|
||||
)
|
||||
|
||||
bl_color = (1.0, 0.45, 0.1, 1.0) # Orange
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
import bpy
|
||||
from bpy.types import NodeTree, Operator, Menu
|
||||
from bpy.app.handlers import persistent
|
||||
|
|
@ -179,7 +178,7 @@ class SLICENODE_OT_GroupToggle(Operator):
|
|||
bl_description = "Enter or exit the selected node group"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
node_name: bpy.props.StringProperty() # pyright: ignore[reportInvalidTypeForm]
|
||||
node_name: str = cast(Any, bpy.props.StringProperty())
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
|
@ -463,7 +462,7 @@ class SLICENODE_OT_Execute(Operator):
|
|||
bl_label = "Execute"
|
||||
bl_description = "Execute"
|
||||
|
||||
node_name: bpy.props.StringProperty() # pyright: ignore[reportInvalidTypeForm]
|
||||
node_name: str = cast(Any, bpy.props.StringProperty())
|
||||
|
||||
def execute(self, context) -> set['OperatorReturnItems']:
|
||||
node_tree = context.space_data.edit_tree # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def transform_slice_paths(
|
|||
if not (has_scale or has_rot or has_trans):
|
||||
return sp.copy_with(paths=list(sp))
|
||||
|
||||
R = None
|
||||
rot_mat: np.ndarray | None = None
|
||||
if has_rot:
|
||||
rx, ry, rz = r_arr
|
||||
cx, sx = np.cos(rx), np.sin(rx)
|
||||
|
|
@ -74,7 +74,7 @@ def transform_slice_paths(
|
|||
[sz, cz, 0.0],
|
||||
[0.0, 0.0, 1.0]
|
||||
], dtype=np.float64)
|
||||
R = Rz @ (Ry @ Rx)
|
||||
rot_mat = Rz @ (Ry @ Rx)
|
||||
|
||||
new_paths = []
|
||||
for p in sp:
|
||||
|
|
@ -91,11 +91,11 @@ def transform_slice_paths(
|
|||
else:
|
||||
new_p[:, :dim] *= s_arr[:dim]
|
||||
|
||||
if has_rot and R is not None and dim > 0:
|
||||
if has_rot and rot_mat is not None and dim > 0:
|
||||
if dim >= 3:
|
||||
new_p[:, :3] = new_p[:, :3] @ R.T
|
||||
new_p[:, :3] = new_p[:, :3] @ rot_mat.T
|
||||
elif dim == 2:
|
||||
rot_2d = R[:2, :2]
|
||||
rot_2d = rot_mat[:2, :2]
|
||||
new_p[:, :2] = new_p[:, :2] @ rot_2d.T
|
||||
|
||||
if has_trans and dim > 0:
|
||||
|
|
@ -752,12 +752,12 @@ def extract_silhouette_contour_edges(mesh_data: Any) -> NDArray[np.float64]:
|
|||
elif isinstance(item, SlicingObject):
|
||||
if item.mesh.size > 0:
|
||||
verts_3d = item.mesh[:, :3, :3]
|
||||
v0_3d = verts_3d[:, 0, :]
|
||||
v1_3d = verts_3d[:, 1, :]
|
||||
v2_3d = verts_3d[:, 2, :]
|
||||
if item.mesh.shape[1] >= 4:
|
||||
normals = item.mesh[:, 3, :]
|
||||
else:
|
||||
v0_3d = verts_3d[:, 0, :]
|
||||
v1_3d = verts_3d[:, 1, :]
|
||||
v2_3d = verts_3d[:, 2, :]
|
||||
normals = np.cross(v1_3d - v0_3d, v2_3d - v0_3d)
|
||||
|
||||
norms_len = np.linalg.norm(normals, axis=1, keepdims=True)
|
||||
|
|
|
|||
|
|
@ -38,22 +38,34 @@ def get_first_attribute_value(so_item: Any, attr_name: str) -> Any:
|
|||
return val.item()
|
||||
return val
|
||||
|
||||
if hasattr(so_item, 'objects') and isinstance(so_item.objects, list):
|
||||
for obj in so_item.objects:
|
||||
v = get_first_attribute_value(obj, attr_name)
|
||||
if v is not None:
|
||||
return v
|
||||
|
||||
if hasattr(so_item, 'collections') and isinstance(so_item.collections, dict):
|
||||
for coll in so_item.collections.values():
|
||||
v = get_first_attribute_value(coll, attr_name)
|
||||
if v is not None:
|
||||
return v
|
||||
|
||||
attr_key = attr_name.lower().replace(" ", "_")
|
||||
if hasattr(so_item, attr_key):
|
||||
val = getattr(so_item, attr_key)
|
||||
if val is not None and not callable(val):
|
||||
if isinstance(val, (list, tuple, np.ndarray)) and len(val) > 0:
|
||||
val = val[0]
|
||||
if hasattr(val, 'item'):
|
||||
return val.item()
|
||||
if hasattr(val, 'item') and callable(getattr(val, 'item')):
|
||||
return getattr(val, 'item')()
|
||||
return val
|
||||
elif hasattr(so_item, attr_name):
|
||||
val = getattr(so_item, attr_name)
|
||||
if val is not None and not callable(val):
|
||||
if isinstance(val, (list, tuple, np.ndarray)) and len(val) > 0:
|
||||
val = val[0]
|
||||
if hasattr(val, 'item'):
|
||||
return val.item()
|
||||
if hasattr(val, 'item') and callable(getattr(val, 'item')):
|
||||
return getattr(val, 'item')()
|
||||
return val
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ from .pdf_utils import PdfStream, escape_pdf_text
|
|||
# Geometry Utilities
|
||||
# =============================================================================
|
||||
|
||||
def circumcircle_3pts(p1: NDArray, p2: NDArray, p3: NDArray) -> tuple[tuple[float, float] | None, float | None]:
|
||||
def circumcircle_3pts(p1: NDArray[Any], p2: NDArray[Any], p3: NDArray[Any]) -> tuple[tuple[float, float] | None, float | None]:
|
||||
"""Calculate center and radius of circumcircle passing through 3 2D points."""
|
||||
ax, ay = float(p1[0]), float(p1[1])
|
||||
bx, by = float(p2[0]), float(p2[1])
|
||||
|
|
@ -518,7 +518,7 @@ def render_sa2_measures_pdf(
|
|||
"""
|
||||
Renders ISO 129-1 compliant SA2 hole measurements (bores, concentric steps, Z-depth, pitch) on PDF stream.
|
||||
"""
|
||||
if raw_geometry is None or not draw_sa2_holes:
|
||||
if raw_geometry is None or not draw_sa2_holes or to_pdf_pt is None:
|
||||
return
|
||||
|
||||
sa2_objects = extract_slicing_objects_bounds(raw_geometry)
|
||||
|
|
@ -586,6 +586,7 @@ def render_sa2_measures_pdf(
|
|||
)
|
||||
|
||||
# Concentric Bores Grouping & Hole Callouts
|
||||
concentric_groups: list[list[tuple[float, float, float]]] = []
|
||||
if draw_sa2_holes and holes:
|
||||
hole_color = (0.10, 0.45, 0.90)
|
||||
s.stroke_color(*hole_color).fill_color(*hole_color)
|
||||
|
|
@ -611,7 +612,7 @@ def render_sa2_measures_pdf(
|
|||
)
|
||||
|
||||
# --- Pitch Dimensions ---
|
||||
if draw_sa2_holes and 'concentric_groups' in locals() and len(concentric_groups) >= 2:
|
||||
if draw_sa2_holes and len(concentric_groups) >= 2:
|
||||
pitch_color = (0.05, 0.58, 0.53)
|
||||
s.stroke_color(*pitch_color).fill_color(*pitch_color)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ from __future__ import annotations
|
|||
from numpy import float64
|
||||
import numpy as np
|
||||
import pyclipper
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, cast
|
||||
import copy
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
from shapely.geometry import Polygon, MultiPolygon, LineString, LinearRing, MultiLineString, GeometryCollection
|
||||
|
||||
from ..infra.blender.blender_mesh_capture import PdfProperties
|
||||
|
||||
|
|
@ -162,13 +163,13 @@ class SlicePaths(list[np.ndarray]):
|
|||
extra_attributes: dict[str, list[np.ndarray]]
|
||||
layer_height: float
|
||||
first_layer_height: float | None
|
||||
transforms: list[dict]
|
||||
transforms: list[dict[str, Any]]
|
||||
object_properties: list[tuple[str, str]]
|
||||
pdf_properties: PdfProperties
|
||||
name: str
|
||||
objects_info: dict | None
|
||||
objects_info: dict[str, Any] | None
|
||||
|
||||
def __init__(self, paths: list[np.ndarray] = [], groups: list[int] = [], extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None, extra_attributes: dict[str, list[np.ndarray]] | None = None, layer_height: float = 0.2, first_layer_height: float | None = None, transforms: list[dict] | None = None, name: str = "", objects_info: dict | 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, extra_attributes: dict[str, list[np.ndarray]] | None = None, layer_height: float = 0.2, first_layer_height: float | None = None, transforms: list[dict[str, Any]] | None = None, name: str = "", objects_info: dict[str, Any] | None = None):
|
||||
super().__init__(paths)
|
||||
|
||||
self.groups = []
|
||||
|
|
@ -357,8 +358,9 @@ class SlicePaths(list[np.ndarray]):
|
|||
merged_names = []
|
||||
merged_objs = []
|
||||
for sp in paths_list:
|
||||
if hasattr(sp, "objects_info") and getattr(sp, "objects_info"):
|
||||
for obj_entry in sp.objects_info.get("objects", []):
|
||||
obj_info = getattr(sp, "objects_info", None)
|
||||
if obj_info:
|
||||
for obj_entry in obj_info.get("objects", []):
|
||||
if obj_entry not in merged_objs:
|
||||
merged_objs.append(dict(obj_entry))
|
||||
n_str = obj_entry.get("name", "")
|
||||
|
|
@ -1048,13 +1050,13 @@ def boolean_slice(subject: SlicePaths, object: SlicePaths, operation: str = 'UNI
|
|||
|
||||
return results
|
||||
|
||||
def geometry_to_paths(geom: BaseGeometry, z: float) -> list[np.ndarray]:
|
||||
def geometry_to_paths(geom: Any, z: float) -> list[np.ndarray]:
|
||||
"""Helper to convert shapely geometries to a list of 3D numpy arrays with a given Z height."""
|
||||
paths = []
|
||||
if geom.is_empty:
|
||||
return paths
|
||||
|
||||
if geom.geom_type == 'Polygon':
|
||||
if isinstance(geom, Polygon):
|
||||
ext_coords = np.array(geom.exterior.coords)
|
||||
ext_coords_3d = np.column_stack([ext_coords, np.full(len(ext_coords), z)])
|
||||
paths.append(ext_coords_3d)
|
||||
|
|
@ -1062,12 +1064,9 @@ def geometry_to_paths(geom: BaseGeometry, z: float) -> list[np.ndarray]:
|
|||
int_coords = np.array(interior.coords)
|
||||
int_coords_3d = np.column_stack([int_coords, np.full(len(int_coords), z)])
|
||||
paths.append(int_coords_3d)
|
||||
elif geom.geom_type == 'MultiPolygon':
|
||||
elif isinstance(geom, (MultiPolygon, GeometryCollection)):
|
||||
for poly in geom.geoms:
|
||||
paths.extend(geometry_to_paths(poly, z))
|
||||
elif geom.geom_type == 'GeometryCollection':
|
||||
for sub_geom in geom.geoms:
|
||||
paths.extend(geometry_to_paths(sub_geom, z))
|
||||
|
||||
return paths
|
||||
|
||||
|
|
@ -1109,8 +1108,8 @@ def buffer_curve_slice(
|
|||
|
||||
poly = line.buffer(
|
||||
distance,
|
||||
cap_style=cap_style,
|
||||
join_style=join_style,
|
||||
cap_style=cast(Literal['round', 'square', 'flat'], cap_style),
|
||||
join_style=cast(Literal['round', 'mitre', 'bevel'], join_style),
|
||||
single_sided=single_sided,
|
||||
quad_segs=16
|
||||
)
|
||||
|
|
@ -1125,25 +1124,25 @@ def buffer_curve_slice(
|
|||
|
||||
return paths_list, groups_list
|
||||
|
||||
def offset_geometry_to_paths(geom: BaseGeometry, z: float) -> list[np.ndarray]:
|
||||
def offset_geometry_to_paths(geom: Any, z: float) -> list[np.ndarray]:
|
||||
"""Helper to convert shapely line geometries to a list of 3D numpy arrays with a given Z height."""
|
||||
paths = []
|
||||
if geom.is_empty:
|
||||
return paths
|
||||
|
||||
if geom.geom_type in ('LineString', 'LinearRing'):
|
||||
if isinstance(geom, (LineString, LinearRing)):
|
||||
coords = np.array(geom.coords)
|
||||
coords_3d = np.column_stack([coords, np.full(len(coords), z)])
|
||||
paths.append(coords_3d)
|
||||
elif geom.geom_type == 'MultiLineString':
|
||||
elif isinstance(geom, MultiLineString):
|
||||
for line in geom.geoms:
|
||||
coords = np.array(line.coords)
|
||||
coords_3d = np.column_stack([coords, np.full(len(coords), z)])
|
||||
paths.append(coords_3d)
|
||||
elif geom.geom_type == 'GeometryCollection':
|
||||
elif isinstance(geom, GeometryCollection):
|
||||
for sub_geom in geom.geoms:
|
||||
paths.extend(offset_geometry_to_paths(sub_geom, z))
|
||||
elif geom.geom_type in ('Polygon', 'MultiPolygon'):
|
||||
elif isinstance(geom, (Polygon, MultiPolygon)):
|
||||
boundary = geom.boundary
|
||||
paths.extend(offset_geometry_to_paths(boundary, z))
|
||||
return paths
|
||||
|
|
@ -1288,7 +1287,6 @@ def paths_to_shapely_polygon(paths: list[np.ndarray]) -> BaseGeometry:
|
|||
return Polygon()
|
||||
|
||||
import pyclipper
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.ops import unary_union
|
||||
|
||||
pc = pyclipper.Pyclipper() # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
|
@ -1606,8 +1604,8 @@ class Pather(object):
|
|||
self.retraction: float = 0.0
|
||||
self.retraction_feedrate: float = 0.0
|
||||
self.lift: float = 0.0
|
||||
self.transforms: list[dict] = list(getattr(paths, "transforms", []))
|
||||
self.objects_info: dict | None = getattr(paths, "objects_info", None)
|
||||
self.transforms: list[dict[str, Any]] = list(getattr(paths, "transforms", []))
|
||||
self.objects_info: dict[str, Any] | None = getattr(paths, "objects_info", None)
|
||||
self.name: str = str(getattr(paths, "name", ""))
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
|||
|
||||
### Geometry Processing
|
||||
- **Get Mesh**: Converts a Blender Object into internal `SlicingObject` format.
|
||||
- **Separate Mesh**: Splits a `SlicingCollection` into two collections (`Selection` and `Inverted`) by comparing a specified mesh attribute against a target value with comparison operators (`==`, `!=`, `>`, `<`, `>=`, `<=`).
|
||||
- **Separate Mesh by Range**: Splits a `SlicingCollection` into multiple output sockets across an integer range (`Start` to `End`), matching a specified integer attribute. Objects in each output socket are renamed with a zero-indexed padded suffix (`f"{base_mesh_name}{i:0{num_digits}d}"` where `num_digits = max(2, len(str(total_parts)))`).
|
||||
- **Rename Mesh**: Overrides the internal metadata name of a `SlicingObject`, `SlicingCollection`, or `SlicingGroup`. Supports `{AttributeName}` attribute placeholders (e.g., `Rebar-L{Rebar Length}M`) that evaluate to index zero of the target attribute for each object, as well as `{Name}` to include the item's previous name (e.g., `{Name}_T{Thickness}mm`).
|
||||
- **Geometry**: Input geometry data.
|
||||
- **Name**: String input socket or property for the new name template.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from numpy.typing import NDArray
|
||||
from pathlib import Path
|
||||
|
|
@ -40,7 +41,7 @@ def write_metadata_xml(group: SlicingGroup, filepath):
|
|||
|
||||
for j, (k, collection) in enumerate(valid_collections.items()):
|
||||
|
||||
sorted_data: list[tuple[NDArray, NDArray, SlicingObject]] = sorted(zip(collection.mesh_start_ids, collection.mesh_end_ids, collection.objects), key=lambda x: object_type_order.get(x[2].object_type, 5))
|
||||
sorted_data: list[tuple[NDArray[Any], NDArray[Any], SlicingObject]] = sorted(zip(collection.mesh_start_ids, collection.mesh_end_ids, collection.objects), key=lambda x: object_type_order.get(x[2].object_type, 5))
|
||||
|
||||
object_elem = ET.SubElement(xml_content, "object", id=str(j+1), instances_count="1")
|
||||
ET.SubElement(object_elem, "metadata", type="object", key="name", value=collection.name)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ def get_evaluated_mesh(obj: Object):
|
|||
try:
|
||||
eval_obj = obj.evaluated_get(depsgraph)
|
||||
m = eval_obj.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph)
|
||||
if m and len(m.vertices) > 0 and not is_hidden and (not obj.modifiers or len(m.vertices) != len(obj.data.vertices)):
|
||||
orig_verts = getattr(obj.data, "vertices", None)
|
||||
if m and len(m.vertices) > 0 and not is_hidden and (not obj.modifiers or (orig_verts is not None and len(m.vertices) != len(orig_verts))):
|
||||
mesh = m
|
||||
elif m:
|
||||
eval_obj.to_mesh_clear()
|
||||
|
|
@ -52,7 +53,9 @@ def get_evaluated_mesh(obj: Object):
|
|||
try:
|
||||
temp_obj = obj.copy()
|
||||
temp_obj.hide_viewport = False
|
||||
bpy.context.scene.collection.objects.link(temp_obj)
|
||||
scene = bpy.context.scene
|
||||
if scene and scene.collection:
|
||||
scene.collection.objects.link(temp_obj)
|
||||
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
depsgraph.update()
|
||||
|
|
@ -63,7 +66,9 @@ def get_evaluated_mesh(obj: Object):
|
|||
except Exception:
|
||||
if temp_obj is not None:
|
||||
try:
|
||||
bpy.context.scene.collection.objects.unlink(temp_obj)
|
||||
scene = bpy.context.scene
|
||||
if scene and scene.collection:
|
||||
scene.collection.objects.unlink(temp_obj)
|
||||
bpy.data.objects.remove(temp_obj, do_unlink=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -80,7 +85,9 @@ def get_evaluated_mesh(obj: Object):
|
|||
pass
|
||||
if is_temp and temp_obj is not None:
|
||||
try:
|
||||
bpy.context.scene.collection.objects.unlink(temp_obj)
|
||||
scene = bpy.context.scene
|
||||
if scene and scene.collection:
|
||||
scene.collection.objects.unlink(temp_obj)
|
||||
bpy.data.objects.remove(temp_obj, do_unlink=True)
|
||||
bpy.context.evaluated_depsgraph_get().update()
|
||||
except Exception:
|
||||
|
|
@ -100,9 +107,9 @@ class SlicingObject():
|
|||
name: str
|
||||
parent: str
|
||||
mesh: NDArray[float64]
|
||||
attributes: dict[str, dict]
|
||||
attributes: dict[str, Any]
|
||||
object_type: str = "ModelPart"
|
||||
modifiers: list[dict] = []
|
||||
modifiers: list[dict[str, Any]] = []
|
||||
extruder: int = 1
|
||||
pdf_properties: PdfProperties
|
||||
object_properties: list[tuple[str, str]]
|
||||
|
|
@ -176,10 +183,10 @@ class SlicingObject():
|
|||
}
|
||||
return merged
|
||||
|
||||
def offset(self, offset: NDArray):
|
||||
def offset(self, offset: NDArray[Any]):
|
||||
self.mesh[:, :3, :] += offset
|
||||
|
||||
def scale(self, factor: float | NDArray):
|
||||
def scale(self, factor: float | NDArray[Any]):
|
||||
self.mesh[:, :3, :] *= factor
|
||||
if isinstance(factor, (list, tuple, np.ndarray)):
|
||||
factor_arr = np.array(factor, dtype=np.float64)
|
||||
|
|
@ -192,7 +199,7 @@ class SlicingObject():
|
|||
norms_len = np.where(norms_len == 0, 1.0, norms_len)
|
||||
self.mesh[:, 3, :] = norms / norms_len
|
||||
|
||||
def rotate(self, angles: NDArray):
|
||||
def rotate(self, angles: NDArray[Any]):
|
||||
# angles: array-like of 3 floats (rx, ry, rz) in radians
|
||||
angles_arr = np.array(angles, dtype=np.float64)
|
||||
rx, ry, rz = angles_arr
|
||||
|
|
@ -268,18 +275,18 @@ class SlicingObject():
|
|||
def max_z(self) -> float: return self.mesh[:, :3, 2].max()
|
||||
|
||||
@property
|
||||
def min_xy(self) -> NDArray: return np.array([self.min_x, self.min_y, 0.0])
|
||||
def min_xy(self) -> NDArray[Any]: return np.array([self.min_x, self.min_y, 0.0])
|
||||
|
||||
@property
|
||||
def max_xy(self) -> NDArray: return np.array([self.max_x, self.max_y, 0.0])
|
||||
def max_xy(self) -> NDArray[Any]: return np.array([self.max_x, self.max_y, 0.0])
|
||||
|
||||
@property
|
||||
def center_xy(self) -> NDArray: return (self.min_xy + self.max_xy) / 2.0
|
||||
def center_xy(self) -> NDArray[Any]: return (self.min_xy + self.max_xy) / 2.0
|
||||
|
||||
class SlicingCollection():
|
||||
name: str
|
||||
objects: list[SlicingObject]
|
||||
transforms: list[dict]
|
||||
transforms: list[dict[str, Any]]
|
||||
|
||||
def __init__(self, objs: list[Object], parent: str):
|
||||
self.objects = [SlicingObject(obj, parent) for obj in objs]
|
||||
|
|
@ -292,15 +299,15 @@ class SlicingCollection():
|
|||
if prop in self.__dict__:
|
||||
del self.__dict__[prop]
|
||||
|
||||
def offset(self, offset: NDArray):
|
||||
def offset(self, offset: NDArray[Any]):
|
||||
for so in self.objects: so.offset(offset)
|
||||
self._clear_cache()
|
||||
|
||||
def scale(self, factor: float | NDArray):
|
||||
def scale(self, factor: float | NDArray[Any]):
|
||||
for so in self.objects: so.scale(factor)
|
||||
self._clear_cache()
|
||||
|
||||
def rotate(self, angles: NDArray):
|
||||
def rotate(self, angles: NDArray[Any]):
|
||||
for so in self.objects: so.rotate(angles)
|
||||
self._clear_cache()
|
||||
|
||||
|
|
@ -315,26 +322,26 @@ class SlicingCollection():
|
|||
return max(height_all)
|
||||
|
||||
@property
|
||||
def meshes(self) -> list[NDArray]:
|
||||
def meshes(self) -> list[NDArray[Any]]:
|
||||
return [o.mesh for o in self.objects]
|
||||
|
||||
@cached_property
|
||||
def mesh_lengths_ids(self) -> NDArray:
|
||||
def mesh_lengths_ids(self) -> NDArray[Any]:
|
||||
return np.array([len(m) for m in self.meshes])
|
||||
|
||||
@cached_property
|
||||
def mesh_start_ids(self) -> NDArray:
|
||||
def mesh_start_ids(self) -> NDArray[Any]:
|
||||
lengths = self.mesh_lengths_ids
|
||||
return np.insert(np.cumsum(lengths)[:-1], 0, 0)
|
||||
|
||||
@cached_property
|
||||
def mesh_end_ids(self) -> NDArray:
|
||||
def mesh_end_ids(self) -> NDArray[Any]:
|
||||
starts = self.mesh_start_ids
|
||||
lengths = self.mesh_lengths_ids
|
||||
return starts + lengths - 1
|
||||
|
||||
@cached_property
|
||||
def all_verts(self) -> NDArray:
|
||||
def all_verts(self) -> NDArray[Any]:
|
||||
all_verts = np.vstack(self.meshes)[:, :3, :]
|
||||
return all_verts.reshape(-1, 3)
|
||||
|
||||
|
|
@ -388,17 +395,17 @@ class SlicingCollection():
|
|||
return max([so.max_z for so in self.objects if np.any(so.mesh)], default=None)
|
||||
|
||||
@property
|
||||
def min_xy(self) -> NDArray | None:
|
||||
def min_xy(self) -> NDArray[Any] | None:
|
||||
if self.min_x is None or self.min_y is None: return None
|
||||
return np.array([self.min_x, self.min_y, 0.0])
|
||||
|
||||
@property
|
||||
def max_xy(self) -> NDArray | None:
|
||||
def max_xy(self) -> NDArray[Any] | None:
|
||||
if self.max_x is None or self.max_y is None: return None
|
||||
return np.array([self.max_x, self.max_y, 0.0])
|
||||
|
||||
@property
|
||||
def center_xy(self) -> NDArray | None:
|
||||
def center_xy(self) -> NDArray[Any] | None:
|
||||
if self.min_xy is None or self.max_xy is None: return None
|
||||
return (self.min_xy + self.max_xy) / 2.0
|
||||
|
||||
|
|
@ -407,10 +414,10 @@ class SlicingGroup():
|
|||
collections: dict[str, SlicingCollection]
|
||||
|
||||
# Metadata
|
||||
wipe_tower_xy: NDArray = np.array([0, 0])
|
||||
wipe_tower_xy: NDArray[Any] = np.array([0, 0])
|
||||
wipe_tower_rotation_deg: float = 0
|
||||
object_offset: NDArray = np.array([0.0, 0.0, 0.0])
|
||||
transforms: list[dict]
|
||||
object_offset: NDArray[Any] = np.array([0.0, 0.0, 0.0])
|
||||
transforms: list[dict[str, Any]]
|
||||
|
||||
def __init__(self, selected_objs: list[Object]) -> None:
|
||||
self.object_offset = np.array([0.0, 0.0, 0.0])
|
||||
|
|
@ -443,18 +450,19 @@ class SlicingGroup():
|
|||
assert bpy.context.scene and bpy.context.scene.unit_settings
|
||||
scale_tx = 1000. * bpy.context.scene.unit_settings.scale_length #type: ignore
|
||||
|
||||
self.wipe_tower_xy = self.center_xy[0:2]
|
||||
if self.center_xy is not None:
|
||||
self.wipe_tower_xy = self.center_xy[0:2]
|
||||
for obj in objs:
|
||||
with get_evaluated_mesh(obj) as (eval_obj, _m):
|
||||
if eval_obj and hasattr(eval_obj, 'nodeslicer'):
|
||||
if eval_obj.nodeslicer.object_type == 'WipeTower':
|
||||
self.wipe_tower_xy = np.array(eval_obj.location[0:2]) * scale_tx
|
||||
self.wipe_tower_rotation_deg = round(eval_obj.rotation_euler[2]*180/math.pi,5)
|
||||
break
|
||||
ns_prop = getattr(eval_obj, 'nodeslicer', None)
|
||||
if eval_obj is not None and ns_prop and getattr(ns_prop, 'object_type', None) == 'WipeTower':
|
||||
self.wipe_tower_xy = np.array(eval_obj.location[0:2]) * scale_tx
|
||||
self.wipe_tower_rotation_deg = round(eval_obj.rotation_euler[2]*180/math.pi,5)
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
def offset(self, offset: NDArray):
|
||||
def offset(self, offset: NDArray[Any]):
|
||||
for k, so in self.collections.items(): so.offset(offset)
|
||||
self.wipe_tower_xy = self.wipe_tower_xy + offset[0:2]
|
||||
|
||||
|
|
@ -499,17 +507,17 @@ class SlicingGroup():
|
|||
def max_z(self) -> float: return max([so.max_z for k, so in self.collections.items() if so.max_z is not None], default=0)
|
||||
|
||||
@property
|
||||
def min_xy(self) -> NDArray:
|
||||
def min_xy(self) -> NDArray[Any]:
|
||||
if self.min_x is None or self.min_y is None: return np.array([.0, .0, .0])
|
||||
return np.array([self.min_x, self.min_y, 0.0])
|
||||
|
||||
@property
|
||||
def max_xy(self) -> NDArray:
|
||||
def max_xy(self) -> NDArray[Any]:
|
||||
if self.max_x is None or self.max_y is None: return np.array([.0, .0, .0])
|
||||
return np.array([self.max_x, self.max_y, 0.0])
|
||||
|
||||
@property
|
||||
def center_xy(self) -> NDArray:
|
||||
def center_xy(self) -> NDArray[Any]:
|
||||
if self.min_xy is None or self.max_xy is None: return np.array([.0, .0, .0])
|
||||
return (self.min_xy + self.max_xy) / 2.0
|
||||
|
||||
|
|
@ -525,7 +533,7 @@ class TriMesh():
|
|||
self.length_tris = t
|
||||
self.matrix_world = m
|
||||
|
||||
def _cached_objects_to_tris(obj_names: tuple[str, ...], matrices: tuple[tuple[float, ...], ...], scale: float) -> tuple[NDArray[float64], dict[str, dict]]:
|
||||
def _cached_objects_to_tris(obj_names: tuple[str, ...], matrices: tuple[tuple[float, ...], ...], scale: float) -> tuple[NDArray[float64], dict[str, dict[str, Any]]]:
|
||||
tris_count: int = 0
|
||||
meshes: list[TriMesh] = []
|
||||
|
||||
|
|
@ -656,7 +664,7 @@ def _cached_objects_to_tris(obj_names: tuple[str, ...], matrices: tuple[tuple[fl
|
|||
|
||||
return tris, attributes_data
|
||||
|
||||
def objects_to_tris(objects: list[Object], scale) -> tuple[NDArray[float64], dict[str, dict]]:
|
||||
def objects_to_tris(objects: list[Object], scale: float) -> tuple[NDArray[float64], dict[str, dict[str, Any]]]:
|
||||
obj_names = tuple(o.name for o in objects)
|
||||
matrices = tuple(tuple(val for row in o.matrix_world for val in row) for o in objects)
|
||||
return _cached_objects_to_tris(obj_names, matrices, scale)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import field
|
||||
import typing
|
||||
from typing import Any, cast
|
||||
import bpy
|
||||
from contextlib import contextmanager
|
||||
|
||||
from typing import Any
|
||||
from bpy.types import NodeSocket, Node
|
||||
|
||||
class NodeException(Exception): ...
|
||||
|
||||
_EvalCache = dict[tuple[int, str, str], Any]
|
||||
_EvalCache = dict[Any, Any]
|
||||
_VisitedSet = set[Any]
|
||||
|
||||
NATIVE_SOCKET_MAP: dict[str, str] = {}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ if hasattr(bpy.types, "NodeSocket") and not hasattr(bpy.types.NodeSocket, "value
|
|||
self._value = val
|
||||
|
||||
try:
|
||||
bpy.types.NodeSocket.value = property(_get_socket_value, _set_socket_value)
|
||||
setattr(bpy.types.NodeSocket, "value", property(_get_socket_value, _set_socket_value))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -55,6 +55,17 @@ class AutoBlIdName:
|
|||
if "bl_idname" not in cls.__dict__:
|
||||
cls.bl_idname = cls.__name__
|
||||
|
||||
if not hasattr(cls, "__annotations__"):
|
||||
cls.__annotations__ = {}
|
||||
for name, val in list(cls.__dict__.items()):
|
||||
val_type_name = type(val).__name__
|
||||
if "PropertyDeferred" in val_type_name or val_type_name.startswith("_Property") or (isinstance(val, tuple) and len(val) > 0 and "Property" in type(val[0]).__name__):
|
||||
cls.__annotations__[name] = val
|
||||
try:
|
||||
delattr(cls, name)
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
class SliceNodeSocket(NodeSocket, AutoBlIdName):
|
||||
default: Any = None
|
||||
bl_color = (1.0, 1.0, 1.0, 1.0)
|
||||
|
|
@ -64,7 +75,11 @@ class SliceNodeSocket(NodeSocket, AutoBlIdName):
|
|||
|
||||
from ...registry import get_label_by_name, register_class
|
||||
class SliceNode(Node, AutoBlIdName):
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
_current_cache: _EvalCache | None = None
|
||||
_current_visited: _VisitedSet | None = None
|
||||
_data: dict[str, Any] | None = None
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
return super().get(key, default) # type: ignore
|
||||
except (AttributeError, TypeError):
|
||||
|
|
@ -73,7 +88,7 @@ class SliceNode(Node, AutoBlIdName):
|
|||
return self._data.get(key, default)
|
||||
return getattr(self, key, default)
|
||||
|
||||
def __setitem__(self, key: str, value: Any) -> None:
|
||||
def __setitem__(self, key: str, value: Any) -> None: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
super().__setitem__(key, value) # type: ignore
|
||||
except (AttributeError, TypeError):
|
||||
|
|
@ -81,7 +96,7 @@ class SliceNode(Node, AutoBlIdName):
|
|||
self._data = {}
|
||||
self._data[key] = value
|
||||
|
||||
def __getitem__(self, key: str) -> Any:
|
||||
def __getitem__(self, key: str) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
return super().__getitem__(key) # type: ignore
|
||||
except (AttributeError, TypeError):
|
||||
|
|
@ -91,11 +106,14 @@ class SliceNode(Node, AutoBlIdName):
|
|||
|
||||
def __contains__(self, key: str) -> bool:
|
||||
try:
|
||||
return super().__contains__(key) # type: ignore
|
||||
contains_fn = getattr(super(), "__contains__", None)
|
||||
if contains_fn:
|
||||
return contains_fn(key) # type: ignore
|
||||
except (AttributeError, TypeError):
|
||||
if hasattr(self, "_data") and isinstance(self._data, dict):
|
||||
return key in self._data
|
||||
return False
|
||||
pass
|
||||
if hasattr(self, "_data") and isinstance(self._data, dict):
|
||||
return key in self._data
|
||||
return False
|
||||
|
||||
def update(self) -> None:
|
||||
self._update_multi_groups()
|
||||
|
|
@ -292,10 +310,13 @@ class SLICENODE_Group(bpy.types.NodeCustomGroup, AutoBlIdName):
|
|||
|
||||
self.sync_sockets()
|
||||
|
||||
slicer_tree: bpy.props.PointerProperty( # pyright: ignore[reportInvalidTypeForm]
|
||||
type=bpy.types.NodeTree,
|
||||
name="Node Tree",
|
||||
update=_tree_updated
|
||||
slicer_tree: bpy.types.NodeTree | None = cast(
|
||||
Any,
|
||||
bpy.props.PointerProperty(
|
||||
type=bpy.types.NodeTree,
|
||||
name="Node Tree",
|
||||
update=_tree_updated
|
||||
)
|
||||
)
|
||||
|
||||
def sync_sockets(self):
|
||||
|
|
@ -375,19 +396,16 @@ class SLICENODE_Group(bpy.types.NodeCustomGroup, AutoBlIdName):
|
|||
return "Group"
|
||||
|
||||
|
||||
def _resolve_group_output(socket: NodeSocket, cache: _EvalCache | None, visited: set[tuple[int, str]] | None):
|
||||
def _resolve_group_output(socket: NodeSocket, cache: _EvalCache | None, visited: _VisitedSet | None):
|
||||
node = socket.node
|
||||
assert isinstance(node, (bpy.types.NodeGroup, SLICENODE_Group))
|
||||
assert node is not None
|
||||
|
||||
if isinstance(node, SLICENODE_Group):
|
||||
tree = node.slicer_tree
|
||||
else:
|
||||
tree = node.node_tree
|
||||
|
||||
tree = getattr(node, "slicer_tree", None) or getattr(node, "node_tree", None)
|
||||
if not tree: return None
|
||||
|
||||
try:
|
||||
idx = list(node.outputs).index(socket)
|
||||
outputs_list = list(node.outputs) if node.outputs is not None else []
|
||||
idx = outputs_list.index(socket)
|
||||
except (ValueError, IndexError):
|
||||
return None
|
||||
|
||||
|
|
@ -410,14 +428,16 @@ def _resolve_group_output(socket: NodeSocket, cache: _EvalCache | None, visited:
|
|||
with _group_context(node, cache=cache):
|
||||
return resolve_input(typing.cast(Any, inner_socket), cache=cache, visited=visited)
|
||||
|
||||
def _resolve_group_input(socket: NodeSocket, cache: _EvalCache | None, visited: set[tuple[int, str]] | None):
|
||||
def _resolve_group_input(socket: NodeSocket, cache: _EvalCache | None, visited: _VisitedSet | None):
|
||||
stack = _get_group_stack(cache)
|
||||
if not stack: return None
|
||||
group_node = stack.pop()
|
||||
|
||||
# NodeGroupInput outputs usually match the group inputs by index
|
||||
try:
|
||||
idx = list(socket.node.outputs).index(socket)
|
||||
assert socket.node is not None
|
||||
outputs_list = list(socket.node.outputs) if socket.node.outputs is not None else []
|
||||
idx = outputs_list.index(socket)
|
||||
outer_socket = group_node.inputs[idx]
|
||||
except (ValueError, IndexError):
|
||||
stack.append(group_node)
|
||||
|
|
@ -428,12 +448,9 @@ def _resolve_group_input(socket: NodeSocket, cache: _EvalCache | None, visited:
|
|||
finally:
|
||||
stack.append(group_node)
|
||||
|
||||
def _follow_reroute(socket: NodeSocket) -> NodeSocket:
|
||||
current: NodeSocket = socket
|
||||
seen: set[int] = set()
|
||||
|
||||
while getattr(current.node, "bl_idname", "") == "NodeReroute":
|
||||
assert current.node
|
||||
def _follow_reroute(current: NodeSocket) -> NodeSocket:
|
||||
seen = set()
|
||||
while current.node and getattr(current.node, "bl_idname", "") == "NodeReroute":
|
||||
ptr = current.node.as_pointer()
|
||||
if ptr in seen:
|
||||
break
|
||||
|
|
@ -451,7 +468,7 @@ def resolve_input(
|
|||
socket: SliceNodeSocket | None,
|
||||
*,
|
||||
cache: _EvalCache | None = None,
|
||||
visited: set[tuple[int, str]] | None = None,
|
||||
visited: _VisitedSet | None = None,
|
||||
):
|
||||
if socket is None:
|
||||
raise Exception(f"The queried socket is non-existant")
|
||||
|
|
@ -494,7 +511,7 @@ def _resolve_output(
|
|||
*,
|
||||
default: Any = None,
|
||||
cache: _EvalCache | None = None,
|
||||
visited: set[tuple[int, str]] | None = None,
|
||||
visited: _VisitedSet | None = None,
|
||||
):
|
||||
if cache is None:
|
||||
cache = _current_execution_cache
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Any
|
||||
import bpy
|
||||
|
||||
class DrawCollectionMixin():
|
||||
items: bpy.props.CollectionProperty # pyright: ignore[reportGeneralTypeIssues]
|
||||
items: Any = None
|
||||
|
||||
def draw_collection(self, items, layout):
|
||||
col = layout.column(align=True)
|
||||
|
|
@ -11,7 +12,7 @@ class DrawCollectionMixin():
|
|||
def _draw_prop(self, text, layout):
|
||||
self.draw_collection(self.items, layout)
|
||||
|
||||
def update_collection(items: bpy.props.CollectionProperty) -> None: # pyright: ignore[reportGeneralTypeIssues]
|
||||
def update_collection(items: Any) -> None:
|
||||
if len(items) == 0:
|
||||
items.add()
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ def update_collection(items: bpy.props.CollectionProperty) -> None: # pyright:
|
|||
break
|
||||
|
||||
class UpdateCollectionMixin():
|
||||
items: bpy.props.CollectionProperty # pyright: ignore[reportGeneralTypeIssues]
|
||||
items: Any = None
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from pathlib import Path
|
||||
import requests, base64, mimetypes, os, time
|
||||
import requests # pyright: ignore[reportMissingModuleSource]
|
||||
import base64, mimetypes, os, time
|
||||
|
||||
from typing import Callable, TypeAlias, TypeVar, Any
|
||||
F = TypeVar('F', bound=Callable[..., Any])
|
||||
|
|
@ -102,7 +103,7 @@ class AirtableInterface:
|
|||
|
||||
for i in range(0, len(records), 10):
|
||||
batch = records[i:i+10]
|
||||
payload = {
|
||||
payload: dict[str, Any] = {
|
||||
"records": [{'fields': r} for r in batch],
|
||||
"typecast": typecast,
|
||||
"returnFieldsByFieldId": returnFieldsByFieldId,
|
||||
|
|
|
|||
|
|
@ -11,20 +11,20 @@ class BaserowBackend:
|
|||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def get_record(self, table_id: str, record_id: int) -> dict:
|
||||
def get_record(self, table_id: str, record_id: int) -> dict[str, Any]:
|
||||
url = f"{self.host}/api/database/rows/table/{table_id}/{record_id}/"
|
||||
response = requests.get(url, headers=self.headers, params={"user_field_names": "true"})
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def upsert_record(self, table_id: str, record_id: int, fields: dict[str, Any]) -> dict:
|
||||
def upsert_record(self, table_id: str, record_id: int, fields: dict[str, Any]) -> dict[str, Any]:
|
||||
try:
|
||||
self.get_record(table_id, record_id)
|
||||
return self.update_record(table_id, record_id, fields)
|
||||
except (requests.exceptions.HTTPError, ValueError):
|
||||
return self.create_record(table_id, fields)
|
||||
|
||||
def create_record(self, table_id: str, fields: dict[str, Any]) -> dict:
|
||||
def create_record(self, table_id: str, fields: dict[str, Any]) -> dict[str, Any]:
|
||||
url = f"{self.host}/api/database/rows/table/{table_id}/"
|
||||
response = requests.post(url, headers=self.headers, json=fields, params={"user_field_names": "true"})
|
||||
try:
|
||||
|
|
@ -34,7 +34,7 @@ class BaserowBackend:
|
|||
raise e
|
||||
return response.json()
|
||||
|
||||
def update_record(self, table_id: str, record_id: int, fields: dict[str, Any]) -> dict:
|
||||
def update_record(self, table_id: str, record_id: int, fields: dict[str, Any]) -> dict[str, Any]:
|
||||
url = f"{self.host}/api/database/rows/table/{table_id}/{record_id}/"
|
||||
response = requests.patch(url, headers=self.headers, json=fields, params={"user_field_names": "true"})
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -73,14 +73,14 @@ class HttpBackend:
|
|||
headers = {**self.headers, **kw.pop('headers', {})}
|
||||
return self.session.delete(self.base + path, headers=headers, timeout=self.timeout, **kw)
|
||||
|
||||
def _json(self, resp, default: dict = {}, ok=(200, 201, 202, 204)) -> dict:
|
||||
def _json(self, resp: requests.Response, default: dict[str, Any] | None = None, ok: tuple[int, ...] = (200, 201, 202, 204)) -> dict[str, Any]:
|
||||
if resp.status_code not in ok:
|
||||
resp.raise_for_status()
|
||||
if resp.status_code == 204 or not resp.content:
|
||||
return default
|
||||
return default if default is not None else {}
|
||||
return resp.json()
|
||||
|
||||
class PrinterHttpBackend(HttpBackend, PrinterBackend):
|
||||
class PrinterHttpBackend(HttpBackend):
|
||||
pass
|
||||
|
||||
class PrusaLinkBackend(PrinterHttpBackend):
|
||||
|
|
@ -326,7 +326,7 @@ class MoonrakerBackend(PrinterHttpBackend):
|
|||
@dataclass
|
||||
class ManagedPrinter:
|
||||
name: str
|
||||
backend: PrinterHttpBackend
|
||||
backend: PrinterBackend
|
||||
status: PrinterStatus = field(default_factory=PrinterStatus)
|
||||
last_error: str | None = None
|
||||
last_command_time: str | None = None
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
import operator
|
||||
import re
|
||||
from typing import TYPE_CHECKING, TypeAlias, Any
|
||||
from typing import TYPE_CHECKING, TypeAlias, Any, cast
|
||||
|
||||
if TYPE_CHECKING:
|
||||
Token: TypeAlias = tuple[str, str]
|
||||
|
|
@ -109,12 +109,14 @@ class BinaryOpNode(ExprNode):
|
|||
if self.op == "=~":
|
||||
if not isinstance(rv, re.Pattern):
|
||||
raise TypeError("Right operand of =~ must be a regex pattern.")
|
||||
return rv.search(str(lv)) is not None
|
||||
pattern_str = cast(re.Pattern[str], rv)
|
||||
return pattern_str.search(str(lv)) is not None
|
||||
|
||||
if self.op == "!~":
|
||||
if not isinstance(rv, re.Pattern):
|
||||
raise TypeError("Right operand of !~ must be a regex pattern.")
|
||||
return rv.search(str(lv)) is None
|
||||
pattern_str = cast(re.Pattern[str], rv)
|
||||
return pattern_str.search(str(lv)) is None
|
||||
|
||||
raise RuntimeError(f"Unknown binary operator: {self.op}")
|
||||
|
||||
|
|
|
|||
15
registry.py
15
registry.py
|
|
@ -5,8 +5,22 @@ from typing import Any, Callable
|
|||
_bpy_class_registry: list[type] = []
|
||||
_timer_registry: list[Callable[..., int | None]] = []
|
||||
|
||||
def setup_bpy_annotations(cls: type) -> type:
|
||||
if not hasattr(cls, "__annotations__"):
|
||||
cls.__annotations__ = {}
|
||||
for name, val in list(cls.__dict__.items()):
|
||||
val_type_name = type(val).__name__
|
||||
if "PropertyDeferred" in val_type_name or val_type_name.startswith("_Property") or (isinstance(val, tuple) and len(val) > 0 and "Property" in type(val[0]).__name__):
|
||||
cls.__annotations__[name] = val
|
||||
try:
|
||||
delattr(cls, name)
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
return cls
|
||||
|
||||
# BPY CLASSES
|
||||
def register_class(cls: type) -> type:
|
||||
setup_bpy_annotations(cls)
|
||||
if cls not in _bpy_class_registry:
|
||||
_bpy_class_registry.append(cls)
|
||||
return cls
|
||||
|
|
@ -29,6 +43,7 @@ def get_class_by_name(name: str) -> type | None:
|
|||
def blender_register_classes():
|
||||
for cls in _bpy_class_registry:
|
||||
try:
|
||||
setup_bpy_annotations(cls)
|
||||
bpy.utils.register_class(cls)
|
||||
except Exception as e:
|
||||
print(f"Nodeslicer: Failed to register {cls}: {e}")
|
||||
|
|
|
|||
|
|
@ -739,6 +739,7 @@ class TestSlice(unittest.TestCase):
|
|||
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject
|
||||
|
||||
node = SLICENODE_SeparateMeshByRange()
|
||||
node.start_val = 0
|
||||
node.end_val = 6
|
||||
|
||||
so = SlicingObject()
|
||||
|
|
@ -758,9 +759,60 @@ class TestSlice(unittest.TestCase):
|
|||
coll.transforms = []
|
||||
node.resolve = lambda name: coll if name == "Mesh" else ("part_id" if name == "Attribute" else None)
|
||||
|
||||
# start_val = 0, end_val = 6 -> total_parts = 7 -> num_digits = 2
|
||||
# socket "1" -> i = 1 -> CA01
|
||||
res_1 = node.evaluate_output("1")
|
||||
self.assertIsNotNone(res_1)
|
||||
self.assertEqual(res_1.objects[0].name, "CA01")
|
||||
|
||||
# socket "2" -> i = 2 -> CA02
|
||||
res_2 = node.evaluate_output("2")
|
||||
self.assertIsNotNone(res_2)
|
||||
self.assertEqual(res_2.objects[0].name, "CA02")
|
||||
|
||||
# Now test start_val = 1, end_val = 6 -> total_parts = 6 -> num_digits = 2
|
||||
# Numbering starts from zero, so socket "1" has i = 0 -> CA00, socket "2" has i = 1 -> CA01
|
||||
node.start_val = 1
|
||||
node.end_val = 6
|
||||
|
||||
res_start1_out1 = node.evaluate_output("1")
|
||||
self.assertIsNotNone(res_start1_out1)
|
||||
self.assertEqual(res_start1_out1.objects[0].name, "CA00")
|
||||
|
||||
res_start1_out2 = node.evaluate_output("2")
|
||||
self.assertIsNotNone(res_start1_out2)
|
||||
self.assertEqual(res_start1_out2.objects[0].name, "CA01")
|
||||
|
||||
# Test total_parts >= 100 -> num_digits = 3
|
||||
node.start_val = 0
|
||||
node.end_val = 100 # total_parts = 101 -> num_digits = 3
|
||||
res_large_1 = node.evaluate_output("1")
|
||||
self.assertIsNotNone(res_large_1)
|
||||
self.assertEqual(res_large_1.objects[0].name, "CA001")
|
||||
|
||||
# Test np.all(mask) where all faces match the target
|
||||
so_full = SlicingObject()
|
||||
so_full.name = "FullMesh"
|
||||
so_full.mesh = np.zeros((4, 4, 3))
|
||||
so_full.attributes = {
|
||||
"part_id": {
|
||||
"domain": "FACE",
|
||||
"type": "INT",
|
||||
"values": np.array([2, 2, 2, 2])
|
||||
}
|
||||
}
|
||||
coll_full = SlicingCollection.__new__(SlicingCollection)
|
||||
coll_full.objects = [so_full]
|
||||
coll_full.name = "FullColl"
|
||||
coll_full.transforms = []
|
||||
node.start_val = 2
|
||||
node.end_val = 5 # total_parts = 4, num_digits = 2
|
||||
node.resolve = lambda name: coll_full if name == "Mesh" else ("part_id" if name == "Attribute" else None)
|
||||
|
||||
res_full_2 = node.evaluate_output("2")
|
||||
self.assertIsNotNone(res_full_2)
|
||||
self.assertEqual(res_full_2.objects[0].name, "FullMesh00")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue