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

@@ -31,7 +31,7 @@ def test_depth_cutout_loads_boundary_blur_revision_and_preserves_existing_group(
saved = bpy.data.node_groups.new("O Image Depth Cutout", "GeometryNodeTree")
saved["anyimage_asset_revision"] = 11
current = load_cutout_node_group("DEPTH_SOLID")
assert current != saved and current["anyimage_asset_revision"] == 18
assert current != saved and current["anyimage_asset_revision"] == 23
assert saved in bpy.data.node_groups.values()
assert saved["anyimage_asset_revision"] == 11
assert load_cutout_node_group("DEPTH_SOLID") == current

View File

@@ -47,7 +47,7 @@ def test_cutout_node_groups_are_loaded_from_the_asset():
for shape in cutout_shape.CUTOUT_NODE_GROUP_NAMES:
assert cutout_object.load_cutout_node_group(shape) is loaded
load.assert_called_with(cutout_shape.CUTOUT_NODE_GROUP_NAMES[shape],
asset_revision=18 if shape == "DEPTH_SOLID" else 11)
asset_revision=23 if shape == "DEPTH_SOLID" else 12 if shape == "DEPTH_BALLOON" else 11)
assert load.call_count == 4

View File

@@ -188,7 +188,7 @@ def test_cutout_presets_load_three_direct_modifier_assets():
assert inputs["Smooth"].default_value == 0
assert inputs["Smooth Weight"].default_value == 1
if shape == "DEPTH_SOLID":
assert inputs["Boundary Smooth"].default_value == 5
assert inputs["Boundary Smooth"].default_value == 2
assert inputs["Boundary Smooth"].parent.name == "Options"
else:
assert "Boundary Smooth" not in inputs

View File

@@ -70,7 +70,7 @@ def artifacts(tmp_path, source):
rgba = np.ones((16, 32, 4), np.float32)
rgba[..., :3] = 3
write_float_exr(rgba, tmp_path / "depth.exr")
(tmp_path / "depth.json").write_text(json.dumps({"projection": "equirectangular", "image_size": [32, 16], "reference_distance": 3}))
(tmp_path / "depth.json").write_text(json.dumps({"projection": "equirectangular", "image_size": [32, 16]}))
result = SimpleNamespace(directory=tmp_path, file=lambda key: tmp_path / {"depth": "depth.exr", "depth_metadata": "depth.json"}[key])
operator = SimpleNamespace(source_object_name=source.name, source_identity=str(source.as_pointer()),
image_identity=str(source.data.as_pointer()), mesh_detail=2,
@@ -90,7 +90,7 @@ def test_result_replaces_source_with_zero_rotation_and_shadeless_material(source
monkeypatch.setattr(conversion, "bpy", SimpleNamespace(data=bpy.data, ops=SimpleNamespace(ed=SimpleNamespace(undo_push=undo))))
obj = conversion.create_panorama_from_result(bpy.context, result, operator)
assert obj.modifiers[0].node_group != saved
assert obj.modifiers[0].node_group["anyimage_asset_revision"] == 14
assert obj.modifiers[0].node_group["anyimage_asset_revision"] == 15
assert saved in bpy.data.node_groups.values() and saved["anyimage_asset_revision"] == 11
assert obj.name == operator.source_object_name and obj.type == "MESH"
np.testing.assert_allclose(obj.location, matrix.translation, atol=1e-6)
@@ -162,7 +162,7 @@ def test_failed_response_keeps_source_and_cleans_owned_data(source, artifacts, m
elif failure == "replaced_image":
operator.image_identity = "replaced"
else:
(result.directory / "depth.json").write_text(json.dumps({"projection": "equirectangular", "image_size": [16, 8], "reference_distance": 3}))
(result.directory / "depth.json").write_text(json.dumps({"projection": "equirectangular", "image_size": [16, 8]}))
with pytest.raises((RuntimeError, ValueError)):
conversion.create_panorama_from_result(bpy.context, result, operator)
for name, values in before.items():

View File

@@ -151,7 +151,7 @@ def test_converted_planes_center_the_origin_without_moving_world_corners():
assert depth_plane["anyimage_mesh_shape"] == "DEPTH_PLANE"
depth_modifier = depth_plane.modifiers[0]
assert depth_modifier.node_group != saved
assert depth_modifier.node_group["anyimage_asset_revision"] == 14
assert depth_modifier.node_group["anyimage_asset_revision"] == 16
assert saved in bpy.data.node_groups.values() and saved["anyimage_asset_revision"] == 11
uniform_scale = depth_modifier[
object_data.modifier_input_identifier(

View File

@@ -89,7 +89,7 @@ def test_job_outputs_geometry_combines_alpha_once_and_skips_transparent_views(pa
metadata = json.loads((context.directory / result["depth_metadata"]).read_text())
assert metadata["projection"] == "equirectangular"
assert metadata["image_size"] == [64, 32]
assert metadata["reference_distance"] == pytest.approx(3, rel=1e-5)
assert set(metadata) == {"projection", "image_size"}
def test_full_resolution_fusion_preserves_detail_and_periodic_alignment():