Optimize Cutout Geometry Nodes
- Refactor Depth Cutout to reuse the projected outline field, reducing redundant boundary calculations and improving performance. - Derive the center field from shared low-frequency fields, eliminating an unnecessary blur operation. - Implement triangle-only strip cleanup for Depth Cutout, as the input is confirmed to be triangulated. - Short-circuit zero-thickness branches in both Cutout and Depth Cutout to avoid unnecessary geometry generation. - Standardize internal attribute naming with a new `_o_` prefix and implement wildcard cleanup for temporary attributes. - Update relevant tests to ensure functionality remains consistent and validate changes against existing behavior.
This commit is contained in:
@@ -77,7 +77,11 @@ def check_asset_library(asset_path):
|
||||
)
|
||||
assert not {attribute.name for attribute in result.attributes} & {
|
||||
".o_anyimage_depth_cut", ".o_anyimage_original_boundary",
|
||||
"_o_anyimage_depth_cut",
|
||||
}, node_group.name
|
||||
assert not any(
|
||||
attribute.name.startswith("_o_") for attribute in result.attributes
|
||||
), node_group.name
|
||||
finally:
|
||||
evaluated.to_mesh_clear()
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
@@ -77,10 +77,11 @@ def reconstruct_camera_depth(group, camera, depth, smoothed):
|
||||
return choose.outputs[0]
|
||||
|
||||
|
||||
def cut_boundary_influence(group, split_boundary, protected=False, *, outline_scale=0.1):
|
||||
def cut_boundary_influence(group, split_boundary, protected=False, *, outline_scale=0.1, boundary=None):
|
||||
"""Give split edges full influence and outline edges a configured share."""
|
||||
nodes, links = group.nodes, group.links
|
||||
boundary = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
|
||||
if boundary is None:
|
||||
boundary = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
|
||||
split = _math(group, "MULTIPLY", boundary, split_boundary)
|
||||
outline = _math(group, "MAXIMUM", _math(group, "SUBTRACT", boundary, split), 0)
|
||||
split_influence = boundary_influence(group, protected, boundary=split)
|
||||
@@ -90,9 +91,11 @@ def cut_boundary_influence(group, split_boundary, protected=False, *, outline_sc
|
||||
return _math(group, "MAXIMUM", split_influence, outline_influence)
|
||||
|
||||
|
||||
def smooth_cut_boundary(group, geometry, split_boundary=False, *, protected=False, outline_scale=0.1):
|
||||
def smooth_cut_boundary(group, geometry, split_boundary=False, *, protected=False, outline_scale=0.1, boundary=None):
|
||||
"""Blend O Smooth through split and outline masks with configurable weights."""
|
||||
influence = cut_boundary_influence(group, split_boundary, protected, outline_scale=outline_scale)
|
||||
influence = cut_boundary_influence(
|
||||
group, split_boundary, protected, outline_scale=outline_scale, boundary=boundary,
|
||||
)
|
||||
# The support band depends on topology, which stays unchanged in the loop.
|
||||
return expand_smooth(
|
||||
group, geometry, iterations_name="Boundary Smooth", weight_name="Smooth Weight",
|
||||
|
||||
@@ -20,7 +20,7 @@ def remove_boundary_triangles(group, geometry):
|
||||
return delete.outputs["Geometry"]
|
||||
|
||||
|
||||
def original_boundary_field(group, original):
|
||||
def original_boundary_field(group, original, rim=None):
|
||||
"""Sample the unsplit outline in UV space after projection and separation."""
|
||||
nodes, links = group.nodes, group.links
|
||||
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
@@ -33,7 +33,8 @@ def original_boundary_field(group, original):
|
||||
nearest.domain = "POINT"
|
||||
links.new(original_uv.outputs["Geometry"], nearest.inputs["Geometry"])
|
||||
links.new(uv.outputs["Attribute"], nearest.inputs["Sample Position"])
|
||||
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
|
||||
if rim is None:
|
||||
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
|
||||
return sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Face separation and connected thickness for Depth Surface assets."""
|
||||
|
||||
from .nodes import evaluate_field, store_float_attribute, store_boolean_attribute, remove_attributes, sample_field
|
||||
from .nodes import evaluate_field, store_float_attribute, store_boolean_attribute, remove_attribute_pattern, sample_field
|
||||
|
||||
|
||||
CUT_ATTRIBUTE = ".o_anyimage_depth_cut"
|
||||
CUT_ATTRIBUTE = "_o_anyimage_depth_cut"
|
||||
|
||||
|
||||
def _math(group, operation, a, b=None):
|
||||
@@ -21,6 +21,13 @@ def _math(group, operation, a, b=None):
|
||||
|
||||
def _remove_strip_faces(group, geometry, *, triangles=False):
|
||||
"""Delete boundary strips from one unchanged topology snapshot."""
|
||||
if triangles:
|
||||
return _remove_triangle_strip_faces(group, geometry)
|
||||
return _remove_quad_strip_faces(group, geometry)
|
||||
|
||||
|
||||
def _remove_quad_strip_faces(group, geometry):
|
||||
"""Delete boundary quad strips while preserving open boundary edges."""
|
||||
nodes, links = group.nodes, group.links
|
||||
|
||||
def equals(value, count):
|
||||
@@ -63,73 +70,101 @@ def _remove_strip_faces(group, geometry, *, triangles=False):
|
||||
)
|
||||
face = nodes.new("GeometryNodeInputMeshFaceNeighbors")
|
||||
selected = combine("MULTIPLY", equals(face.outputs["Vertex Count"], 4), opposite)
|
||||
if triangles:
|
||||
def sample(value, index, domain):
|
||||
node = nodes.new("GeometryNodeFieldAtIndex")
|
||||
node.data_type, node.domain = "INT", domain
|
||||
links.new(value, node.inputs["Value"])
|
||||
links.new(index, node.inputs["Index"])
|
||||
return node.outputs["Value"]
|
||||
delete = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete.domain, delete.mode = "FACE", "ALL"
|
||||
links.new(geometry, delete.inputs["Geometry"])
|
||||
links.new(selected, delete.inputs["Selection"])
|
||||
return delete.outputs["Geometry"]
|
||||
|
||||
def offset_corner(corner, offset):
|
||||
node = nodes.new("GeometryNodeOffsetCornerInFace")
|
||||
links.new(corner, node.inputs["Corner Index"])
|
||||
node.inputs["Offset"].default_value = offset
|
||||
return node.outputs["Corner Index"]
|
||||
|
||||
def vertex(corner):
|
||||
node = nodes.new("GeometryNodeVertexOfCorner")
|
||||
links.new(corner, node.inputs["Corner Index"])
|
||||
return node.outputs["Vertex Index"]
|
||||
def _remove_triangle_strip_faces(group, geometry):
|
||||
"""Delete symmetric triangle strip pairs and all-boundary ears."""
|
||||
nodes, links = group.nodes, group.links
|
||||
|
||||
def boundary(corner):
|
||||
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
||||
links.new(corner, edge.inputs["Corner Index"])
|
||||
count = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
||||
return equals(sample(count.outputs["Face Count"], edge.outputs["Next Edge Index"], "EDGE"), 1)
|
||||
def equals(value, count):
|
||||
compare = nodes.new("FunctionNodeCompare")
|
||||
compare.data_type, compare.operation = "INT", "EQUAL"
|
||||
links.new(value, compare.inputs[2])
|
||||
if isinstance(count, int):
|
||||
compare.inputs[3].default_value = count
|
||||
else:
|
||||
links.new(count, compare.inputs[3])
|
||||
return compare.outputs["Result"]
|
||||
|
||||
# Evaluate each internal edge as a symmetric pair of triangles.
|
||||
edge_index = nodes.new("GeometryNodeInputIndex")
|
||||
sides = []
|
||||
for side in (0, 1):
|
||||
corners = nodes.new("GeometryNodeCornersOfEdge")
|
||||
links.new(edge_index.outputs[0], corners.inputs["Edge Index"])
|
||||
corners.inputs["Sort Index"].default_value = side
|
||||
corner = corners.outputs["Corner Index"]
|
||||
face_index = nodes.new("GeometryNodeFaceOfCorner")
|
||||
links.new(corner, face_index.inputs["Corner Index"])
|
||||
face_size = nodes.new("GeometryNodeInputMeshFaceNeighbors")
|
||||
triangle = equals(sample(face_size.outputs["Vertex Count"], face_index.outputs["Face Index"], "FACE"), 3)
|
||||
end, tip = offset_corner(corner, 1), offset_corner(corner, 2)
|
||||
sides.append((triangle, vertex(corner), vertex(tip), boundary(end), boundary(tip)))
|
||||
a, b = sides
|
||||
same_direction = equals(a[1], b[1])
|
||||
same_order = combine("MAXIMUM", combine("MULTIPLY", a[3], b[3]), combine("MULTIPLY", a[4], b[4]))
|
||||
reverse_order = combine("MAXIMUM", combine("MULTIPLY", a[3], b[4]), combine("MULTIPLY", a[4], b[3]))
|
||||
pair = combine("MAXIMUM",
|
||||
combine("MULTIPLY", same_direction, reverse_order),
|
||||
combine("MULTIPLY", equals(same_direction, 0), same_order))
|
||||
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
||||
for condition in (equals(neighbors.outputs["Face Count"], 2), a[0], b[0], equals(equals(a[2], b[2]), 0)):
|
||||
pair = combine("MULTIPLY", pair, condition)
|
||||
def combine(operation, a, b):
|
||||
node = nodes.new("FunctionNodeIntegerMath")
|
||||
node.operation = operation
|
||||
links.new(a, node.inputs[0])
|
||||
links.new(b, node.inputs[1])
|
||||
return node.outputs[0]
|
||||
|
||||
# Explicit edge-to-face sampling marks both members without averaging
|
||||
# through neighboring vertices or iterating over newly exposed edges.
|
||||
face_index = nodes.new("GeometryNodeInputIndex")
|
||||
corners = nodes.new("GeometryNodeCornersOfFace")
|
||||
links.new(face_index.outputs[0], corners.inputs["Face Index"])
|
||||
boundary_count, paired = None, None
|
||||
for offset in range(3):
|
||||
corner = offset_corner(corners.outputs["Corner Index"], offset)
|
||||
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
||||
links.new(corner, edge.inputs["Corner Index"])
|
||||
match = sample(pair, edge.outputs["Next Edge Index"], "EDGE")
|
||||
paired = match if paired is None else combine("MAXIMUM", paired, match)
|
||||
rim = boundary(corner)
|
||||
boundary_count = rim if boundary_count is None else combine("ADD", boundary_count, rim)
|
||||
triangle_selected = combine("MULTIPLY", equals(corners.outputs["Total"], 3),
|
||||
combine("MAXIMUM", paired, equals(boundary_count, 3)))
|
||||
selected = combine("MAXIMUM", selected, triangle_selected)
|
||||
def sample(value, index, domain):
|
||||
node = nodes.new("GeometryNodeFieldAtIndex")
|
||||
node.data_type, node.domain = "INT", domain
|
||||
links.new(value, node.inputs["Value"])
|
||||
links.new(index, node.inputs["Index"])
|
||||
return node.outputs["Value"]
|
||||
|
||||
def offset_corner(corner, offset):
|
||||
node = nodes.new("GeometryNodeOffsetCornerInFace")
|
||||
links.new(corner, node.inputs["Corner Index"])
|
||||
node.inputs["Offset"].default_value = offset
|
||||
return node.outputs["Corner Index"]
|
||||
|
||||
def vertex(corner):
|
||||
node = nodes.new("GeometryNodeVertexOfCorner")
|
||||
links.new(corner, node.inputs["Corner Index"])
|
||||
return node.outputs["Vertex Index"]
|
||||
|
||||
def boundary(corner):
|
||||
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
||||
links.new(corner, edge.inputs["Corner Index"])
|
||||
count = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
||||
return equals(sample(count.outputs["Face Count"], edge.outputs["Next Edge Index"], "EDGE"), 1)
|
||||
|
||||
edge_index = nodes.new("GeometryNodeInputIndex")
|
||||
sides = []
|
||||
for side in (0, 1):
|
||||
corners = nodes.new("GeometryNodeCornersOfEdge")
|
||||
links.new(edge_index.outputs[0], corners.inputs["Edge Index"])
|
||||
corners.inputs["Sort Index"].default_value = side
|
||||
corner = corners.outputs["Corner Index"]
|
||||
face_index = nodes.new("GeometryNodeFaceOfCorner")
|
||||
links.new(corner, face_index.inputs["Corner Index"])
|
||||
face_size = nodes.new("GeometryNodeInputMeshFaceNeighbors")
|
||||
triangle = equals(sample(face_size.outputs["Vertex Count"], face_index.outputs["Face Index"], "FACE"), 3)
|
||||
end, tip = offset_corner(corner, 1), offset_corner(corner, 2)
|
||||
sides.append((triangle, vertex(corner), vertex(tip), boundary(end), boundary(tip)))
|
||||
a, b = sides
|
||||
same_direction = equals(a[1], b[1])
|
||||
same_order = combine("MAXIMUM", combine("MULTIPLY", a[3], b[3]), combine("MULTIPLY", a[4], b[4]))
|
||||
reverse_order = combine("MAXIMUM", combine("MULTIPLY", a[3], b[4]), combine("MULTIPLY", a[4], b[3]))
|
||||
pair = combine(
|
||||
"MAXIMUM",
|
||||
combine("MULTIPLY", same_direction, reverse_order),
|
||||
combine("MULTIPLY", equals(same_direction, 0), same_order),
|
||||
)
|
||||
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
||||
for condition in (equals(neighbors.outputs["Face Count"], 2), a[0], b[0], equals(equals(a[2], b[2]), 0)):
|
||||
pair = combine("MULTIPLY", pair, condition)
|
||||
|
||||
face_index = nodes.new("GeometryNodeInputIndex")
|
||||
corners = nodes.new("GeometryNodeCornersOfFace")
|
||||
links.new(face_index.outputs[0], corners.inputs["Face Index"])
|
||||
boundary_count, paired = None, None
|
||||
for offset in range(3):
|
||||
corner = offset_corner(corners.outputs["Corner Index"], offset)
|
||||
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
||||
links.new(corner, edge.inputs["Corner Index"])
|
||||
match = sample(pair, edge.outputs["Next Edge Index"], "EDGE")
|
||||
paired = match if paired is None else combine("MAXIMUM", paired, match)
|
||||
rim = boundary(corner)
|
||||
boundary_count = rim if boundary_count is None else combine("ADD", boundary_count, rim)
|
||||
selected = combine(
|
||||
"MULTIPLY",
|
||||
equals(corners.outputs["Total"], 3),
|
||||
combine("MAXIMUM", paired, equals(boundary_count, 3)),
|
||||
)
|
||||
delete = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete.domain, delete.mode = "FACE", "ALL"
|
||||
links.new(geometry, delete.inputs["Geometry"])
|
||||
@@ -335,7 +370,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
||||
links.new(rim, back_parts.inputs["Geometry"])
|
||||
rear = back_parts.outputs["Geometry"]
|
||||
if back_mask is not None:
|
||||
rear = store_float_attribute(group, rear, back_mask, 1.0, domain="FACE")
|
||||
rear = store_float_attribute(group, rear, back_mask, 1.0)
|
||||
for value in (
|
||||
saved,
|
||||
rear,
|
||||
@@ -398,7 +433,7 @@ def _projection_math(nodes, operation):
|
||||
|
||||
|
||||
def project_depth_surface(
|
||||
group, geometry, camera_position, uniform_scale, reference_depth, depth_scale, split_strength
|
||||
group, geometry, camera_position, uniform_scale, reference_depth, depth_scale
|
||||
):
|
||||
"""Interpolate the rectangular plane into the model camera coordinates."""
|
||||
nodes, links = group.nodes, group.links
|
||||
@@ -439,7 +474,4 @@ def project_depth_surface(
|
||||
links.new(geometry, project_geometry.inputs["Geometry"])
|
||||
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
|
||||
|
||||
return remove_attributes(
|
||||
group, project_geometry.outputs["Geometry"], CUT_ATTRIBUTE,
|
||||
enabled=_math(group, "GREATER_THAN", split_strength, 0.0),
|
||||
)
|
||||
return remove_attribute_pattern(group, project_geometry.outputs["Geometry"], "_o_*")
|
||||
|
||||
@@ -165,3 +165,12 @@ def remove_attributes(group, geometry, *names, enabled=None):
|
||||
group.links.new(geometry, choose.inputs["True"])
|
||||
geometry = choose.outputs["Output"]
|
||||
return geometry
|
||||
|
||||
|
||||
def remove_attribute_pattern(group, geometry, pattern):
|
||||
"""Remove every attribute matching an internal wildcard pattern."""
|
||||
node = group.nodes.new("GeometryNodeRemoveAttribute")
|
||||
node.inputs["Name"].default_value = pattern
|
||||
node.pattern_mode = "WILDCARD"
|
||||
group.links.new(geometry, node.inputs["Geometry"])
|
||||
return node.outputs["Geometry"]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import bpy
|
||||
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
|
||||
from ..common.nodes import interface_socket, group_input, math_node, float_input, menu_switch, read_float_attribute, store_float_attribute, prepare_node_group
|
||||
from ..common.cutout import _extrude_layers, _offset_geometry_y, _remove_zero_thickness, _set_layer_y, _shade_output
|
||||
from ..common.cutout import _extrude_layers, _offset_geometry_y, _set_layer_y, _shade_output
|
||||
|
||||
|
||||
NODE_GROUP_NAME = "O Image Cutout"
|
||||
@@ -108,6 +108,13 @@ def _set_segmented_layer_y(
|
||||
def _build_shell(group, geometry, thickness):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
zero = math_node(nodes, "LESS_THAN")
|
||||
zero.inputs[1].default_value = 1e-6
|
||||
links.new(thickness, zero.inputs[0])
|
||||
choose = nodes.new("GeometryNodeSwitch")
|
||||
choose.input_type = "GEOMETRY"
|
||||
links.new(zero.outputs[0], choose.inputs["Switch"])
|
||||
links.new(geometry, choose.inputs["True"])
|
||||
front = math_node(nodes, "MULTIPLY")
|
||||
front.inputs[1].default_value = 0.0
|
||||
links.new(thickness, front.inputs[0])
|
||||
@@ -121,12 +128,20 @@ def _build_shell(group, geometry, thickness):
|
||||
back.outputs[0],
|
||||
thickness,
|
||||
)
|
||||
return _remove_zero_thickness(group, geometry, shaped, thickness)
|
||||
links.new(shaped, choose.inputs["False"])
|
||||
return choose.outputs[0]
|
||||
|
||||
|
||||
def _build_balloon(group, geometry, thickness):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
zero = math_node(nodes, "LESS_THAN")
|
||||
zero.inputs[1].default_value = 1e-6
|
||||
links.new(thickness, zero.inputs[0])
|
||||
choose = nodes.new("GeometryNodeSwitch")
|
||||
choose.input_type = "GEOMETRY"
|
||||
links.new(zero.outputs[0], choose.inputs["Switch"])
|
||||
links.new(geometry, choose.inputs["True"])
|
||||
profile = read_float_attribute(group, 'o_balloon')
|
||||
front_scale = math_node(nodes, "MULTIPLY")
|
||||
links.new(profile, front_scale.inputs[0])
|
||||
@@ -142,7 +157,8 @@ def _build_balloon(group, geometry, thickness):
|
||||
negative_front.outputs[0],
|
||||
front_scale.outputs[0],
|
||||
)
|
||||
return _remove_zero_thickness(group, geometry, shaped, thickness)
|
||||
links.new(shaped, choose.inputs["False"])
|
||||
return choose.outputs[0]
|
||||
|
||||
|
||||
def build_image_cutout_group():
|
||||
|
||||
@@ -3,7 +3,7 @@ import bpy
|
||||
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
|
||||
from ..common.nodes import interface_socket, group_input, float_input, read_float_attribute, store_float_attribute, prepare_node_group, sample_field, evaluate_field
|
||||
from ..common.boundary_smoothing import smooth_cut_boundary
|
||||
from ..common.cutout import _position_field, _remove_zero_thickness, _shade_output
|
||||
from ..common.cutout import _position_field, _shade_output
|
||||
from ..common.depth_surface import _math, project_depth_surface, build_surface_camera, split_depth_surface, sample_face_camera, thicken_depth_surface
|
||||
from ..common.smoothing import edge_boundary_field
|
||||
from ..common.cutout_boundary import original_boundary_field, remove_boundary_triangles, taper_split_profile
|
||||
@@ -43,22 +43,28 @@ def blend_normal(group, original, smoothed, weight):
|
||||
return normalize.outputs[0]
|
||||
|
||||
|
||||
def build_volume_fields(group):
|
||||
def build_volume_fields(group, rim=None):
|
||||
"""Evaluate the center and thickness on the original front surface."""
|
||||
links = group.links
|
||||
rim = evaluate_field(group, edge_boundary_field(group.nodes, links), "FLOAT", "POINT")
|
||||
if rim is None:
|
||||
rim = evaluate_field(group, edge_boundary_field(group.nodes, links), "FLOAT", "POINT")
|
||||
profile = read_float_attribute(group, "o_balloon")
|
||||
position = group.nodes.new("GeometryNodeInputPosition")
|
||||
axes = group.nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(position.outputs[0], axes.inputs[0])
|
||||
seed = _math(group, "SUBTRACT", axes.outputs["Y"], profile)
|
||||
interior = _math(group, "SUBTRACT", 1, rim)
|
||||
blur = group.nodes.new("GeometryNodeBlurAttribute")
|
||||
blur.data_type = "FLOAT"
|
||||
blur.inputs["Iterations"].default_value = 256
|
||||
links.new(seed, blur.inputs["Value"])
|
||||
links.new(interior, blur.inputs["Weight"])
|
||||
center = blur.outputs[0]
|
||||
depth = axes.outputs["Y"]
|
||||
depth_slow = group.nodes.new("GeometryNodeBlurAttribute")
|
||||
depth_slow.data_type = "FLOAT"
|
||||
depth_slow.inputs["Iterations"].default_value = 256
|
||||
links.new(depth, depth_slow.inputs["Value"])
|
||||
links.new(interior, depth_slow.inputs["Weight"])
|
||||
profile_slow = group.nodes.new("GeometryNodeBlurAttribute")
|
||||
profile_slow.data_type = "FLOAT"
|
||||
profile_slow.inputs["Iterations"].default_value = 256
|
||||
links.new(profile, profile_slow.inputs["Value"])
|
||||
links.new(interior, profile_slow.inputs["Weight"])
|
||||
center = _math(group, "SUBTRACT", depth_slow.outputs[0], profile_slow.outputs[0])
|
||||
residual = _math(group, "SUBTRACT", axes.outputs["Y"], center)
|
||||
full = _math(group, "ADD", residual, profile)
|
||||
minimum = _math(group, "MULTIPLY", profile, 0.05)
|
||||
@@ -66,7 +72,8 @@ def build_volume_fields(group):
|
||||
control = group_input(group.nodes, {"Thickness"})
|
||||
thickness = _math(group, "MULTIPLY", positive, control.outputs["Thickness"])
|
||||
return {
|
||||
"profile": profile, "depth": axes.outputs["Y"], "interior": interior,
|
||||
"profile": profile, "depth": depth, "interior": interior,
|
||||
"depth_slow": depth_slow.outputs[0], "profile_slow": profile_slow.outputs[0],
|
||||
"center": center, "thickness": thickness,
|
||||
}
|
||||
|
||||
@@ -113,9 +120,8 @@ def build_front_fields(group, volume, front_normal, normal_weight=1):
|
||||
links.new(weight, result.inputs["Weight"])
|
||||
return result.outputs[0]
|
||||
|
||||
profile, depth, interior = volume["profile"], volume["depth"], volume["interior"]
|
||||
depth_slow = blur(depth, interior)
|
||||
profile_slow = blur(profile, interior)
|
||||
profile, depth = volume["profile"], volume["depth"]
|
||||
depth_slow, profile_slow = volume["depth_slow"], volume["profile_slow"]
|
||||
depth_bump = _math(group, "SUBTRACT", depth, depth_slow)
|
||||
profile_bump = _math(group, "SUBTRACT", profile, profile_slow)
|
||||
numerator = _math(group, "MULTIPLY", depth_bump, profile_bump)
|
||||
@@ -269,14 +275,16 @@ def _build_depth_surface(group, geometry, controls):
|
||||
group, geometry, sample_face_camera(group, image), split, controls.outputs["Reference Depth"], triangles=True,
|
||||
)
|
||||
geometry = remove_boundary_triangles(group, geometry)
|
||||
outline = evaluate_field(group, edge_boundary_field(group.nodes, group.links), "BOOLEAN", "POINT")
|
||||
boundary = edge_boundary_field(group.nodes, group.links)
|
||||
outline = evaluate_field(group, boundary, "BOOLEAN", "POINT")
|
||||
rim = evaluate_field(group, boundary, "FLOAT", "POINT")
|
||||
camera = build_surface_camera(
|
||||
group, _position_field(group, image), corner_camera, cut_vertex, split, always_vertex=outline,
|
||||
)
|
||||
projection = project_depth_surface(
|
||||
group, geometry, camera, scale, controls.outputs["Reference Depth"], controls.outputs["Depth Scale"], split,
|
||||
group, geometry, camera, scale, controls.outputs["Reference Depth"], controls.outputs["Depth Scale"],
|
||||
)
|
||||
projection = smooth_cut_boundary(group, projection, cut_vertex)
|
||||
projection = smooth_cut_boundary(group, projection, cut_vertex, boundary=rim)
|
||||
# Clamped depth pixels can collapse adjacent outline samples after projection.
|
||||
edge = group.nodes.new("GeometryNodeInputMeshEdgeVertices")
|
||||
length = group.nodes.new("ShaderNodeVectorMath")
|
||||
@@ -292,7 +300,7 @@ def _build_depth_surface(group, geometry, controls):
|
||||
links.new(projection, merge.inputs["Geometry"])
|
||||
links.new(_math(group, "MULTIPLY", typical.outputs["Mean"], 1e-6), merge.inputs["Distance"])
|
||||
projection = merge.outputs["Geometry"]
|
||||
projection = taper_split_profile(group, projection, original_boundary_field(group, original))
|
||||
projection = taper_split_profile(group, projection, original_boundary_field(group, original, rim))
|
||||
# The mode field is shared by the geometry and thickness choices.
|
||||
mode = group.nodes.new("GeometryNodeMenuSwitch")
|
||||
mode.data_type = "BOOLEAN"
|
||||
@@ -302,7 +310,17 @@ def _build_depth_surface(group, geometry, controls):
|
||||
mode.inputs["Balloon"].default_value = False
|
||||
mode.inputs["Shell"].default_value = True
|
||||
is_shell = mode.outputs[0]
|
||||
volume = build_volume_fields(group)
|
||||
selected_thickness = group.nodes.new("GeometryNodeSwitch")
|
||||
selected_thickness.input_type = "FLOAT"
|
||||
links.new(is_shell, selected_thickness.inputs["Switch"])
|
||||
links.new(controls.outputs["Thickness"], selected_thickness.inputs["False"])
|
||||
links.new(controls.outputs["Shell Thickness"], selected_thickness.inputs["True"])
|
||||
has_thickness = _math(group, "GREATER_THAN", selected_thickness.outputs[0], 1e-6)
|
||||
zero_shell = group.nodes.new("GeometryNodeSwitch")
|
||||
zero_shell.input_type = "GEOMETRY"
|
||||
links.new(has_thickness, zero_shell.inputs["Switch"])
|
||||
links.new(projection, zero_shell.inputs["False"])
|
||||
volume = build_volume_fields(group, rim)
|
||||
normal_weight = build_normal_weight(group, is_shell)
|
||||
normal = build_front_normal(group, normal_weight)
|
||||
front = build_front_fields(group, volume, normal, normal_weight)
|
||||
@@ -324,12 +342,8 @@ def _build_depth_surface(group, geometry, controls):
|
||||
})
|
||||
oriented = build_shell_position(group, shell["position"], shell["front"], is_shell, fields)
|
||||
links.new(oriented, shell["position_input"])
|
||||
amount = group.nodes.new("GeometryNodeSwitch")
|
||||
amount.input_type = "FLOAT"
|
||||
links.new(is_shell, amount.inputs["Switch"])
|
||||
links.new(controls.outputs["Thickness"], amount.inputs["False"])
|
||||
links.new(controls.outputs["Shell Thickness"], amount.inputs["True"])
|
||||
return _remove_zero_thickness(group, projection, shell["geometry"], amount.outputs[0])
|
||||
links.new(shell["geometry"], zero_shell.inputs["True"])
|
||||
return zero_shell.outputs[0]
|
||||
|
||||
|
||||
def build_image_depth_cutout_group():
|
||||
@@ -361,7 +375,7 @@ def build_image_depth_cutout_group():
|
||||
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 = store_float_attribute(group, controls.outputs['Geometry'], NORMAL_REDUCTION_ATTRIBUTE_NAME, 0.0, domain='FACE')
|
||||
geometry = store_float_attribute(group, controls.outputs['Geometry'], NORMAL_REDUCTION_ATTRIBUTE_NAME, 0.0)
|
||||
geometry = _build_depth_surface(group, geometry, controls)
|
||||
_shade_output(group, geometry)
|
||||
prepare_node_group(group)
|
||||
|
||||
@@ -7,9 +7,9 @@ import bpy
|
||||
from ..common.validity import delete_invalid_points
|
||||
|
||||
from ..common.depth_surface import (
|
||||
_math, split_depth_surface, build_surface_camera, CUT_ATTRIBUTE,
|
||||
_math, split_depth_surface, build_surface_camera,
|
||||
)
|
||||
from ..common.nodes import float_input, group_input, interface_socket, prepare_node_group, evaluate_field, remove_attributes
|
||||
from ..common.nodes import float_input, group_input, interface_socket, prepare_node_group, evaluate_field, remove_attribute_pattern
|
||||
from ..common.material_slots import inherit_material_slots
|
||||
from ..common.boundary_smoothing import smooth_boundary_depth, smooth_cut_boundary
|
||||
|
||||
@@ -175,10 +175,7 @@ def build_image_depth_panorama_group():
|
||||
deform = nodes.new("GeometryNodeSetPosition")
|
||||
links.new(geometry, deform.inputs["Geometry"])
|
||||
links.new(radial.outputs[0], deform.inputs["Position"])
|
||||
geometry = remove_attributes(
|
||||
group, deform.outputs[0], CUT_ATTRIBUTE,
|
||||
enabled=_math(group, "GREATER_THAN", strength, 0.0),
|
||||
)
|
||||
geometry = remove_attribute_pattern(group, deform.outputs[0], "_o_*")
|
||||
geometry = smooth_cut_boundary(group, geometry, cut_vertex, outline_scale=1.0)
|
||||
flip = nodes.new("GeometryNodeFlipFaces")
|
||||
links.new(geometry, flip.inputs["Mesh"])
|
||||
|
||||
@@ -130,7 +130,7 @@ def build_image_depth_plane_group(image_plane_group):
|
||||
surface = project_depth_surface(
|
||||
group, geometry, camera,
|
||||
scale_input.outputs["Uniform Scale"], depth_input.outputs["Reference Depth"],
|
||||
depth_input.outputs["Depth Scale"], split_input.outputs["Depth Split"],
|
||||
depth_input.outputs["Depth Scale"],
|
||||
)
|
||||
surface = smooth_cut_boundary(
|
||||
group, surface, cut_vertex, protected=original_boundary, outline_scale=1.0,
|
||||
|
||||
Reference in New Issue
Block a user