- Added a proposal for depth surface strip cleanup, detailing the rationale and changes to the depth plane and cutout nodes. - Created specifications for the new cleanup requirements, including scenarios for strip removal and boundary edge detection. - Developed tasks for implementing shared strip detection logic and integrating new interfaces for depth nodes. - Established a new CLI structure for organizing node groups and model commands, replacing old entry points with a more intuitive command structure. - Introduced a prototype for panorama surface generation, including requirements for distance reconstruction and geometry node surfaces. - Added comprehensive tests for depth strip cleanup functionality, ensuring correct behavior for various mesh topologies and edge cases.
90 lines
5.0 KiB
Python
90 lines
5.0 KiB
Python
from collections import Counter
|
|
|
|
import bpy
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from tests.support.nodes import CUTOUT_BUILDERS
|
|
from tools.nodes.arrangement.flow import origin_socket
|
|
|
|
from anyimage.operators.cutout_tool.object import load_cutout_node_group
|
|
|
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
|
|
|
from tests.support.nodes import validate_node_structure
|
|
|
|
|
|
@pytest.mark.parametrize("source", ["definition", "asset"])
|
|
def test_cutout_interfaces_and_thickness_modes(source):
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
groups = {shape: (CUTOUT_BUILDERS[shape]() if source == "definition" else load_cutout_node_group(shape)) for shape in ("FLAT", "SOLID", "DEPTH_SOLID", "DEPTH_BALLOON")}
|
|
assert groups["FLAT"] == groups["SOLID"]
|
|
assert len(set(groups.values())) == 3
|
|
for group in set(groups.values()):
|
|
validate_node_structure(group)
|
|
assert group.is_modifier and group.get("anyimage_asset_revision") == 11
|
|
assert not any(node.bl_idname == "GeometryNodeGroup" for node in group.nodes)
|
|
inputs = {item.name: item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT"}
|
|
assert "Shape" not in inputs and "Rim Thickness" not in inputs
|
|
assert next(s for s in group.interface.items_tree if s.name == "Thickness").default_value == 1
|
|
assert all(item.structure_type == "SINGLE" for item in inputs.values())
|
|
if "Mode" in inputs:
|
|
assert inputs["Thickness"].subtype == "DISTANCE"
|
|
assert inputs["Mode"].default_value == "Balloon"
|
|
if "Double Sided" in inputs:
|
|
assert inputs["Double Sided"].default_value
|
|
if group == groups["DEPTH_SOLID"]:
|
|
assert np.isclose(inputs["Edge Round"].default_value, 0.2)
|
|
assert inputs["Edge Round"].parent.name == "Options"
|
|
assert list(inputs)[list(inputs).index("Front Inflation") - 1] == "Edge Round"
|
|
assert inputs["Normal Smooth"].default_value == 50
|
|
assert inputs["Front Inflation"].default_value == 1
|
|
assert inputs["Front Inflation"].parent.name == "Options"
|
|
assert not set(inputs) & {"Edge Round Range", "Back Rim Range", "Normal Influence", "Edge Round Amount", "Back Rim Fullness"}
|
|
options = [s.name for s in inputs.values() if s.parent and s.parent.name == "Options"]
|
|
expected = ["Cleanup Threshold"]
|
|
if group == groups["DEPTH_SOLID"]:
|
|
expected += ["Reference Depth", "Edge Round", "Front Inflation", "Normal Smooth"]
|
|
elif group == groups["DEPTH_BALLOON"]:
|
|
expected += ["Reference Depth"]
|
|
expected += ["Smooth Weight"]
|
|
if group == groups["DEPTH_BALLOON"]:
|
|
expected += ["Depth Direction"]
|
|
assert options == expected, (group.name, options)
|
|
assert inputs["Smooth"].socket_type == "NodeSocketInt" and inputs["Smooth"].default_value == 0
|
|
assert inputs["Smooth Weight"].default_value == 1
|
|
incoming = [link for link in group.links
|
|
if link.to_node.bl_idname != "NodeReroute"
|
|
and origin_socket(link.from_socket).node.bl_idname == "NodeGroupInput"
|
|
and origin_socket(link.from_socket).name == "Geometry"]
|
|
assert len(incoming) == 1 and incoming[0].to_node.bl_idname == "GeometryNodeDeleteGeometry"
|
|
mesh = bpy.data.meshes.new("Profile")
|
|
mesh.from_pydata([(-1,-1,0), (1,-1,0), (1,1,0), (-1,1,0), (0,0,0)], [], [(0,1,4), (1,2,4), (2,3,4), (3,0,4)])
|
|
profile = mesh.attributes.new("o_balloon", "FLOAT", "POINT")
|
|
profile.data.foreach_set("value", [0,0,0,0,0.6])
|
|
obj = bpy.data.objects.new("Profile", mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
modifier = obj.modifiers.new("Cutout", "NODES")
|
|
modifier.node_group = groups["SOLID"]
|
|
for mode, thickness in ((0, 0), (0, 1), (1, 0), (1, 0.4)):
|
|
for name, value in (("Mode", mode), ("Thickness", thickness)):
|
|
subtype = ("DISTANCE" if mode else "NONE") if name == "Thickness" else None
|
|
set_modifier_input(modifier, modifier_input_identifier(modifier.node_group, name, subtype=subtype), value)
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
|
result = evaluated.to_mesh()
|
|
try:
|
|
z = np.array([vertex.co.z for vertex in result.vertices])
|
|
assert np.isfinite(z).all() and len(z)
|
|
if thickness == 0:
|
|
assert len(result.polygons) == 4 and np.max(abs(z)) < 1e-7
|
|
assert all(abs(item.value - 1) < 1e-7 for item in result.attributes["o_normal_scale"].data)
|
|
else:
|
|
radius = 0.6 if mode == 0 else 0.2
|
|
assert np.isclose(z.min(), -radius) and np.isclose(z.max(), radius)
|
|
uses = Counter(tuple(sorted((a,b))) for face in result.polygons for a,b in zip(face.vertices, (*face.vertices[1:],face.vertices[0])))
|
|
assert set(uses.values()) == {2}
|
|
finally:
|
|
evaluated.to_mesh_clear()
|