Add new tests and functionality for image processing and geometry handling

- Introduced color_image.py for preparing standalone color images for tests.
- Added hdr_image.py to create HDR textures for testing.
- Implemented test_image_depth_panorama.py to evaluate panorama topology, UVs, and depth discontinuities.
- Created test_shadeless.py to validate the opacity and HDR color endpoints of the Shadeless group.
- Developed moge3_export.py for exporting dynamic MoGe-3 backbone and ONNX graphs.
- Added material.py to inherit material slots onto generated geometry.
- Built image_depth_panorama.py to construct a quad panorama with depth separation and sampling.
This commit is contained in:
2026-09-10 14:42:17 +08:00
parent 27ca25224a
commit 489003c2cc
136 changed files with 5758 additions and 1083 deletions

View File

@@ -78,17 +78,25 @@ def create_image_plane_object(
thickness=0.0,
):
mesh = create_image_plane_mesh(name, bounds)
mesh.materials.append(material)
plane = bpy.data.objects.new(name, mesh)
context.collection.objects.link(plane)
if matrix_world is None:
from mathutils import Matrix
plane = None
try:
mesh.materials.append(material)
plane = bpy.data.objects.new(name, mesh)
context.collection.objects.link(plane)
if matrix_world is None:
from mathutils import Matrix
matrix_world = Matrix.Identity(4)
plane.matrix_world = build_image_plane_matrix(matrix_world, bounds)
add_image_plane_processing(
plane,
subdivisions=subdivisions,
thickness=thickness,
)
matrix_world = Matrix.Identity(4)
plane.matrix_world = build_image_plane_matrix(matrix_world, bounds)
add_image_plane_processing(
plane,
subdivisions=subdivisions,
thickness=thickness,
)
except Exception:
if plane is not None:
bpy.data.objects.remove(plane, do_unlink=True)
if mesh.users == 0:
bpy.data.meshes.remove(mesh)
raise
return plane

View File

