- Implemented `image_plane.py` to build the Image Plane Geometry Nodes group with customizable parameters for geometry, subdivide, and thickness. - Created `image_plane_validation.py` to validate the functionality of the image plane, ensuring correct geometry generation and material assignment. - Developed `material_layers.py` to construct shader node groups for image materials, including depth and normal handling. - Authored `plane_layouts.py` to define node arrangements for image geometry assets. - Introduced `shadeless.py` to create a shadeless shader node group that displays color without scene lighting while preserving shadows and reflections.
94 lines
5.1 KiB
Python
94 lines
5.1 KiB
Python
"""Validate direct Cutout assets and their evaluated defaults."""
|
|
from pathlib import Path
|
|
import sys
|
|
from collections import Counter
|
|
import bpy
|
|
import numpy as np
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
sys.path.insert(
|
|
0,
|
|
str(next((ROOT / "src" / "anyimage" / "wheels").glob("blendjob-*.whl"))),
|
|
)
|
|
from anyimage.operators.cutout_tool.object import load_cutout_node_group
|
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
|
from node_layout import validate_node_layout
|
|
|
|
|
|
def main():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
groups = {shape: 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_layout(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 += ["Split Threshold", "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.from_node.bl_idname == "NodeGroupInput" and 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()
|
|
print("Validated three Cutout assets, four presets, both thickness modes and closed defaults")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|