refactor: Update cutout and depth handling by removing unused functions and adjusting parameters
This commit is contained in:
@@ -20,8 +20,8 @@ def remove_boundary_triangles(group, geometry):
|
||||
return delete.outputs["Geometry"]
|
||||
|
||||
|
||||
def taper_split_profile(group, original, geometry):
|
||||
"""Taper balloon thickness through the shared two-ring influence builder."""
|
||||
def original_boundary_field(group, original):
|
||||
"""Sample the unsplit outline in UV space after projection and separation."""
|
||||
nodes, links = group.nodes, group.links
|
||||
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
uv.data_type = "FLOAT_VECTOR"
|
||||
@@ -34,8 +34,13 @@ def taper_split_profile(group, original, geometry):
|
||||
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")
|
||||
protected = sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
|
||||
influence = boundary_influence(group, protected)
|
||||
return sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
|
||||
|
||||
|
||||
def taper_split_profile(group, geometry, original_boundary):
|
||||
"""Taper balloon thickness through the shared two-ring influence builder."""
|
||||
nodes = group.nodes
|
||||
influence = boundary_influence(group, original_boundary)
|
||||
controls = group_input(nodes, {"Depth Split"})
|
||||
band = _math(group, "MULTIPLY", influence, _math(group, "GREATER_THAN", controls.outputs["Depth Split"], 0))
|
||||
retained = _math(group, "SQRT", _math(group, "MAXIMUM", _math(group, "SUBTRACT", 1, band), 0))
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
"""Build a shared center on the original Cutout mesh."""
|
||||
|
||||
|
||||
from .nodes import evaluate_field, group_input, sample_field
|
||||
|
||||
|
||||
def vector(group, operation, first, second=None):
|
||||
node = group.nodes.new("ShaderNodeVectorMath")
|
||||
node.operation = operation
|
||||
for value, socket in ((first, node.inputs[0]), (second, node.inputs[3 if operation == "SCALE" else 1])):
|
||||
if value is None:
|
||||
continue
|
||||
if hasattr(value, "node"):
|
||||
group.links.new(value, socket)
|
||||
else:
|
||||
socket.default_value = value
|
||||
return node.outputs[0]
|
||||
|
||||
|
||||
def axes(group, value):
|
||||
node = group.nodes.new("ShaderNodeSeparateXYZ")
|
||||
group.links.new(value, node.inputs[0])
|
||||
return node.outputs
|
||||
|
||||
|
||||
def combine(group, x, y, z=0):
|
||||
node = group.nodes.new("ShaderNodeCombineXYZ")
|
||||
for value, socket in zip((x, y, z), node.inputs):
|
||||
if hasattr(value, "node"):
|
||||
group.links.new(value, socket)
|
||||
else:
|
||||
socket.default_value = value
|
||||
return node.outputs[0]
|
||||
|
||||
|
||||
def set_position(group, geometry, position):
|
||||
node = group.nodes.new("GeometryNodeSetPosition")
|
||||
group.links.new(geometry, node.inputs["Geometry"])
|
||||
group.links.new(position, node.inputs["Position"])
|
||||
return node.outputs[0]
|
||||
|
||||
|
||||
def sample_surface(group, geometry, value, data_type, domain_position, query):
|
||||
"""Keep point indices on points before the face interpolation adapts domains."""
|
||||
field = evaluate_field(group, sample_field(group, geometry, value, data_type), data_type, "POINT")
|
||||
flat = set_position(group, geometry, domain_position)
|
||||
sample = group.nodes.new("GeometryNodeSampleNearestSurface")
|
||||
sample.data_type = data_type
|
||||
group.links.new(flat, sample.inputs["Mesh"])
|
||||
group.links.new(field, sample.inputs["Value"])
|
||||
group.links.new(query, sample.inputs["Sample Position"])
|
||||
return sample.outputs[0]
|
||||
|
||||
|
||||
def build_center_surface(group, unsplit):
|
||||
"""Smooth the unsplit displaced surface without moving its XY coordinates."""
|
||||
nodes, links = group.nodes, group.links
|
||||
controls = group_input(nodes, {"Center Smooth"})
|
||||
position = nodes.new("GeometryNodeInputPosition").outputs[0]
|
||||
components = axes(group, position)
|
||||
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
uv.data_type = "FLOAT_VECTOR"
|
||||
uv.inputs["Name"].default_value = "UVMap"
|
||||
coordinates = uv.outputs["Attribute"]
|
||||
blur = nodes.new("GeometryNodeBlurAttribute")
|
||||
blur.data_type = "FLOAT"
|
||||
links.new(components["Z"], blur.inputs["Value"])
|
||||
links.new(controls.outputs["Center Smooth"], blur.inputs["Iterations"])
|
||||
support = set_position(group, unsplit, combine(group, components["X"], components["Y"], blur.outputs[0]))
|
||||
height = sample_surface(group, support, components["Z"], "FLOAT", coordinates, coordinates)
|
||||
normal = nodes.new("GeometryNodeInputNormal").outputs[0]
|
||||
normal_blur = nodes.new("GeometryNodeBlurAttribute")
|
||||
normal_blur.data_type = "FLOAT_VECTOR"
|
||||
links.new(evaluate_field(group, normal, "FLOAT_VECTOR", "POINT"), normal_blur.inputs["Value"])
|
||||
links.new(controls.outputs["Center Smooth"], normal_blur.inputs["Iterations"])
|
||||
normal = normal_blur.outputs[0]
|
||||
normal = vector(group, "NORMALIZE", sample_surface(group, support, normal, "FLOAT_VECTOR", coordinates, coordinates))
|
||||
return {"geometry": support, "preview": support, "height": height, "normal": normal}
|
||||
@@ -1,98 +0,0 @@
|
||||
"""Adapt camera depth along the original outline with batched weight updates."""
|
||||
|
||||
from .boundary_smoothing import boundary_influence, reconstruct_camera_depth
|
||||
from .depth_surface import CUT_ATTRIBUTE, _math, ray_distance
|
||||
from .nodes import evaluate_field, group_input, read_float_attribute, sample_field
|
||||
from .smoothing import edge_boundary_field
|
||||
|
||||
|
||||
def _outline_influence(group, original, geometry):
|
||||
"""Identify original outline points and protect split boundary intersections."""
|
||||
nodes, links = group.nodes, group.links
|
||||
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
|
||||
nearest = nodes.new("GeometryNodeSampleNearest")
|
||||
nearest.domain = "POINT"
|
||||
links.new(original, nearest.inputs["Geometry"])
|
||||
position = nodes.new("GeometryNodeInputPosition").outputs["Position"]
|
||||
links.new(position, nearest.inputs["Sample Position"])
|
||||
original_rim = sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
|
||||
original_rim = _math(group, "GREATER_THAN", original_rim, 0)
|
||||
cut = _math(group, "GREATER_THAN", read_float_attribute(group, CUT_ATTRIBUTE), 0)
|
||||
protected = _math(group, "MULTIPLY", _math(group, "GREATER_THAN", rim, 0),
|
||||
_math(group, "MAXIMUM", cut, _math(group, "SUBTRACT", 1, original_rim)))
|
||||
# Resolve planar correspondence before replacing positions with camera depth.
|
||||
return sample_field(group, geometry, boundary_influence(group, protected), "FLOAT")
|
||||
|
||||
|
||||
def smooth_outline_camera(group, original, geometry, camera, uniform_scale):
|
||||
"""Update outline weights and blur ray distance in fixed-weight batches."""
|
||||
nodes, links = group.nodes, group.links
|
||||
controls = group_input(nodes, {"Outline Depth Fix", "Boundary Smooth"})
|
||||
influence = _outline_influence(group, original, geometry)
|
||||
depth = ray_distance(group, camera)
|
||||
|
||||
endpoints = nodes.new("GeometryNodeInputMeshEdgeVertices")
|
||||
length = nodes.new("ShaderNodeVectorMath")
|
||||
length.operation = "DISTANCE"
|
||||
links.new(endpoints.outputs["Position 1"], length.inputs[0])
|
||||
links.new(endpoints.outputs["Position 2"], length.inputs[1])
|
||||
statistic = nodes.new("GeometryNodeAttributeStatistic")
|
||||
statistic.data_type, statistic.domain = "FLOAT", "EDGE"
|
||||
links.new(geometry, statistic.inputs["Geometry"])
|
||||
links.new(length.outputs["Value"], statistic.inputs["Attribute"])
|
||||
scale = _math(group, "DIVIDE", uniform_scale,
|
||||
_math(group, "MAXIMUM", statistic.outputs["Median"], 1e-8))
|
||||
|
||||
seed = nodes.new("ShaderNodeCombineXYZ")
|
||||
links.new(depth, seed.inputs["Z"])
|
||||
carrier = nodes.new("GeometryNodeSetPosition")
|
||||
links.new(geometry, carrier.inputs["Geometry"])
|
||||
links.new(seed.outputs[0], carrier.inputs["Position"])
|
||||
repeat_output = nodes.new("GeometryNodeRepeatOutput")
|
||||
repeat_input = nodes.new("GeometryNodeRepeatInput")
|
||||
repeat_input.pair_with_output(repeat_output)
|
||||
links.new(carrier.outputs[0], repeat_input.inputs["Geometry"])
|
||||
links.new(controls.outputs["Outline Depth Fix"], repeat_input.inputs["Iterations"])
|
||||
|
||||
position = nodes.new("GeometryNodeInputPosition")
|
||||
current = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(position.outputs[0], current.inputs[0])
|
||||
reference = nodes.new("GeometryNodeBlurAttribute")
|
||||
reference.data_type = "FLOAT"
|
||||
reference.inputs["Iterations"].default_value = 1
|
||||
reference.inputs["Weight"].default_value = 0.5
|
||||
links.new(current.outputs["Z"], reference.inputs["Value"])
|
||||
residual = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", current.outputs["Z"], reference.outputs[0]))
|
||||
ratio = _math(group, "MULTIPLY", residual, scale)
|
||||
weight = nodes.new("ShaderNodeMapRange")
|
||||
weight.interpolation_type, weight.clamp = "SMOOTHSTEP", True
|
||||
weight.inputs["From Min"].default_value = 0.25
|
||||
weight.inputs["From Max"].default_value = 0.5
|
||||
links.new(ratio, weight.inputs["Value"])
|
||||
active = _math(group, "MULTIPLY", influence, _math(group, "GREATER_THAN", depth, 1e-8))
|
||||
active = sample_field(group, geometry, active, "FLOAT")
|
||||
weighted = _math(group, "MULTIPLY", weight.outputs[0], active)
|
||||
# Sampling the round's input prevents the inner Blur from updating its weight.
|
||||
frozen = sample_field(group, repeat_input.outputs["Geometry"], weighted, "FLOAT")
|
||||
blur = nodes.new("GeometryNodeBlurAttribute")
|
||||
blur.data_type = "FLOAT"
|
||||
links.new(current.outputs["Z"], blur.inputs["Value"])
|
||||
links.new(frozen, blur.inputs["Weight"])
|
||||
links.new(controls.outputs["Boundary Smooth"], blur.inputs["Iterations"])
|
||||
updated = nodes.new("ShaderNodeCombineXYZ")
|
||||
links.new(blur.outputs[0], updated.inputs["Z"])
|
||||
move = nodes.new("GeometryNodeSetPosition")
|
||||
links.new(repeat_input.outputs["Geometry"], move.inputs["Geometry"])
|
||||
links.new(updated.outputs[0], move.inputs["Position"])
|
||||
links.new(move.outputs[0], repeat_output.inputs["Geometry"])
|
||||
|
||||
result = sample_field(group, repeat_output.outputs["Geometry"], current.outputs["Z"], "FLOAT")
|
||||
enabled = _math(group, "MULTIPLY", active,
|
||||
_math(group, "MULTIPLY", controls.outputs["Outline Depth Fix"],
|
||||
controls.outputs["Boundary Smooth"]))
|
||||
choose = nodes.new("GeometryNodeSwitch")
|
||||
choose.input_type = "FLOAT"
|
||||
links.new(enabled, choose.inputs["Switch"])
|
||||
links.new(depth, choose.inputs["False"])
|
||||
links.new(result, choose.inputs["True"])
|
||||
return reconstruct_camera_depth(group, camera, depth, choose.outputs[0])
|
||||
@@ -5,11 +5,10 @@ from anyimage.operators.cutout_tool.shape import CUTOUT_Z_TO_X, NORMAL_SCALE_ATT
|
||||
from ..common.nodes import interface_socket, group_input, math_node, float_input, menu_switch, read_float_attribute, store_float_attribute, prepare_node_group, sample_field, evaluate_field
|
||||
from ..common.smoothing import expand_smooth
|
||||
from ..common.boundary_smoothing import smooth_cut_boundary
|
||||
from ..common.outline_smoothing import smooth_outline_camera
|
||||
from ..common.cutout import _island_cleanup, _position_field, _remove_zero_thickness, _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 remove_boundary_triangles, taper_split_profile
|
||||
from ..common.cutout_boundary import original_boundary_field, remove_boundary_triangles, taper_split_profile
|
||||
|
||||
|
||||
NODE_GROUP_NAME = "O Image Depth Cutout"
|
||||
@@ -92,7 +91,7 @@ def build_shell_fields(group, geometry, fields, is_shell, normal_weight=1):
|
||||
normal = nodes.new("GeometryNodeInputNormal")
|
||||
blur = nodes.new("GeometryNodeBlurAttribute")
|
||||
blur.data_type = "FLOAT_VECTOR"
|
||||
blur.inputs["Iterations"].default_value = 100
|
||||
links.new(group_input(nodes, {"Normal Smooth"}).outputs["Normal Smooth"], blur.inputs["Iterations"])
|
||||
links.new(normal.outputs[0], blur.inputs["Value"])
|
||||
normalized = blend_normal(group, normal.outputs[0], blur.outputs[0], normal_weight)
|
||||
rear_normal = sample_field(group, surface.outputs[0], normalized, "FLOAT_VECTOR")
|
||||
@@ -135,7 +134,9 @@ def build_front_fields(group, volume, front_normal, normal_weight=1):
|
||||
smooth = group.nodes.new("GeometryNodeBlurAttribute")
|
||||
smooth.data_type = "FLOAT_VECTOR"
|
||||
links.new(normal, smooth.inputs["Value"])
|
||||
smooth.inputs["Iterations"].default_value = 512
|
||||
iterations = group_input(group.nodes, {"Normal Smooth"}).outputs["Normal Smooth"]
|
||||
# Preserve the existing extra smoothing of the inflation direction at 100.
|
||||
links.new(_math(group, "MULTIPLY", iterations, 5.12), smooth.inputs["Iterations"])
|
||||
normalized = blend_normal(group, normal, smooth.outputs[0], normal_weight)
|
||||
return {"structure": structure, "inflation_normal": normalized}
|
||||
|
||||
@@ -195,7 +196,7 @@ def build_front_normal(group, normal_weight):
|
||||
blur = group.nodes.new("GeometryNodeBlurAttribute")
|
||||
blur.data_type = "FLOAT_VECTOR"
|
||||
links.new(normal.outputs[0], blur.inputs["Value"])
|
||||
blur.inputs["Iterations"].default_value = 100
|
||||
links.new(group_input(group.nodes, {"Normal Smooth"}).outputs["Normal Smooth"], blur.inputs["Iterations"])
|
||||
normalized = blend_normal(group, normal.outputs[0], blur.outputs[0], normal_weight)
|
||||
return evaluate_field(group, normalized, "FLOAT_VECTOR", "POINT")
|
||||
|
||||
@@ -273,7 +274,6 @@ def _build_depth_surface(group, geometry, controls):
|
||||
)
|
||||
geometry = remove_boundary_triangles(group, geometry)
|
||||
camera = build_surface_camera(group, _position_field(group, image), corner_camera, cut_vertex, split)
|
||||
camera = smooth_outline_camera(group, original, geometry, camera, scale)
|
||||
projection = project_depth_surface(
|
||||
group, geometry, camera, scale, controls.outputs["Reference Depth"], controls.outputs["Depth Scale"], split,
|
||||
)
|
||||
@@ -293,7 +293,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, original, projection)
|
||||
projection = taper_split_profile(group, projection, original_boundary_field(group, original))
|
||||
# The mode field is shared by the geometry and thickness choices.
|
||||
mode = group.nodes.new("GeometryNodeMenuSwitch")
|
||||
mode.data_type = "BOOLEAN"
|
||||
@@ -341,7 +341,6 @@ def build_image_depth_cutout_group():
|
||||
group = bpy.data.node_groups.new(name, 'GeometryNodeTree')
|
||||
group.is_modifier, group.use_fake_user = (True, True)
|
||||
group.color_tag = 'GEOMETRY'
|
||||
group['anyimage_asset_revision'] = 26
|
||||
interface_socket(group, 'Geometry', 'INPUT', 'NodeSocketGeometry')
|
||||
interface_socket(group, 'Mode', 'INPUT', 'NodeSocketMenu')
|
||||
float_input(group, 'Thickness', 1.0).description = 'Control how much the surface bulges in Balloon mode. Zero removes the bulge.'
|
||||
@@ -358,12 +357,12 @@ def build_image_depth_cutout_group():
|
||||
float_input(group, 'Reference Depth', 1.0, subtype='DISTANCE', parent=options)
|
||||
interface_socket(group, 'Depth Axis', 'INPUT', 'NodeSocketMenu', options).menu_expanded = True
|
||||
boundary_smooth = interface_socket(group, 'Boundary Smooth', 'INPUT', 'NodeSocketInt', options)
|
||||
boundary_smooth.default_value, boundary_smooth.min_value, boundary_smooth.max_value = 2, 1, 20
|
||||
boundary_smooth.description = 'Control boundary smoothing within each Outline Depth Fix pass. Higher values apply more smoothing per pass.'
|
||||
outline_smooth = interface_socket(group, 'Outline Depth Fix', 'INPUT', 'NodeSocketInt', options)
|
||||
outline_smooth.default_value, outline_smooth.min_value, outline_smooth.max_value = 16, 0, 200
|
||||
outline_smooth.description = 'Reduce abrupt depth changes near the original Cutout outline. Higher values apply more correction passes. Zero disables correction.'
|
||||
float_input(group, 'Front Inflation', 0.0, maximum=1, subtype='FACTOR', parent=options).description = 'Control how much the front surface bulges in Balloon mode. Zero keeps the original front surface.'
|
||||
boundary_smooth.default_value, boundary_smooth.min_value, boundary_smooth.max_value = 0, 0, 16
|
||||
boundary_smooth.description = 'Smooth geometry near the original outline and Depth Split edges. Zero disables boundary smoothing.'
|
||||
normal_smooth = interface_socket(group, 'Normal Smooth', 'INPUT', 'NodeSocketInt', options)
|
||||
normal_smooth.default_value, normal_smooth.min_value, normal_smooth.max_value = 64, 16, 512
|
||||
normal_smooth.description = 'Control smoothing of front and rear displacement normals. Higher values produce smoother directions.'
|
||||
float_input(group, 'Front Inflation', 0.2, maximum=1, subtype='FACTOR', parent=options).description = 'Control how much the front surface bulges in Balloon mode. Zero keeps the original front surface.'
|
||||
data = group.interface.new_panel(name='Data', default_closed=True)
|
||||
float_input(group, 'Uniform Scale', 1.0, parent=data).hide_in_modifier = True
|
||||
interface_socket(group, 'Depth Image', 'INPUT', 'NodeSocketImage', data).hide_in_modifier = True
|
||||
|
||||
@@ -53,7 +53,7 @@ def build_image_depth_plane_group(image_plane_group):
|
||||
float_input(group, "Smooth Weight", 1.0, maximum=1, subtype="FACTOR", parent=options).description = "Control the strength of each mesh smoothing pass. Higher values move vertices more."
|
||||
float_input(group, "Reference Depth", 1.0, subtype="DISTANCE", parent=options)
|
||||
boundary_smooth = _interface_socket(group, "Boundary Smooth", "INPUT", "NodeSocketInt", parent=options)
|
||||
boundary_smooth.default_value, boundary_smooth.min_value, boundary_smooth.max_value = 2, 0, 20
|
||||
boundary_smooth.default_value, boundary_smooth.min_value, boundary_smooth.max_value = 4, 0, 16
|
||||
boundary_smooth.description = "Smooth depth and geometry near cut edges while preserving the original image border. Zero disables boundary smoothing."
|
||||
validity_threshold = _interface_socket(group, "Mask Threshold", "INPUT", "NodeSocketFloat", parent=options)
|
||||
validity_threshold.description = "Set the minimum depth mask value treated as valid. Higher values exclude more partially valid boundary areas."
|
||||
|
||||
Reference in New Issue
Block a user