Files
AnyImage/src/anyimage/operators/convert_to_panorama/object.py
nan 31074bd511 Add tests and implement boundary smoothing and cutout functionality
- Introduced tests for boundary smoothing in `test_boundary_smoothing.py`, verifying that smoothing reduces stair-step artifacts while preserving boundary integrity.
- Added tests for cutout boundary behavior in `test_cutout_boundary.py`, ensuring proper handling of boundary triangles and normal blending.
- Created `test_temporary_attributes.py` to check the cleanup of temporary attributes across depth modifier branches, ensuring no warnings are raised.
- Implemented point culling functionality in `test_validity.py`, confirming that invalid points are removed without affecting adjacent geometry.
- Developed boundary smoothing logic in `boundary_smoothing.py`, allowing for depth smoothing while maintaining edge integrity.
- Added cutout boundary management in `cutout_boundary.py`, including functions for removing boundary triangles and tapering thickness profiles.
- Implemented validity checks in `validity.py` to ensure proper deletion of invalid vertices and associated wire edges.
2026-09-11 20:37:20 +08:00

72 lines
3.3 KiB
Python

"""Apply packed panorama artifacts around the source Empty's camera origin."""
import json
import math
from pathlib import Path
import bpy
from ...common.depth import load_depth_result_image
from ...common.image import require_conversion_source
from ...common.color_image import material_color_image
from ...common.material import create_image_material
from ...common.node import load_node_group
from ...common.object import finalize_object_result, modifier_input_identifier, set_modifier_input
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)
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}
def create_panorama_from_result(context, result, operator):
source = require_conversion_source(operator)
depth = material = mesh = obj = None
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)
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")
mesh = bpy.data.meshes.new(source.name)
mesh.from_pydata([(0, 0, 0)], [], [])
mesh.materials.append(material)
obj = bpy.data.objects.new(source.name, mesh)
context.collection.objects.link(obj)
obj.location = source.matrix_world.translation
obj.scale = source.matrix_world.to_scale()
obj["anyimage_mesh_shape"] = "PANORAMA"
modifier = obj.modifiers.new(group.name, "NODES")
modifier.node_group = group
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)
except Exception:
if obj is not None:
bpy.data.objects.remove(obj, do_unlink=True)
if mesh is not None and mesh.users == 0:
bpy.data.meshes.remove(mesh)
if material is not None and material.users == 0:
bpy.data.materials.remove(material, do_unlink=True)
if depth is not None and depth.users == 0:
bpy.data.images.remove(depth, do_unlink=True)
raise
if "FINISHED" not in bpy.ops.ed.undo_push(message="Convert to Panorama"):
raise RuntimeError("Unable to create the Convert to Panorama Undo step")
return obj