Files
AnyImage/tools/validate_balloon_asset.py
nan 2b79a342b0 Add comprehensive tests for cutout and mesh functionalities
- Introduced `test_cutout.py` to validate cutout pie menu behavior, image lasso options, and mesh detail calculations.
- Added `test_mesh.py` to ensure correct tangent normal rasterization, plane source topology, and depth surface evaluations.
- Created `test_mesh_blender.py` to verify material node groups, tangent baking, and local cutout building from Blender images.
- Implemented tests for handling alpha regions and ensuring geometry nodes maintain expected behavior.
2026-08-26 10:42:29 +08:00

136 lines
4.1 KiB
Python

from pathlib import Path
import sys
import bpy
PROJECT_ROOT = Path(__file__).parents[1]
sys.path.insert(0, str(PROJECT_ROOT / "src"))
sys.path.insert(
0,
str(next((PROJECT_ROOT / "src" / "anyimage" / "wheels").glob("blendjob-*.whl"))),
)
from anyimage.operators.balloon.depth import (
BALLOON_DEPTH_ATTRIBUTE,
add_balloon_shape_modifier,
balloon_shape_node_group,
)
from anyimage.operators.balloon.operator import (
mark_balloon_material,
merge_balloon_normal_image,
reusable_balloon_material,
)
from anyimage.operators.material import (
BALLOON_MATERIAL_NODE_GROUP_NAME,
material_node_group,
set_modifier_input,
)
def input_socket(group, name):
return next(
item
for item in group.interface.items_tree
if item.item_type == "SOCKET"
and item.in_out == "INPUT"
and item.name == name
)
def main():
bpy.ops.wm.read_factory_settings(use_empty=True)
group = balloon_shape_node_group()
assert group.is_modifier
assert input_socket(group, "Double Sided Depth").default_value is False
smoother = next(
node
for node in group.nodes
if node.bl_idname == "ShaderNodeMapRange"
and node.interpolation_type == "SMOOTHERSTEP"
)
assert smoother.clamp
assert not any(
node.bl_idname == "ShaderNodeMath"
and node.operation == "MAXIMUM"
and node.label == "Clamp Depth"
for node in group.nodes
)
mesh = bpy.data.meshes.new("Balloon Validation Mesh")
mesh.from_pydata(
(
(-1.0, 0.0, 0.0),
(-0.5, 0.0, 0.0),
(0.0, 0.0, 0.0),
(1.0, 0.0, 0.0),
),
(),
(),
)
depth = mesh.attributes.new(BALLOON_DEPTH_ATTRIBUTE, "FLOAT", "POINT")
depth.data.foreach_set("value", (0.0, 2.0, 2.0, 2.0))
obj = bpy.data.objects.new("Balloon Validation", mesh)
bpy.context.scene.collection.objects.link(obj)
modifier = add_balloon_shape_modifier(obj, 1.0)
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
result = evaluated.to_mesh()
try:
x = [vertex.co.x for vertex in result.vertices]
assert abs(x[0] + 1.0) < 1e-5
assert abs(x[1] + 1.25) < 1e-5
assert abs(x[2]) < 1e-5
assert abs(x[3] - 1.0) < 1e-5
finally:
evaluated.to_mesh_clear()
set_modifier_input(modifier, input_socket(group, "Thickness").identifier, 2.0)
obj.update_tag(refresh={"DATA"})
bpy.context.view_layer.update()
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
result = evaluated.to_mesh()
try:
x = [vertex.co.x for vertex in result.vertices]
assert abs(x[0] + 1.0) < 1e-5
assert abs(x[3] - 2.0) < 1e-5
finally:
evaluated.to_mesh_clear()
material_group = material_node_group(
surface_group_name=BALLOON_MATERIAL_NODE_GROUP_NAME,
)
normal_maps = {
node.space
for node in material_group.nodes
if node.bl_idname == "ShaderNodeNormalMap"
}
assert normal_maps == {"TANGENT", "OBJECT"}
assert input_socket(material_group, "Object Space").default_value is False
assert input_socket(material_group, "Normal Scale").default_value == 1.0
source_image = bpy.data.images.new("Reuse Source", 2, 1)
shared_normal = bpy.data.images.new("Shared Normal", 2, 1)
shared_normal.pixels.foreach_set(
(128 / 255, 128 / 255, 1, 1, 128 / 255, 128 / 255, 1, 1)
)
material = bpy.data.materials.new("Unrelated Material Name")
mark_balloon_material(material, source_image, shared_normal)
assert reusable_balloon_material(
source_image,
normal_required=True,
) is material
update_normal = bpy.data.images.new("Normal Update", 2, 1)
update_normal.pixels.foreach_set(
(1, 0.25, 0.75, 1, 128 / 255, 128 / 255, 1, 1)
)
merge_balloon_normal_image(shared_normal, update_normal)
pixels = list(shared_normal.pixels)
assert abs(pixels[0] - 1.0) < 1e-5
assert abs(pixels[4] - 128 / 255) < 1e-5
print(f"Validated Blender {bpy.app.version_string}: {group.name}")
if __name__ == "__main__":
main()