Files
AnyImage/tools/nodes/groups/image_layer.py
nan 938d95db0f 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.
2026-09-12 19:20:06 +08:00

94 lines
4.9 KiB
Python

"""Build O Image Layer."""
import bpy
from anyimage.common.material import IMAGE_MATERIAL_NODE_GROUP_NAME
from ..common.shader import build_material_nodes, connect_material_outputs
def build_image_layer_group():
group_name = IMAGE_MATERIAL_NODE_GROUP_NAME
existing = bpy.data.node_groups.get(group_name)
if existing is not None:
return existing
group = bpy.data.node_groups.new(group_name, 'ShaderNodeTree')
interface = group.interface
input_sockets = [('Color', 'NodeSocketColor'), ('Alpha', 'NodeSocketFloat'), ('Bump Scale', 'NodeSocketFloat')]
input_sockets.insert(2, ('Normal', 'NodeSocketColor'))
input_sockets.insert(3, ('Normal Scale', 'NodeSocketFloat'))
input_sockets.insert(4, ('Object Space', 'NodeSocketBool'))
input_sockets.insert(2, ('Alpha Fix', 'NodeSocketFloat'))
options = interface.new_panel(name='Options', default_closed=True)
option_names = {'Alpha Fix', 'Normal Scale', 'Object Space'}
for name, socket_type in input_sockets:
socket = interface.new_socket(name=name, in_out='INPUT', socket_type=socket_type, parent=options if name in option_names else None)
if name in {'Alpha', 'Alpha Fix'}:
socket.default_value = 1.0 if name == 'Alpha' else 0.0
socket.min_value = 0.0
socket.max_value = 1.0
socket.subtype = 'FACTOR'
elif name == 'Bump Scale':
socket.description = 'Control bump detail derived from image brightness. Negative values reverse the bumps. Zero disables the effect.'
socket.default_value = 0.0
socket.min_value = -10000.0
socket.max_value = 10000.0
elif name == 'Normal':
socket.default_value = (0.5, 0.5, 1.0, 1.0)
elif name == 'Normal Scale':
socket.description = 'Control the strength of surface detail from the normal map.'
socket.default_value = 1.0
socket.min_value = 0.0
socket.max_value = 10.0
elif name == 'Object Space':
socket.description = 'Interpret the normal map in object space when enabled, or tangent space when disabled.'
socket.default_value = False
if name == 'Alpha Fix':
socket.description = 'Reduce semi-transparent fringes around image edges. Higher values remove more faint edge pixels. Zero keeps the original transparency.'
output_sockets = [('Color', 'NodeSocketColor'), ('Roughness', 'NodeSocketFloat'), ('Alpha', 'NodeSocketFloat'), ('Normal', 'NodeSocketVector')]
for name, socket_type in output_sockets:
interface.new_socket(name=name, in_out='OUTPUT', socket_type=socket_type)
nodes, links = group.nodes, group.links
group_input, group_output, roughness_curve, bump = build_material_nodes(group)
tangent_normal = nodes.new('ShaderNodeNormalMap')
tangent_normal.space = 'TANGENT'
tangent_normal.location = (-500.0, -100.0)
object_normal = nodes.new('ShaderNodeNormalMap')
object_normal.space = 'OBJECT'
object_normal.location = (-500.0, -350.0)
choose_normal = nodes.new('ShaderNodeMix')
choose_normal.data_type = 'VECTOR'
choose_normal.location = (-150.0, -150.0)
for normal_map in (tangent_normal, object_normal):
links.new(group_input.outputs['Normal'], normal_map.inputs['Color'])
links.new(group_input.outputs['Normal Scale'], normal_map.inputs['Strength'])
links.new(group_input.outputs['Object Space'], choose_normal.inputs[0])
links.new(tangent_normal.outputs['Normal'], choose_normal.inputs[4])
links.new(object_normal.outputs['Normal'], choose_normal.inputs[5])
links.new(choose_normal.outputs['Result'], bump.inputs['Normal'])
processed_alpha = group_input.outputs['Alpha']
alpha_curve = nodes.new('ShaderNodeFloatCurve')
alpha_curve.mapping.initialize()
alpha_curve.mapping.extend = 'HORIZONTAL'
alpha_curve.mapping.use_clip = True
alpha_points = alpha_curve.mapping.curves[0].points
alpha_points[0].location = (0.0, 0.0)
alpha_points[-1].location = (1.0, 1.0)
alpha_points.new(0.8, 0.0)
alpha_points.new(0.925, 0.925)
for point in alpha_points:
point.handle_type = 'AUTO_CLAMPED'
alpha_curve.mapping.update()
group_input.location = (-750.0, 150.0)
group_output.location = (350.0, 450.0)
alpha_curve.location = (-350.0, 1100.0)
roughness_curve.location = (-350.0, 650.0)
links.new(group_input.outputs['Alpha'], alpha_curve.inputs['Value'])
links.new(group_input.outputs['Alpha Fix'], alpha_curve.inputs['Factor'])
processed_alpha = alpha_curve.outputs['Value']
connect_material_outputs(group, group_input, group_output, roughness_curve, bump, processed_alpha, (-250.0, 150.0))
group.color_tag = 'SHADER'
group.description = f'Prepare {group_name} material outputs.'
group.use_fake_user = True
for node in nodes:
for socket in node.outputs:
socket.hide = not socket.is_linked
return group