feat: Implement weight cutout boundary smoothing for image depth cutout

- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements.
- Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values.
- Updated specifications to define the behavior of boundary-specific smoothing strength and default settings.
- Created tasks to verify the implementation of boundary behavior and asset updates.
- Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance.
- Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials.
- Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
This commit is contained in:
2026-09-14 12:44:20 +08:00
parent 09778c4b48
commit dc000e50b1
53 changed files with 1350 additions and 364 deletions

View File

@@ -1,4 +1,4 @@
"""Exercise automatic arrangement without changing geometry or publishing assets."""
"""Exercise automatic arrangement while preserving node programs and geometry."""
import bpy
import numpy as np
@@ -6,12 +6,12 @@ import pytest
from tools.nodes.build import build_node_groups
from tools.nodes.check import GEOMETRY_NAMES
from tools.nodes.check import ASSET_PATH, GEOMETRY_NAMES, SHADER_NAMES
from tools.nodes.arrangement.arrange import arrange_nodes
from tools.nodes.arrangement.dimensions import estimate_node_dimensions
@pytest.fixture(params=sorted(GEOMETRY_NAMES))
@pytest.fixture(params=sorted(GEOMETRY_NAMES | SHADER_NAMES))
def node_group(request):
bpy.ops.wm.read_factory_settings(use_empty=True)
return next(group for group in build_node_groups() if group.name == request.param)
@@ -40,9 +40,18 @@ def _program(node_group):
settings = {
name: _value(getattr(node, name))
for name in ("operation", "data_type", "input_type", "domain", "mode", "clamp",
"interpolation", "interpolation_type", "extension", "pattern_mode")
"interpolation", "interpolation_type", "extension", "pattern_mode",
"space", "uv_map", "attribute_type", "attribute_name", "rotation_type",
"invert", "blend_type", "clamp_factor", "clamp_result", "distribution")
if hasattr(node, name)
}
if hasattr(node, "mapping"):
mapping = node.mapping
settings["mapping"] = (
mapping.extend, mapping.use_clip,
tuple((tuple(point.location), point.handle_type)
for curve in mapping.curves for point in curve.points),
)
inputs = [
(socket.identifier, _value(socket.default_value) if hasattr(socket, "default_value") else None,
[_source(link.from_socket) for link in sorted(
@@ -88,7 +97,7 @@ def _surface(node_group):
pixels[..., 2] = 2 + xx * 0.02
image.pixels.foreach_set(pixels.ravel())
values = {"Subdivide": 1, "Thickness": 0.2, "Depth Image": image,
"Uniform Scale": 1.0, "Reference Depth": 2.0, "Depth Scale": 0.8,
"Uniform Scale": 1.0, "Reference Depth": 2.5, "Depth Scale": 0.8,
"Smooth": 1, "Normal Smooth": 2, "Depth Split": 0.25}
mode = None
for item in node_group.interface.items_tree:
@@ -119,9 +128,9 @@ def _surface(node_group):
return evaluate
def test_arrangement_preserves_program_and_geometry(node_group):
def test_arrangement_preserves_program_and_evaluation(node_group):
program = _program(node_group)
evaluate = _surface(node_group)
evaluate = _surface(node_group) if node_group.bl_idname == "GeometryNodeTree" else list
geometry = evaluate()
arrange_nodes(node_group)
assert _program(node_group) == program
@@ -142,3 +151,13 @@ def test_arrangement_preserves_program_and_geometry(node_group):
arrange_nodes(node_group)
assert _program(node_group) == program
assert sum(node.bl_idname == "NodeGroupInput" for node in node_group.nodes) == input_count
@pytest.mark.parametrize("name", sorted(SHADER_NAMES))
def test_shader_asset_preserves_definition(name):
bpy.ops.wm.read_factory_settings(use_empty=True)
definition = next(group for group in build_node_groups() if group.name == name)
expected = _program(definition)
with bpy.data.libraries.load(str(ASSET_PATH)) as (_, target):
target.node_groups = [name]
assert _program(target.node_groups[0]) == expected