- 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.
131 lines
5.6 KiB
Python
131 lines
5.6 KiB
Python
"""Materialize the joint calculation and connection plan in node trees."""
|
|
|
|
from .branches import calculation_blocks
|
|
from .dimensions import estimate_node_dimensions, visible_socket
|
|
from .flow import origin_socket, socket_links
|
|
from .layout import plan_layout
|
|
from ..common.nodes import prepare_node_group, replace_link_source
|
|
|
|
|
|
def _remove_routes(node_group):
|
|
previous = [node for node in node_group.nodes if node.bl_idname == "NodeReroute"]
|
|
|
|
for node in [node for node in node_group.nodes if node not in previous]:
|
|
for socket in node.inputs:
|
|
links = socket_links(socket)
|
|
if not any(link.from_node in previous for link in links):
|
|
continue
|
|
if not socket.is_multi_input:
|
|
replace_link_source(node_group, links[0], origin_socket(links[0].from_socket))
|
|
continue
|
|
sources = [origin_socket(link.from_socket) for link in links]
|
|
for link in links:
|
|
node_group.links.remove(link)
|
|
seen = set()
|
|
for source in reversed(sources):
|
|
original = source
|
|
if source in seen:
|
|
# Blender coalesces identical direct links; retain repeated inputs.
|
|
duplicate = node_group.nodes.new("NodeReroute")
|
|
node_group.links.new(source, duplicate.inputs[0])
|
|
source = duplicate.outputs[0]
|
|
seen.add(original)
|
|
node_group.links.new(source, socket)
|
|
for node in previous:
|
|
node_group.nodes.remove(node)
|
|
|
|
|
|
def _localize_inputs(node_group, nodes, blocks, core):
|
|
"""Read interface values locally in each consuming calculation body."""
|
|
ids = {node: i for i, node in enumerate(nodes)}
|
|
block_of = {i: k for k, block in enumerate(blocks) for i in block["members"]}
|
|
for node in list(nodes):
|
|
if node.bl_idname != "NodeGroupInput":
|
|
continue
|
|
original = ("core", ids[node]) if ids[node] in core else ("block", block_of[ids[node]])
|
|
uses = list(node_group.links)
|
|
direct = [ids[edge.to_node] for edge in uses if edge.from_node == node and ids[edge.to_node] in core]
|
|
if direct:
|
|
original = ("core", direct[0])
|
|
local_reads = {original: node}
|
|
for edge in uses:
|
|
if edge.from_node != node:
|
|
continue
|
|
target = ids[edge.to_node]
|
|
owner = ("block", block_of[target]) if target in block_of else ("core", target)
|
|
if owner not in local_reads:
|
|
clone = node_group.nodes.new("NodeGroupInput")
|
|
ids[clone] = len(nodes)
|
|
nodes.append(clone)
|
|
if owner[0] == "block":
|
|
blocks[owner[1]]["members"].append(ids[clone])
|
|
block_of[ids[clone]] = owner[1]
|
|
else:
|
|
core.add(ids[clone])
|
|
local_reads[owner] = clone
|
|
source = next(
|
|
socket
|
|
for socket in local_reads[owner].outputs
|
|
if socket.identifier == edge.from_socket.identifier
|
|
)
|
|
if source != edge.from_socket:
|
|
replace_link_source(node_group, edge, source)
|
|
|
|
|
|
def arrange_nodes(node_group):
|
|
"""Arrange calculation bodies and shared channels before creating reroutes."""
|
|
for frame in [node for node in node_group.nodes if node.bl_idname == "NodeFrame"]:
|
|
for child in [node for node in node_group.nodes if node.parent == frame]:
|
|
position = tuple(child.location_absolute)
|
|
child.parent = None
|
|
child.location = position
|
|
node_group.nodes.remove(frame)
|
|
_remove_routes(node_group)
|
|
nodes = list(node_group.nodes)
|
|
if not nodes:
|
|
return
|
|
blocks, core = calculation_blocks(nodes, list(node_group.links))
|
|
_localize_inputs(node_group, nodes, blocks, core)
|
|
prepare_node_group(node_group)
|
|
for node in nodes:
|
|
node.select = False
|
|
if node.bl_idname in {"ShaderNodeMath", "ShaderNodeVectorMath", "FunctionNodeIntegerMath"}:
|
|
node.hide = all(socket.is_linked for socket in node.inputs if visible_socket(socket))
|
|
sizes = {i: estimate_node_dimensions(node) for i, node in enumerate(nodes)}
|
|
edges = list(node_group.links)
|
|
positions, routes = plan_layout(nodes, edges, sizes, blocks, core)
|
|
for i, (x, y) in positions.items():
|
|
nodes[i].location = (x, -y)
|
|
nodes[i].width = sizes[i].width
|
|
cache = {}
|
|
for index, path in routes.items():
|
|
edge = edges[index]
|
|
source = edge.from_socket
|
|
for x, y in path[1:-1]:
|
|
key = (source, round(x, 4), round(y, 4))
|
|
reroute = cache.get(key)
|
|
if reroute is None:
|
|
reroute = node_group.nodes.new("NodeReroute")
|
|
reroute.location = (x - 8, -y + 8)
|
|
node_group.links.new(source, reroute.inputs[0])
|
|
cache[key] = reroute
|
|
source = reroute.outputs[0]
|
|
if len(path) > 2:
|
|
replace_link_source(node_group, edge, source)
|
|
for block in blocks:
|
|
if len(block["members"]) < 5:
|
|
continue
|
|
left, top, right, bottom = block["box"]
|
|
frame = node_group.nodes.new("NodeFrame")
|
|
frame["arrangement_frame"] = True
|
|
frame.name = frame.label = block["title"]
|
|
frame.shrink = False
|
|
frame.location = (left - 28, -top + 48)
|
|
frame.width = right - left + 56
|
|
frame.height = bottom - top + 76
|
|
for i in block["members"]:
|
|
absolute = nodes[i].location.copy()
|
|
nodes[i].parent = frame
|
|
nodes[i].location = absolute - frame.location
|
|
prepare_node_group(node_group)
|