Add depth surface processing and layout tools
- Implemented `cutout_depth_surface.py` for face separation and connected thickness calculations for depth surface assets. - Created `cutout_depth_surface_layout.py` to arrange nodes for staggered calculation branches in depth surface processing. - Authored `geometry_layouts.py` to define layouts for various geometry assets, including image cutouts and depth planes. - Introduced `node_layout.py` for shared presentation rules, facilitating node arrangement and validation in geometry-node layouts.
This commit is contained in:
@@ -531,7 +531,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Smooth Iterations",
|
||||
"Cleanup",
|
||||
"Smooth",
|
||||
"Stretch Limit",
|
||||
"Split",
|
||||
"Reference Depth",
|
||||
"Depth Direction",
|
||||
"Uniform Scale",
|
||||
@@ -542,7 +542,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
assert [item.name for item in panels[0].interface_items] == [
|
||||
"Cleanup",
|
||||
"Smooth",
|
||||
"Stretch Limit",
|
||||
"Split",
|
||||
"Reference Depth",
|
||||
"Depth Direction",
|
||||
]
|
||||
@@ -634,7 +634,8 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Geometry",
|
||||
"Thickness",
|
||||
"Depth Scale",
|
||||
"Stretch Limit",
|
||||
"Split",
|
||||
"Cleanup",
|
||||
"Reference Depth",
|
||||
"Uniform Scale",
|
||||
"Depth Image",
|
||||
@@ -653,9 +654,10 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
)
|
||||
dependencies.setdefault(link.from_socket.name, set()).add(shape)
|
||||
assert dependencies == {
|
||||
"Geometry": {"DEPTH_SURFACE"},
|
||||
"Thickness": set(cutout_shape.CUTOUT_SHAPES),
|
||||
"Depth Scale": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
"Stretch Limit": {"DEPTH_SURFACE"},
|
||||
"Split": {"DEPTH_SURFACE"},
|
||||
"Double Sided": {"DEPTH_BALLOON"},
|
||||
"Reference Depth": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
"Depth Direction": {"DEPTH_BALLOON"},
|
||||
@@ -672,7 +674,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
)
|
||||
assert all(
|
||||
not any(node.bl_idname == "GeometryNodeInputMeshIsland" for node in group.nodes)
|
||||
for group in child_groups.values()
|
||||
for shape, group in child_groups.items() if shape != "DEPTH_SURFACE"
|
||||
)
|
||||
assert any(node.bl_idname == "GeometryNodeBlurAttribute" for node in cutout.nodes)
|
||||
assert all(
|
||||
@@ -710,112 +712,33 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
)
|
||||
assert cleanup.default_value == 0.0
|
||||
assert cleanup.subtype == "NONE"
|
||||
stretch_limit = next(
|
||||
split_control = next(
|
||||
item
|
||||
for item in cutout.interface.items_tree
|
||||
if item.item_type == "SOCKET" and item.name == "Stretch Limit"
|
||||
if item.item_type == "SOCKET" and item.name == "Split"
|
||||
)
|
||||
child_stretch_limit = next(
|
||||
child_split = next(
|
||||
item
|
||||
for item in child_groups["DEPTH_SURFACE"].interface.items_tree
|
||||
if item.item_type == "SOCKET" and item.name == "Stretch Limit"
|
||||
if item.item_type == "SOCKET" and item.name == "Split"
|
||||
)
|
||||
assert stretch_limit.default_value == 0.0
|
||||
assert stretch_limit.min_value == 0.0
|
||||
assert stretch_limit.max_value == 1.0
|
||||
assert stretch_limit.subtype == "FACTOR"
|
||||
assert getattr(stretch_limit.parent, "name", "") == "Options"
|
||||
assert child_stretch_limit.default_value == 0.0
|
||||
assert child_stretch_limit.min_value == 0.0
|
||||
assert child_stretch_limit.max_value == 1.0
|
||||
assert child_stretch_limit.subtype == "FACTOR"
|
||||
assert getattr(child_stretch_limit.parent, "name", "") == "Options"
|
||||
assert split_control.default_value == 0.0
|
||||
assert split_control.min_value == 0.0
|
||||
assert split_control.max_value == 1.0
|
||||
assert split_control.subtype == "FACTOR"
|
||||
assert getattr(split_control.parent, "name", "") == "Options"
|
||||
assert child_split.default_value == 0.0
|
||||
assert child_split.min_value == 0.0
|
||||
assert child_split.max_value == 1.0
|
||||
assert child_split.subtype == "FACTOR"
|
||||
assert getattr(child_split.parent, "name", "") == "Options"
|
||||
depth_surface_nodes = child_groups["DEPTH_SURFACE"].nodes
|
||||
captures = [
|
||||
node
|
||||
for node in depth_surface_nodes
|
||||
if node.bl_idname == "GeometryNodeCaptureAttribute"
|
||||
]
|
||||
assert len(captures) == 1
|
||||
assert captures[0].domain == "POINT"
|
||||
assert [item.name for item in captures[0].capture_items] == [
|
||||
"Original Edge Length"
|
||||
]
|
||||
captured_distance = next(
|
||||
link.from_node
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
if link.to_node == captures[0]
|
||||
and link.to_socket.name == "Original Edge Length"
|
||||
)
|
||||
assert captured_distance.bl_idname == "ShaderNodeVectorMath"
|
||||
assert captured_distance.operation == "DISTANCE"
|
||||
ratio = next(
|
||||
link.to_node
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
if link.from_node == captured_distance
|
||||
and link.to_node.bl_idname == "ShaderNodeMath"
|
||||
and link.to_node.operation == "DIVIDE"
|
||||
)
|
||||
assert any(
|
||||
link.from_node == captures[0]
|
||||
and link.to_node == ratio
|
||||
and link.to_socket == ratio.inputs[0]
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
)
|
||||
assert any(
|
||||
link.from_node == captured_distance
|
||||
and link.to_node == ratio
|
||||
and link.to_socket == ratio.inputs[1]
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
)
|
||||
captured_distance = next(
|
||||
link.from_node
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
if link.to_node == captures[0]
|
||||
and link.to_socket.name == "Original Edge Length"
|
||||
)
|
||||
assert captured_distance.bl_idname == "ShaderNodeVectorMath"
|
||||
assert captured_distance.operation == "DISTANCE"
|
||||
ratio = next(
|
||||
link.to_node
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
if link.from_node == captured_distance
|
||||
and link.to_node.bl_idname == "ShaderNodeMath"
|
||||
and link.to_node.operation == "DIVIDE"
|
||||
)
|
||||
assert any(
|
||||
link.from_node == captures[0]
|
||||
and link.to_node == ratio
|
||||
and link.to_socket == ratio.inputs[0]
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
)
|
||||
assert any(
|
||||
link.from_node == captured_distance
|
||||
and link.to_node == ratio
|
||||
and link.to_socket == ratio.inputs[1]
|
||||
for link in child_groups["DEPTH_SURFACE"].links
|
||||
)
|
||||
samples = [
|
||||
node
|
||||
for node in depth_surface_nodes
|
||||
if node.bl_idname == "GeometryNodeSampleIndex"
|
||||
]
|
||||
assert not samples
|
||||
delete_domains = [
|
||||
node.domain
|
||||
for node in depth_surface_nodes
|
||||
if node.bl_idname == "GeometryNodeDeleteGeometry"
|
||||
]
|
||||
assert delete_domains.count("EDGE") == 1
|
||||
assert delete_domains.count("POINT") == 2
|
||||
assert any(
|
||||
node.bl_idname == "GeometryNodeInputMeshEdgeNeighbors"
|
||||
for node in depth_surface_nodes
|
||||
)
|
||||
assert any(
|
||||
node.bl_idname == "GeometryNodeInputMeshVertexNeighbors"
|
||||
for node in depth_surface_nodes
|
||||
)
|
||||
assert any(node.bl_idname == "GeometryNodeSplitEdges" for node in depth_surface_nodes)
|
||||
from validate_cutout import validate_depth_surface_attributes
|
||||
validate_depth_surface_attributes(child_groups["DEPTH_SURFACE"])
|
||||
assert [node.domain for node in depth_surface_nodes
|
||||
if node.bl_idname == "GeometryNodeDeleteGeometry"] == ["FACE"]
|
||||
assert "Stretch Limit" not in [item.name for item in child_groups["DEPTH_SURFACE"].interface.items_tree]
|
||||
cleanup_scale = next(
|
||||
link.to_node
|
||||
for link in cutout.links
|
||||
@@ -1547,20 +1470,20 @@ def _add_cutout_modifier(obj, shape):
|
||||
return modifier
|
||||
|
||||
|
||||
def _depth_surface_stretch_object():
|
||||
def _depth_surface_step_object():
|
||||
vertices = [(float(x), float(y), 0.0) for y in range(3) for x in range(3)]
|
||||
faces = []
|
||||
for y in range(2):
|
||||
for x in range(2):
|
||||
a = y * 3 + x
|
||||
faces.extend(((a, a + 1, a + 4), (a, a + 4, a + 3)))
|
||||
data = bpy.data.meshes.new("Depth Surface Stretch")
|
||||
data = bpy.data.meshes.new("Depth Surface Step")
|
||||
data.from_pydata(vertices, (), faces)
|
||||
uv_layer = data.uv_layers.new(name="UVMap")
|
||||
for loop in data.loops:
|
||||
coordinate = data.vertices[loop.vertex_index].co
|
||||
uv_layer.data[loop.index].uv = (coordinate.x * 0.5, coordinate.y * 0.5)
|
||||
obj = bpy.data.objects.new("Depth Surface Stretch", data)
|
||||
obj = bpy.data.objects.new("Depth Surface Step", data)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
modifier = _add_cutout_modifier(obj, "DEPTH_SURFACE")
|
||||
|
||||
@@ -1617,40 +1540,17 @@ def _evaluated_topology(obj):
|
||||
evaluated.to_mesh_clear()
|
||||
|
||||
|
||||
def test_depth_surface_stretch_limit_tracks_projection_and_cleans_loose_topology():
|
||||
def test_depth_surface_split_preserves_original_faces():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
obj, modifier = _depth_surface_stretch_object()
|
||||
stretch_id = object_data.modifier_input_identifier(
|
||||
modifier.node_group,
|
||||
"Stretch Limit",
|
||||
)
|
||||
depth_scale_id = object_data.modifier_input_identifier(
|
||||
modifier.node_group,
|
||||
"Depth Scale",
|
||||
)
|
||||
|
||||
object_data.set_modifier_input(modifier, stretch_id, 0.0)
|
||||
bpy.context.view_layer.update()
|
||||
assert _evaluated_topology(obj) == (9, 8, set(), set())
|
||||
|
||||
object_data.set_modifier_input(modifier, stretch_id, 0.5)
|
||||
obj.update_tag(refresh={"DATA"})
|
||||
bpy.context.view_layer.update()
|
||||
assert _evaluated_topology(obj) == (9, 8, set(), set())
|
||||
|
||||
object_data.set_modifier_input(modifier, stretch_id, 0.6)
|
||||
obj.update_tag(refresh={"DATA"})
|
||||
bpy.context.view_layer.update()
|
||||
vertices, faces, loose_edges, loose_points = _evaluated_topology(obj)
|
||||
assert 0 < faces < 8
|
||||
assert vertices < 9
|
||||
assert not loose_edges
|
||||
assert not loose_points
|
||||
|
||||
object_data.set_modifier_input(modifier, depth_scale_id, 0.0)
|
||||
obj.update_tag(refresh={"DATA"})
|
||||
bpy.context.view_layer.update()
|
||||
assert _evaluated_topology(obj) == (9, 8, set(), set())
|
||||
obj, modifier = _depth_surface_step_object()
|
||||
split_id = object_data.modifier_input_identifier(modifier.node_group, "Split")
|
||||
for value in (0., .5, 1.):
|
||||
object_data.set_modifier_input(modifier, split_id, value)
|
||||
obj.update_tag(refresh={"DATA"})
|
||||
bpy.context.view_layer.update()
|
||||
vertices, faces, loose_edges, loose_points = _evaluated_topology(obj)
|
||||
assert vertices >= 9 and faces == 8
|
||||
assert not loose_edges and not loose_points
|
||||
|
||||
|
||||
def test_cutout_thickness_keeps_one_connected_shell():
|
||||
|
||||
Reference in New Issue
Block a user