@@ -3,13 +3,9 @@
import bpy
from ...common.image import (
color_data_name,
configure_color_alpha,
image_empty_bounds,
image_pixels,
load_color_result_image,
load_normal_result_image,
require_image_empty,
require_conversion_source,
)
from ...common.depth import (
depth_uniform_scale,
@@ -17,6 +13,7 @@ from ...common.depth import (
load_depth_result_image,
)
from ...common.material import create_image_material
from ...common.color_image import material_color_image
from ...common.object import (
finalize_object_result,
modifier_input_identifier,
@@ -30,20 +27,6 @@ from .image_plane import (
)
def create_color_image(source_object):
source = source_object.data
image = source.copy()
if getattr(source, "source", None) == "GENERATED" or getattr(
source, "is_dirty", False
):
pixels = image_pixels(source)
image.pixels.foreach_set(pixels)
image.update()
image.name = color_data_name(source_object)
configure_color_alpha(image)
return image
def create_plane_object(context, source_object, subdivisions, material):
source_name = source_object.name
plane = create_image_plane_object(
@@ -54,10 +37,17 @@ def create_plane_object(context, source_object, subdivisions, material):
matrix_world=source_object.matrix_world,
subdivisions=subdivisions,
)
for polygon in plane.data.polygons:
polygon.use_smooth = True
plane["anyimage_mesh_shape"] = "PLANE"
return finalize_object_result(context, source_object, plane)
try:
for polygon in plane.data.polygons:
polygon.use_smooth = True
plane["anyimage_mesh_shape"] = "PLANE"
return finalize_object_result(context, source_object, plane)
except Exception:
mesh = plane.data
bpy.data.objects.remove(plane, do_unlink=True)
if mesh.users == 0:
bpy.data.meshes.remove(mesh)
raise
def create_depth_plane_object(
@@ -102,54 +92,43 @@ def create_depth_plane_object(
modifier_input_identifier(node_group, name),
value,
)
return finalize_object_result(context, source_object, plane)
except Exception:
if plane is not None:
bpy.data.objects.remove(plane, do_unlink=True)
if mesh.users == 0:
bpy.data.meshes.remove(mesh)
raise
return finalize_object_result(context, source_object, plane)
def create_depth_plane_from_result(context, result, operator):
normal_space = {"DEPTH": "OBJECT", "RELIEF": "TANGENT"}[operator.plane_type]
source_object = require_image_empty(operator.source_object_name)
color_image = load_color_result_image(source_object, result.directory)
color_image.name = color_data_name(source_object)
source_object = require_conversion_source(operator)
material = None
depth_image = None
normal_image = None
try:
depth_metadata = load_depth_metadata(result.file("depth_metadata"))
depth_image = load_depth_result_image(
result.file("depth"),
source_object,
depth_metadata,
)
normal_image = load_normal_result_image(
result.file(f"{normal_space.lower()}_normal"),
source_object,
)
material = create_image_material(
source_object.data,
color_image,
normal_image=normal_image,
normal_space=normal_space,
scene=context.scene,
)
create_depth_plane_object(
context,
source_object,
operator.mesh_detail,
material,
depth_image,
depth_metadata,
operator.plane_type,
)
with material_color_image(input_path=operator.color_path) as color_image:
depth_metadata = load_depth_metadata(result.file("depth_metadata"))
depth_image = load_depth_result_image(
result.file("depth"), source_object, depth_metadata,
)
normal_image = load_normal_result_image(
result.file(f"{normal_space.lower()}_normal"), source_object,
)
material = create_image_material(
source_object.data, color_image,
normal_image=normal_image, normal_space=normal_space,
scene=context.scene,
)
create_depth_plane_object(
context, source_object, operator.mesh_detail, material,
depth_image, depth_metadata, operator.plane_type,
)
except Exception:
if material is not None and material.users == 0:
bpy.data.materials.remove(material, do_unlink=True)
for image in (color_image, depth_image, normal_image):
for image in (depth_image, normal_image):
if image is not None and image.users == 0:
bpy.data.images.remove(image, do_unlink=True)
raise

View File

@@ -15,18 +15,18 @@ from ...common.ai import (
require_environment,
require_input_path,
require_model,
report_ai_error,
)
from ...common.image import (
cleanup_image_input,
image_user_settings,
is_image_empty,
prepare_image_input,
source_start_frame,
video_total_frames,
)
from ...common.image import is_image_empty
from ...common.material import create_image_material
from ...common.color_image import (
cleanup_material_color_input,
material_analysis_input,
material_color_image,
prepare_material_color_input,
require_static_color_image,
)
from .object import (
create_color_image,
create_depth_plane_from_result,
create_plane_object,
)
@@ -49,7 +49,10 @@ class GenerateDepthPlane(JobOperatorBase, bpy.types.Operator):
starting_message = "Preparing depth conversion"
input_path: bpy.props.StringProperty(options=_HIDDEN)
color_path: bpy.props.StringProperty(options=_HIDDEN)
source_object_name: bpy.props.StringProperty(options=_HIDDEN)
source_identity: bpy.props.StringProperty(options=_HIDDEN)
image_identity: bpy.props.StringProperty(options=_HIDDEN)
plane_type: bpy.props.EnumProperty(
items=(("DEPTH", "Depth Plane", ""), ("RELIEF", "Relief Plane", "")),
default="DEPTH",
@@ -64,7 +67,6 @@ class GenerateDepthPlane(JobOperatorBase, bpy.types.Operator):
max=MAX_MAX_AI_INPUT_SIZE,
options=_HIDDEN,
)
delete_input: bpy.props.BoolProperty(default=False, options=_HIDDEN)
def request(self, context):
require_environment()
@@ -74,7 +76,7 @@ class GenerateDepthPlane(JobOperatorBase, bpy.types.Operator):
"input": str(input_path),
"device": production_device().lower(),
"video_frames": 1,
"start_frame": source_start_frame(self.source_object_name),
"start_frame": 1,
"max_input_size": int(self.max_input_size),
"plane_type": self.plane_type,
}
@@ -84,7 +86,7 @@ class GenerateDepthPlane(JobOperatorBase, bpy.types.Operator):
input_path,
self.model,
self.resolution_level,
source_start_frame(self.source_object_name),
1,
)
)
return parameters
@@ -94,38 +96,42 @@ class GenerateDepthPlane(JobOperatorBase, bpy.types.Operator):
return f"{self.plane_type.title()} Plane is ready"
def cleanup(self):
cleanup_image_input(self.input_path, self.delete_input)
cleanup_material_color_input(self.color_path)
def start_depth_plane_job(operator, context, plane_type):
source_object = context.object
if video_total_frames(source_object.data) != 1:
operator.report({"ERROR"}, "Depth conversion currently supports still images")
return {"CANCELLED"}
setup_result = invoke_ai_setup_if_needed()
if setup_result is not None:
return setup_result
try:
source_path, delete_input = prepare_image_input(
source_object.data,
context.scene,
)
model, resolution_level = depth_plane_settings(context)
except (RuntimeError, ValueError) as error:
require_static_color_image(source_object.data)
except ValueError as error:
operator.report({"ERROR"}, str(error))
return {"CANCELLED"}
result = bpy.ops.anyimage.generate_depth_plane(
input_path=str(source_path),
source_object_name=source_object.name,
mesh_detail=operator.mesh_detail,
plane_type=plane_type,
model=model,
resolution_level=resolution_level,
max_input_size=configured_max_ai_input_size(context),
delete_input=delete_input,
)
if "CANCELLED" in result and delete_input:
cleanup_image_input(source_path, True)
setup_result = invoke_ai_setup_if_needed(operator)
if setup_result is not None:
return setup_result
color_path = None
try:
color_path = prepare_material_color_input(source_object.data)
source_path = material_analysis_input(color_path)
model, resolution_level = depth_plane_settings(context)
result = bpy.ops.anyimage.generate_depth_plane(
input_path=str(source_path),
color_path=str(color_path),
source_object_name=source_object.name,
source_identity=str(source_object.as_pointer()),
image_identity=str(source_object.data.as_pointer()),
mesh_detail=operator.mesh_detail,
plane_type=plane_type,
model=model,
resolution_level=resolution_level,
max_input_size=configured_max_ai_input_size(context),
)
except (OSError, RuntimeError, ValueError) as error:
cleanup_material_color_input(color_path)
report_ai_error(operator, error)
return {"CANCELLED"}
if "CANCELLED" in result:
cleanup_material_color_input(color_path)
return {"CANCELLED"} if "CANCELLED" in result else {"FINISHED"}
@@ -143,20 +149,26 @@ class ConvertToPlane(bpy.types.Operator):
def execute(self, context):
source_object = context.object
color_image = create_color_image(source_object)
frame_start, frame_offset, frame_duration, use_cyclic = image_user_settings(
source_object
)
material = create_image_material(
source_object.data,
color_image,
frame_start=frame_start,
color_frame_offset=frame_offset,
color_frame_duration=frame_duration,
use_cyclic=use_cyclic,
scene=context.scene,
)
create_plane_object(context, source_object, self.mesh_detail, material)
try:
require_static_color_image(source_object.data)
except ValueError as error:
self.report({"ERROR"}, str(error))
return {"CANCELLED"}
material = None
try:
with material_color_image(source_object.data) as color_image:
material = create_image_material(
source_object.data, color_image,
scene=context.scene,
)
create_plane_object(context, source_object, self.mesh_detail, material)
except Exception as error:
if material is not None and material.users == 0:
bpy.data.materials.remove(material, do_unlink=True)
if isinstance(error, ValueError):
self.report({"ERROR"}, str(error))
return {"CANCELLED"}
raise
return {"FINISHED"}