"""Node graph builders used only while generating bundled assets.""" def group_input(nodes, location, visible_names): node = nodes.new("NodeGroupInput") node.location = location visible = set(visible_names) for output in node.outputs: output.hide = output.name not in visible return node def edge_boundary_field(nodes, links, location): x, y = location vertex_neighbors = nodes.new("GeometryNodeInputMeshVertexNeighbors") vertex_neighbors.location = (x, y + 150.0) 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 loose_vertex.location = (x + 200.0, y + 150.0) links.new(vertex_neighbors.outputs["Vertex Count"], loose_vertex_a) edge_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors") edge_neighbors.location = (x, y - 150.0) 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 boundary_edge.location = (x + 200.0, y - 150.0) links.new(edge_neighbors.outputs["Face Count"], boundary_edge_a) edge_domain = nodes.new("GeometryNodeFieldOnDomain") edge_domain.data_type = "BOOLEAN" edge_domain.domain = "EDGE" edge_domain.location = (x + 400.0, y - 150.0) links.new(boundary_edge.outputs["Result"], edge_domain.inputs["Value"]) is_boundary = nodes.new("FunctionNodeBooleanMath") is_boundary.operation = "OR" is_boundary.location = (x + 600.0, y) 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, x_offset=0.0, iterations_name="Iterations" ): nodes = group.nodes links = group.links existing_nodes = set(nodes) iteration_controls = group_input(nodes, (550.0, 1150.0), {iterations_name}) weight_controls = group_input(nodes, (2300.0, 1450.0), {"Smooth"}) repeat_output = nodes.new("GeometryNodeRepeatOutput") repeat_output.location = (3050.0, 550.0) repeat_input = nodes.new("GeometryNodeRepeatInput") repeat_input.pair_with_output(repeat_output) repeat_output.repeat_items.new("GEOMETRY", "Geometry") repeat_input.location = (550.0, 550.0) links.new( iteration_controls.outputs[iterations_name], repeat_input.inputs["Iterations"], ) links.new(geometry, repeat_input.inputs["Geometry"]) boundary_field = edge_boundary_field(nodes, links, (750.0, -350.0)) boundary_points = nodes.new("GeometryNodeFieldOnDomain") boundary_points.data_type = "BOOLEAN" boundary_points.domain = "POINT" boundary_points.location = (1550.0, -350.0) links.new(boundary_field, boundary_points.inputs["Value"]) boundary_geometry = nodes.new("GeometryNodeSeparateGeometry") boundary_geometry.domain = "POINT" boundary_geometry.location = (1750.0, -150.0) links.new(repeat_input.outputs["Geometry"], boundary_geometry.inputs["Geometry"]) links.new(boundary_points.outputs["Value"], boundary_geometry.inputs["Selection"]) position = nodes.new("GeometryNodeInputPosition") position.location = (750.0, 850.0) 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 smooth_all.location = (950.0, 850.0) links.new(position.outputs["Position"], smooth_all.inputs["Value"]) boundary_position = nodes.new("GeometryNodeInputPosition") boundary_position.location = (1350.0, 250.0) 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 smooth_boundary.location = (1550.0, 250.0) links.new(boundary_position.outputs["Position"], smooth_boundary.inputs["Value"]) nearest_boundary = nodes.new("GeometryNodeSampleNearest") nearest_boundary.domain = "POINT" nearest_boundary.location = (1800.0, 150.0) links.new(boundary_geometry.outputs["Selection"], nearest_boundary.inputs["Geometry"]) sample_boundary = nodes.new("GeometryNodeSampleIndex") sample_boundary.data_type = "FLOAT_VECTOR" sample_boundary.domain = "POINT" sample_boundary.clamp = False sample_boundary.location = (2050.0, 250.0) 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" choose_boundary.location = (2300.0, 350.0) direct_boundary = edge_boundary_field(nodes, links, (750.0, 450.0)) links.new(direct_boundary, choose_boundary.inputs["Switch"]) links.new(smooth_all.outputs["Value"], choose_boundary.inputs["False"]) links.new(sample_boundary.outputs["Value"], choose_boundary.inputs["True"]) boundary_mix = nodes.new("ShaderNodeMix") boundary_mix.data_type = "VECTOR" boundary_mix.inputs[0].default_value = 1.0 boundary_mix.location = (2550.0, 400.0) links.new(smooth_all.outputs["Value"], boundary_mix.inputs[4]) links.new(choose_boundary.outputs["Output"], boundary_mix.inputs[5]) normal = nodes.new("GeometryNodeInputNormal") normal.location = (1350.0, 1050.0) 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 smooth_normal.location = (1550.0, 1050.0) links.new(normal.outputs["Normal"], smooth_normal.inputs["Value"]) normal_coherence = nodes.new("ShaderNodeVectorMath") normal_coherence.operation = "LENGTH" normal_coherence.location = (1800.0, 1050.0) links.new(smooth_normal.outputs["Value"], normal_coherence.inputs[0]) sharp_switch = nodes.new("GeometryNodeSwitch") sharp_switch.input_type = "FLOAT" sharp_switch.inputs["Switch"].default_value = True sharp_switch.inputs["False"].default_value = 1.0 sharp_switch.location = (2050.0, 1050.0) links.new(normal_coherence.outputs["Value"], sharp_switch.inputs["True"]) smooth_weight = nodes.new("ShaderNodeMath") smooth_weight.operation = "MULTIPLY" smooth_weight.location = (2300.0, 1050.0) links.new(weight_controls.outputs["Smooth"], smooth_weight.inputs[0]) links.new(sharp_switch.outputs["Output"], smooth_weight.inputs[1]) target_position = nodes.new("GeometryNodeSwitch") target_position.input_type = "VECTOR" target_position.inputs["Switch"].default_value = True target_position.location = (2550.0, 700.0) links.new(smooth_all.outputs["Value"], target_position.inputs["False"]) links.new(boundary_mix.outputs["Result"], target_position.inputs["True"]) current_position = nodes.new("GeometryNodeInputPosition") current_position.location = (2300.0, 850.0) mix_position = nodes.new("ShaderNodeMix") mix_position.data_type = "VECTOR" mix_position.location = (2800.0, 850.0) links.new(smooth_weight.outputs[0], mix_position.inputs[0]) links.new(current_position.outputs["Position"], mix_position.inputs[4]) links.new(target_position.outputs["Output"], mix_position.inputs[5]) set_position = nodes.new("GeometryNodeSetPosition") set_position.location = (2800.0, 550.0) 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"]) for node in set(nodes) - existing_nodes: node.location.x += x_offset return repeat_output.outputs["Geometry"]