Refactor and enhance geometry node groups for depth plane and material layers
- Updated the depth plane node group to load from an asset and simplified the interface. - Created a new build script for depth plane geometry nodes. - Introduced material layer groups for image and depth materials with appropriate sockets. - Added functionality to connect balloon normal scale to the image material. - Enhanced tests to validate the new node groups and their connections. - Ensured that production code does not create node groups directly.
This commit is contained in:
@@ -495,6 +495,23 @@ def test_balloon_build_starts_from_the_unified_single_sheet():
|
||||
profile = np.empty(len(result.data.vertices), dtype=np.float32)
|
||||
profile_attribute.data.foreach_get("value", profile)
|
||||
assert profile.min() == 0.0 and profile.max() > 0.0
|
||||
material_tree = result.data.materials[0].node_tree
|
||||
normal_scale_attribute = next(
|
||||
node
|
||||
for node in material_tree.nodes
|
||||
if node.bl_idname == "ShaderNodeAttribute"
|
||||
)
|
||||
assert normal_scale_attribute.attribute_type == "GEOMETRY"
|
||||
assert normal_scale_attribute.attribute_name == shapes.BALLOON_ATTRIBUTE_NAME
|
||||
image_layer = _group_node(material.material_node_group(), material_tree)
|
||||
normal_scale_link = next(
|
||||
link
|
||||
for link in material_tree.links
|
||||
if link.to_node == image_layer
|
||||
and link.to_socket == image_layer.inputs["Normal Scale"]
|
||||
)
|
||||
assert normal_scale_link.from_node == normal_scale_attribute
|
||||
assert normal_scale_link.from_socket == normal_scale_attribute.outputs["Fac"]
|
||||
assert result.modifiers[0].node_group.name == shapes.CUTOUT_NODE_GROUP_NAME
|
||||
inward = result.matrix_world.to_3x3() @ Vector((1.0, 0.0, 0.0))
|
||||
assert np.allclose(tuple(inward), (0.0, 0.0, -1.0), atol=1e-6)
|
||||
@@ -523,6 +540,10 @@ def test_teddy_preference_writes_the_shared_balloon_attribute():
|
||||
profile = np.empty(len(result.data.vertices), dtype=np.float32)
|
||||
attribute.data.foreach_get("value", profile)
|
||||
assert profile.min() == 0.0 and profile.max() > 0.0
|
||||
assert not any(
|
||||
node.bl_idname == "ShaderNodeAttribute"
|
||||
for node in result.data.materials[0].node_tree.nodes
|
||||
)
|
||||
|
||||
|
||||
def test_cutout_material_uses_ior_1_2():
|
||||
@@ -1228,3 +1249,38 @@ def test_local_cutout_builds_from_blender_image_pixels_without_a_job():
|
||||
assert np.allclose(
|
||||
copied[:, :, 3], np.flipud(rgba[top:bottom, left:right, 3])
|
||||
)
|
||||
|
||||
|
||||
def test_balloon_normal_scale_uses_existing_image_layer_input():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
assert shapes.BALLOON_CUTOUT_SHAPES == frozenset(
|
||||
{"BALLOON", "DEPTH_BALLOON"}
|
||||
)
|
||||
group = bpy.data.node_groups.new(
|
||||
material.IMAGE_MATERIAL_NODE_GROUP_NAME,
|
||||
"ShaderNodeTree",
|
||||
)
|
||||
group.interface.new_socket(
|
||||
name="Normal Scale",
|
||||
in_out="INPUT",
|
||||
socket_type="NodeSocketFloat",
|
||||
)
|
||||
image_material = bpy.data.materials.new("Balloon")
|
||||
image_material.use_nodes = True
|
||||
image_material.node_tree.nodes.clear()
|
||||
image_layer = image_material.node_tree.nodes.new("ShaderNodeGroup")
|
||||
image_layer.node_tree = group
|
||||
|
||||
cutout_object._connect_balloon_normal_scale(image_material)
|
||||
|
||||
assert _inputs(group) == ["Normal Scale"]
|
||||
attribute = next(
|
||||
node
|
||||
for node in image_material.node_tree.nodes
|
||||
if node != image_layer
|
||||
)
|
||||
assert attribute.bl_idname == "ShaderNodeAttribute"
|
||||
assert attribute.attribute_name == shapes.BALLOON_ATTRIBUTE_NAME
|
||||
link = next(iter(image_material.node_tree.links))
|
||||
assert link.from_socket == attribute.outputs["Fac"]
|
||||
assert link.to_socket == image_layer.inputs["Normal Scale"]
|
||||
|
||||
32
tests/test_node_assets.py
Normal file
32
tests/test_node_assets.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE_ROOT = PROJECT_ROOT / "src" / "anyimage"
|
||||
|
||||
|
||||
class NodeAssetTest(unittest.TestCase):
|
||||
def test_production_code_does_not_create_node_groups(self):
|
||||
offenders = []
|
||||
for path in SOURCE_ROOT.rglob("*.py"):
|
||||
if "bpy.data.node_groups.new" in path.read_text(encoding="utf-8"):
|
||||
offenders.append(path.relative_to(PROJECT_ROOT).as_posix())
|
||||
self.assertEqual(offenders, [])
|
||||
|
||||
def test_runtime_node_group_entries_load_assets(self):
|
||||
material_source = (
|
||||
SOURCE_ROOT / "operators" / "common" / "mesh_result.py"
|
||||
).read_text(encoding="utf-8")
|
||||
plane_source = (
|
||||
SOURCE_ROOT / "operators" / "convert_to_plane" / "nodes.py"
|
||||
).read_text(encoding="utf-8")
|
||||
self.assertIn("return load_asset_node_group(group_name)", material_source)
|
||||
self.assertIn(
|
||||
"return load_asset_node_group(DEPTH_PLANE_NODE_GROUP_NAME)",
|
||||
plane_source,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user