"""Build the Mesh Plane Geometry Nodes group.""" import bpy MESH_PLANE_GROUP_NAME = "O Mesh Plane" def new_socket(interface, name, in_out, socket_type, parent=None): socket = interface.new_socket( name=name, in_out=in_out, socket_type=socket_type, parent=parent, ) if in_out == "INPUT" and hasattr(socket, "structure_type"): socket.structure_type = "SINGLE" return socket def new_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 build_mesh_plane_group(): group = bpy.data.node_groups.new(MESH_PLANE_GROUP_NAME, "GeometryNodeTree") group.is_modifier = True interface = group.interface geometry = new_socket(interface, "Geometry", "INPUT", "NodeSocketGeometry") geometry.structure_type = "SINGLE" subdivide = new_socket(interface, "Subdivide", "INPUT", "NodeSocketInt") subdivide.default_value = 0 subdivide.min_value = 0 subdivide.max_value = 10 thickness = new_socket(interface, "Thickness", "INPUT", "NodeSocketFloat") thickness.default_value = 0.0 thickness.min_value = 0.0 thickness.max_value = 10000.0 thickness.subtype = "DISTANCE" data_panel = interface.new_panel(name="Data", default_closed=True) material = new_socket( interface, "Material", "INPUT", "NodeSocketMaterial", parent=data_panel, ) material.hide_in_modifier = True new_socket(interface, "Geometry", "OUTPUT", "NodeSocketGeometry") nodes = group.nodes links = group.links geometry_input = new_group_input(nodes, (-1800.0, 500.0), {"Geometry"}) subdivide_input = new_group_input(nodes, (-1800.0, 0.0), {"Subdivide"}) thickness_input = new_group_input(nodes, (100.0, -250.0), {"Thickness"}) material_input = new_group_input(nodes, (900.0, 0.0), {"Material"}) output = nodes.new("NodeGroupOutput") output.location = (1350.0, 500.0) bounds = nodes.new("GeometryNodeBoundBox") bounds.location = (-1600.0, 500.0) links.new(geometry_input.outputs["Geometry"], bounds.inputs["Geometry"]) dimensions = nodes.new("ShaderNodeVectorMath") dimensions.operation = "SUBTRACT" dimensions.location = (-1400.0, 500.0) links.new(bounds.outputs["Max"], dimensions.inputs[0]) links.new(bounds.outputs["Min"], dimensions.inputs[1]) separate_dimensions = nodes.new("ShaderNodeSeparateXYZ") separate_dimensions.location = (-1200.0, 500.0) links.new(dimensions.outputs["Vector"], separate_dimensions.inputs[0]) short_segments = nodes.new("ShaderNodeMath") short_segments.operation = "POWER" short_segments.inputs[0].default_value = 2.0 short_segments.location = (-1400.0, 0.0) links.new(subdivide_input.outputs["Subdivide"], short_segments.inputs[1]) vertex_counts = [] for index, (axis, other_axis) in enumerate((("X", "Y"), ("Y", "X"))): y = 300.0 - index * 300.0 ratio = nodes.new("ShaderNodeMath") ratio.operation = "DIVIDE" ratio.location = (-1000.0, y) links.new(separate_dimensions.outputs[axis], ratio.inputs[0]) links.new(separate_dimensions.outputs[other_axis], ratio.inputs[1]) at_least_one = nodes.new("ShaderNodeMath") at_least_one.operation = "MAXIMUM" at_least_one.inputs[1].default_value = 1.0 at_least_one.location = (-800.0, y) links.new(ratio.outputs[0], at_least_one.inputs[0]) scaled_segments = nodes.new("ShaderNodeMath") scaled_segments.operation = "MULTIPLY" scaled_segments.location = (-600.0, y) links.new(short_segments.outputs[0], scaled_segments.inputs[0]) links.new(at_least_one.outputs[0], scaled_segments.inputs[1]) rounded_segments = nodes.new("ShaderNodeMath") rounded_segments.operation = "ROUND" rounded_segments.location = (-400.0, y) links.new(scaled_segments.outputs[0], rounded_segments.inputs[0]) vertices = nodes.new("ShaderNodeMath") vertices.operation = "ADD" vertices.inputs[1].default_value = 1.0 vertices.location = (-200.0, y) links.new(rounded_segments.outputs[0], vertices.inputs[0]) vertex_counts.append(vertices.outputs[0]) grid = nodes.new("GeometryNodeMeshGrid") grid.location = (50.0, 500.0) links.new(separate_dimensions.outputs["X"], grid.inputs["Size X"]) links.new(separate_dimensions.outputs["Y"], grid.inputs["Size Y"]) links.new(vertex_counts[0], grid.inputs["Vertices X"]) links.new(vertex_counts[1], grid.inputs["Vertices Y"]) position = nodes.new("GeometryNodeInputPosition") position.location = (-200.0, 900.0) separate_position = nodes.new("ShaderNodeSeparateXYZ") separate_position.location = (0.0, 900.0) links.new(position.outputs["Position"], separate_position.inputs[0]) uv_components = [] for index, axis in enumerate(("X", "Y")): y = 900.0 - index * 180.0 normalize = nodes.new("ShaderNodeMath") normalize.operation = "DIVIDE" normalize.location = (200.0, y) links.new(separate_position.outputs[axis], normalize.inputs[0]) links.new(separate_dimensions.outputs[axis], normalize.inputs[1]) offset_uv = nodes.new("ShaderNodeMath") offset_uv.operation = "ADD" offset_uv.inputs[1].default_value = 0.5 offset_uv.location = (400.0, y) links.new(normalize.outputs[0], offset_uv.inputs[0]) uv_components.append(offset_uv.outputs[0]) uv_coordinates = nodes.new("ShaderNodeCombineXYZ") uv_coordinates.location = (600.0, 800.0) links.new(uv_components[0], uv_coordinates.inputs["X"]) links.new(uv_components[1], uv_coordinates.inputs["Y"]) store_grid_uv = nodes.new("GeometryNodeStoreNamedAttribute") store_grid_uv.data_type = "FLOAT2" store_grid_uv.domain = "CORNER" store_grid_uv.inputs["Name"].default_value = "UVMap" store_grid_uv.location = (250.0, 500.0) links.new(grid.outputs["Mesh"], store_grid_uv.inputs["Geometry"]) links.new(uv_coordinates.outputs["Vector"], store_grid_uv.inputs["Value"]) center_sum = nodes.new("ShaderNodeVectorMath") center_sum.operation = "ADD" center_sum.location = (-200.0, 1150.0) links.new(bounds.outputs["Min"], center_sum.inputs[0]) links.new(bounds.outputs["Max"], center_sum.inputs[1]) center = nodes.new("ShaderNodeVectorMath") center.operation = "SCALE" center.inputs[3].default_value = 0.5 center.location = (50.0, 1150.0) links.new(center_sum.outputs["Vector"], center.inputs[0]) place_grid = nodes.new("GeometryNodeTransform") place_grid.location = (500.0, 500.0) links.new(store_grid_uv.outputs["Geometry"], place_grid.inputs["Geometry"]) links.new(center.outputs["Vector"], place_grid.inputs["Translation"]) cell_sizes = [] for index, axis in enumerate(("X", "Y")): y = 100.0 - index * 180.0 segments = nodes.new("ShaderNodeMath") segments.operation = "SUBTRACT" segments.inputs[1].default_value = 1.0 segments.location = (-100.0, y) links.new(vertex_counts[index], segments.inputs[0]) cell_size = nodes.new("ShaderNodeMath") cell_size.operation = "DIVIDE" cell_size.location = (100.0, y) links.new(separate_dimensions.outputs[axis], cell_size.inputs[0]) links.new(segments.outputs[0], cell_size.inputs[1]) cell_sizes.append(cell_size.outputs[0]) cell_area = nodes.new("ShaderNodeMath") cell_area.operation = "MULTIPLY" cell_area.location = (300.0, 100.0) links.new(cell_sizes[0], cell_area.inputs[0]) links.new(cell_sizes[1], cell_area.inputs[1]) target_step = nodes.new("ShaderNodeMath") target_step.operation = "SQRT" target_step.location = (500.0, 100.0) links.new(cell_area.outputs[0], target_step.inputs[0]) depth_ratio = nodes.new("ShaderNodeMath") depth_ratio.operation = "DIVIDE" depth_ratio.location = (300.0, -100.0) links.new(thickness_input.outputs["Thickness"], depth_ratio.inputs[0]) links.new(target_step.outputs[0], depth_ratio.inputs[1]) rounded_depth = nodes.new("ShaderNodeMath") rounded_depth.operation = "ROUND" rounded_depth.location = (500.0, -100.0) links.new(depth_ratio.outputs[0], rounded_depth.inputs[0]) depth_segments = nodes.new("ShaderNodeMath") depth_segments.operation = "MAXIMUM" depth_segments.inputs[1].default_value = 1.0 depth_segments.location = (700.0, -100.0) links.new(rounded_depth.outputs[0], depth_segments.inputs[0]) depth_vertices = nodes.new("ShaderNodeMath") depth_vertices.operation = "ADD" depth_vertices.inputs[1].default_value = 1.0 depth_vertices.location = (900.0, -100.0) links.new(depth_segments.outputs[0], depth_vertices.inputs[0]) cube_size = nodes.new("ShaderNodeCombineXYZ") cube_size.location = (700.0, 250.0) links.new(separate_dimensions.outputs["X"], cube_size.inputs["X"]) links.new(separate_dimensions.outputs["Y"], cube_size.inputs["Y"]) links.new(thickness_input.outputs["Thickness"], cube_size.inputs["Z"]) cube = nodes.new("GeometryNodeMeshCube") cube.location = (900.0, 250.0) links.new(cube_size.outputs["Vector"], cube.inputs["Size"]) links.new(vertex_counts[0], cube.inputs["Vertices X"]) links.new(vertex_counts[1], cube.inputs["Vertices Y"]) links.new(depth_vertices.outputs[0], cube.inputs["Vertices Z"]) store_cube_uv = nodes.new("GeometryNodeStoreNamedAttribute") store_cube_uv.data_type = "FLOAT2" store_cube_uv.domain = "CORNER" store_cube_uv.inputs["Name"].default_value = "UVMap" store_cube_uv.location = (1100.0, 250.0) links.new(cube.outputs["Mesh"], store_cube_uv.inputs["Geometry"]) links.new(uv_coordinates.outputs["Vector"], store_cube_uv.inputs["Value"]) place_cube = nodes.new("GeometryNodeTransform") place_cube.location = (1300.0, 250.0) links.new(store_cube_uv.outputs["Geometry"], place_cube.inputs["Geometry"]) links.new(center.outputs["Vector"], place_cube.inputs["Translation"]) has_thickness = nodes.new("FunctionNodeCompare") has_thickness.data_type = "FLOAT" has_thickness.operation = "GREATER_THAN" has_thickness.location = (450.0, -350.0) links.new(thickness_input.outputs["Thickness"], has_thickness.inputs[0]) geometry_switch = nodes.new("GeometryNodeSwitch") geometry_switch.input_type = "GEOMETRY" geometry_switch.location = (750.0, 500.0) links.new(has_thickness.outputs["Result"], geometry_switch.inputs["Switch"]) links.new(place_grid.outputs["Geometry"], geometry_switch.inputs["False"]) links.new(place_cube.outputs["Geometry"], geometry_switch.inputs["True"]) set_material = nodes.new("GeometryNodeSetMaterial") set_material.location = (1050.0, 500.0) links.new(geometry_switch.outputs["Output"], set_material.inputs["Geometry"]) links.new(material_input.outputs["Material"], set_material.inputs["Material"]) links.new(set_material.outputs["Geometry"], output.inputs["Geometry"]) group.color_tag = "GEOMETRY" group.description = ( "Build an aspect-adaptive image plane and add optional centered thickness." ) group.use_fake_user = True return group