"""Frame complex field calculations by their consuming inputs.""" from .flow import origin_socket FRAME_PADDING = 32 FRAME_HEADING = 48 FRAME_MIN_NODES = 12 def remove_field_frames(node_group): """Detach generated containers before calculating absolute positions again.""" frames = {node for node in node_group.nodes if node.get("arrangement_frame")} for node in node_group.nodes: if node.parent in frames: position = tuple(node.location_absolute) node.parent = None node.location = position for frame in frames: node_group.nodes.remove(frame) def create_field_frames(node_group, bodies, core): """Keep a complex calculation and its internal wire passages in one frame.""" for members, (left, right, top, bottom) in bodies: if sum(node.bl_idname not in {"NodeGroupInput", "NodeReroute"} for node in members) < FRAME_MIN_NODES: continue members = set(members) outlets = [link for node in members for socket in node.outputs for link in socket.links if link.to_node not in members] targets = [] pending = list(outlets) visited = set() while pending: link = pending.pop() if link.to_node in core: targets.append(link) elif link.to_node not in visited: visited.add(link.to_node) pending.extend(edge for socket in link.to_node.outputs for edge in socket.links) target = min(targets, key=lambda link: (link.to_node.location_absolute.x, link.to_node.name, link.to_socket.name), default=None) pending = {node for node in node_group.nodes if node.bl_idname == "NodeReroute" and node.get("arrangement_branch") and origin_socket(node.outputs[0]).node in members} while ready := {node for node in pending if all(link.to_node in members for link in node.outputs[0].links)}: members.update(ready) pending.difference_update(ready) frame = node_group.nodes.new("NodeFrame") frame["arrangement_frame"] = True frame.name = f"{target.to_node.name} ยท {target.to_socket.name}" if target else "Field Calculation" frame.label = frame.name frame.shrink = False frame.location = (left - FRAME_PADDING, top + FRAME_HEADING) frame.width = right - left + FRAME_PADDING * 2 frame.height = top - bottom + FRAME_HEADING + FRAME_PADDING frame.use_custom_color = True frame.color = (0.12, 0.16, 0.18) for node in members: node.parent = frame