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:
@@ -8,6 +8,8 @@ from pathlib import Path
|
||||
|
||||
import bpy
|
||||
|
||||
from ..server.media.input import validate_input_path
|
||||
|
||||
VIEW_TRANSFORM_COLOR_SPACES = (
|
||||
("ACES 1.3", "ACES 1.3 sRGB"),
|
||||
("ACES 2.0", "ACES 2.0 sRGB"),
|
||||
@@ -333,11 +335,6 @@ def extract_frame_range(image, image_user):
|
||||
return start_frame, frame_count
|
||||
|
||||
|
||||
def source_start_frame(source_object_name):
|
||||
source_object = bpy.data.objects.get(source_object_name)
|
||||
return extract_frame_range(source_object.data, source_object.image_user)[0] if source_object else 1
|
||||
|
||||
|
||||
def image_path(image):
|
||||
filepath_from_user = getattr(image, "filepath_from_user", None)
|
||||
filepath = (
|
||||
@@ -395,22 +392,19 @@ def normal_data_name(source_object):
|
||||
return f"{image_base_name(source_object.data)}_normal.png"
|
||||
|
||||
|
||||
def color_data_name(source_object):
|
||||
return f"{image_base_name(source_object.data)}_color.png"
|
||||
|
||||
|
||||
def save_packed_image(image, _scene):
|
||||
output_path = Path(tempfile.mkdtemp(prefix="anyimage-input-")) / "input.png"
|
||||
original_file_format = image.file_format
|
||||
try:
|
||||
image.file_format = "PNG"
|
||||
image.save(filepath=str(output_path), save_copy=True)
|
||||
except (OSError, RuntimeError) as error:
|
||||
output_path.unlink(missing_ok=True)
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
cleanup_image_input(output_path, True)
|
||||
raise RuntimeError("Unable to export the packed image") from error
|
||||
finally:
|
||||
image.file_format = original_file_format
|
||||
if not output_path.is_file():
|
||||
cleanup_image_input(output_path, True)
|
||||
raise RuntimeError("Blender did not export the packed image")
|
||||
return output_path
|
||||
|
||||
@@ -450,9 +444,11 @@ def prepare_image_input(image, scene):
|
||||
if getattr(image, "source", None) == "MOVIE":
|
||||
if not source_path.is_file():
|
||||
raise RuntimeError(
|
||||
"The image empty must reference an existing video file"
|
||||
f"Source video file does not exist: {source_path}"
|
||||
)
|
||||
return source_path, False
|
||||
if getattr(image, "source", None) == "SEQUENCE":
|
||||
validate_input_path(source_path, images_only=True)
|
||||
sequence_frames = collect_sequence_frames(image)
|
||||
if sequence_frames:
|
||||
temporary_directory = Path(tempfile.mkdtemp(prefix="anyimage-input-"))
|
||||
@@ -469,73 +465,11 @@ def prepare_image_input(image, scene):
|
||||
return temporary_directory, True
|
||||
if not source_path.is_file():
|
||||
raise RuntimeError(
|
||||
"The image empty must reference an existing image file"
|
||||
f"Source image file does not exist: {source_path}"
|
||||
)
|
||||
return source_path, False
|
||||
|
||||
|
||||
def prepare_image_bounds_input(image, source_rgba, bounds):
|
||||
"""Write one rectangular RGBA input owned by its temporary directory."""
|
||||
import numpy as np
|
||||
|
||||
left, top, right, bottom = (int(value) for value in bounds)
|
||||
rgba = np.asarray(source_rgba, dtype=np.float32)
|
||||
expected_shape = (int(image.size[1]), int(image.size[0]), 4)
|
||||
if rgba.shape != expected_shape:
|
||||
raise ValueError("The source RGBA does not match the Blender Image")
|
||||
if (
|
||||
left < 0
|
||||
or top < 0
|
||||
or right > expected_shape[1]
|
||||
or bottom > expected_shape[0]
|
||||
):
|
||||
raise ValueError("The image bounds are outside the Blender Image")
|
||||
if right <= left or bottom <= top:
|
||||
raise ValueError("The image bounds are empty")
|
||||
cropped = rgba[top:bottom, left:right]
|
||||
output_directory = Path(tempfile.mkdtemp(prefix="anyimage-bounds-"))
|
||||
output_path = output_directory / "input.png"
|
||||
temporary_image = bpy.data.images.new(
|
||||
"AnyImage Bounds Input",
|
||||
width=right - left,
|
||||
height=bottom - top,
|
||||
alpha=True,
|
||||
)
|
||||
original_format = temporary_image.file_format
|
||||
try:
|
||||
temporary_image.colorspace_settings.name = image.colorspace_settings.name
|
||||
temporary_image.alpha_mode = "STRAIGHT"
|
||||
temporary_image.pixels.foreach_set(np.flipud(cropped).ravel())
|
||||
temporary_image.file_format = "PNG"
|
||||
temporary_image.save(filepath=str(output_path), save_copy=True)
|
||||
except Exception:
|
||||
cleanup_image_input(output_path, True)
|
||||
raise
|
||||
finally:
|
||||
temporary_image.file_format = original_format
|
||||
bpy.data.images.remove(temporary_image, do_unlink=True)
|
||||
if not output_path.is_file():
|
||||
cleanup_image_input(output_path, True)
|
||||
raise RuntimeError("Blender did not export the bounds image input")
|
||||
return output_path
|
||||
|
||||
|
||||
def load_packed_rgba_image(path, source_image, name):
|
||||
"""Load an encoded local RGBA file and make its Blender data self-contained."""
|
||||
result_image = None
|
||||
try:
|
||||
result_image = bpy.data.images.load(str(path), check_existing=False)
|
||||
result_image.colorspace_settings.name = source_image.colorspace_settings.name
|
||||
result_image.alpha_mode = source_image.alpha_mode
|
||||
result_image.name = name
|
||||
result_image.pack()
|
||||
except (OSError, RuntimeError, TypeError, ValueError) as error:
|
||||
if result_image is not None:
|
||||
bpy.data.images.remove(result_image, do_unlink=True)
|
||||
raise RuntimeError("Unable to prepare the RGBA image result") from error
|
||||
return result_image
|
||||
|
||||
|
||||
def configure_image_user(
|
||||
image_user,
|
||||
frame_count=None,
|
||||
@@ -674,6 +608,15 @@ def has_other_image_empty(source_object, *, removed_objects=()):
|
||||
)
|
||||
|
||||
|
||||
def require_conversion_source(operator):
|
||||
"""Validate the object and image captured when a conversion was submitted."""
|
||||
source = require_image_empty(operator.source_object_name)
|
||||
if (str(source.as_pointer()) != operator.source_identity
|
||||
or str(source.data.as_pointer()) != operator.image_identity):
|
||||
raise ValueError("The conversion source image has changed")
|
||||
return source
|
||||
|
||||
|
||||
def image_content_state(image):
|
||||
"""Capture encoded content and unsaved pixels for an Image transaction."""
|
||||
import numpy as np
|
||||
@@ -728,7 +671,7 @@ def restore_image_content(image, state):
|
||||
|
||||
|
||||
def replace_empty_image(source_object, result_image, *, placement_bounds=None,
|
||||
removed_objects=()):
|
||||
removed_objects=(), isolate_image=False):
|
||||
"""Replace one Empty image, retaining shared source images."""
|
||||
try:
|
||||
source_image = source_object.data
|
||||
@@ -767,7 +710,7 @@ def replace_empty_image(source_object, result_image, *, placement_bounds=None,
|
||||
)
|
||||
original_content = None
|
||||
try:
|
||||
shared = has_other_image_empty(source_object, removed_objects=removed_objects)
|
||||
shared = isolate_image or has_other_image_empty(source_object, removed_objects=removed_objects)
|
||||
if shared:
|
||||
final_image = result_image
|
||||
else:
|
||||
@@ -866,22 +809,6 @@ def load_image_edit_result(
|
||||
)
|
||||
|
||||
|
||||
def load_color_result_image(
|
||||
source_object,
|
||||
output_directory,
|
||||
*,
|
||||
frame_prefix="image.",
|
||||
):
|
||||
"""Load one packed Color texture result for a generated material."""
|
||||
return _load_packed_result_image(
|
||||
source_object.data,
|
||||
output_directory,
|
||||
frame_prefix=frame_prefix,
|
||||
result_name=color_data_name(source_object),
|
||||
alpha_mode=color_alpha_mode(source_object.data.colorspace_settings.name),
|
||||
)
|
||||
|
||||
|
||||
def image_empty_bounds(source_object):
|
||||
image_width, image_height = source_object.data.size
|
||||
if image_width <= 0 or image_height <= 0:
|
||||
|
||||
Reference in New Issue
Block a user