- 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.
93 lines
4.2 KiB
Python
93 lines
4.2 KiB
Python
"""Reload saved assets and evaluate geometry in the target Blender process."""
|
|
from math import isfinite
|
|
from pathlib import Path
|
|
|
|
import bpy
|
|
|
|
|
|
ASSET_PATH = Path(__file__).resolve().parents[2] / "src/anyimage/assets/O_AnyImage.blend"
|
|
GEOMETRY_NAMES = {
|
|
"O Image Depth Panorama",
|
|
"O Image Plane", "O Image Depth Plane", "O Image Relief Plane",
|
|
"O Image Cutout", "O Image Depth Cutout", "O Image Depth Balloon",
|
|
}
|
|
SHADER_NAMES = {"O Image Layer", "O Image Depth Layer", "O Shadeless"}
|
|
|
|
|
|
def check_asset_library(asset_path):
|
|
"""Check library completeness and finite, nonempty evaluated geometry."""
|
|
with bpy.data.libraries.load(str(asset_path), link=True) as (source, target):
|
|
assert set(source.node_groups) == GEOMETRY_NAMES | SHADER_NAMES
|
|
target.node_groups = sorted(GEOMETRY_NAMES | SHADER_NAMES)
|
|
pending, checked = list(target.node_groups), set()
|
|
while pending:
|
|
group = pending.pop()
|
|
if group in checked or group.bl_idname != "GeometryNodeTree":
|
|
continue
|
|
checked.add(group)
|
|
for node in group.nodes:
|
|
assert node.bl_idname != "GeometryNodeCaptureAttribute", group.name
|
|
if node.bl_idname == "GeometryNodeGroup" and node.node_tree:
|
|
pending.append(node.node_tree)
|
|
depth_image = bpy.data.images.new("Asset Check Depth", width=8, height=8, float_buffer=True)
|
|
depth_image.colorspace_settings.name = "Non-Color"
|
|
depth_image.pixels.foreach_set([
|
|
value for y in range(8) for x in range(8)
|
|
for value in ((x + 0.5) / 8 - 0.5, (y + 0.5) / 8 - 0.5, 2.0, 1.0)
|
|
])
|
|
for node_group in target.node_groups:
|
|
expected_type = "GeometryNodeTree" if node_group.name in GEOMETRY_NAMES else "ShaderNodeTree"
|
|
assert node_group.bl_idname == expected_type and node_group.library is not None, node_group.name
|
|
if expected_type == "ShaderNodeTree":
|
|
continue
|
|
mesh = bpy.data.meshes.new("Asset Check Plane")
|
|
mesh.from_pydata(
|
|
[(x / 3 - 0.5, y / 3 - 0.5, 0) for y in range(4) for x in range(4)], [],
|
|
[(y * 4 + x, y * 4 + x + 1, (y + 1) * 4 + x + 1, (y + 1) * 4 + x)
|
|
for y in range(3) for x in range(3)],
|
|
)
|
|
uv = mesh.uv_layers.new(name="UVMap")
|
|
for loop in mesh.loops:
|
|
position = mesh.vertices[loop.vertex_index].co
|
|
uv.data[loop.index].uv = (position.x + 0.5, position.y + 0.5)
|
|
profile = mesh.attributes.new("o_balloon", "FLOAT", "POINT")
|
|
for value in profile.data:
|
|
value.value = 0.2
|
|
obj = bpy.data.objects.new("Asset Check", mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
modifier = obj.modifiers.new("Asset Check", "NODES")
|
|
modifier.node_group = node_group
|
|
values = {"Subdivide": 0, "Thickness": 0.2, "Reference Depth": 2.0,
|
|
"Depth Image": depth_image, "Uniform Scale": 1.0, "Normal Smooth": 0}
|
|
for item in node_group.interface.items_tree:
|
|
if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name in values:
|
|
modifier[item.identifier] = values[item.name]
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
|
result = evaluated.to_mesh()
|
|
try:
|
|
assert len(result.vertices) and len(result.polygons), node_group.name
|
|
assert all(isfinite(value) for vertex in result.vertices for value in vertex.co), node_group.name
|
|
assert not list(modifier.node_warnings), (
|
|
node_group.name, [warning.message for warning in modifier.node_warnings],
|
|
)
|
|
assert not {attribute.name for attribute in result.attributes} & {
|
|
".o_anyimage_depth_cut", ".o_anyimage_original_boundary",
|
|
}, node_group.name
|
|
finally:
|
|
evaluated.to_mesh_clear()
|
|
bpy.data.objects.remove(obj, do_unlink=True)
|
|
bpy.data.meshes.remove(mesh)
|
|
bpy.data.images.remove(depth_image)
|
|
print(f"Checked {len(target.node_groups)} saved node groups in Blender {bpy.app.version_string}")
|
|
|
|
|
|
def main():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
check_asset_library(ASSET_PATH)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|