Implement arrangement framework for node groups

- Introduced `frames.py` to manage field complex calculations and their arrangement within node groups.
- Added functionality to create and remove field frames based on node arrangements.
- Developed `passages.py` to find unobstructed paths for connections around calculation frames.
- Implemented `references.py` to route shared results on peripheral tracks, ensuring efficient distribution of outputs.
- Created `sections.py` to reserve space for field calculations adjacent to geometry consumers.
- Added `sources.py` to position shared calculation sources effectively beside their distribution channels.
- Enhanced outline smoothing with `outline_smoothing.py` to adapt camera depth along original outlines.
This commit is contained in:
2026-09-12 12:19:07 +08:00
parent dd32ccdc44
commit 5b2dc59f79
57 changed files with 2253 additions and 700 deletions

View File

@@ -1,7 +1,6 @@
"""Apply packed panorama artifacts around the source Empty's camera origin."""
import json
import math
from pathlib import Path
import bpy
@@ -18,15 +17,13 @@ def load_panorama_metadata(path):
metadata = json.loads(Path(path).read_text(encoding="utf-8"))
try:
size = tuple(int(value) for value in metadata["image_size"])
distance = float(metadata["reference_distance"])
valid = (metadata["projection"] == "equirectangular" and len(size) == 2
and size[1] > 0 and size[0] == 2 * size[1]
and math.isfinite(distance) and distance > 0)
and size[1] > 0 and size[0] == 2 * size[1])
except (KeyError, TypeError, ValueError) as error:
raise ValueError("Panorama metadata is invalid") from error
if not valid:
raise ValueError("Panorama metadata is invalid")
return {"image_size": size, "reference_distance": distance}
return {"image_size": size}
def create_panorama_from_result(context, result, operator):
@@ -35,7 +32,7 @@ def create_panorama_from_result(context, result, operator):
try:
with material_color_image(input_path=operator.color_path) as color:
metadata = load_panorama_metadata(result.file("depth_metadata"))
group = load_node_group("O Image Depth Panorama", asset_revision=14)
group = load_node_group("O Image Depth Panorama", asset_revision=15)
depth = load_depth_result_image(result.file("depth"), source, metadata)
material = create_image_material(source.data, color, shadeless=True, scene=context.scene,
texture_extension="REPEAT")
@@ -52,7 +49,6 @@ def create_panorama_from_result(context, result, operator):
modifier.show_group_selector = False
for name, value in (
("Subdivide", operator.mesh_detail), ("Depth Image", depth),
("Reference Distance", metadata["reference_distance"]),
):
set_modifier_input(modifier, modifier_input_identifier(group, name), value)
finalize_object_result(context, source, obj)

View File

@@ -62,7 +62,7 @@ def create_depth_plane_object(
node_group = load_node_group({
"DEPTH": "O Image Depth Plane",
"RELIEF": "O Image Relief Plane",
}[plane_type], asset_revision=14 if plane_type == "DEPTH" else None)
}[plane_type], asset_revision=16 if plane_type == "DEPTH" else None)
source_name = source_object.name
bounds = image_empty_bounds(source_object)
mesh = create_image_plane_mesh(source_name, bounds)

View File

@@ -21,11 +21,11 @@ class SelectionContours:
)
image_size = tuple(int(value) for value in self.image_size)
if not components or any(not component for component in components):
raise ValueError("The Selection has no usable contour components")
raise ValueError("The Selection has no usable outline components")
if any(len(contour) < 3 for component in components for contour in component):
raise ValueError("A Selection contour has fewer than three points")
raise ValueError("A Selection outline has fewer than three points")
if len(image_size) != 2 or min(image_size) <= 0:
raise ValueError("The Selection contour image size is invalid")
raise ValueError("The Selection outline image size is invalid")
object.__setattr__(self, "components", components)
object.__setattr__(self, "image_size", image_size)

View File

@@ -99,7 +99,7 @@ def _assign_float_point_attribute(mesh, name, values):
def load_cutout_node_group(shape):
return load_node_group(CUTOUT_NODE_GROUP_NAMES[shape], asset_revision=18 if shape == "DEPTH_SOLID" else 11)
return load_node_group(CUTOUT_NODE_GROUP_NAMES[shape], asset_revision=23 if shape == "DEPTH_SOLID" else 12 if shape == "DEPTH_BALLOON" else 11)
def _connect_cutout_normal_scale(material):