- Introduced tests for boundary smoothing in `test_boundary_smoothing.py`, verifying that smoothing reduces stair-step artifacts while preserving boundary integrity. - Added tests for cutout boundary behavior in `test_cutout_boundary.py`, ensuring proper handling of boundary triangles and normal blending. - Created `test_temporary_attributes.py` to check the cleanup of temporary attributes across depth modifier branches, ensuring no warnings are raised. - Implemented point culling functionality in `test_validity.py`, confirming that invalid points are removed without affecting adjacent geometry. - Developed boundary smoothing logic in `boundary_smoothing.py`, allowing for depth smoothing while maintaining edge integrity. - Added cutout boundary management in `cutout_boundary.py`, including functions for removing boundary triangles and tapering thickness profiles. - Implemented validity checks in `validity.py` to ensure proper deletion of invalid vertices and associated wire edges.
64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
"""Estimate node bounds and socket offsets in unscaled editor units."""
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
RULES = json.loads(Path(__file__).with_suffix(".json").read_text(encoding="utf-8"))
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class NodeDimensions:
|
|
width: float
|
|
height: float
|
|
sockets: dict
|
|
|
|
|
|
def visible_socket(socket):
|
|
"""Include only sockets drawn for the node's current operation."""
|
|
return socket.enabled and not socket.is_unavailable and not socket.hide
|
|
|
|
|
|
def estimate_node_dimensions(node):
|
|
"""Reserve socket rows, expanded vector values, and node-specific controls."""
|
|
if node.bl_idname == "NodeReroute":
|
|
return NodeDimensions(16, 16, {socket: 8 for socket in (*node.inputs, *node.outputs)})
|
|
inputs = [s for s in node.inputs if visible_socket(s)]
|
|
outputs = [s for s in node.outputs if visible_socket(s)]
|
|
width = RULES["wide_width"] if (
|
|
"Attribute" in node.bl_idname or node.bl_idname == "GeometryNodeGroup"
|
|
) else RULES["width"]
|
|
if node.hide:
|
|
height = max(RULES["collapsed_height"], 16 + 12 * max(len(inputs), len(outputs)))
|
|
sockets = {
|
|
socket: (index + 1) * height / (len(side) + 1)
|
|
for side in (inputs, outputs)
|
|
for index, socket in enumerate(side)
|
|
}
|
|
return NodeDimensions(width, height, sockets)
|
|
|
|
# Conservative reserves include spacing between UI sections.
|
|
controls = RULES["controls"][node.bl_idname]
|
|
for collection in ("repeat_items", "state_items", "input_items", "main_items", "generation_items", "enum_items"):
|
|
controls += len(getattr(node, collection, ()))
|
|
if node.bl_idname == "GeometryNodeGroup" and node.node_tree:
|
|
controls += sum(item.item_type == "PANEL" for item in node.node_tree.interface.items_tree)
|
|
cursor = RULES["header"] + RULES["padding"]
|
|
sockets = {}
|
|
for socket in outputs:
|
|
sockets[socket] = cursor + RULES["row"] / 2
|
|
cursor += RULES["row"]
|
|
cursor += controls * RULES["row"] + RULES["padding"]
|
|
for socket in inputs:
|
|
rows = 1
|
|
if not socket.is_linked and not socket.hide_value:
|
|
if socket.type in {"VECTOR", "ROTATION"}:
|
|
rows = 4
|
|
elif socket.type == "MATRIX":
|
|
rows = 5
|
|
extra = max(0, len(socket.links) - 1) * 8 if socket.is_multi_input else 0
|
|
sockets[socket] = cursor + RULES["row"] / 2 + extra / 2
|
|
cursor += rows * RULES["row"] + extra
|
|
return NodeDimensions(width, cursor + RULES["padding"], sockets)
|