- 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.
85 lines
3.4 KiB
Python
85 lines
3.4 KiB
Python
"""Evaluate complete-image projection and rectangular surface topology."""
|
|
from tests.support.paths import PROJECT_ROOT
|
|
|
|
import bpy
|
|
import numpy as np
|
|
import pytest
|
|
|
|
ROOT = PROJECT_ROOT
|
|
from tools.nodes.groups.image_plane import build_image_plane_group
|
|
from tools.nodes.groups.image_depth_plane import build_image_depth_plane_group
|
|
from tools.nodes.groups.image_relief_plane import build_image_relief_plane_group
|
|
|
|
|
|
@pytest.fixture(params=("definition", "asset"))
|
|
def surface(request):
|
|
return create_surface(request.param, "DEPTH")
|
|
|
|
|
|
@pytest.fixture(params=("definition", "asset"))
|
|
def relief(request):
|
|
return create_surface(request.param, "RELIEF")
|
|
|
|
|
|
def create_surface(source, plane_type):
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
group_name = f"O Image {plane_type.title()} Plane"
|
|
if source == "asset":
|
|
with bpy.data.libraries.load(str(ROOT / "src/anyimage/assets/O_AnyImage.blend")) as (library, target):
|
|
target.node_groups = [group_name]
|
|
group = target.node_groups[0]
|
|
plane = group.nodes["Group"].node_tree
|
|
else:
|
|
plane = build_image_plane_group()
|
|
builder = build_image_depth_plane_group if plane_type == "DEPTH" else build_image_relief_plane_group
|
|
group = builder(plane)
|
|
mesh = bpy.data.meshes.new("Rectangle")
|
|
mesh.from_pydata([(-2, -1, 0), (2, -1, 0), (2, 1, 0), (-2, 1, 0)], [], [(0, 1, 2, 3)])
|
|
material = bpy.data.materials.new("Image")
|
|
mesh.materials.append(material)
|
|
obj = bpy.data.objects.new("Surface", mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
modifier = obj.modifiers.new("Surface", "NODES")
|
|
modifier.node_group = group
|
|
image = bpy.data.images.new("Camera", width=16, height=8, float_buffer=True)
|
|
image.colorspace_settings.name = "Non-Color"
|
|
image.alpha_mode = "CHANNEL_PACKED"
|
|
yy, xx = np.mgrid[:8, :16]
|
|
# Camera principal point is deliberately off center; Y points down.
|
|
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
|
pixels[..., 0] = ((xx + 0.5) - 5) / 4
|
|
pixels[..., 1] = (3 - (yy + 0.5)) / 4
|
|
pixels[..., 2] = 2 + (xx + 0.5) / 16
|
|
pixels[..., 3] = 1
|
|
image.pixels.foreach_set(pixels.ravel())
|
|
inputs = {item.name: item for item in group.interface.items_tree
|
|
if item.item_type == "SOCKET" and item.in_out == "INPUT"}
|
|
def set_value(name, value):
|
|
modifier[inputs[name].identifier] = value
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
|
|
for name, value in (("Subdivide", 2), ("Depth Image", image),
|
|
("Uniform Scale", 0.5), ("Reference Depth", 1.2),
|
|
("Depth Scale", 0.0), ("Thickness", 0.0)):
|
|
set_value(name, value)
|
|
if "Boundary Smooth" in inputs:
|
|
set_value("Boundary Smooth", 0)
|
|
return obj, set_value, plane, inputs, image
|
|
|
|
|
|
def evaluated(obj):
|
|
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
|
mesh = result.to_mesh()
|
|
try:
|
|
return (
|
|
np.array([v.co[:] for v in mesh.vertices]),
|
|
[tuple(face.vertices) for face in mesh.polygons],
|
|
np.array([value.uv[:] for value in mesh.uv_layers["UVMap"].data]) if mesh.uv_layers else np.empty((0, 2)),
|
|
[material.name if material else None for material in mesh.materials],
|
|
[face.material_index for face in mesh.polygons],
|
|
[attr.name for attr in mesh.attributes],
|
|
)
|
|
finally:
|
|
result.to_mesh_clear()
|