- Unified the canonical local frame for generated geometries (Plane, Depth Plane, Relief Plane, Cutout, Depth Cutout) to align with Blender's object coordinate system, setting X as horizontal, Z as up, and +Y as depth. - Removed the `Depth Axis` parameter from `O Image Cutout` and `O Image Depth Cutout`, simplifying the geometry generation process. - Updated the depth direction defaults to use +Y instead of -Z for `O Image Relief Plane` and Depth Symmetry. - Adjusted the geometry nodes to ensure thickness, projection, and shell displacements operate along the Y-axis. - Implemented new capabilities for handling object space normals to align with the new canonical frame. - Enhanced the Depth Symmetry functionality to mirror across the canonical Y-axis and perform a final rotation to maintain the correct orientation. - Added tests to validate the new coordinate system and ensure compatibility with existing assets.
438 lines
21 KiB
Python
438 lines
21 KiB
Python
"""Face separation and connected thickness for Depth Surface assets."""
|
|
|
|
from .nodes import evaluate_field, store_float_attribute, store_boolean_attribute, remove_attributes, sample_field
|
|
|
|
|
|
CUT_ATTRIBUTE = ".o_anyimage_depth_cut"
|
|
|
|
|
|
def _math(group, operation, a, b=None):
|
|
node = group.nodes.new("ShaderNodeMath")
|
|
node.operation = operation
|
|
for index, value in enumerate((a, b)):
|
|
if value is None:
|
|
continue
|
|
if isinstance(value, (int, float)):
|
|
node.inputs[index].default_value = value
|
|
else:
|
|
group.links.new(value, node.inputs[index])
|
|
return node.outputs[0]
|
|
|
|
|
|
def _remove_strip_faces(group, geometry, *, triangles=False):
|
|
"""Delete boundary strips from one unchanged topology snapshot."""
|
|
nodes, links = group.nodes, group.links
|
|
|
|
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"]
|
|
|
|
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]
|
|
|
|
boundaries = []
|
|
for offset in range(4):
|
|
index = nodes.new("GeometryNodeInputIndex")
|
|
corners = nodes.new("GeometryNodeCornersOfFace")
|
|
links.new(index.outputs[0], corners.inputs["Face Index"])
|
|
corner = nodes.new("GeometryNodeOffsetCornerInFace")
|
|
corner.inputs["Offset"].default_value = offset
|
|
links.new(corners.outputs["Corner Index"], corner.inputs["Corner Index"])
|
|
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
|
links.new(corner.outputs["Corner Index"], edge.inputs["Corner Index"])
|
|
sample = nodes.new("GeometryNodeFieldAtIndex")
|
|
sample.data_type, sample.domain = "INT", "EDGE"
|
|
links.new(edge.outputs["Next Edge Index"], sample.inputs["Index"])
|
|
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
links.new(neighbors.outputs["Face Count"], sample.inputs["Value"])
|
|
boundaries.append(equals(sample.outputs["Value"], 1))
|
|
opposite = combine(
|
|
"MAXIMUM",
|
|
combine("MULTIPLY", boundaries[0], boundaries[2]),
|
|
combine("MULTIPLY", boundaries[1], boundaries[3]),
|
|
)
|
|
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"]
|
|
|
|
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)
|
|
|
|
# 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)
|
|
|
|
# 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)
|
|
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 sample_face_camera(group, image):
|
|
"""Sample camera XYZ at each planar face's UV center."""
|
|
nodes, links = group.nodes, group.links
|
|
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
|
uv.data_type = "FLOAT_VECTOR"
|
|
uv.inputs["Name"].default_value = "UVMap"
|
|
face_uv = nodes.new("GeometryNodeFieldOnDomain")
|
|
face_uv.data_type = "FLOAT_VECTOR"
|
|
face_uv.domain = "FACE"
|
|
links.new(uv.outputs["Attribute"], face_uv.inputs["Value"])
|
|
texture = nodes.new("GeometryNodeImageTexture")
|
|
texture.interpolation = "Linear"
|
|
texture.extension = "EXTEND"
|
|
links.new(image, texture.inputs["Image"])
|
|
links.new(face_uv.outputs["Value"], texture.inputs["Vector"])
|
|
return texture.outputs["Color"]
|
|
|
|
|
|
def ray_distance(group, camera):
|
|
"""Measure the distance from the camera origin to a camera-space point."""
|
|
length = group.nodes.new("ShaderNodeVectorMath")
|
|
length.operation = "LENGTH"
|
|
group.links.new(camera, length.inputs[0])
|
|
return length.outputs["Value"]
|
|
|
|
|
|
def split_depth_surface(group, geometry, face_camera, strength, reference_depth, *,
|
|
face_distance=None, face_center=None, triangles=False):
|
|
"""Split relative ray-distance jumps over the base face-center spacing."""
|
|
nodes, links = group.nodes, group.links
|
|
if face_center is None:
|
|
face_center = nodes.new("GeometryNodeInputPosition").outputs[0]
|
|
face_camera = evaluate_field(group, face_camera, "FLOAT_VECTOR", "FACE")
|
|
if face_distance is None:
|
|
face_distance = ray_distance(group, face_camera)
|
|
centers, depths = [], []
|
|
for side in (0, 1):
|
|
corner = nodes.new("GeometryNodeCornersOfEdge")
|
|
corner.inputs["Sort Index"].default_value = side
|
|
face = nodes.new("GeometryNodeFaceOfCorner")
|
|
links.new(corner.outputs["Corner Index"], face.inputs["Corner Index"])
|
|
for data_type, field, results in (
|
|
("FLOAT_VECTOR", face_center, centers),
|
|
("FLOAT", face_distance, depths),
|
|
):
|
|
sample = nodes.new("GeometryNodeFieldAtIndex")
|
|
sample.data_type = data_type
|
|
sample.domain = "FACE"
|
|
links.new(face.outputs["Face Index"], sample.inputs["Index"])
|
|
links.new(field, sample.inputs["Value"])
|
|
results.append(sample.outputs["Value"])
|
|
distance = nodes.new("ShaderNodeVectorMath")
|
|
distance.operation = "DISTANCE"
|
|
for index, center in enumerate(centers):
|
|
links.new(center, distance.inputs[index])
|
|
delta = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", *depths))
|
|
local_distance = _math(group, "MAXIMUM", _math(group, "MULTIPLY", _math(group, "ADD", *depths), 0.5), 1e-8)
|
|
reference = _math(group, "MAXIMUM", reference_depth, 1e-8)
|
|
# Uniform Scale cancels between the distance jump and local spacing correction.
|
|
# Cross multiplication also avoids dividing by the local distance.
|
|
selected = _math(
|
|
group,
|
|
"GREATER_THAN",
|
|
_math(group, "MULTIPLY", _math(group, "MULTIPLY", delta, reference), strength),
|
|
_math(group, "MULTIPLY", _math(group, "MULTIPLY", distance.outputs["Value"], local_distance), 2.5),
|
|
)
|
|
selected = _math(
|
|
group,
|
|
"MULTIPLY",
|
|
selected,
|
|
_math(group, "GREATER_THAN", distance.outputs["Value"], 0.0),
|
|
)
|
|
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
selected = _math(
|
|
group,
|
|
"MULTIPLY",
|
|
selected,
|
|
_math(group, "COMPARE", neighbors.outputs["Face Count"], 2.0),
|
|
)
|
|
cut_edges = nodes.new("GeometryNodeFieldOnDomain")
|
|
cut_edges.data_type = "FLOAT"
|
|
cut_edges.domain = "EDGE"
|
|
links.new(selected, cut_edges.inputs["Value"])
|
|
cut_vertices = nodes.new("GeometryNodeFieldOnDomain")
|
|
cut_vertices.data_type = "FLOAT"
|
|
cut_vertices.domain = "POINT"
|
|
links.new(cut_edges.outputs["Value"], cut_vertices.inputs["Value"])
|
|
corners, cut = store_boolean_attribute(
|
|
group, geometry, CUT_ATTRIBUTE,
|
|
_math(group, "GREATER_THAN", cut_vertices.outputs["Value"], 0.0), domain="CORNER",
|
|
)
|
|
split = nodes.new("GeometryNodeSplitEdges")
|
|
links.new(corners, split.inputs["Mesh"])
|
|
links.new(selected, split.inputs["Selection"])
|
|
enabled = _math(group, "GREATER_THAN", strength, 0.0)
|
|
choose = nodes.new("GeometryNodeSwitch")
|
|
choose.input_type = "GEOMETRY"
|
|
links.new(enabled, choose.inputs["Switch"])
|
|
links.new(geometry, choose.inputs["False"])
|
|
links.new(_remove_strip_faces(group, split.outputs["Mesh"], triangles=triangles), choose.inputs["True"])
|
|
return choose.outputs["Output"], face_camera, cut
|
|
|
|
|
|
def build_surface_camera(group, original, separated, cut_vertex, strength):
|
|
"""Change cut depth along the original ray, preserving every other vertex."""
|
|
enabled = group.nodes.new("GeometryNodeSwitch")
|
|
enabled.input_type = "BOOLEAN"
|
|
group.links.new(_math(group, "GREATER_THAN", strength, 0.0), enabled.inputs["Switch"])
|
|
enabled.inputs["False"].default_value = False
|
|
group.links.new(cut_vertex, enabled.inputs["True"])
|
|
original_axes = group.nodes.new("ShaderNodeSeparateXYZ")
|
|
separated_axes = group.nodes.new("ShaderNodeSeparateXYZ")
|
|
group.links.new(original, original_axes.inputs[0])
|
|
group.links.new(separated, separated_axes.inputs[0])
|
|
ratio = _math(
|
|
group, "DIVIDE", separated_axes.outputs["Z"], original_axes.outputs["Z"]
|
|
)
|
|
projected = group.nodes.new("ShaderNodeVectorMath")
|
|
projected.operation = "SCALE"
|
|
group.links.new(original, projected.inputs[0])
|
|
group.links.new(ratio, projected.inputs[3])
|
|
choose = group.nodes.new("GeometryNodeSwitch")
|
|
choose.input_type = "VECTOR"
|
|
group.links.new(enabled.outputs["Output"], choose.inputs["Switch"])
|
|
group.links.new(original, choose.inputs["False"])
|
|
group.links.new(projected.outputs[0], choose.inputs["True"])
|
|
return evaluate_field(group, choose.outputs["Output"], "FLOAT_VECTOR", "POINT")
|
|
|
|
|
|
def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
|
back_mask=None, weld_zero_thickness=False):
|
|
"""Connect caps with single quads by source vertex and cap identity."""
|
|
nodes, links = group.nodes, group.links
|
|
position = nodes.new("GeometryNodeInputPosition")
|
|
index = nodes.new("GeometryNodeInputIndex")
|
|
# Encode identity in the construction geometry before extrusion. The source
|
|
# surface remains a separate sampling input; no position or ID is stored.
|
|
identity = nodes.new("ShaderNodeCombineXYZ")
|
|
for operation, axis in (("MODULO", "X"), ("DIVIDE", "Y")):
|
|
part = nodes.new("FunctionNodeIntegerMath")
|
|
part.operation = operation
|
|
part.inputs[1].default_value = 4096
|
|
links.new(index.outputs[0], part.inputs[0])
|
|
links.new(part.outputs[0], identity.inputs[axis])
|
|
sheet = nodes.new("GeometryNodeSetPosition")
|
|
links.new(geometry, sheet.inputs["Geometry"])
|
|
links.new(identity.outputs[0], sheet.inputs["Position"])
|
|
saved = sheet.outputs["Geometry"]
|
|
layer_axes = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(position.outputs[0], layer_axes.inputs[0])
|
|
row = nodes.new("FunctionNodeIntegerMath")
|
|
row.operation = "MULTIPLY"
|
|
row.inputs[1].default_value = 4096
|
|
links.new(layer_axes.outputs["Z"], row.inputs[0])
|
|
vertex = nodes.new("FunctionNodeIntegerMath")
|
|
vertex.operation = "ADD"
|
|
links.new(layer_axes.outputs["X"], vertex.inputs[0])
|
|
links.new(row.outputs[0], vertex.inputs[1])
|
|
source_index = vertex.outputs[0]
|
|
source_position = sample_field(group, geometry, position.outputs[0], "FLOAT_VECTOR", index=source_index)
|
|
source_thickness = sample_field(group, geometry, thickness, "FLOAT", index=source_index)
|
|
|
|
extrude = nodes.new("GeometryNodeExtrudeMesh")
|
|
extrude.mode = "FACES"
|
|
extrude.inputs["Individual"].default_value = False
|
|
extrusion_offset = nodes.new("ShaderNodeCombineXYZ")
|
|
extrusion_offset.inputs["Y"].default_value = -1.0
|
|
links.new(extrusion_offset.outputs[0], extrude.inputs["Offset"])
|
|
links.new(saved, extrude.inputs["Mesh"])
|
|
sides = nodes.new("GeometryNodeSeparateGeometry")
|
|
sides.domain = "FACE"
|
|
links.new(extrude.outputs["Mesh"], sides.inputs["Geometry"])
|
|
links.new(extrude.outputs["Side"], sides.inputs["Selection"])
|
|
offset = nodes.new("ShaderNodeCombineXYZ")
|
|
offset.inputs["Y"].default_value = -1.0
|
|
back = nodes.new("GeometryNodeSetPosition")
|
|
links.new(saved, back.inputs["Geometry"])
|
|
links.new(offset.outputs[0], back.inputs["Offset"])
|
|
flip_back = nodes.new("GeometryNodeFlipFaces")
|
|
links.new(back.outputs["Geometry"], flip_back.inputs["Mesh"])
|
|
flip_sides = nodes.new("GeometryNodeFlipFaces")
|
|
links.new(sides.outputs["Selection"], flip_sides.inputs["Mesh"])
|
|
join = nodes.new("GeometryNodeJoinGeometry")
|
|
rear = flip_back.outputs["Mesh"]
|
|
rim = flip_sides.outputs["Mesh"]
|
|
back_parts = nodes.new("GeometryNodeJoinGeometry")
|
|
links.new(rear, back_parts.inputs["Geometry"])
|
|
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")
|
|
for value in (
|
|
saved,
|
|
rear,
|
|
):
|
|
links.new(value, join.inputs["Geometry"])
|
|
construction = join.outputs["Geometry"]
|
|
if weld_zero_thickness:
|
|
positive = _math(group, "GREATER_THAN", source_thickness, 1e-12)
|
|
flattened = nodes.new("ShaderNodeCombineXYZ")
|
|
for axis in ("X", "Z"):
|
|
links.new(layer_axes.outputs[axis], flattened.inputs[axis])
|
|
links.new(_math(group, "MULTIPLY", positive, layer_axes.outputs["Y"]), flattened.inputs["Y"])
|
|
collapse = nodes.new("GeometryNodeSetPosition")
|
|
links.new(construction, collapse.inputs["Geometry"])
|
|
links.new(flattened.outputs[0], collapse.inputs["Position"])
|
|
construction = collapse.outputs["Geometry"]
|
|
merge = nodes.new("GeometryNodeMergeByDistance")
|
|
merge.mode = "ALL"
|
|
merge.inputs["Distance"].default_value = 0.1
|
|
links.new(construction, merge.inputs["Geometry"])
|
|
displacement = nodes.new("ShaderNodeCombineXYZ")
|
|
distance = _math(group, "MULTIPLY", layer_axes.outputs["Y"], source_thickness)
|
|
links.new(distance, displacement.inputs["Y"])
|
|
restored = nodes.new("ShaderNodeVectorMath")
|
|
restored.operation = "ADD"
|
|
links.new(source_position, restored.inputs[0])
|
|
links.new(displacement.outputs[0], restored.inputs[1])
|
|
result = nodes.new("GeometryNodeSetPosition")
|
|
links.new(merge.outputs["Geometry"], result.inputs["Geometry"])
|
|
links.new(restored.outputs[0], result.inputs["Position"])
|
|
if normal_smooth is not None:
|
|
normal = nodes.new("GeometryNodeInputNormal")
|
|
blur = nodes.new("GeometryNodeBlurAttribute")
|
|
blur.data_type = "FLOAT_VECTOR"
|
|
blur.inputs["Iterations"].default_value = normal_smooth
|
|
links.new(normal.outputs[0], blur.inputs["Value"])
|
|
normalize = nodes.new("ShaderNodeVectorMath")
|
|
normalize.operation = "NORMALIZE"
|
|
links.new(blur.outputs[0], normalize.inputs[0])
|
|
source_normal = sample_field(group, geometry, normalize.outputs[0], "FLOAT_VECTOR", index=source_index)
|
|
normal_offset = nodes.new("ShaderNodeVectorMath")
|
|
normal_offset.operation = "SCALE"
|
|
links.new(source_normal, normal_offset.inputs[0])
|
|
links.new(distance, normal_offset.inputs[3])
|
|
links.new(normal_offset.outputs[0], restored.inputs[1])
|
|
nodes.remove(displacement)
|
|
return {
|
|
"geometry": result.outputs["Geometry"],
|
|
"front": source_position,
|
|
"source_index": source_index,
|
|
"position": restored.outputs[0],
|
|
"position_input": result.inputs["Position"],
|
|
}
|
|
|
|
|
|
def _projection_math(nodes, operation):
|
|
node = nodes.new("ShaderNodeMath")
|
|
node.operation = operation
|
|
return node
|
|
|
|
|
|
def project_depth_surface(
|
|
group, geometry, camera_position, uniform_scale, reference_depth, depth_scale, split_strength
|
|
):
|
|
"""Interpolate the rectangular plane into the model camera coordinates."""
|
|
nodes, links = group.nodes, group.links
|
|
scaled = nodes.new("ShaderNodeVectorMath")
|
|
scaled.operation = "SCALE"
|
|
links.new(camera_position, scaled.inputs[0])
|
|
links.new(uniform_scale, scaled.inputs[3])
|
|
separate = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(scaled.outputs["Vector"], separate.inputs["Vector"])
|
|
relative_depth = _projection_math(nodes, "SUBTRACT")
|
|
links.new(separate.outputs["Z"], relative_depth.inputs[0])
|
|
links.new(reference_depth, relative_depth.inputs[1])
|
|
front = _projection_math(nodes, "MULTIPLY")
|
|
links.new(relative_depth.outputs[0], front.inputs[0])
|
|
links.new(depth_scale, front.inputs[1])
|
|
|
|
negative_z = _projection_math(nodes, "MULTIPLY")
|
|
negative_z.inputs[1].default_value = -1.0
|
|
links.new(separate.outputs["Y"], negative_z.inputs[0])
|
|
projected = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(separate.outputs["X"], projected.inputs["X"])
|
|
links.new(front.outputs[0], projected.inputs["Y"])
|
|
links.new(negative_z.outputs[0], projected.inputs["Z"])
|
|
current_position = nodes.new("GeometryNodeInputPosition")
|
|
blend = nodes.new("ShaderNodeMix")
|
|
blend.data_type = "VECTOR"
|
|
blend.clamp_factor = False
|
|
links.new(depth_scale, blend.inputs[0])
|
|
links.new(current_position.outputs["Position"], blend.inputs[4])
|
|
links.new(projected.outputs["Vector"], blend.inputs[5])
|
|
blended_axes = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(blend.outputs["Result"], blended_axes.inputs["Vector"])
|
|
surface_position = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(blended_axes.outputs["X"], surface_position.inputs["X"])
|
|
links.new(front.outputs[0], surface_position.inputs["Y"])
|
|
links.new(blended_axes.outputs["Z"], surface_position.inputs["Z"])
|
|
project_geometry = nodes.new("GeometryNodeSetPosition")
|
|
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),
|
|
)
|