- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements. - Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values. - Updated specifications to define the behavior of boundary-specific smoothing strength and default settings. - Created tasks to verify the implementation of boundary behavior and asset updates. - Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance. - Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials. - Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
66 lines
2.7 KiB
Python
66 lines
2.7 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 == "NodeFrame":
|
|
return NodeDimensions(node.width, node.height, {})
|
|
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 in {"GeometryNodeGroup", "ShaderNodeGroup", "ShaderNodeFloatCurve"}
|
|
) 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 in {"GeometryNodeGroup", "ShaderNodeGroup"} 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)
|