Refactor depth reference handling and cleanup

- Unified reference depth calculation to use a single implementation in common/depth.py.
- Removed server-side reference depth computation and adjusted depth.json to only include image_size and intrinsics.
- Updated depth texture handling to ignore transparent, invalid, and non-positive pixels when calculating reference depth.
- Changed reference depth calculation to use the 95th percentile of valid depth values.
- Adjusted depth direction fitting to return a flat direction when no samples are available.
- Removed cleanup threshold parameters from Cutout and related nodes, simplifying the interface.
- Updated tests to verify new reference depth logic and ensure compatibility with existing functionality.
This commit is contained in:
2026-09-15 22:50:16 +08:00
parent 94130ff38f
commit 2dfa9b2add
37 changed files with 665 additions and 639 deletions

View File

@@ -3,27 +3,6 @@ from .nodes import math_node
from anyimage.operators.cutout_tool.shape import BALLOON_ATTRIBUTE_NAME
def _island_cleanup(group, geometry, cleanup):
nodes = group.nodes
links = group.links
mesh_island = nodes.new("GeometryNodeInputMeshIsland")
face_area = nodes.new("GeometryNodeInputMeshFaceArea")
island_area = nodes.new("GeometryNodeAccumulateField")
island_area.data_type = "FLOAT"
island_area.domain = "FACE"
links.new(face_area.outputs["Area"], island_area.inputs["Value"])
links.new(mesh_island.outputs["Island Index"], island_area.inputs["Group ID"])
tiny = math_node(nodes, "LESS_THAN")
links.new(island_area.outputs["Total"], tiny.inputs[0])
links.new(cleanup, tiny.inputs[1])
delete = nodes.new("GeometryNodeDeleteGeometry")
delete.domain = "FACE"
links.new(geometry, delete.inputs["Geometry"])
links.new(tiny.outputs[0], delete.inputs["Selection"])
return delete.outputs["Geometry"]
def _profile_field(group, geometry):
nodes = group.nodes
links = group.links

View File

@@ -2,7 +2,7 @@
import bpy
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
from ..common.nodes import interface_socket, group_input, math_node, float_input, menu_switch, read_float_attribute, store_float_attribute, prepare_node_group
from ..common.cutout import _extrude_layers, _island_cleanup, _offset_geometry_y, _remove_zero_thickness, _set_layer_y, _shade_output
from ..common.cutout import _extrude_layers, _offset_geometry_y, _remove_zero_thickness, _set_layer_y, _shade_output
NODE_GROUP_NAME = "O Image Cutout"
@@ -157,8 +157,6 @@ def build_image_cutout_group():
interface_socket(group, 'Mode', 'INPUT', 'NodeSocketMenu')
float_input(group, 'Thickness', 1.0, maximum=2.0).description = 'Control how much the surface bulges in Balloon mode. Zero removes the bulge.'
float_input(group, 'Shell Thickness', 0.0, maximum=1.0, subtype='DISTANCE').description = 'Add thickness behind the original surface while keeping the front surface in place.'
options = group.interface.new_panel(name='Options', default_closed=True)
float_input(group, 'Cleanup Threshold', 0.0, parent=options)
interface_socket(group, 'Geometry', 'OUTPUT', 'NodeSocketGeometry')
controls = group_input(group.nodes, set())
balloon = _build_balloon(group, controls.outputs['Geometry'], controls.outputs['Thickness'])
@@ -167,14 +165,6 @@ def build_image_cutout_group():
group.links.new(balloon, choose.inputs['Balloon'])
group.links.new(shell, choose.inputs['Shell'])
geometry = choose.outputs[0]
planar_inputs = [link.to_socket for link in group.links if link.from_node.bl_idname == 'NodeGroupInput' and link.from_socket.name == 'Geometry']
base = group_input(group.nodes, {'Geometry', 'Cleanup Threshold'})
area = math_node(group.nodes, 'MULTIPLY')
area.inputs[1].default_value = 0.01
group.links.new(base.outputs['Cleanup Threshold'], area.inputs[0])
cleaned = _island_cleanup(group, base.outputs['Geometry'], area.outputs[0])
for target in planar_inputs:
group.links.new(cleaned, target)
profile = read_float_attribute(group, 'o_balloon')
amount = group_input(group.nodes, {'Thickness'})
enabled = math_node(group.nodes, 'GREATER_THAN')

