Add depth surface processing and layout tools
- Implemented `cutout_depth_surface.py` for face separation and connected thickness calculations for depth surface assets. - Created `cutout_depth_surface_layout.py` to arrange nodes for staggered calculation branches in depth surface processing. - Authored `geometry_layouts.py` to define layouts for various geometry assets, including image cutouts and depth planes. - Introduced `node_layout.py` for shared presentation rules, facilitating node arrangement and validation in geometry-node layouts.
This commit is contained in:
@@ -3,6 +3,14 @@
|
||||
|
||||
import bpy
|
||||
from node_graph import expand_smooth
|
||||
from cutout_depth_surface import (
|
||||
store_surface_camera,
|
||||
remove_surface_attributes,
|
||||
split_depth_surface,
|
||||
thicken_depth_surface,
|
||||
)
|
||||
from cutout_depth_surface_layout import layout_depth_surface
|
||||
from geometry_layouts import layout_geometry
|
||||
|
||||
from anyimage.operators.cutout_tool.shape import (
|
||||
BALLOON_ATTRIBUTE_NAME,
|
||||
@@ -427,81 +435,6 @@ def _position_field(group, depth_image, location):
|
||||
return position_texture.outputs["Color"]
|
||||
|
||||
|
||||
def _capture_edge_length(group, geometry, name, location):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
x, y = location
|
||||
edge_vertices = nodes.new("GeometryNodeInputMeshEdgeVertices")
|
||||
edge_vertices.location = (x, y + 250.0)
|
||||
edge_length = nodes.new("ShaderNodeVectorMath")
|
||||
edge_length.operation = "DISTANCE"
|
||||
edge_length.location = (x + 220.0, y + 250.0)
|
||||
links.new(edge_vertices.outputs["Position 1"], edge_length.inputs[0])
|
||||
links.new(edge_vertices.outputs["Position 2"], edge_length.inputs[1])
|
||||
capture = nodes.new("GeometryNodeCaptureAttribute")
|
||||
capture.domain = "POINT"
|
||||
capture.capture_items.clear()
|
||||
capture.capture_items.new("FLOAT", name)
|
||||
capture.location = (x + 440.0, y)
|
||||
links.new(geometry, capture.inputs["Geometry"])
|
||||
links.new(edge_length.outputs["Value"], capture.inputs[name])
|
||||
return capture.outputs["Geometry"], capture.outputs[name], edge_length.outputs["Value"]
|
||||
|
||||
|
||||
def _remove_stretched_points(
|
||||
group,
|
||||
geometry,
|
||||
original_edge_length,
|
||||
current_edge_length,
|
||||
stretch_limit,
|
||||
location,
|
||||
):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
x, y = location
|
||||
|
||||
remaining_ratio = _math(nodes, "DIVIDE", (x + 220.0, y + 300.0))
|
||||
links.new(original_edge_length, remaining_ratio.inputs[0])
|
||||
links.new(current_edge_length, remaining_ratio.inputs[1])
|
||||
stretched = _math(nodes, "LESS_THAN", (x + 450.0, y + 200.0))
|
||||
links.new(remaining_ratio.outputs[0], stretched.inputs[0])
|
||||
links.new(stretch_limit, stretched.inputs[1])
|
||||
delete_stretched = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete_stretched.domain = "POINT"
|
||||
delete_stretched.mode = "ALL"
|
||||
delete_stretched.location = (x + 680.0, y)
|
||||
links.new(geometry, delete_stretched.inputs["Geometry"])
|
||||
links.new(stretched.outputs[0], delete_stretched.inputs["Selection"])
|
||||
|
||||
edge_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
||||
edge_neighbors.location = (x + 930.0, y + 500.0)
|
||||
loose_edge = _math(nodes, "LESS_THAN", (x + 1150.0, y + 500.0))
|
||||
loose_edge.inputs[1].default_value = 1.0
|
||||
links.new(edge_neighbors.outputs["Face Count"], loose_edge.inputs[0])
|
||||
delete_loose_edges = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete_loose_edges.domain = "EDGE"
|
||||
delete_loose_edges.mode = "ALL"
|
||||
delete_loose_edges.location = (x + 1370.0, y)
|
||||
links.new(delete_stretched.outputs["Geometry"], delete_loose_edges.inputs["Geometry"])
|
||||
links.new(loose_edge.outputs[0], delete_loose_edges.inputs["Selection"])
|
||||
|
||||
vertex_neighbors = nodes.new("GeometryNodeInputMeshVertexNeighbors")
|
||||
vertex_neighbors.location = (x + 1620.0, y + 500.0)
|
||||
loose_point = _math(nodes, "LESS_THAN", (x + 1840.0, y + 500.0))
|
||||
loose_point.inputs[1].default_value = 1.0
|
||||
links.new(vertex_neighbors.outputs["Face Count"], loose_point.inputs[0])
|
||||
delete_loose_points = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete_loose_points.domain = "POINT"
|
||||
delete_loose_points.mode = "ALL"
|
||||
delete_loose_points.location = (x + 2060.0, y)
|
||||
links.new(
|
||||
delete_loose_edges.outputs["Geometry"],
|
||||
delete_loose_points.inputs["Geometry"],
|
||||
)
|
||||
links.new(loose_point.outputs[0], delete_loose_points.inputs["Selection"])
|
||||
return delete_loose_points.outputs["Geometry"]
|
||||
|
||||
|
||||
def _plane_depth(group, direction, reference_depth, location):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
@@ -673,20 +606,22 @@ def _build_depth_surface(
|
||||
geometry,
|
||||
thickness,
|
||||
depth_scale,
|
||||
stretch_limit,
|
||||
split,
|
||||
uniform_scale,
|
||||
reference_depth,
|
||||
depth_image,
|
||||
cleanup,
|
||||
):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
geometry, original_edge_length, current_edge_length = _capture_edge_length(
|
||||
group,
|
||||
geometry,
|
||||
"Original Edge Length",
|
||||
(400.0, 850.0),
|
||||
geometry = split_depth_surface(
|
||||
group, geometry, depth_image, split, uniform_scale, depth_scale
|
||||
)
|
||||
geometry = _island_cleanup(group, geometry, cleanup, (1800.0, 850.0))
|
||||
original_camera = _position_field(group, depth_image, (-200.0, -200.0))
|
||||
geometry, camera_position = store_surface_camera(
|
||||
group, geometry, original_camera, split
|
||||
)
|
||||
camera_position = _position_field(group, depth_image, (-200.0, -200.0))
|
||||
scaled = nodes.new("ShaderNodeVectorMath")
|
||||
scaled.operation = "SCALE"
|
||||
scaled.location = (3100.0, -100.0)
|
||||
@@ -731,45 +666,12 @@ def _build_depth_surface(
|
||||
links.new(geometry, project_geometry.inputs["Geometry"])
|
||||
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
|
||||
|
||||
geometry = _remove_stretched_points(
|
||||
group,
|
||||
project_geometry.outputs["Geometry"],
|
||||
original_edge_length,
|
||||
current_edge_length,
|
||||
stretch_limit,
|
||||
(4700.0, 400.0),
|
||||
)
|
||||
zero_z = nodes.new("ShaderNodeValue")
|
||||
zero_z.location = (7100.0, 100.0)
|
||||
zero_z.outputs["Value"].default_value = 0.0
|
||||
geometry = _set_geometry_z(
|
||||
group,
|
||||
geometry,
|
||||
zero_z.outputs["Value"],
|
||||
(7200.0, 400.0),
|
||||
)
|
||||
|
||||
back = _math(nodes, "SUBTRACT", (3900.0, -100.0))
|
||||
links.new(front.outputs[0], back.inputs[0])
|
||||
links.new(thickness, back.inputs[1])
|
||||
shaped = _set_segmented_layer_z(
|
||||
group,
|
||||
geometry,
|
||||
front.outputs[0],
|
||||
back.outputs[0],
|
||||
thickness,
|
||||
(8300.0, 400.0),
|
||||
)
|
||||
front_only = _set_geometry_z(
|
||||
group,
|
||||
geometry,
|
||||
front.outputs[0],
|
||||
(8500.0, -200.0),
|
||||
)
|
||||
geometry = project_geometry.outputs["Geometry"]
|
||||
shaped = thicken_depth_surface(group, geometry, thickness)
|
||||
geometry = _remove_zero_thickness(
|
||||
group, front_only, shaped, thickness, (11800.0, 400.0)
|
||||
group, project_geometry.outputs["Geometry"], shaped, thickness, (11800.0, 400.0)
|
||||
)
|
||||
return geometry
|
||||
return remove_surface_attributes(group, geometry)
|
||||
|
||||
|
||||
def build_node_group(shape):
|
||||
@@ -791,7 +693,7 @@ def build_node_group(shape):
|
||||
thickness.description = "Final thickness in scene distance"
|
||||
|
||||
depth_image = None
|
||||
stretch_limit = None
|
||||
split = None
|
||||
if shape == "DEPTH_BALLOON":
|
||||
depth_scale = _float_input(group, "Depth Scale", 1.0)
|
||||
depth_scale.description = "Scale the fitted-plane-corrected metric depth"
|
||||
@@ -828,19 +730,18 @@ def build_node_group(shape):
|
||||
)
|
||||
elif shape == "DEPTH_SURFACE":
|
||||
options = group.interface.new_panel(name="Options", default_closed=True)
|
||||
stretch_limit = _float_input(
|
||||
split = _float_input(
|
||||
group,
|
||||
"Stretch Limit",
|
||||
"Split",
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
subtype="FACTOR",
|
||||
parent=options,
|
||||
)
|
||||
stretch_limit.description = (
|
||||
"Delete faces when original edge length divided by projected edge "
|
||||
"length falls below this threshold; zero disables culling"
|
||||
)
|
||||
split.description = "Separate depth discontinuities; zero disables splitting"
|
||||
cleanup = _float_input(group, "Cleanup", 0.0, 0.0, None, parent=options)
|
||||
cleanup.description = "Remove split islands below this local-area threshold"
|
||||
reference_depth = _float_input(
|
||||
group,
|
||||
"Reference Depth",
|
||||
@@ -880,7 +781,8 @@ def build_node_group(shape):
|
||||
"Uniform Scale",
|
||||
"Reference Depth",
|
||||
"Depth Direction",
|
||||
"Stretch Limit",
|
||||
"Split",
|
||||
"Cleanup",
|
||||
},
|
||||
)
|
||||
geometry = controls.outputs["Geometry"]
|
||||
@@ -913,14 +815,19 @@ def build_node_group(shape):
|
||||
geometry,
|
||||
controls.outputs["Thickness"],
|
||||
controls.outputs["Depth Scale"],
|
||||
controls.outputs["Stretch Limit"],
|
||||
controls.outputs["Split"],
|
||||
controls.outputs["Uniform Scale"],
|
||||
controls.outputs["Reference Depth"],
|
||||
controls.outputs["Depth Image"],
|
||||
controls.outputs["Cleanup"],
|
||||
)
|
||||
output_location = (12100.0, 400.0)
|
||||
_localize_group_inputs(group, controls)
|
||||
_shade_output(group, geometry, output_location)
|
||||
if shape == "DEPTH_SURFACE":
|
||||
layout_depth_surface(group)
|
||||
else:
|
||||
layout_geometry(group)
|
||||
group.color_tag = "GEOMETRY"
|
||||
group.description = {
|
||||
"SURFACE": "Symmetrically thicken a Surface sheet.",
|
||||
@@ -983,19 +890,16 @@ def build_cutout_modifier_group(shape_groups):
|
||||
parent=options,
|
||||
)
|
||||
smooth.description = "Blend each smoothing pass into the selected shape"
|
||||
stretch_limit = _float_input(
|
||||
split = _float_input(
|
||||
group,
|
||||
"Stretch Limit",
|
||||
"Split",
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
subtype="FACTOR",
|
||||
parent=options,
|
||||
)
|
||||
stretch_limit.description = (
|
||||
"Delete Depth Surface faces when original edge length divided by projected "
|
||||
"edge length falls below this threshold; zero disables culling"
|
||||
)
|
||||
split.description = "Separate Depth Surface discontinuities; zero disables splitting"
|
||||
|
||||
reference_depth = _float_input(
|
||||
group,
|
||||
@@ -1054,7 +958,10 @@ def build_cutout_modifier_group(shape_groups):
|
||||
node.node_tree = shape_groups[shape]
|
||||
node.location = (-850.0, y)
|
||||
node.width = 220.0
|
||||
group.links.new(geometry, node.inputs["Geometry"])
|
||||
group.links.new(
|
||||
base_controls.outputs["Geometry"] if shape == "DEPTH_SURFACE" else geometry,
|
||||
node.inputs["Geometry"],
|
||||
)
|
||||
visible = {
|
||||
socket.name
|
||||
for socket in node.inputs
|
||||
@@ -1064,7 +971,10 @@ def build_cutout_modifier_group(shape_groups):
|
||||
for socket in node.inputs:
|
||||
if not socket.name or socket.name == "Geometry":
|
||||
continue
|
||||
group.links.new(controls.outputs[socket.name], socket)
|
||||
group.links.new(
|
||||
cleanup_area.outputs[0] if socket.name == "Cleanup" else controls.outputs[socket.name],
|
||||
socket,
|
||||
)
|
||||
branch_nodes[shape] = node
|
||||
|
||||
menu_switch = group.nodes.new("GeometryNodeMenuSwitch")
|
||||
@@ -1114,10 +1024,14 @@ def build_cutout_modifier_group(shape_groups):
|
||||
group.links.new(geometry, axis_switch.inputs["-Z"])
|
||||
group.links.new(orient.outputs["Geometry"], axis_switch.inputs["+X"])
|
||||
_shade_output(group, axis_switch.outputs["Output"], (3850.0, 550.0))
|
||||
layout_geometry(group)
|
||||
group.color_tag = "GEOMETRY"
|
||||
group.description = (
|
||||
"Build Surface, Balloon, Depth Balloon, or Depth Surface from one "
|
||||
"menu-controlled Cutout modifier."
|
||||
)
|
||||
for node in group.nodes:
|
||||
for socket in node.outputs:
|
||||
socket.hide = not socket.is_linked
|
||||
group.use_fake_user = True
|
||||
return group
|
||||
|
||||
Reference in New Issue
Block a user