Refactor geometry node arrangement system

- Removed obsolete files: sections.py, semantics.json, and sources.py.
- Introduced new modules: branches.py and layout.py for improved calculation block identification and layout planning.
- Implemented a unified layout strategy for geometry nodes, ensuring consistent spacing and organization.
- Added comprehensive documentation for the new layout design and its goals.
- Established a migration plan for transitioning existing assets to the new layout system.
- Updated tests to validate the new arrangement logic and ensure compatibility with existing geometry node structures.
This commit is contained in:
2026-09-13 23:42:12 +08:00
parent 1bf858b17e
commit 09778c4b48
19 changed files with 727 additions and 1637 deletions

View File

@@ -1,13 +1,8 @@
"""Infer geometry flow and socket relationships for layout and preview."""
import json
from collections import defaultdict
from graphlib import TopologicalSorter
from pathlib import Path
from .dimensions import visible_socket
SEMANTICS = json.loads(Path(__file__).with_name("semantics.json").read_text(encoding="utf-8"))
SHARED_GEOMETRY_USES = 3
@@ -15,39 +10,12 @@ def socket_links(socket):
return sorted(socket.links, key=lambda link: link.multi_input_sort_id, reverse=True)
def origin_socket(socket):
while socket.node.bl_idname == "NodeReroute":
socket = socket.node.inputs[0].links[0].from_socket
return socket
def graph(node_group):
def key(node):
if node.bl_idname in {"NodeGroupInput", "NodeReroute"}:
return (1, tuple(sorted((link.to_node.name, link.to_socket.identifier,
socket_links(link.to_socket).index(link))
for socket in node.outputs for link in socket.links)))
return (0, node.name)
nodes = sorted(node_group.nodes, key=key)
parents = {node: [] for node in nodes}
children = {node: set() for node in nodes}
for link in node_group.links:
if link.from_node not in parents[link.to_node]:
parents[link.to_node].append(link.from_node)
children[link.from_node].add(link.to_node)
for sources in parents.values():
sources.sort(key=key)
order = list(TopologicalSorter(parents).static_order())
ancestors = {}
for node in order:
ancestors[node] = set(parents[node]) | set().union(*(ancestors[p] for p in parents[node]))
return parents, children, order, ancestors
def geometry_paths(node_group):
"""Trace every geometry input and split paths at forks and merges."""
output = next((node for node in node_group.nodes if node.bl_idname == "NodeGroupOutput" and node.is_active_output), None)
@@ -114,37 +82,6 @@ def geometry_paths(node_group):
return parents, paths
def shared_geometry_outputs(nodes):
"""Find geometry outputs reused by several distinct operations."""
shared = []
for node in nodes:
for socket in node.outputs:
if socket.type != "GEOMETRY":
continue
targets, pending = set(), [link.to_node for link in socket.links]
while pending:
target = pending.pop()
if target.bl_idname == "NodeReroute":
pending.extend(link.to_node for link in target.outputs[0].links)
else:
targets.add(target)
if len(targets) >= SHARED_GEOMETRY_USES:
shared.append(socket)
return shared
def peer_links(node):
"""Read ordered data operands from the semantic table, excluding controls."""
rule = SEMANTICS.get(node.bl_idname)
if rule is None or ("operations" in rule and node.operation not in rule["operations"]):
return []
return [link for socket in list(node.inputs)[rule["peer_inputs_from"]:]
if visible_socket(socket) and socket.type != "CUSTOM"
for link in socket_links(socket)]
def node_roles(node_group):
"""Classify by contribution to the result, not by available socket types."""
core, _ = geometry_paths(node_group)