- Introduced `frames.py` to manage field complex calculations and their arrangement within node groups. - Added functionality to create and remove field frames based on node arrangements. - Developed `passages.py` to find unobstructed paths for connections around calculation frames. - Implemented `references.py` to route shared results on peripheral tracks, ensuring efficient distribution of outputs. - Created `sections.py` to reserve space for field calculations adjacent to geometry consumers. - Added `sources.py` to position shared calculation sources effectively beside their distribution channels. - Enhanced outline smoothing with `outline_smoothing.py` to adapt camera depth along original outlines.
153 lines
7.3 KiB
Python
153 lines
7.3 KiB
Python
"""Build shared boundary fields and iterative smoothing."""
|
|
from .nodes import group_input, sample_field
|
|
|
|
|
|
def edge_boundary_field(nodes, links):
|
|
vertex_neighbors = nodes.new("GeometryNodeInputMeshVertexNeighbors")
|
|
loose_vertex = nodes.new("FunctionNodeCompare")
|
|
loose_vertex.data_type = "INT"
|
|
loose_vertex.operation = "EQUAL"
|
|
loose_vertex_a = next(
|
|
socket for socket in loose_vertex.inputs if socket.identifier == "A_INT"
|
|
)
|
|
loose_vertex_b = next(
|
|
socket for socket in loose_vertex.inputs if socket.identifier == "B_INT"
|
|
)
|
|
loose_vertex_b.default_value = 1
|
|
links.new(vertex_neighbors.outputs["Vertex Count"], loose_vertex_a)
|
|
|
|
edge_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
boundary_edge = nodes.new("FunctionNodeCompare")
|
|
boundary_edge.data_type = "INT"
|
|
boundary_edge.operation = "EQUAL"
|
|
boundary_edge_a = next(
|
|
socket for socket in boundary_edge.inputs if socket.identifier == "A_INT"
|
|
)
|
|
boundary_edge_b = next(
|
|
socket for socket in boundary_edge.inputs if socket.identifier == "B_INT"
|
|
)
|
|
boundary_edge_b.default_value = 1
|
|
links.new(edge_neighbors.outputs["Face Count"], boundary_edge_a)
|
|
edge_domain = nodes.new("GeometryNodeFieldOnDomain")
|
|
edge_domain.data_type = "BOOLEAN"
|
|
edge_domain.domain = "EDGE"
|
|
links.new(boundary_edge.outputs["Result"], edge_domain.inputs["Value"])
|
|
|
|
is_boundary = nodes.new("FunctionNodeBooleanMath")
|
|
is_boundary.operation = "OR"
|
|
links.new(edge_domain.outputs["Value"], is_boundary.inputs[0])
|
|
links.new(loose_vertex.outputs["Result"], is_boundary.inputs[1])
|
|
return is_boundary.outputs["Boolean"]
|
|
|
|
|
|
def expand_smooth(
|
|
group, geometry, iterations_name="Iterations", weight_name="Smooth", *,
|
|
influence=None, pin_sharp=True,
|
|
):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
iteration_controls = group_input(nodes, {iterations_name})
|
|
weight_controls = group_input(nodes, {weight_name})
|
|
repeat_output = nodes.new("GeometryNodeRepeatOutput")
|
|
repeat_input = nodes.new("GeometryNodeRepeatInput")
|
|
repeat_input.pair_with_output(repeat_output)
|
|
links.new(
|
|
iteration_controls.outputs[iterations_name],
|
|
repeat_input.inputs["Iterations"],
|
|
)
|
|
|
|
boundary_field = edge_boundary_field(nodes, links)
|
|
boundary_points = nodes.new("GeometryNodeFieldOnDomain")
|
|
boundary_points.data_type = "BOOLEAN"
|
|
boundary_points.domain = "POINT"
|
|
links.new(boundary_field, boundary_points.inputs["Value"])
|
|
|
|
boundary_geometry = nodes.new("GeometryNodeSeparateGeometry")
|
|
boundary_geometry.domain = "POINT"
|
|
links.new(repeat_input.outputs["Geometry"], boundary_geometry.inputs["Geometry"])
|
|
links.new(boundary_points.outputs["Value"], boundary_geometry.inputs["Selection"])
|
|
links.new(geometry, repeat_input.inputs["Geometry"])
|
|
nearest_boundary = nodes.new("GeometryNodeSampleNearest")
|
|
nearest_boundary.domain = "POINT"
|
|
if influence is not None:
|
|
# Both boundary meshes keep the same compact index order: only positions change.
|
|
source_boundary = nodes.new("GeometryNodeSeparateGeometry")
|
|
source_boundary.domain = "POINT"
|
|
links.new(geometry, source_boundary.inputs["Geometry"])
|
|
links.new(boundary_points.outputs["Value"], source_boundary.inputs["Selection"])
|
|
links.new(source_boundary.outputs["Selection"], nearest_boundary.inputs["Geometry"])
|
|
source_position = nodes.new("GeometryNodeInputPosition").outputs["Position"]
|
|
fixed_position = sample_field(group, geometry, source_position, "FLOAT_VECTOR")
|
|
links.new(fixed_position, nearest_boundary.inputs["Sample Position"])
|
|
else:
|
|
links.new(boundary_geometry.outputs["Selection"], nearest_boundary.inputs["Geometry"])
|
|
|
|
position = nodes.new("GeometryNodeInputPosition")
|
|
smooth_all = nodes.new("GeometryNodeBlurAttribute")
|
|
smooth_all.data_type = "FLOAT_VECTOR"
|
|
smooth_all.inputs["Iterations"].default_value = 1
|
|
smooth_all.inputs["Weight"].default_value = 1.0
|
|
links.new(position.outputs["Position"], smooth_all.inputs["Value"])
|
|
|
|
boundary_position = nodes.new("GeometryNodeInputPosition")
|
|
smooth_boundary = nodes.new("GeometryNodeBlurAttribute")
|
|
smooth_boundary.data_type = "FLOAT_VECTOR"
|
|
smooth_boundary.inputs["Iterations"].default_value = 1
|
|
smooth_boundary.inputs["Weight"].default_value = 0.5
|
|
links.new(boundary_position.outputs["Position"], smooth_boundary.inputs["Value"])
|
|
sample_boundary = nodes.new("GeometryNodeSampleIndex")
|
|
sample_boundary.data_type = "FLOAT_VECTOR"
|
|
sample_boundary.domain = "POINT"
|
|
sample_boundary.clamp = False
|
|
links.new(boundary_geometry.outputs["Selection"], sample_boundary.inputs["Geometry"])
|
|
links.new(smooth_boundary.outputs["Value"], sample_boundary.inputs["Value"])
|
|
links.new(nearest_boundary.outputs["Index"], sample_boundary.inputs["Index"])
|
|
|
|
choose_boundary = nodes.new("GeometryNodeSwitch")
|
|
choose_boundary.input_type = "VECTOR"
|
|
links.new(boundary_points.outputs["Value"], choose_boundary.inputs["Switch"])
|
|
links.new(smooth_all.outputs["Value"], choose_boundary.inputs["False"])
|
|
links.new(sample_boundary.outputs["Value"], choose_boundary.inputs["True"])
|
|
|
|
weight = weight_controls.outputs[weight_name]
|
|
if pin_sharp:
|
|
normal = nodes.new("GeometryNodeInputNormal")
|
|
smooth_normal = nodes.new("GeometryNodeBlurAttribute")
|
|
smooth_normal.data_type = "FLOAT_VECTOR"
|
|
smooth_normal.inputs["Iterations"].default_value = 10
|
|
smooth_normal.inputs["Weight"].default_value = 1.0
|
|
links.new(normal.outputs["Normal"], smooth_normal.inputs["Value"])
|
|
normal_coherence = nodes.new("ShaderNodeVectorMath")
|
|
normal_coherence.operation = "LENGTH"
|
|
links.new(smooth_normal.outputs["Value"], normal_coherence.inputs[0])
|
|
smooth_weight = nodes.new("ShaderNodeMath")
|
|
smooth_weight.operation = "MULTIPLY"
|
|
links.new(weight_controls.outputs[weight_name], smooth_weight.inputs[0])
|
|
links.new(normal_coherence.outputs["Value"], smooth_weight.inputs[1])
|
|
weight = smooth_weight.outputs[0]
|
|
if influence is not None:
|
|
masked_weight = nodes.new("ShaderNodeMath")
|
|
masked_weight.operation = "MULTIPLY"
|
|
links.new(weight, masked_weight.inputs[0])
|
|
links.new(influence, masked_weight.inputs[1])
|
|
weight = masked_weight.outputs[0]
|
|
|
|
current_position = nodes.new("GeometryNodeInputPosition")
|
|
mix_position = nodes.new("ShaderNodeMix")
|
|
mix_position.data_type = "VECTOR"
|
|
links.new(weight, mix_position.inputs[0])
|
|
links.new(current_position.outputs["Position"], mix_position.inputs[4])
|
|
links.new(choose_boundary.outputs["Output"], mix_position.inputs[5])
|
|
set_position = nodes.new("GeometryNodeSetPosition")
|
|
links.new(repeat_input.outputs["Geometry"], set_position.inputs["Geometry"])
|
|
links.new(mix_position.outputs["Result"], set_position.inputs["Position"])
|
|
links.new(set_position.outputs["Geometry"], repeat_output.inputs["Geometry"])
|
|
if influence is not None:
|
|
enabled = nodes.new("GeometryNodeSwitch")
|
|
enabled.input_type = "GEOMETRY"
|
|
links.new(iteration_controls.outputs[iterations_name], enabled.inputs["Switch"])
|
|
links.new(geometry, enabled.inputs["False"])
|
|
links.new(repeat_output.outputs["Geometry"], enabled.inputs["True"])
|
|
return enabled.outputs["Output"]
|
|
return repeat_output.outputs["Geometry"]
|