- Introduced a new design for selection rasterization that decouples the selection geometry from the image content, ensuring that the SelectionMask is independent of the source image's alpha and color. - Updated the `rasterize_selection_path` function to only accept selection paths and image dimensions, removing parameters related to source alpha and alpha thresholds. - Implemented a new approach for handling selection paths using non-zero winding fill, ensuring overlapping areas are preserved. - Enhanced performance by utilizing a scanline algorithm for rasterization, significantly reducing processing time for high-resolution images. - Updated internal documentation and added comprehensive tests to validate the new behavior and performance improvements. - Ensured backward compatibility by maintaining existing interfaces where necessary while introducing breaking changes in the selection processing logic.
210 lines
8.5 KiB
Python
210 lines
8.5 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, parent=None):
|
|
socket = group.interface.new_socket(
|
|
name=name,
|
|
in_out=in_out,
|
|
socket_type=socket_type,
|
|
parent=parent,
|
|
)
|
|
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 _math(nodes, operation, location):
|
|
node = nodes.new("ShaderNodeMath")
|
|
node.operation = operation
|
|
node.location = location
|
|
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
|
|
geometry = _interface_socket(group, "Geometry", "INPUT", "NodeSocketGeometry")
|
|
geometry.structure_type = "SINGLE"
|
|
subdivide = _interface_socket(group, "Subdivide", "INPUT", "NodeSocketInt")
|
|
subdivide.default_value = 6
|
|
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_scale = _interface_socket(group, "Depth Scale", "INPUT", "NodeSocketFloat")
|
|
depth_scale.default_value = 1.0
|
|
depth_scale.min_value = 0.0
|
|
options = group.interface.new_panel(name="Options", default_closed=True)
|
|
base_plane_depth = _interface_socket(
|
|
group,
|
|
"Base Plane Depth",
|
|
"INPUT",
|
|
"NodeSocketFloat",
|
|
parent=options,
|
|
)
|
|
base_plane_depth.default_value = 1.0
|
|
base_plane_depth.min_value = 0.0
|
|
base_plane_depth.subtype = "DISTANCE"
|
|
data = group.interface.new_panel(name="Data", default_closed=True)
|
|
uniform_scale = _interface_socket(
|
|
group,
|
|
"Uniform Scale",
|
|
"INPUT",
|
|
"NodeSocketFloat",
|
|
parent=data,
|
|
)
|
|
uniform_scale.default_value = 1.0
|
|
uniform_scale.min_value = 0.0
|
|
uniform_scale.hide_in_modifier = True
|
|
depth_image = _interface_socket(
|
|
group,
|
|
"Depth Image",
|
|
"INPUT",
|
|
"NodeSocketImage",
|
|
parent=data,
|
|
)
|
|
depth_image.hide_in_modifier = True
|
|
_interface_socket(group, "Geometry", "OUTPUT", "NodeSocketGeometry")
|
|
|
|
nodes = group.nodes
|
|
links = group.links
|
|
geometry_input = _group_input(
|
|
nodes,
|
|
(-1900.0, 500.0),
|
|
{"Geometry", "Subdivide"},
|
|
)
|
|
shape_input = _group_input(
|
|
nodes,
|
|
(-1550.0, 950.0),
|
|
{"Thickness", "Depth Scale", "Base Plane Depth"},
|
|
)
|
|
image_input = _group_input(
|
|
nodes,
|
|
(-1200.0, -350.0),
|
|
{"Depth Image", "Uniform Scale"},
|
|
)
|
|
group_output = nodes.new("NodeGroupOutput")
|
|
group_output.location = (1550.0, 500.0)
|
|
mesh_plane = nodes.new("GeometryNodeGroup")
|
|
mesh_plane.node_tree = mesh_plane_group
|
|
mesh_plane.location = (-1600.0, 500.0)
|
|
links.new(geometry_input.outputs["Geometry"], mesh_plane.inputs["Geometry"])
|
|
links.new(geometry_input.outputs["Subdivide"], mesh_plane.inputs["Subdivide"])
|
|
links.new(shape_input.outputs["Thickness"], mesh_plane.inputs["Thickness"])
|
|
|
|
positive_half = _math(nodes, "MULTIPLY", (-1300.0, 300.0))
|
|
positive_half.inputs[1].default_value = 0.5
|
|
links.new(shape_input.outputs["Thickness"], positive_half.inputs[0])
|
|
translation = nodes.new("ShaderNodeCombineXYZ")
|
|
translation.location = (-1100.0, 300.0)
|
|
links.new(positive_half.outputs[0], translation.inputs["Z"])
|
|
place_block = nodes.new("GeometryNodeTransform")
|
|
place_block.location = (-900.0, 500.0)
|
|
links.new(mesh_plane.outputs["Geometry"], place_block.inputs["Geometry"])
|
|
links.new(translation.outputs["Vector"], place_block.inputs["Translation"])
|
|
|
|
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
|
uv.data_type = "FLOAT_VECTOR"
|
|
uv.inputs["Name"].default_value = "UVMap"
|
|
uv.location = (-950.0, -350.0)
|
|
texture = nodes.new("GeometryNodeImageTexture")
|
|
texture.interpolation = "Linear"
|
|
texture.extension = "EXTEND"
|
|
texture.location = (-700.0, -350.0)
|
|
links.new(image_input.outputs["Depth Image"], texture.inputs["Image"])
|
|
links.new(uv.outputs["Attribute"], texture.inputs["Vector"])
|
|
depth_components = nodes.new("ShaderNodeSeparateXYZ")
|
|
depth_components.location = (-450.0, -350.0)
|
|
links.new(texture.outputs["Color"], depth_components.inputs["Vector"])
|
|
metric_depth = _math(nodes, "MULTIPLY", (-200.0, -350.0))
|
|
links.new(depth_components.outputs["X"], metric_depth.inputs[0])
|
|
links.new(image_input.outputs["Uniform Scale"], metric_depth.inputs[1])
|
|
relative_depth = _math(nodes, "SUBTRACT", (50.0, -350.0))
|
|
links.new(shape_input.outputs["Base Plane Depth"], relative_depth.inputs[0])
|
|
links.new(metric_depth.outputs[0], relative_depth.inputs[1])
|
|
full_offset = _math(nodes, "MULTIPLY", (300.0, -350.0))
|
|
links.new(relative_depth.outputs[0], full_offset.inputs[0])
|
|
links.new(shape_input.outputs["Depth Scale"], full_offset.inputs[1])
|
|
|
|
inward_limit = _math(nodes, "MULTIPLY", (50.0, -600.0))
|
|
inward_limit.inputs[1].default_value = -0.999
|
|
links.new(shape_input.outputs["Thickness"], inward_limit.inputs[0])
|
|
limited_offset = _math(nodes, "MAXIMUM", (300.0, -600.0))
|
|
links.new(full_offset.outputs[0], limited_offset.inputs[0])
|
|
links.new(inward_limit.outputs[0], limited_offset.inputs[1])
|
|
has_thickness = _math(nodes, "GREATER_THAN", (50.0, -850.0))
|
|
has_thickness.inputs[1].default_value = 1e-6
|
|
links.new(shape_input.outputs["Thickness"], has_thickness.inputs[0])
|
|
safe_offset = nodes.new("GeometryNodeSwitch")
|
|
safe_offset.input_type = "FLOAT"
|
|
safe_offset.location = (550.0, -450.0)
|
|
links.new(has_thickness.outputs[0], safe_offset.inputs["Switch"])
|
|
links.new(full_offset.outputs[0], safe_offset.inputs["False"])
|
|
links.new(limited_offset.outputs[0], safe_offset.inputs["True"])
|
|
|
|
position = nodes.new("GeometryNodeInputPosition")
|
|
position.location = (-650.0, 50.0)
|
|
position_components = nodes.new("ShaderNodeSeparateXYZ")
|
|
position_components.location = (-400.0, 50.0)
|
|
links.new(position.outputs["Position"], position_components.inputs["Vector"])
|
|
layer_ratio = _math(nodes, "DIVIDE", (-150.0, 50.0))
|
|
links.new(position_components.outputs["Z"], layer_ratio.inputs[0])
|
|
links.new(shape_input.outputs["Thickness"], layer_ratio.inputs[1])
|
|
nonnegative_ratio = _math(nodes, "MAXIMUM", (350.0, 50.0))
|
|
nonnegative_ratio.inputs[1].default_value = 0.0
|
|
links.new(layer_ratio.outputs[0], nonnegative_ratio.inputs[0])
|
|
clamped_ratio = _math(nodes, "MINIMUM", (600.0, 50.0))
|
|
clamped_ratio.inputs[1].default_value = 1.0
|
|
links.new(nonnegative_ratio.outputs[0], clamped_ratio.inputs[0])
|
|
layer_weight = nodes.new("GeometryNodeSwitch")
|
|
layer_weight.input_type = "FLOAT"
|
|
layer_weight.location = (850.0, 50.0)
|
|
layer_weight.inputs["False"].default_value = 1.0
|
|
links.new(has_thickness.outputs[0], layer_weight.inputs["Switch"])
|
|
links.new(clamped_ratio.outputs[0], layer_weight.inputs["True"])
|
|
|
|
weighted_offset = _math(nodes, "MULTIPLY", (850.0, -350.0))
|
|
links.new(safe_offset.outputs["Output"], weighted_offset.inputs[0])
|
|
links.new(layer_weight.outputs["Output"], weighted_offset.inputs[1])
|
|
offset = nodes.new("ShaderNodeCombineXYZ")
|
|
offset.location = (1050.0, -200.0)
|
|
links.new(weighted_offset.outputs[0], offset.inputs["Z"])
|
|
set_position = nodes.new("GeometryNodeSetPosition")
|
|
set_position.location = (1050.0, 500.0)
|
|
links.new(place_block.outputs["Geometry"], set_position.inputs["Geometry"])
|
|
links.new(offset.outputs["Vector"], set_position.inputs["Offset"])
|
|
shade_smooth = nodes.new("GeometryNodeSetShadeSmooth")
|
|
shade_smooth.domain = "FACE"
|
|
shade_smooth.inputs["Shade Smooth"].default_value = True
|
|
shade_smooth.location = (1300.0, 500.0)
|
|
links.new(set_position.outputs["Geometry"], shade_smooth.inputs["Geometry"])
|
|
links.new(shade_smooth.outputs["Geometry"], group_output.inputs["Geometry"])
|
|
group.color_tag = "GEOMETRY"
|
|
group.description = (
|
|
"Build a single-sided depth terrain block with a fixed base."
|
|
)
|
|
group.use_fake_user = True
|
|
return group
|