Add new functionality for image rectification and depth metadata handling

- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`.
- Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`.
- Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration.
- Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency.
- Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments.
- Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
This commit is contained in:
2026-09-12 19:20:06 +08:00
parent e8268504c2
commit 938d95db0f
175 changed files with 3812 additions and 6864 deletions

View File

@@ -107,19 +107,19 @@ def _set_segmented_layer_z(
return merge.outputs["Geometry"]
def _build_cutout(group, geometry, thickness):
def _build_shell(group, geometry, thickness):
nodes = group.nodes
links = group.links
half = math_node(nodes, "MULTIPLY")
half.inputs[1].default_value = 0.5
links.new(thickness, half.inputs[0])
front = math_node(nodes, "MULTIPLY")
front.inputs[1].default_value = 0.0
links.new(thickness, front.inputs[0])
back = math_node(nodes, "MULTIPLY")
back.inputs[1].default_value = -1.0
links.new(half.outputs[0], back.inputs[0])
links.new(thickness, back.inputs[0])
shaped = _set_segmented_layer_z(
group,
geometry,
half.outputs[0],
front.outputs[0],
back.outputs[0],
thickness,
)
@@ -158,26 +158,26 @@ def build_image_cutout_group():
group = bpy.data.node_groups.new(name, 'GeometryNodeTree')
group.is_modifier, group.use_fake_user = (True, True)
group.color_tag = 'GEOMETRY'
group['anyimage_asset_revision'] = 11
group['anyimage_asset_revision'] = 13
interface_socket(group, 'Geometry', 'INPUT', 'NodeSocketGeometry')
interface_socket(group, 'Mode', 'INPUT', 'NodeSocketMenu')
float_input(group, 'Thickness', 1.0).description = 'Balloon profile multiplier; zero returns the sheet'
float_input(group, 'Uniform Thickness', 0.0, subtype='DISTANCE').description = 'Distance used only by Uniform thickness mode'
float_input(group, 'Thickness', 1.0).description = 'Control how much the surface bulges in Balloon mode. Zero removes the bulge.'
float_input(group, 'Shell Thickness', 0.0, subtype='DISTANCE').description = 'Add thickness behind the original surface while keeping the front surface in place.'
iterations = interface_socket(group, 'Smooth', 'INPUT', 'NodeSocketInt')
iterations.description = 'Set the number of mesh smoothing passes. Higher values smooth the surface more but may soften details. Zero disables smoothing.'
iterations.default_value = 0
iterations.min_value, iterations.max_value = (0, 20)
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)
data = group.interface.new_panel(name='Data', default_closed=True)
interface_socket(group, 'Inward Axis', 'INPUT', 'NodeSocketMenu', data).hide_in_modifier = True
float_input(group, 'Smooth Weight', 1.0, maximum=1, subtype='FACTOR', parent=options).description = 'Control the strength of each mesh smoothing pass. Higher values move vertices more.'
interface_socket(group, 'Depth Axis', 'INPUT', 'NodeSocketMenu', options).menu_expanded = True
interface_socket(group, 'Geometry', 'OUTPUT', 'NodeSocketGeometry')
controls = group_input(group.nodes, set())
balloon = _build_balloon(group, controls.outputs['Geometry'], controls.outputs['Thickness'])
uniform = _build_cutout(group, controls.outputs['Geometry'], controls.outputs['Uniform Thickness'])
choose = menu_switch(group, 'Mode', ('Balloon', 'Uniform'), 'GEOMETRY')
shell = _build_shell(group, controls.outputs['Geometry'], controls.outputs['Shell Thickness'])
choose = menu_switch(group, 'Mode', ('Balloon', 'Shell'), 'GEOMETRY')
group.links.new(balloon, choose.inputs['Balloon'])
group.links.new(uniform, choose.inputs['Uniform'])
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'})
@@ -198,15 +198,15 @@ def build_image_cutout_group():
inflated.inputs['False'].default_value = 1
group.links.new(enabled.outputs[0], inflated.inputs['Switch'])
group.links.new(profile, inflated.inputs['True'])
mode = menu_switch(group, 'Mode', ('Balloon', 'Uniform'), 'FLOAT')
mode.inputs['Uniform'].default_value = 1
mode = menu_switch(group, 'Mode', ('Balloon', 'Shell'), 'FLOAT')
mode.inputs['Shell'].default_value = 1
group.links.new(inflated.outputs[0], mode.inputs['Balloon'])
scale = mode.outputs[0]
geometry = store_float_attribute(group, geometry, NORMAL_SCALE_ATTRIBUTE_NAME, scale)
orient = group.nodes.new('GeometryNodeTransform')
orient.inputs['Rotation'].default_value = Matrix(CUTOUT_Z_TO_X).to_euler()
group.links.new(geometry, orient.inputs['Geometry'])
axis = menu_switch(group, 'Inward Axis', ('-Z', '+X'), 'GEOMETRY')
axis = menu_switch(group, 'Depth Axis', ('-Z', '+X'), 'GEOMETRY')
group.links.new(geometry, axis.inputs['-Z'])
group.links.new(orient.outputs[0], axis.inputs['+X'])
_shade_output(group, axis.outputs[0])
@@ -216,8 +216,8 @@ def build_image_cutout_group():
continue
if item.name == 'Mode':
item.default_value = 'Balloon'
elif item.name == 'Inward Axis':
elif item.name == 'Depth Axis':
item.default_value = '-Z'
elif item.name == 'Uniform Thickness':
elif item.name == 'Shell Thickness':
item.name = 'Thickness'
return group