from pathlib import Path import sys import bpy import numpy as np PROJECT_ROOT = Path(__file__).parents[1] sys.path.insert(0, str(PROJECT_ROOT / "src")) sys.path.insert( 0, str(next((PROJECT_ROOT / "src" / "snapform" / "wheels").glob("blendjob-*.whl"))), ) from snapform.blender.depth_plane import ( get_modifier_input, set_modifier_input, ) from snapform.blender.surface import create_matching_camera, load_geometry_node_group def set_input(modifier, group, name, value): item = next( item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name == name ) set_modifier_input(modifier, item.identifier, value) def add_face_attributes(mesh, count): confidence = mesh.attributes.new("o_confidence", "FLOAT", "FACE") confidence.data.foreach_set("value", [1.0] * count) def main(): bpy.ops.wm.read_factory_settings(use_empty=True) group = load_geometry_node_group() assert load_geometry_node_group() == group assert group.is_modifier geometry_input = next( item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name == "Geometry" ) assert geometry_input.structure_type == "SINGLE" input_sockets = [ item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" ] assert all(item.structure_type == "SINGLE" for item in input_sockets) assert [item.name for item in input_sockets[:4]] == [ "Geometry", "Iterations", "Resolution", "Smooth", ] iterations = next(item for item in input_sockets if item.name == "Iterations") assert iterations.default_value == 2 assert iterations.min_value == 0 resolution = next(item for item in input_sockets if item.name == "Resolution") assert abs(resolution.default_value - 0.1) < 1e-6 assert group.asset_data is None assert not any(node.bl_idname == "GeometryNodeGroup" for node in group.nodes) assert not any(node.bl_idname == "GeometryNodeTriangulate" for node in group.nodes) group_inputs = [ node for node in group.nodes if node.bl_idname == "NodeGroupInput" ] assert len(group_inputs) >= 7 for node in group_inputs: visible_outputs = [ output for output in node.outputs if output.name and not output.hide ] assert 1 <= len(visible_outputs) <= 4 uv_stores = [ node for node in group.nodes if node.bl_idname == "GeometryNodeStoreNamedAttribute" and node.inputs["Name"].default_value == "UVMap" ] assert len(uv_stores) == 1 assert uv_stores[0].data_type == "FLOAT2" panels = { item.name: item for item in group.interface.items_tree if item.item_type == "PANEL" } assert not panels["Culling"].default_closed assert panels["Options"].default_closed assert [item.name for item in panels["Culling"].interface_items] == [ "Depth Continuity", "Confidence", ] assert [item.name for item in panels["Options"].interface_items] == [ "Fix Topology", ] smooth = next( item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name == "Smooth" ) assert smooth.default_value == 0.5 for name, default_value in ( ("Confidence", 0.0), ("Depth Continuity", 50.0), ): item = next( item for item in panels["Culling"].interface_items if item.name == name ) assert item.min_value == 0.0 assert item.max_value == 100.0 assert item.default_value == default_value assert item.subtype == "PERCENTAGE" if bpy.app.version >= (5, 0, 0): assert group.library is not None mesh = bpy.data.meshes.new("Validation Mesh") mesh.from_pydata( [(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0)], [], [(0, 2, 1), (1, 2, 3)], ) discontinuity = mesh.attributes.new( "o_depth_discontinuity", "FLOAT", "FACE", ) discontinuity.data.foreach_set("value", [0.03] * len(mesh.polygons)) uv = mesh.uv_layers.new(name="UVMap") uv.data.foreach_set("uv", [0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1]) add_face_attributes(mesh, 2) assert "o_confidence" in mesh.attributes assert not { "o_background", "o_background_face", "o_face_confidence", "o_confidence_percentile", }.intersection(mesh.attributes.keys()) obj = bpy.data.objects.new("Validation Object", mesh) bpy.context.scene.collection.objects.link(obj) modifier = obj.modifiers.new(name=group.name, type="NODES") modifier.node_group = group modifier.show_group_selector = False assert not modifier.show_group_selector set_input(modifier, group, "Resolution", 0.000001) set_input(modifier, group, "Iterations", 0) set_input(modifier, group, "Fix Topology", 0) set_input(modifier, group, "Smooth", 0.0) evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()) result = evaluated.to_mesh() try: assert len(result.polygons) == 2 assert "UVMap" in result.attributes assert result.attributes["UVMap"].data_type == "FLOAT2" finally: evaluated.to_mesh_clear() set_input(modifier, group, "Depth Continuity", 100.0) discontinuity_item = next( item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.name == "Depth Continuity" ) assert get_modifier_input(modifier, discontinuity_item.identifier) == 100.0 obj.update_tag(refresh={"DATA"}) bpy.context.view_layer.update() culled = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()) culled_result = culled.to_mesh() try: assert len(culled_result.polygons) == 0 assert len(culled_result.vertices) == 0 finally: culled.to_mesh_clear() size = 10 dense_vertices = [ (x * 0.1, y * 0.1, 0.0) for y in range(size) for x in range(size) ] dense_faces = [] for y in range(size - 1): for x in range(size - 1): a = y * size + x b = a + 1 c = a + size d = c + 1 dense_faces.extend(((a, c, b), (b, c, d))) dense_mesh = bpy.data.meshes.new("Dense Validation Mesh") dense_mesh.from_pydata(dense_vertices, [], dense_faces) dense_uv = dense_mesh.uv_layers.new(name="UVMap") dense_uv.data.foreach_set("uv", [0.0] * (len(dense_mesh.loops) * 2)) add_face_attributes(dense_mesh, len(dense_faces)) dense_object = bpy.data.objects.new("Dense Validation Object", dense_mesh) bpy.context.scene.collection.objects.link(dense_object) dense_modifier = dense_object.modifiers.new(name=group.name, type="NODES") dense_modifier.node_group = group set_input(dense_modifier, group, "Resolution", 0.22) dense_evaluated = dense_object.evaluated_get( bpy.context.evaluated_depsgraph_get() ) dense_result = dense_evaluated.to_mesh() try: assert 4 < len(dense_result.polygons) < len(dense_faces) assert all(polygon.normal.z < -0.01 for polygon in dense_result.polygons) finally: dense_evaluated.to_mesh_clear() source = bpy.data.objects.new("Validation Source", None) bpy.context.scene.collection.objects.link(source) source.location = (2.0, 3.0, 4.0) bpy.context.view_layer.update() obj.matrix_world = source.matrix_world.copy() bpy.context.view_layer.update() camera = create_matching_camera( bpy.context, source, obj, "Validation", np.asarray( ((100.0, 0.0, 50.0), (0.0, 100.0, 50.0), (0.0, 0.0, 1.0)) ), (101, 101), {"scale_x": 1.0, "scale_z": 1.0, "location": (0.0, 0.0, 2.0)}, ) assert camera.parent == obj camera_location = tuple( round(value, 5) for value in camera.matrix_world.translation ) assert camera_location == ( 2.0, 3.0, 6.0, ) print(f"Validated Blender {bpy.app.version_string}: {group.name}") if __name__ == "__main__": main()