Add new functionality for image rectification and depth metadata handling

- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`.
- Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`.
- Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration.
- Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency.
- Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments.
- Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
This commit is contained in:
2026-09-12 19:20:06 +08:00
parent e8268504c2
commit 938d95db0f
175 changed files with 3812 additions and 6864 deletions

View File

@@ -15,23 +15,24 @@ from ...common.object import (
set_modifier_input,
)
from ...common.node import load_node_group
from .geometry import (
CUTOUT_ALPHA_THRESHOLD,
from .depth_calibration import (
DEFAULT_DEPTH_BASELINE,
DEFAULT_DEPTH_DIRECTION,
depth_balloon_projection_parameters,
metric_depth_reference,
use_adaptive_depth_calibration,
)
from .geometry import (
CUTOUT_ALPHA_THRESHOLD,
crop_plane_bounds,
cutout_cleanup_value,
cutout_sampling_spacing,
depth_balloon_projection_parameters,
metric_depth_reference,
selection_calibration,
use_adaptive_depth_calibration,
world_plane_metric,
)
from .shape import (
BALLOON_ATTRIBUTE_NAME,
NORMAL_SCALE_ATTRIBUTE_NAME,
CUTOUT_X_TO_Z,
CUTOUT_NODE_GROUP_NAMES,
DEPTH_CUTOUT_SHAPES,
)
@@ -43,38 +44,11 @@ class NormalMode(StrEnum):
OBJECT = "OBJECT"
class CutoutInwardAxis(StrEnum):
NEGATIVE_Z = "NEGATIVE_Z"
POSITIVE_X = "POSITIVE_X"
def _cutout_world_matrix(source_matrix, crop_center, inward_axis):
def _cutout_world_matrix(source_matrix, crop_center):
from mathutils import Matrix
center_x, center_y = (float(value) for value in crop_center)
matrix = source_matrix @ Matrix.Translation((center_x, center_y, 0.0))
if CutoutInwardAxis(inward_axis) == CutoutInwardAxis.POSITIVE_X:
matrix @= Matrix(CUTOUT_X_TO_Z)
return matrix
def orient_object_normal_image(image, inward_axis):
"""Convert native Z-axis Cutout normals to the selected object axes."""
if CutoutInwardAxis(inward_axis) == CutoutInwardAxis.NEGATIVE_Z:
return image
import numpy as np
pixels = np.empty(int(image.size[0]) * int(image.size[1]) * 4, dtype=np.float32)
image.pixels.foreach_get(pixels)
rgba = pixels.reshape((-1, 4))
normal = rgba[:, :3] * 2.0 - 1.0
rgba[:, :3] = np.column_stack(
(-normal[:, 2], -normal[:, 0], normal[:, 1])
) * 0.5 + 0.5
image.pixels.foreach_set(pixels)
image.update()
image.pack()
return image
return source_matrix @ Matrix.Translation((center_x, center_y, 0.0))
def _assign_uv(mesh, uv):
@@ -99,7 +73,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=23 if shape == "DEPTH_SOLID" else 12 if shape == "DEPTH_BALLOON" else 11)
return load_node_group(CUTOUT_NODE_GROUP_NAMES[shape], asset_revision=26 if shape == "DEPTH_SOLID" else 14 if shape == "DEPTH_BALLOON" else 13)
def _connect_cutout_normal_scale(material):
@@ -134,13 +108,12 @@ def create_shape_object(
depth_metadata=None,
normal_image=None,
normal_mode=NormalMode.NONE,
inward_axis=CutoutInwardAxis.NEGATIVE_Z,
color_image,
gesture="LASSO",
):
import numpy as np
normal_mode = NormalMode(normal_mode)
inward_axis = CutoutInwardAxis(inward_axis)
bounds = crop_plane_bounds(
source_object,
content_bounds,
@@ -229,7 +202,6 @@ def create_shape_object(
mesh_object.matrix_world = _cutout_world_matrix(
source_object.matrix_world,
crop_center,
inward_axis,
)
mesh_object["anyimage_mesh_shape"] = shape
node_group = load_cutout_node_group(shape)
@@ -237,10 +209,6 @@ def create_shape_object(
modifier.node_group = node_group
modifier.show_group_selector = False
for name, value in (
(
"Inward Axis",
0 if inward_axis == CutoutInwardAxis.NEGATIVE_Z else 1,
),
("Cleanup Threshold", cleanup_value),
(
"Thickness",
@@ -249,9 +217,24 @@ def create_shape_object(
):
set_modifier_input(
modifier,
modifier_input_identifier(node_group, name),
modifier_input_identifier(
node_group, name,
subtype="NONE" if name == "Thickness" and shape == "DEPTH_SOLID" else None,
),
value,
)
if shape in {"FLAT", "SOLID", "DEPTH_SOLID"} and gesture == "POLYLINE":
set_modifier_input(
modifier,
modifier_input_identifier(node_group, "Mode"),
1,
)
if shape == "SOLID":
set_modifier_input(
modifier,
modifier_input_identifier(node_group, "Thickness", subtype="DISTANCE"),
0.5,
)
if shape in DEPTH_CUTOUT_SHAPES:
for name, value in (
("Depth Scale", 1.0),