- Updated the depth plane node group to load from an asset and simplified the interface. - Created a new build script for depth plane geometry nodes. - Introduced material layer groups for image and depth materials with appropriate sockets. - Added functionality to connect balloon normal scale to the image material. - Enhanced tests to validate the new node groups and their connections. - Ensured that production code does not create node groups directly.
191 lines
8.0 KiB
Python
191 lines
8.0 KiB
Python
"""Build the Depth Plane Geometry Nodes group."""
|
|
|
|
import bpy
|
|
|
|
|
|
DEPTH_PLANE_NODE_GROUP_NAME = "O Image Depth Plane"
|
|
|
|
|
|
def _interface_socket(group, name, in_out, socket_type):
|
|
socket = group.interface.new_socket(
|
|
name=name,
|
|
in_out=in_out,
|
|
socket_type=socket_type,
|
|
)
|
|
if in_out == "INPUT" and hasattr(socket, "structure_type"):
|
|
socket.structure_type = "SINGLE"
|
|
return socket
|
|
|
|
|
|
def _group_input(nodes, location, visible_names):
|
|
node = nodes.new("NodeGroupInput")
|
|
node.location = location
|
|
visible = set(visible_names)
|
|
for output in node.outputs:
|
|
output.hide = output.name not in visible
|
|
return node
|
|
|
|
|
|
def build_depth_plane_group(mesh_plane_group):
|
|
group = bpy.data.node_groups.get(DEPTH_PLANE_NODE_GROUP_NAME)
|
|
if group is not None:
|
|
return group
|
|
group = bpy.data.node_groups.new(
|
|
DEPTH_PLANE_NODE_GROUP_NAME,
|
|
"GeometryNodeTree",
|
|
)
|
|
group.is_modifier = True
|
|
_interface_socket(group, "Geometry", "INPUT", "NodeSocketGeometry")
|
|
subdivide = _interface_socket(group, "Subdivide", "INPUT", "NodeSocketInt")
|
|
subdivide.default_value = 7
|
|
subdivide.min_value = 0
|
|
subdivide.max_value = 10
|
|
thickness = _interface_socket(group, "Thickness", "INPUT", "NodeSocketFloat")
|
|
thickness.default_value = 0.1
|
|
thickness.min_value = 0.0
|
|
thickness.subtype = "DISTANCE"
|
|
depth_amount = _interface_socket(group, "Depth Amount", "INPUT", "NodeSocketFloat")
|
|
depth_amount.default_value = 1.0
|
|
depth_amount.min_value = 0.0
|
|
reference_depth = _interface_socket(
|
|
group, "Reference Depth", "INPUT", "NodeSocketFloat"
|
|
)
|
|
reference_depth.hide_in_modifier = True
|
|
uniform_scale = _interface_socket(
|
|
group, "Uniform Scale", "INPUT", "NodeSocketFloat"
|
|
)
|
|
uniform_scale.hide_in_modifier = True
|
|
depth_image = _interface_socket(group, "Depth Image", "INPUT", "NodeSocketImage")
|
|
depth_image.hide_in_modifier = True
|
|
material = _interface_socket(group, "Material", "INPUT", "NodeSocketMaterial")
|
|
material.hide_in_modifier = True
|
|
_interface_socket(group, "Geometry", "OUTPUT", "NodeSocketGeometry")
|
|
|
|
nodes = group.nodes
|
|
links = group.links
|
|
geometry_input = _group_input(
|
|
nodes,
|
|
(-1850.0, 450.0),
|
|
{"Geometry", "Subdivide", "Material"},
|
|
)
|
|
shape_input = _group_input(
|
|
nodes,
|
|
(-1250.0, 850.0),
|
|
{"Thickness", "Depth Amount"},
|
|
)
|
|
image_input = _group_input(
|
|
nodes,
|
|
(-1050.0, 50.0),
|
|
{"Depth Image", "Reference Depth", "Uniform Scale"},
|
|
)
|
|
cleanup_input = _group_input(nodes, (650.0, -200.0), {"Thickness"})
|
|
group_output = nodes.new("NodeGroupOutput")
|
|
group_output.location = (1850.0, 450.0)
|
|
mesh_plane = nodes.new("GeometryNodeGroup")
|
|
mesh_plane.node_tree = mesh_plane_group
|
|
mesh_plane.inputs["Thickness"].default_value = 0.0
|
|
mesh_plane.location = (-1550.0, 450.0)
|
|
links.new(geometry_input.outputs["Geometry"], mesh_plane.inputs["Geometry"])
|
|
links.new(geometry_input.outputs["Subdivide"], mesh_plane.inputs["Subdivide"])
|
|
links.new(geometry_input.outputs["Material"], mesh_plane.inputs["Material"])
|
|
|
|
extrude = nodes.new("GeometryNodeExtrudeMesh")
|
|
extrude.mode = "FACES"
|
|
extrude.inputs["Offset"].default_value = (0.0, 0.0, -1.0)
|
|
extrude.inputs["Individual"].default_value = False
|
|
extrude.location = (-1200.0, 450.0)
|
|
links.new(mesh_plane.outputs["Geometry"], extrude.inputs["Mesh"])
|
|
links.new(shape_input.outputs["Thickness"], extrude.inputs["Offset Scale"])
|
|
geometry = extrude.outputs["Mesh"]
|
|
|
|
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
|
uv.data_type = "FLOAT_VECTOR"
|
|
uv.inputs["Name"].default_value = "UVMap"
|
|
uv.location = (-1000.0, 50.0)
|
|
texture = nodes.new("GeometryNodeImageTexture")
|
|
texture.location = (-800.0, 50.0)
|
|
links.new(image_input.outputs["Depth Image"], texture.inputs["Image"])
|
|
links.new(uv.outputs["Attribute"], texture.inputs["Vector"])
|
|
separate = nodes.new("ShaderNodeSeparateXYZ")
|
|
separate.location = (-600.0, 50.0)
|
|
links.new(texture.outputs["Color"], separate.inputs["Vector"])
|
|
relative_depth = nodes.new("ShaderNodeMath")
|
|
relative_depth.operation = "SUBTRACT"
|
|
relative_depth.location = (-600.0, -150.0)
|
|
links.new(image_input.outputs["Reference Depth"], relative_depth.inputs[0])
|
|
links.new(separate.outputs["X"], relative_depth.inputs[1])
|
|
local_depth = nodes.new("ShaderNodeMath")
|
|
local_depth.operation = "MULTIPLY"
|
|
local_depth.location = (-350.0, -150.0)
|
|
links.new(relative_depth.outputs[0], local_depth.inputs[0])
|
|
links.new(image_input.outputs["Uniform Scale"], local_depth.inputs[1])
|
|
raw_depth = nodes.new("ShaderNodeMath")
|
|
raw_depth.operation = "MULTIPLY"
|
|
raw_depth.location = (-100.0, 150.0)
|
|
links.new(local_depth.outputs[0], raw_depth.inputs[0])
|
|
links.new(shape_input.outputs["Depth Amount"], raw_depth.inputs[1])
|
|
inward_limit = nodes.new("ShaderNodeMath")
|
|
inward_limit.operation = "MULTIPLY"
|
|
inward_limit.inputs[1].default_value = -0.98
|
|
inward_limit.location = (-100.0, -50.0)
|
|
links.new(shape_input.outputs["Thickness"], inward_limit.inputs[0])
|
|
clamp = nodes.new("ShaderNodeMath")
|
|
clamp.operation = "MAXIMUM"
|
|
clamp.location = (150.0, 50.0)
|
|
links.new(raw_depth.outputs[0], clamp.inputs[0])
|
|
links.new(inward_limit.outputs[0], clamp.inputs[1])
|
|
has_thickness = nodes.new("ShaderNodeMath")
|
|
has_thickness.operation = "GREATER_THAN"
|
|
has_thickness.inputs[1].default_value = 1e-6
|
|
has_thickness.location = (-100.0, -350.0)
|
|
links.new(shape_input.outputs["Thickness"], has_thickness.inputs[0])
|
|
choose_depth = nodes.new("GeometryNodeSwitch")
|
|
choose_depth.input_type = "FLOAT"
|
|
choose_depth.location = (400.0, 50.0)
|
|
links.new(has_thickness.outputs[0], choose_depth.inputs["Switch"])
|
|
links.new(raw_depth.outputs[0], choose_depth.inputs["False"])
|
|
links.new(clamp.outputs[0], choose_depth.inputs["True"])
|
|
combine = nodes.new("ShaderNodeCombineXYZ")
|
|
combine.location = (600.0, 50.0)
|
|
links.new(choose_depth.outputs["Output"], combine.inputs["Z"])
|
|
front = nodes.new("FunctionNodeBooleanMath")
|
|
front.operation = "NOT"
|
|
front.location = (400.0, 300.0)
|
|
links.new(extrude.outputs["Top"], front.inputs[0])
|
|
set_position = nodes.new("GeometryNodeSetPosition")
|
|
set_position.location = (800.0, 450.0)
|
|
links.new(geometry, set_position.inputs["Geometry"])
|
|
links.new(front.outputs[0], set_position.inputs["Selection"])
|
|
links.new(combine.outputs["Vector"], set_position.inputs["Offset"])
|
|
geometry = set_position.outputs["Geometry"]
|
|
|
|
zero_thickness = nodes.new("ShaderNodeMath")
|
|
zero_thickness.operation = "LESS_THAN"
|
|
zero_thickness.inputs[1].default_value = 1e-6
|
|
zero_thickness.location = (1000.0, -150.0)
|
|
links.new(cleanup_input.outputs["Thickness"], zero_thickness.inputs[0])
|
|
back_or_side = nodes.new("FunctionNodeBooleanMath")
|
|
back_or_side.operation = "OR"
|
|
back_or_side.location = (1000.0, 150.0)
|
|
links.new(extrude.outputs["Top"], back_or_side.inputs[0])
|
|
links.new(extrude.outputs["Side"], back_or_side.inputs[1])
|
|
delete_selection = nodes.new("FunctionNodeBooleanMath")
|
|
delete_selection.operation = "AND"
|
|
delete_selection.location = (1200.0, 50.0)
|
|
links.new(zero_thickness.outputs[0], delete_selection.inputs[0])
|
|
links.new(back_or_side.outputs[0], delete_selection.inputs[1])
|
|
delete_geometry = nodes.new("GeometryNodeDeleteGeometry")
|
|
delete_geometry.domain = "FACE"
|
|
delete_geometry.location = (1350.0, 450.0)
|
|
links.new(geometry, delete_geometry.inputs["Geometry"])
|
|
links.new(delete_selection.outputs[0], delete_geometry.inputs["Selection"])
|
|
shade_smooth = nodes.new("GeometryNodeSetShadeSmooth")
|
|
shade_smooth.domain = "FACE"
|
|
shade_smooth.inputs["Shade Smooth"].default_value = True
|
|
shade_smooth.location = (1600.0, 450.0)
|
|
links.new(delete_geometry.outputs["Geometry"], shade_smooth.inputs["Geometry"])
|
|
links.new(shade_smooth.outputs["Geometry"], group_output.inputs["Geometry"])
|
|
group.use_fake_user = True
|
|
return group
|
|
|