This commit is contained in:
80
tools/nodes/arrangement/branches.py
Normal file
80
tools/nodes/arrangement/branches.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Identify connected calculations by their nearest geometry or shader consumers."""
|
||||
|
||||
from collections import defaultdict
|
||||
from graphlib import TopologicalSorter
|
||||
|
||||
|
||||
def calculation_blocks(nodes, edges):
|
||||
"""Group fields with equal consumers, then attach their local interface reads."""
|
||||
ids = {node: i for i, node in enumerate(nodes)}
|
||||
parents = {i: set() for i in range(len(nodes))}
|
||||
children = {i: set() for i in parents}
|
||||
for edge in edges:
|
||||
parents[ids[edge.to_node]].add(ids[edge.from_node])
|
||||
children[ids[edge.from_node]].add(ids[edge.to_node])
|
||||
order = list(TopologicalSorter(parents).static_order())
|
||||
core = {
|
||||
i
|
||||
for i, n in enumerate(nodes)
|
||||
if n.bl_idname == "NodeGroupOutput"
|
||||
or n.bl_idname in {"ShaderNodeNormalMap", "ShaderNodeBump", "ShaderNodeDisplacement"}
|
||||
or any(s.type in {"GEOMETRY", "SHADER"} and s.is_linked for s in n.outputs)
|
||||
or getattr(n, "paired_output", None) is not None
|
||||
}
|
||||
inputs = {i for i, n in enumerate(nodes) if n.bl_idname == "NodeGroupInput"}
|
||||
core -= inputs
|
||||
fields = set(range(len(nodes))) - core - inputs
|
||||
destinations = {}
|
||||
for i in reversed(order):
|
||||
destinations[i] = frozenset().union(
|
||||
*(frozenset([j]) if j in core else destinations[j] for j in children[i])
|
||||
)
|
||||
groups = defaultdict(set)
|
||||
for i in fields:
|
||||
groups[destinations[i]].add(i)
|
||||
blocks = []
|
||||
for targets, members in groups.items():
|
||||
pending = set(members)
|
||||
while pending:
|
||||
component = set()
|
||||
queue = [min(pending)]
|
||||
while queue:
|
||||
i = queue.pop()
|
||||
if i not in pending:
|
||||
continue
|
||||
pending.remove(i)
|
||||
component.add(i)
|
||||
queue.extend((parents[i] | children[i]) & pending)
|
||||
target = min(targets) if targets else None
|
||||
ports = list(
|
||||
dict.fromkeys(
|
||||
e.to_socket.name
|
||||
for e in edges
|
||||
if ids[e.from_node] in component and ids[e.to_node] == target
|
||||
)
|
||||
)
|
||||
if ports:
|
||||
title = nodes[target].name + " · " + ", ".join(ports)
|
||||
else:
|
||||
outlets = [i for i in component if children[i] - component]
|
||||
source = (
|
||||
max(outlets, key=lambda i: (len(children[i] - component), i))
|
||||
if outlets
|
||||
else max(component)
|
||||
)
|
||||
title = "共享 · " + nodes[source].name + " → " + str(len(targets)) + " 处计算"
|
||||
blocks.append(dict(title=title, members=sorted(component), targets=sorted(targets)))
|
||||
block_of = {i: k for k, b in enumerate(blocks) for i in b["members"]}
|
||||
# Keep one local original read; additional consumers receive local copies later.
|
||||
for i in sorted(inputs):
|
||||
owners = [block_of[j] for j in sorted(children[i]) if j in block_of]
|
||||
if any(j in core for j in children[i]):
|
||||
core.add(i)
|
||||
elif owners:
|
||||
k = owners[0]
|
||||
blocks[k]["members"].append(i)
|
||||
block_of[i] = k
|
||||
else:
|
||||
core.add(i)
|
||||
|
||||
return blocks, core
|
||||
Reference in New Issue
Block a user