- 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.
50 lines
2.6 KiB
Python
50 lines
2.6 KiB
Python
"""Clean cutout boundary triangles and taper the split thickness profile."""
|
|
|
|
from .depth_surface import _math
|
|
from .nodes import evaluate_field, group_input, read_float_attribute, sample_field, store_float_attribute
|
|
from .smoothing import edge_boundary_field
|
|
from .boundary_smoothing import boundary_influence
|
|
|
|
|
|
def remove_boundary_triangles(group, geometry):
|
|
"""Remove all-boundary triangles once on the current topology."""
|
|
nodes, links = group.nodes, group.links
|
|
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
|
|
all_boundary = _math(group, "GREATER_THAN", evaluate_field(group, rim, "FLOAT", "FACE"), 0.99999)
|
|
count = nodes.new("GeometryNodeInputMeshFaceNeighbors").outputs["Vertex Count"]
|
|
selected = _math(group, "MULTIPLY", all_boundary, _math(group, "COMPARE", count, 3))
|
|
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 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")
|
|
uv.data_type = "FLOAT_VECTOR"
|
|
uv.inputs["Name"].default_value = "UVMap"
|
|
original_uv = nodes.new("GeometryNodeSetPosition")
|
|
links.new(original, original_uv.inputs["Geometry"])
|
|
links.new(uv.outputs["Attribute"], original_uv.inputs["Position"])
|
|
nearest = nodes.new("GeometryNodeSampleNearest")
|
|
nearest.domain = "POINT"
|
|
links.new(original_uv.outputs["Geometry"], nearest.inputs["Geometry"])
|
|
links.new(uv.outputs["Attribute"], nearest.inputs["Sample Position"])
|
|
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"])
|
|
|
|
|
|
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))
|
|
profile = _math(group, "MULTIPLY", read_float_attribute(group, "o_balloon"), retained)
|
|
return store_float_attribute(group, geometry, "o_balloon", profile)
|