View File

@@ -1,9 +1,9 @@
"""Build O Image Depth Cutout."""
import bpy
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
from ..common.nodes import interface_socket, group_input, math_node, float_input, read_float_attribute, store_float_attribute, prepare_node_group, sample_field, evaluate_field
from ..common.nodes import interface_socket, group_input, float_input, read_float_attribute, store_float_attribute, prepare_node_group, sample_field, evaluate_field
from ..common.boundary_smoothing import smooth_cut_boundary
from ..common.cutout import _island_cleanup, _position_field, _remove_zero_thickness, _shade_output
from ..common.cutout import _position_field, _remove_zero_thickness, _shade_output
from ..common.depth_surface import _math, project_depth_surface, build_surface_camera, split_depth_surface, sample_face_camera, thicken_depth_surface
from ..common.smoothing import edge_boundary_field
from ..common.cutout_boundary import original_boundary_field, remove_boundary_triangles, taper_split_profile
@@ -344,7 +344,6 @@ def build_image_depth_cutout_group():
float_input(group, 'Depth Scale', 1.0, maximum=2.0)
float_input(group, 'Depth Split', 0.1, maximum=1, subtype='FACTOR').description = 'Split the mesh where depth changes abruptly. Higher values detect smaller depth jumps. Zero disables splitting.'
options = group.interface.new_panel(name='Options', default_closed=True)
float_input(group, 'Cleanup Threshold', 0.0, parent=options)
float_input(group, 'Smooth Weight', 1.0, maximum=1, subtype='FACTOR', parent=options).description = 'Control the strength of boundary smoothing. Higher values move boundary vertices more.'
float_input(group, 'Reference Depth', 1.0, subtype='DISTANCE', parent=options)
boundary_smooth = interface_socket(group, 'Boundary Smooth', 'INPUT', 'NodeSocketInt', options)
@@ -358,14 +357,9 @@ def build_image_depth_cutout_group():
float_input(group, 'Uniform Scale', 1.0, parent=data).hide_in_modifier = True
interface_socket(group, 'Depth Image', 'INPUT', 'NodeSocketImage', data).hide_in_modifier = True
interface_socket(group, 'Geometry', 'OUTPUT', 'NodeSocketGeometry')
base = group_input(group.nodes, {'Geometry', 'Cleanup Threshold'})
area = math_node(group.nodes, 'MULTIPLY')
area.inputs[1].default_value = 0.01
group.links.new(base.outputs['Cleanup Threshold'], area.inputs[0])
cleaned = _island_cleanup(group, base.outputs['Geometry'], area.outputs[0])
cleaned = store_float_attribute(group, cleaned, NORMAL_REDUCTION_ATTRIBUTE_NAME, 0.0, domain='FACE')
controls = group_input(group.nodes, set())
geometry = _build_depth_surface(group, cleaned, controls)
geometry = store_float_attribute(group, controls.outputs['Geometry'], NORMAL_REDUCTION_ATTRIBUTE_NAME, 0.0, domain='FACE')
geometry = _build_depth_surface(group, geometry, controls)
_shade_output(group, geometry)
prepare_node_group(group)
for item in group.interface.items_tree:

View File

@@ -42,7 +42,7 @@ def build_image_relief_plane_group(image_plane_group):
thickness.description = "Set the thickness of the flat base beneath the relief."
vector_input(group, "Depth Direction", (0, 0, 1), subtype="DIRECTION").description = "Set the direction used to orient the reference depth plane."
depth_offset = _interface_socket(group, "Depth Offset", "INPUT", "NodeSocketFloat")
depth_offset.default_value = 1.0
depth_offset.default_value = 0.0
depth_offset.min_value = -10.0
depth_offset.max_value = 10.0
depth_offset.subtype = "DISTANCE"