- Added validation documentation for AI model tiers. - Introduced `background.py` for background removal logic, including inference functions for alpha and foreground extraction. - Created `onnx_birefnet.py` to handle BiRefNet model sessions and alpha inference. - Developed tests for background models, ensuring proper dispatch and session reuse. - Added tests for BiRefNet ONNX model to validate alpha output and error handling for invalid values. - Implemented model export scripts for BiRefNet and HAT Sharper, ensuring proper weight compression and licensing. - Created `deform_sampling.py` for deformable convolution operations used in BiRefNet. - Added utility functions for compressing ONNX weights without altering graph arithmetic. - Introduced a reference plane node for depth field adjustments in geometry processing.
163 lines
7.6 KiB
Python
163 lines
7.6 KiB
Python
"""Build O Image Depth Balloon."""
|
|
import bpy
|
|
from mathutils import Matrix
|
|
from anyimage.operators.cutout_tool.shape import CUTOUT_Z_TO_X, NORMAL_SCALE_ATTRIBUTE_NAME
|
|
from ..common.nodes import (
|
|
interface_socket, group_input, math_node, float_input, bool_input, vector_input,
|
|
menu_switch, read_float_attribute, store_float_attribute, prepare_node_group,
|
|
)
|
|
from ..common.smoothing import expand_smooth
|
|
from ..common.reference_plane import reference_plane_depth
|
|
from ..common.cutout import _extrude_layers, _island_cleanup, _position_field, _profile_field, _remove_zero_thickness, _set_layer_z, _shade_output
|
|
|
|
|
|
NODE_GROUP_NAME = "O Image Depth Balloon"
|
|
|
|
|
|
def _set_geometry_z(group, geometry, z_value):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
position = nodes.new("GeometryNodeInputPosition")
|
|
separate = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(position.outputs["Position"], separate.inputs["Vector"])
|
|
combine = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(separate.outputs["X"], combine.inputs["X"])
|
|
links.new(separate.outputs["Y"], combine.inputs["Y"])
|
|
links.new(z_value, combine.inputs["Z"])
|
|
set_position = nodes.new("GeometryNodeSetPosition")
|
|
links.new(geometry, set_position.inputs["Geometry"])
|
|
links.new(combine.outputs["Vector"], set_position.inputs["Position"])
|
|
return set_position.outputs["Geometry"]
|
|
|
|
|
|
def _build_depth_balloon(
|
|
group,
|
|
geometry,
|
|
thickness,
|
|
depth_scale,
|
|
double_sided,
|
|
uniform_scale,
|
|
depth_direction,
|
|
depth_image,
|
|
depth_reference,
|
|
):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
profile, profile_factor = _profile_field(group, geometry)
|
|
camera_position = _position_field(group, depth_image)
|
|
separate_camera = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(camera_position, separate_camera.inputs["Vector"])
|
|
metric_depth = math_node(nodes, "MULTIPLY")
|
|
links.new(separate_camera.outputs["Z"], metric_depth.inputs[0])
|
|
links.new(uniform_scale, metric_depth.inputs[1])
|
|
plane_depth = reference_plane_depth(
|
|
group,
|
|
depth_direction,
|
|
depth_reference,
|
|
)
|
|
corrected_depth = math_node(nodes, "SUBTRACT")
|
|
links.new(plane_depth, corrected_depth.inputs[0])
|
|
links.new(metric_depth.outputs[0], corrected_depth.inputs[1])
|
|
nonnegative_depth = math_node(nodes, "MAXIMUM")
|
|
nonnegative_depth.inputs[1].default_value = 0.0
|
|
links.new(corrected_depth.outputs[0], nonnegative_depth.inputs[0])
|
|
scaled_depth = math_node(nodes, "MULTIPLY")
|
|
links.new(nonnegative_depth.outputs[0], scaled_depth.inputs[0])
|
|
links.new(depth_scale, scaled_depth.inputs[1])
|
|
front_depth = scaled_depth.outputs[0]
|
|
base_front = math_node(nodes, "MULTIPLY")
|
|
links.new(profile, base_front.inputs[0])
|
|
links.new(thickness, base_front.inputs[1])
|
|
base_back = math_node(nodes, "MULTIPLY")
|
|
links.new(profile, base_back.inputs[0])
|
|
links.new(thickness, base_back.inputs[1])
|
|
negate_back = math_node(nodes, "MULTIPLY")
|
|
negate_back.inputs[1].default_value = -1.0
|
|
links.new(base_back.outputs[0], negate_back.inputs[0])
|
|
front_mix = nodes.new("ShaderNodeMix")
|
|
front_mix.data_type = "FLOAT"
|
|
front_mix.clamp_factor = True
|
|
links.new(profile_factor, front_mix.inputs["Factor"])
|
|
links.new(base_front.outputs[0], front_mix.inputs["A"])
|
|
links.new(front_depth, front_mix.inputs["B"])
|
|
mirrored_front = math_node(nodes, "MULTIPLY")
|
|
mirrored_front.inputs[1].default_value = -1.0
|
|
links.new(front_mix.outputs["Result"], mirrored_front.inputs[0])
|
|
back_choice = nodes.new("GeometryNodeSwitch")
|
|
back_choice.input_type = "FLOAT"
|
|
links.new(double_sided, back_choice.inputs["Switch"])
|
|
links.new(negate_back.outputs[0], back_choice.inputs["False"])
|
|
links.new(mirrored_front.outputs[0], back_choice.inputs["True"])
|
|
extrude = _extrude_layers(group, geometry)
|
|
shaped = _set_layer_z(
|
|
group,
|
|
geometry,
|
|
extrude,
|
|
front_mix.outputs["Result"],
|
|
back_choice.outputs["Output"],
|
|
)
|
|
front_only = _set_geometry_z(
|
|
group, geometry, front_mix.outputs["Result"]
|
|
)
|
|
return _remove_zero_thickness(group, front_only, shaped, thickness)
|
|
|
|
|
|
def build_image_depth_balloon_group():
|
|
name = NODE_GROUP_NAME
|
|
existing = bpy.data.node_groups.get(name)
|
|
if existing is not None:
|
|
return existing
|
|
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
|
|
interface_socket(group, 'Geometry', 'INPUT', 'NodeSocketGeometry')
|
|
float_input(group, 'Thickness', 1.0).description = 'Balloon profile multiplier; zero returns the sheet'
|
|
float_input(group, 'Depth Scale', 1.0)
|
|
bool_input(group, 'Double Sided', True)
|
|
iterations = interface_socket(group, 'Smooth', 'INPUT', 'NodeSocketInt')
|
|
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, 'Reference Depth', 1.0, subtype='DISTANCE', parent=options)
|
|
float_input(group, 'Smooth Weight', 1.0, maximum=1, subtype='FACTOR', parent=options)
|
|
vector_input(group, 'Depth Direction', (0, 0, 1), subtype='DIRECTION', 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, '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')
|
|
controls = group_input(group.nodes, set())
|
|
geometry = _build_depth_balloon(group, controls.outputs['Geometry'], controls.outputs['Thickness'], controls.outputs['Depth Scale'], controls.outputs['Double Sided'], controls.outputs['Uniform Scale'], controls.outputs['Depth Direction'], controls.outputs['Depth Image'], controls.outputs['Reference Depth'])
|
|
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)
|
|
geometry = expand_smooth(group, geometry, iterations_name='Smooth', weight_name='Smooth Weight')
|
|
profile = read_float_attribute(group, 'o_balloon')
|
|
scale = profile
|
|
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')
|
|
group.links.new(geometry, axis.inputs['-Z'])
|
|
group.links.new(orient.outputs[0], axis.inputs['+X'])
|
|
_shade_output(group, axis.outputs[0])
|
|
prepare_node_group(group)
|
|
for item in group.interface.items_tree:
|
|
if item.item_type != 'SOCKET' or item.in_out != 'INPUT':
|
|
continue
|
|
if item.name == 'Mode':
|
|
item.default_value = 'Balloon'
|
|
elif item.name == 'Inward Axis':
|
|
item.default_value = '-Z'
|
|
elif item.name == 'Uniform Thickness':
|
|
item.name = 'Thickness'
|
|
return group
|