|
|
|
|
@@ -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_*")
|
|
|
|
|
|