from pathlib import Path import bpy GROUP_NAME = "O DA3 Surface" MOGE_SURFACE_GROUP_NAME = "O MoGe2 Surface" DEPTH_PLANE_GROUP_NAME = "O DA3 Depth Plane" MOGE_DEPTH_PLANE_GROUP_NAME = "O MoGe2 Depth Plane" SHADELESS_GROUP_NAME = "O Shadeless" MATERIAL_DISPLACEMENT_ATTRIBUTE = "o_material_displacement" BACKGROUND_DEPTH_ATTRIBUTE = "o_background_depth" DEPTH_SCALE_ATTRIBUTE = "o_depth_scale" OUTPUT_PATH = ( Path(__file__).parents[1] / "src" / "snapform" / "assets" / "O_SnapForm.blend" ) def input_socket(node, name, fallback): value = node.inputs.get(name) return value if value is not None else node.inputs[fallback] 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_compare(nodes, data_type, operation, location, value): node = nodes.new("FunctionNodeCompare") node.data_type = data_type node.operation = operation node.location = location input_socket(node, "B", 1).default_value = value return node 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 add_edge_boundary_field(nodes, links, location): x, y = location vertex_neighbors = nodes.new("GeometryNodeInputMeshVertexNeighbors") vertex_neighbors.location = (x, y + 100.0) loose_vertex = new_compare( nodes, "INT", "EQUAL", (x + 180.0, y + 100.0), 1, ) links.new( vertex_neighbors.outputs["Vertex Count"], input_socket(loose_vertex, "A", 0), ) edge_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors") edge_neighbors.location = (x, y - 100.0) boundary_edge = new_compare( nodes, "INT", "EQUAL", (x + 180.0, y - 100.0), 1, ) links.new( edge_neighbors.outputs["Face Count"], input_socket(boundary_edge, "A", 0), ) edge_to_point = nodes.new("GeometryNodeFieldOnDomain") edge_to_point.data_type = "BOOLEAN" edge_to_point.domain = "EDGE" edge_to_point.location = (x + 370.0, y - 100.0) links.new(boundary_edge.outputs["Result"], edge_to_point.inputs["Value"]) combine = nodes.new("FunctionNodeBooleanMath") combine.operation = "OR" combine.location = (x + 560.0, y) links.new(loose_vertex.outputs["Result"], combine.inputs[0]) links.new(edge_to_point.outputs["Value"], combine.inputs[1]) return combine.outputs[0] def add_topology_fix_zone(group, geometry, iterations, location): nodes = group.nodes links = group.links x, y = location repeat_output = nodes.new("GeometryNodeRepeatOutput") repeat_output.location = (x + 1450.0, y) repeat_input = nodes.new("GeometryNodeRepeatInput") repeat_input.pair_with_output(repeat_output) repeat_input.location = (x, y) links.new(geometry, repeat_input.inputs["Geometry"]) links.new(iterations, repeat_input.inputs["Iterations"]) edge_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors") edge_neighbors.location = (x + 100.0, y + 420.0) non_manifold = new_compare( nodes, "INT", "GREATER_THAN", (x + 280.0, y + 500.0), 2, ) boundary = new_compare( nodes, "INT", "LESS_THAN", (x + 280.0, y + 320.0), 2, ) links.new( edge_neighbors.outputs["Face Count"], input_socket(non_manifold, "A", 0), ) links.new( edge_neighbors.outputs["Face Count"], input_socket(boundary, "A", 0), ) non_manifold_face = nodes.new("GeometryNodeFieldOnDomain") non_manifold_face.data_type = "BOOLEAN" non_manifold_face.domain = "FACE" non_manifold_face.location = (x + 480.0, y + 500.0) boundary_face = nodes.new("GeometryNodeFieldOnDomain") boundary_face.data_type = "BOOLEAN" boundary_face.domain = "FACE" boundary_face.location = (x + 480.0, y + 320.0) links.new(non_manifold.outputs["Result"], non_manifold_face.inputs["Value"]) links.new(boundary.outputs["Result"], boundary_face.inputs["Value"]) broken_face = nodes.new("FunctionNodeBooleanMath") broken_face.operation = "AND" broken_face.location = (x + 670.0, y + 420.0) links.new(non_manifold_face.outputs["Value"], broken_face.inputs[0]) links.new(boundary_face.outputs["Value"], broken_face.inputs[1]) remove_broken = nodes.new("GeometryNodeSeparateGeometry") remove_broken.domain = "FACE" remove_broken.location = (x + 670.0, y) links.new(repeat_input.outputs["Geometry"], remove_broken.inputs["Geometry"]) links.new(broken_face.outputs[0], remove_broken.inputs["Selection"]) remaining_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors") remaining_neighbors.location = (x + 650.0, y - 420.0) remaining_boundary = new_compare( nodes, "INT", "LESS_THAN", (x + 830.0, y - 420.0), 2, ) links.new( remaining_neighbors.outputs["Face Count"], input_socket(remaining_boundary, "A", 0), ) boundary_face_2 = nodes.new("GeometryNodeFieldOnDomain") boundary_face_2.data_type = "BOOLEAN" boundary_face_2.domain = "FACE" boundary_face_2.location = (x + 1010.0, y - 420.0) boundary_edge_2 = nodes.new("GeometryNodeFieldOnDomain") boundary_edge_2.data_type = "BOOLEAN" boundary_edge_2.domain = "EDGE" boundary_edge_2.location = (x + 1190.0, y - 420.0) links.new(remaining_boundary.outputs["Result"], boundary_face_2.inputs["Value"]) links.new(boundary_face_2.outputs["Value"], boundary_edge_2.inputs["Value"]) face_groups = nodes.new("GeometryNodeEdgesToFaceGroups") face_groups.location = (x + 1010.0, y - 220.0) links.new( boundary_edge_2.outputs["Value"], face_groups.inputs["Boundary Edges"], ) accumulate = nodes.new("GeometryNodeAccumulateField") accumulate.data_type = "INT" accumulate.domain = "FACE" accumulate.location = (x + 1190.0, y - 160.0) links.new(remaining_neighbors.outputs["Face Count"], accumulate.inputs["Value"]) links.new(face_groups.outputs["Face Group ID"], accumulate.inputs["Group ID"]) single_face = new_compare( nodes, "INT", "LESS_THAN", (x + 1370.0, y - 160.0), 2, ) links.new(accumulate.outputs["Total"], input_socket(single_face, "A", 0)) remove_single = nodes.new("GeometryNodeSeparateGeometry") remove_single.domain = "FACE" remove_single.location = (x + 1250.0, y + 80.0) links.new(remove_broken.outputs["Inverted"], remove_single.inputs["Geometry"]) links.new(single_face.outputs["Result"], remove_single.inputs["Selection"]) links.new(remove_single.outputs["Inverted"], repeat_output.inputs["Geometry"]) return repeat_output.outputs["Geometry"] def add_smooth_step(group, geometry, amount, location): nodes = group.nodes links = group.links x, y = location position = nodes.new("GeometryNodeInputPosition") position.location = (x + 100.0, y + 500.0) smooth_all = nodes.new("GeometryNodeBlurAttribute") smooth_all.data_type = "FLOAT_VECTOR" smooth_all.location = (x + 300.0, y + 500.0) 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_field = add_edge_boundary_field( nodes, links, (x + 100.0, y - 350.0), ) boundary_on_points = nodes.new("GeometryNodeFieldOnDomain") boundary_on_points.data_type = "BOOLEAN" boundary_on_points.domain = "POINT" boundary_on_points.location = (x + 700.0, y - 350.0) links.new(boundary_field, boundary_on_points.inputs["Value"]) boundary_geometry = nodes.new("GeometryNodeSeparateGeometry") boundary_geometry.domain = "POINT" boundary_geometry.location = (x + 880.0, y - 220.0) links.new(geometry, boundary_geometry.inputs["Geometry"]) links.new(boundary_on_points.outputs["Value"], boundary_geometry.inputs["Selection"]) smooth_boundary = nodes.new("GeometryNodeBlurAttribute") smooth_boundary.data_type = "FLOAT_VECTOR" smooth_boundary.location = (x + 500.0, y + 300.0) smooth_boundary.inputs["Iterations"].default_value = 1 smooth_boundary.inputs["Weight"].default_value = 0.5 links.new(position.outputs["Position"], smooth_boundary.inputs["Value"]) nearest_boundary = nodes.new("GeometryNodeSampleNearest") nearest_boundary.domain = "POINT" nearest_boundary.location = (x + 1050.0, y - 120.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 = (x + 1220.0, y + 20.0) links.new(boundary_geometry.outputs["Selection"], sample_boundary.inputs["Geometry"]) links.new(nearest_boundary.outputs["Index"], sample_boundary.inputs["Index"]) links.new(smooth_boundary.outputs["Value"], sample_boundary.inputs["Value"]) boundary_switch = nodes.new("GeometryNodeSwitch") boundary_switch.input_type = "VECTOR" boundary_switch.location = (x + 1050.0, y + 350.0) links.new(boundary_on_points.outputs["Value"], boundary_switch.inputs["Switch"]) links.new(smooth_all.outputs["Value"], boundary_switch.inputs["False"]) links.new(sample_boundary.outputs["Value"], boundary_switch.inputs["True"]) normal = nodes.new("GeometryNodeInputNormal") normal.location = (x + 300.0, y + 100.0) blur_normal = nodes.new("GeometryNodeBlurAttribute") blur_normal.data_type = "FLOAT_VECTOR" blur_normal.location = (x + 500.0, y + 100.0) blur_normal.inputs["Iterations"].default_value = 10 links.new(normal.outputs["Normal"], blur_normal.inputs["Value"]) normal_length = nodes.new("ShaderNodeVectorMath") normal_length.operation = "LENGTH" normal_length.location = (x + 700.0, y + 100.0) links.new(blur_normal.outputs["Value"], normal_length.inputs[0]) weight = nodes.new("ShaderNodeMath") weight.operation = "MULTIPLY" weight.location = (x + 1050.0, y + 150.0) links.new(normal_length.outputs["Value"], weight.inputs[0]) links.new(amount, weight.inputs[1]) mix = nodes.new("ShaderNodeMix") mix.data_type = "VECTOR" mix.location = (x + 1270.0, y + 300.0) links.new(weight.outputs[0], input_socket(mix, "Factor", 0)) links.new(position.outputs["Position"], input_socket(mix, "A", 4)) links.new(boundary_switch.outputs["Output"], input_socket(mix, "B", 5)) set_position = nodes.new("GeometryNodeSetPosition") set_position.location = (x + 1450.0, y) links.new(geometry, set_position.inputs["Geometry"]) links.new(mix.outputs["Result"], set_position.inputs["Position"]) return set_position.outputs["Geometry"] def add_remesh_zone( group, geometry, resolution, iterations, fix_steps, smooth_amount, location, ): nodes = group.nodes links = group.links x, y = location repeat_output = nodes.new("GeometryNodeRepeatOutput") repeat_output.location = (x + 3300.0, y) repeat_input = nodes.new("GeometryNodeRepeatInput") repeat_input.pair_with_output(repeat_output) repeat_input.location = (x, y) links.new(geometry, repeat_input.inputs["Geometry"]) links.new(iterations, repeat_input.inputs["Iterations"]) half_resolution = nodes.new("ShaderNodeMath") half_resolution.operation = "MULTIPLY" half_resolution.inputs[1].default_value = 0.5 half_resolution.location = (x + 100.0, y - 250.0) links.new(resolution, half_resolution.inputs[0]) merge = nodes.new("GeometryNodeMergeByDistance") merge.mode = "CONNECTED" merge.location = (x + 300.0, y) links.new(repeat_input.outputs["Geometry"], merge.inputs["Geometry"]) links.new(half_resolution.outputs[0], merge.inputs["Distance"]) fixed = add_topology_fix_zone( group, merge.outputs["Geometry"], fix_steps, (x + 550.0, y), ) smoothed = add_smooth_step( group, fixed, smooth_amount, (x + 2100.0, y), ) links.new(smoothed, repeat_output.inputs["Geometry"]) return repeat_output.outputs["Geometry"] def add_uv_reprojection(group, source, geometry, location): nodes = group.nodes links = group.links x, y = location position = nodes.new("GeometryNodeInputPosition") position.location = (x, y - 200.0) source_uv = nodes.new("GeometryNodeInputNamedAttribute") source_uv.data_type = "FLOAT_VECTOR" source_uv.inputs["Name"].default_value = "UVMap" source_uv.location = (x, y) sample_uv = nodes.new("GeometryNodeSampleNearestSurface") sample_uv.data_type = "FLOAT_VECTOR" sample_uv.location = (x + 430.0, y) links.new(source, sample_uv.inputs["Mesh"]) links.new(source_uv.outputs["Attribute"], sample_uv.inputs["Value"]) links.new(position.outputs["Position"], sample_uv.inputs["Sample Position"]) store_uv = nodes.new("GeometryNodeStoreNamedAttribute") store_uv.data_type = "FLOAT2" store_uv.domain = "CORNER" store_uv.location = (x + 1050.0, y + 250.0) store_uv.inputs["Name"].default_value = "UVMap" links.new(geometry, store_uv.inputs["Geometry"]) links.new(sample_uv.outputs["Value"], store_uv.inputs["Value"]) return store_uv.outputs["Geometry"] def build_group( group_name=GROUP_NAME, include_confidence=True, include_discontinuity=True, include_mask=False, discontinuity_name="Depth Continuity", discontinuity_attribute="o_depth_discontinuity", discontinuity_default=50.0, ): group = bpy.data.node_groups.new(group_name, "GeometryNodeTree") group.is_modifier = True interface = group.interface geometry_input_socket = new_socket( interface, "Geometry", "INPUT", "NodeSocketGeometry", ) geometry_input_socket.structure_type = "SINGLE" remesh_iterations = new_socket( interface, "Iterations", "INPUT", "NodeSocketInt", ) remesh_iterations.default_value = 2 remesh_iterations.min_value = 0 remesh_iterations.max_value = 8 resolution = new_socket( interface, "Resolution", "INPUT", "NodeSocketFloat", ) resolution.default_value = 0.1 resolution.min_value = 0.000001 resolution.subtype = "DISTANCE" smooth_amount = new_socket( interface, "Smooth", "INPUT", "NodeSocketFloat", ) smooth_amount.default_value = 0.5 smooth_amount.min_value = 0.0 smooth_amount.max_value = 1.0 smooth_amount.subtype = "FACTOR" culling_panel = None if include_confidence or include_discontinuity or include_mask: culling_panel = interface.new_panel( name="Culling", default_closed=False, ) if include_discontinuity: discontinuity = new_socket( interface, discontinuity_name, "INPUT", "NodeSocketFloat", parent=culling_panel, ) discontinuity.default_value = discontinuity_default discontinuity.min_value = 0.0 discontinuity.max_value = 100.0 discontinuity.subtype = "PERCENTAGE" if include_confidence: confidence = new_socket( interface, "Confidence", "INPUT", "NodeSocketFloat", parent=culling_panel, ) confidence.default_value = 0.0 confidence.min_value = 0.0 confidence.max_value = 100.0 confidence.subtype = "PERCENTAGE" if include_mask: mask_socket = new_socket( interface, "Mask", "INPUT", "NodeSocketBool", parent=culling_panel, ) mask_socket.default_value = False options_panel = interface.new_panel( name="Options", default_closed=True, ) fix_steps = new_socket( interface, "Fix Topology", "INPUT", "NodeSocketInt", parent=options_panel, ) fix_steps.default_value = 3 fix_steps.min_value = 1 fix_steps.max_value = 8 new_socket(interface, "Geometry", "OUTPUT", "NodeSocketGeometry") nodes = group.nodes links = group.links geometry_input = new_group_input(nodes, (-650.0, 50.0), {"Geometry"}) confidence_input = None if include_confidence: confidence_input = new_group_input( nodes, (-1800.0, 800.0), {"Confidence"}, ) discontinuity_input = None if include_discontinuity: discontinuity_input = new_group_input( nodes, (-1800.0, 50.0), {discontinuity_name}, ) remesh_input = new_group_input( nodes, (-100.0, -500.0), {"Resolution", "Iterations", "Smooth"}, ) topology_input = new_group_input(nodes, (350.0, -700.0), {"Fix Topology"}) island_input = new_group_input(nodes, (3400.0, 100.0), {"Resolution"}) uv_source_input = new_group_input(nodes, (4200.0, -300.0), {"Geometry"}) group_output = nodes.new("NodeGroupOutput") group_output.location = (5650.0, 100.0) geometry = geometry_input.outputs["Geometry"] if include_confidence: confidence_attribute = nodes.new("GeometryNodeInputNamedAttribute") confidence_attribute.data_type = "FLOAT" confidence_attribute.inputs["Name"].default_value = "o_confidence" confidence_attribute.location = (-1800.0, 600.0) confidence_scale = nodes.new("ShaderNodeMath") confidence_scale.operation = "MULTIPLY" confidence_scale.inputs[1].default_value = 0.01 confidence_scale.location = (-1600.0, 800.0) confidence_compare = new_compare( nodes, "FLOAT", "LESS_THAN", (-1550.0, 600.0), 0.0, ) links.new( confidence_attribute.outputs["Attribute"], input_socket(confidence_compare, "A", 0), ) links.new( confidence_input.outputs["Confidence"], confidence_scale.inputs[0], ) links.new( confidence_scale.outputs[0], input_socket(confidence_compare, "B", 1), ) delete_confidence = nodes.new("GeometryNodeDeleteGeometry") delete_confidence.domain = "FACE" delete_confidence.location = (-1050.0, 600.0) links.new(geometry, delete_confidence.inputs["Geometry"]) links.new( confidence_compare.outputs["Result"], delete_confidence.inputs["Selection"], ) geometry = delete_confidence.outputs["Geometry"] culled_geometry = geometry if include_discontinuity: discontinuity_data = nodes.new("GeometryNodeInputNamedAttribute") discontinuity_data.data_type = "FLOAT" discontinuity_data.inputs["Name"].default_value = discontinuity_attribute discontinuity_data.location = (-1800.0, 350.0) culling_scale = nodes.new("ShaderNodeMath") culling_scale.operation = "MULTIPLY" culling_scale.inputs[1].default_value = 0.01 culling_scale.location = (-1650.0, -50.0) inverse_culling = nodes.new("ShaderNodeMath") inverse_culling.operation = "SUBTRACT" inverse_culling.inputs[0].default_value = 0.5 inverse_culling.location = (-1450.0, -50.0) exponent_scale = nodes.new("ShaderNodeMath") exponent_scale.operation = "MULTIPLY" exponent_scale.inputs[1].default_value = 10.0 exponent_scale.location = (-1250.0, -50.0) threshold_power = nodes.new("ShaderNodeMath") threshold_power.operation = "POWER" threshold_power.inputs[0].default_value = 2.0 threshold_power.location = (-1050.0, -50.0) threshold_scale = nodes.new("ShaderNodeMath") threshold_scale.operation = "MULTIPLY" threshold_scale.inputs[1].default_value = 0.04 threshold_scale.location = (-850.0, -50.0) discontinuity_compare = new_compare( nodes, "FLOAT", "GREATER_THAN", (-650.0, 350.0), 0.0, ) links.new( discontinuity_input.outputs[discontinuity_name], culling_scale.inputs[0], ) links.new(culling_scale.outputs[0], inverse_culling.inputs[1]) links.new(inverse_culling.outputs[0], exponent_scale.inputs[0]) links.new(exponent_scale.outputs[0], threshold_power.inputs[1]) links.new(threshold_power.outputs[0], threshold_scale.inputs[0]) links.new( discontinuity_data.outputs["Attribute"], input_socket(discontinuity_compare, "A", 0), ) links.new( threshold_scale.outputs[0], input_socket(discontinuity_compare, "B", 1), ) delete_geometry = nodes.new("GeometryNodeDeleteGeometry") delete_geometry.domain = "FACE" delete_geometry.location = (-350.0, 100.0) links.new(geometry, delete_geometry.inputs["Geometry"]) links.new( discontinuity_compare.outputs["Result"], delete_geometry.inputs["Selection"], ) culled_geometry = delete_geometry.outputs["Geometry"] remeshed = add_remesh_zone( group, culled_geometry, remesh_input.outputs["Resolution"], remesh_input.outputs["Iterations"], topology_input.outputs["Fix Topology"], remesh_input.outputs["Smooth"], (-300.0, 100.0), ) mesh_island = nodes.new("GeometryNodeInputMeshIsland") mesh_island.location = (3400.0, 700.0) face_area = nodes.new("GeometryNodeInputMeshFaceArea") face_area.location = (3400.0, 550.0) island_area = nodes.new("GeometryNodeAccumulateField") island_area.data_type = "FLOAT" island_area.domain = "FACE" island_area.location = (3600.0, 630.0) links.new(face_area.outputs["Area"], island_area.inputs["Value"]) links.new(mesh_island.outputs["Island Index"], island_area.inputs["Group ID"]) resolution_squared = nodes.new("ShaderNodeMath") resolution_squared.operation = "POWER" resolution_squared.inputs[1].default_value = 2.0 resolution_squared.location = (3600.0, 250.0) links.new(island_input.outputs["Resolution"], resolution_squared.inputs[0]) tiny_island = new_compare( nodes, "FLOAT", "LESS_THAN", (3800.0, 630.0), 0.0, ) links.new(island_area.outputs["Total"], input_socket(tiny_island, "A", 0)) links.new(resolution_squared.outputs[0], input_socket(tiny_island, "B", 1)) remove_tiny = nodes.new("GeometryNodeDeleteGeometry") remove_tiny.domain = "FACE" remove_tiny.location = (4000.0, 100.0) links.new(remeshed, remove_tiny.inputs["Geometry"]) links.new(tiny_island.outputs["Result"], remove_tiny.inputs["Selection"]) reprojected = add_uv_reprojection( group, uv_source_input.outputs["Geometry"], remove_tiny.outputs["Geometry"], (4100.0, 100.0), ) shade_smooth = nodes.new("GeometryNodeSetShadeSmooth") shade_smooth.domain = "FACE" shade_smooth.location = (5400.0, 100.0) shade_smooth.inputs["Shade Smooth"].default_value = True links.new(reprojected, shade_smooth.inputs["Geometry"]) links.new(shade_smooth.outputs["Geometry"], group_output.inputs["Geometry"]) group.color_tag = "GEOMETRY" group.description = ( "Cull DA3 faces, repair merged topology, preserve boundaries, " "and reproject UVs." ) group.use_fake_user = True return group def configure_moge_surface_group(group): nodes = group.nodes links = group.links controls = new_group_input( nodes, (-1800.0, 1050.0), {"Mask"}, ) mask_attribute = nodes.new("GeometryNodeInputNamedAttribute") mask_attribute.data_type = "BOOLEAN" mask_attribute.inputs["Name"].default_value = "o_mask" mask_attribute.location = (-1550.0, 1050.0) invert_mask = nodes.new("FunctionNodeBooleanMath") invert_mask.operation = "NOT" invert_mask.location = (-1350.0, 1050.0) use_mask = nodes.new("FunctionNodeBooleanMath") use_mask.operation = "AND" use_mask.location = (-1150.0, 1050.0) links.new(mask_attribute.outputs["Attribute"], invert_mask.inputs[0]) links.new(controls.outputs["Mask"], use_mask.inputs[0]) links.new(invert_mask.outputs[0], use_mask.inputs[1]) first_processing = min( ( node for node in nodes if node.bl_idname == "GeometryNodeDeleteGeometry" ), key=lambda node: node.location.x, ) geometry_input = first_processing.inputs["Geometry"] previous_link = geometry_input.links[0] previous_geometry = previous_link.from_socket links.remove(previous_link) delete_mask = nodes.new("GeometryNodeDeleteGeometry") delete_mask.domain = "FACE" delete_mask.location = (-750.0, 650.0) links.new(previous_geometry, delete_mask.inputs["Geometry"]) links.new(use_mask.outputs[0], delete_mask.inputs["Selection"]) links.new(delete_mask.outputs["Geometry"], geometry_input) group.description = ( "Cull MoGe-2 mask and depth-edge faces, repair topology, preserve boundaries, " "and reproject UVs." ) group.use_fake_user = True return group def build_sequence_frame(nodes, links, scene_frame, frames_input, y): relative_frame = nodes.new("ShaderNodeMath") relative_frame.operation = "SUBTRACT" relative_frame.location = (300.0, y) links.new(scene_frame, relative_frame.inputs[0]) links.new(frames_input.outputs["Frame Start"], relative_frame.inputs[1]) safe_frames = nodes.new("ShaderNodeMath") safe_frames.operation = "MAXIMUM" safe_frames.inputs[1].default_value = 1.0 safe_frames.location = (300.0, y - 200.0) links.new(frames_input.outputs["Frames"], safe_frames.inputs[0]) first_frame = nodes.new("ShaderNodeMath") first_frame.operation = "ADD" first_frame.inputs[1].default_value = 1.0 first_frame.location = (500.0, y) links.new(relative_frame.outputs[0], first_frame.inputs[0]) clamp_minimum = nodes.new("ShaderNodeMath") clamp_minimum.operation = "MAXIMUM" clamp_minimum.inputs[1].default_value = 1.0 clamp_minimum.location = (700.0, y) links.new(first_frame.outputs[0], clamp_minimum.inputs[0]) clamp_maximum = nodes.new("ShaderNodeMath") clamp_maximum.operation = "MINIMUM" clamp_maximum.location = (900.0, y) links.new(clamp_minimum.outputs[0], clamp_maximum.inputs[0]) links.new(safe_frames.outputs[0], clamp_maximum.inputs[1]) cyclic_frame = nodes.new("ShaderNodeMath") cyclic_frame.operation = "FLOORED_MODULO" cyclic_frame.location = (700.0, y - 200.0) links.new(relative_frame.outputs[0], cyclic_frame.inputs[0]) links.new(safe_frames.outputs[0], cyclic_frame.inputs[1]) cyclic_first_frame = nodes.new("ShaderNodeMath") cyclic_first_frame.operation = "ADD" cyclic_first_frame.inputs[1].default_value = 1.0 cyclic_first_frame.location = (900.0, y - 200.0) links.new(cyclic_frame.outputs[0], cyclic_first_frame.inputs[0]) frame_switch = nodes.new("GeometryNodeSwitch") frame_switch.input_type = "INT" frame_switch.location = (1100.0, y) links.new(frames_input.outputs["Cyclic"], frame_switch.inputs["Switch"]) links.new(clamp_maximum.outputs[0], frame_switch.inputs["False"]) links.new(cyclic_first_frame.outputs[0], frame_switch.inputs["True"]) return frame_switch.outputs["Output"] def build_depth_plane_group(): group = bpy.data.node_groups.new(DEPTH_PLANE_GROUP_NAME, "GeometryNodeTree") group.is_modifier = True interface = group.interface geometry_input_socket = new_socket( interface, "Geometry", "INPUT", "NodeSocketGeometry", ) geometry_input_socket.structure_type = "SINGLE" displacement = new_socket( interface, "Displacement", "INPUT", "NodeSocketMenu", ) subdivision = new_socket( interface, "Subdivision", "INPUT", "NodeSocketInt", ) subdivision.default_value = 6 subdivision.min_value = 0 subdivision.max_value = 10 displacement_strength = new_socket( interface, "Depth Scale", "INPUT", "NodeSocketFloat", ) displacement_strength.default_value = 1.0 displacement_strength.min_value = -10000.0 displacement_strength.max_value = 10000.0 displacement_strength.structure_type = "SINGLE" background_depth = new_socket( interface, "Background Depth", "INPUT", "NodeSocketFloat", ) background_depth.default_value = 1.0 background_depth.min_value = -10000.0 background_depth.max_value = 10000.0 background_depth.subtype = "DISTANCE" background_depth.structure_type = "SINGLE" data_panel = interface.new_panel(name="Data", default_closed=True) depth_image = new_socket( interface, "Depth Image", "INPUT", "NodeSocketImage", parent=data_panel, ) color_image = new_socket( interface, "Color Image", "INPUT", "NodeSocketImage", parent=data_panel, ) material = new_socket( interface, "Material", "INPUT", "NodeSocketMaterial", parent=data_panel, ) frame_data = [] for name, socket_type, default_value in ( ("Frame", "NodeSocketInt", 1), ("Frame Start", "NodeSocketInt", 1), ("Frames", "NodeSocketInt", 1), ("Cyclic", "NodeSocketBool", False), ): socket = new_socket( interface, name, "INPUT", socket_type, parent=data_panel, ) socket.default_value = default_value if name == "Frame": socket.structure_type = "SINGLE" if name == "Frames": socket.min_value = 1 frame_data.append(socket) depth_image.hide_in_modifier = True color_image.hide_in_modifier = True material.hide_in_modifier = True for socket in frame_data: socket.hide_in_modifier = True new_socket(interface, "Geometry", "OUTPUT", "NodeSocketGeometry") nodes = group.nodes links = group.links bounds_input = new_group_input(nodes, (-2200.0, 700.0), {"Geometry"}) grid_input = new_group_input(nodes, (-2200.0, 150.0), {"Subdivision"}) images_input = new_group_input( nodes, (100.0, 1250.0), {"Depth Image", "Color Image"}, ) frames_input = new_group_input( nodes, (50.0, 1750.0), { "Frame", "Frame Start", "Frames", "Cyclic", }, ) background_input = new_group_input( nodes, (350.0, 250.0), {"Background Depth"}, ) background_depth_value = nodes.new("ShaderNodeMath") background_depth_value.operation = "MULTIPLY" background_depth_value.inputs[1].default_value = -1.0 background_depth_value.location = (550.0, 250.0) links.new( background_input.outputs["Background Depth"], background_depth_value.inputs[0], ) displacement_input = new_group_input( nodes, (950.0, -150.0), {"Displacement", "Depth Scale"}, ) material_input = new_group_input(nodes, (1450.0, 250.0), {"Material"}) attributes_input = new_group_input( nodes, (2150.0, 50.0), {"Background Depth"}, ) group_output = nodes.new("NodeGroupOutput") group_output.location = (3050.0, 650.0) bounds = nodes.new("GeometryNodeBoundBox") bounds.location = (-1950.0, 700.0) links.new(bounds_input.outputs["Geometry"], bounds.inputs["Geometry"]) dimensions = nodes.new("ShaderNodeVectorMath") dimensions.operation = "SUBTRACT" dimensions.location = (-1750.0, 700.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 = (-1550.0, 700.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 = (-1950.0, 150.0) links.new(grid_input.outputs["Subdivision"], short_segments.inputs[1]) vertex_counts = [] for index, (axis, other_axis) in enumerate((("X", "Y"), ("Y", "X"))): y = 500.0 - index * 300.0 ratio = nodes.new("ShaderNodeMath") ratio.operation = "DIVIDE" ratio.location = (-1350.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 = (-1150.0, y) links.new(ratio.outputs[0], at_least_one.inputs[0]) scaled_segments = nodes.new("ShaderNodeMath") scaled_segments.operation = "MULTIPLY" scaled_segments.location = (-950.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 = (-750.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 = (-550.0, y) links.new(rounded_segments.outputs[0], vertices.inputs[0]) vertex_counts.append(vertices.outputs[0]) grid = nodes.new("GeometryNodeMeshGrid") grid.location = (-300.0, 650.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"]) store_uv = nodes.new("GeometryNodeStoreNamedAttribute") store_uv.data_type = "FLOAT2" store_uv.domain = "CORNER" store_uv.inputs["Name"].default_value = "UVMap" store_uv.location = (-50.0, 650.0) links.new(grid.outputs["Mesh"], store_uv.inputs["Geometry"]) links.new(grid.outputs["UV Map"], store_uv.inputs["Value"]) center = nodes.new("ShaderNodeVectorMath") center.operation = "ADD" center.location = (-550.0, 950.0) links.new(bounds.outputs["Min"], center.inputs[0]) links.new(bounds.outputs["Max"], center.inputs[1]) half_center = nodes.new("ShaderNodeVectorMath") half_center.operation = "SCALE" half_center.inputs[3].default_value = 0.5 half_center.location = (-300.0, 950.0) links.new(center.outputs["Vector"], half_center.inputs[0]) transform_grid = nodes.new("GeometryNodeTransform") transform_grid.location = (200.0, 650.0) links.new(store_uv.outputs["Geometry"], transform_grid.inputs["Geometry"]) links.new(half_center.outputs["Vector"], transform_grid.inputs["Translation"]) depth_texture = nodes.new("GeometryNodeImageTexture") depth_texture.interpolation = "Linear" depth_texture.extension = "EXTEND" depth_texture.location = (350.0, 1150.0) foreground_texture = nodes.new("GeometryNodeImageTexture") foreground_texture.interpolation = "Closest" foreground_texture.extension = "EXTEND" foreground_texture.location = (350.0, 900.0) links.new(images_input.outputs["Depth Image"], depth_texture.inputs["Image"]) links.new( images_input.outputs["Color Image"], foreground_texture.inputs["Image"], ) links.new(grid.outputs["UV Map"], depth_texture.inputs["Vector"]) links.new(grid.outputs["UV Map"], foreground_texture.inputs["Vector"]) sequence_frame = build_sequence_frame( nodes, links, frames_input.outputs["Frame"], frames_input, 2200.0, ) links.new(sequence_frame, depth_texture.inputs["Frame"]) links.new(sequence_frame, foreground_texture.inputs["Frame"]) clamp_foreground = nodes.new("ShaderNodeMath") clamp_foreground.operation = "MAXIMUM" clamp_foreground.location = (600.0, 650.0) links.new(depth_texture.outputs["Color"], clamp_foreground.inputs[0]) links.new( background_depth_value.outputs[0], clamp_foreground.inputs[1], ) foreground_mask = new_compare( nodes, "FLOAT", "GREATER_THAN", (600.0, 900.0), 0.5, ) links.new( foreground_texture.outputs["Alpha"], input_socket(foreground_mask, "A", 0), ) masked_depth = nodes.new("ShaderNodeMix") masked_depth.data_type = "FLOAT" masked_depth.location = (800.0, 650.0) links.new(foreground_mask.outputs["Result"], masked_depth.inputs[0]) links.new( background_depth_value.outputs[0], masked_depth.inputs[2], ) links.new(clamp_foreground.outputs[0], masked_depth.inputs[3]) displacement_menu = nodes.new("GeometryNodeMenuSwitch") displacement_menu.data_type = "FLOAT" displacement_menu.enum_items.clear() displacement_menu.enum_items.new("Geometry") displacement_menu.enum_items.new("Material") displacement_menu.inputs["Geometry"].default_value = 1.0 displacement_menu.inputs["Material"].default_value = 0.0 displacement_menu.location = (1150.0, 200.0) links.new( displacement_input.outputs["Displacement"], displacement_menu.inputs["Menu"], ) displacement.default_value = "Geometry" geometry_mode = displacement_menu.outputs[0] material_mode = nodes.new("ShaderNodeMath") material_mode.operation = "SUBTRACT" material_mode.inputs[0].default_value = 1.0 material_mode.location = (1350.0, 100.0) links.new(geometry_mode, material_mode.inputs[1]) displacement_scale = nodes.new("ShaderNodeMath") displacement_scale.operation = "MULTIPLY" displacement_scale.location = (1200.0, 650.0) links.new(masked_depth.outputs["Result"], displacement_scale.inputs[0]) geometry_strength = nodes.new("ShaderNodeMath") geometry_strength.operation = "MULTIPLY" geometry_strength.location = (1150.0, 450.0) links.new(geometry_mode, geometry_strength.inputs[0]) links.new(displacement_input.outputs["Depth Scale"], geometry_strength.inputs[1]) links.new(geometry_strength.outputs[0], displacement_scale.inputs[1]) normal = nodes.new("GeometryNodeInputNormal") normal.location = (1200.0, 950.0) offset = nodes.new("ShaderNodeVectorMath") offset.operation = "SCALE" offset.location = (1400.0, 750.0) links.new(normal.outputs["Normal"], offset.inputs[0]) links.new(displacement_scale.outputs[0], offset.inputs[3]) set_position = nodes.new("GeometryNodeSetPosition") set_position.location = (1450.0, 650.0) links.new(transform_grid.outputs["Geometry"], set_position.inputs["Geometry"]) links.new(offset.outputs["Vector"], set_position.inputs["Offset"]) set_material = nodes.new("GeometryNodeSetMaterial") set_material.location = (1650.0, 650.0) links.new(set_position.outputs["Geometry"], set_material.inputs["Geometry"]) links.new(material_input.outputs["Material"], set_material.inputs["Material"]) shade_smooth = nodes.new("GeometryNodeSetShadeSmooth") shade_smooth.domain = "FACE" shade_smooth.inputs["Shade Smooth"].default_value = True shade_smooth.location = (1850.0, 650.0) links.new(set_material.outputs["Geometry"], shade_smooth.inputs["Geometry"]) attribute_values = ( ( MATERIAL_DISPLACEMENT_ATTRIBUTE, "BOOLEAN", material_mode.outputs[0], ), ( BACKGROUND_DEPTH_ATTRIBUTE, "FLOAT", background_depth_value.outputs[0], ), ( DEPTH_SCALE_ATTRIBUTE, "FLOAT", displacement_input.outputs["Depth Scale"], ), ) geometry = shade_smooth.outputs["Geometry"] x = 2100.0 for name, data_type, value in attribute_values: store = nodes.new("GeometryNodeStoreNamedAttribute") store.data_type = data_type store.domain = "POINT" store.inputs["Name"].default_value = name store.location = (x, 650.0) links.new(geometry, store.inputs["Geometry"]) links.new(value, store.inputs["Value"]) geometry = store.outputs["Geometry"] x += 200.0 links.new(geometry, group_output.inputs["Geometry"]) group.color_tag = "GEOMETRY" group.description = ( "Build an aspect-adaptive DA3 depth grid, clamp its foreground, " "and pass background and displacement controls to the material." ) group.use_fake_user = True return group def build_moge_depth_plane_group(): group = bpy.data.node_groups.new( MOGE_DEPTH_PLANE_GROUP_NAME, "GeometryNodeTree", ) group.is_modifier = True interface = group.interface new_socket(interface, "Geometry", "INPUT", "NodeSocketGeometry") displacement = new_socket( interface, "Displacement", "INPUT", "NodeSocketMenu" ) subdivision = new_socket( interface, "Subdivision", "INPUT", "NodeSocketInt" ) subdivision.default_value = 6 subdivision.min_value = 0 subdivision.max_value = 10 depth_scale = new_socket( interface, "Depth Scale", "INPUT", "NodeSocketFloat" ) depth_scale.default_value = 1.0 depth_scale.structure_type = "SINGLE" background_depth = new_socket( interface, "Background Depth", "INPUT", "NodeSocketFloat" ) background_depth.default_value = 1.0 background_depth.min_value = -10000.0 background_depth.max_value = 10000.0 background_depth.subtype = "DISTANCE" background_depth.structure_type = "SINGLE" data_panel = interface.new_panel(name="Data", default_closed=True) depth_image = new_socket( interface, "Depth Image", "INPUT", "NodeSocketImage", data_panel ) color_image = new_socket( interface, "Color Image", "INPUT", "NodeSocketImage", data_panel ) material = new_socket( interface, "Material", "INPUT", "NodeSocketMaterial", data_panel ) for socket in (depth_image, color_image, material): socket.hide_in_modifier = True frame_sockets = [] for name, socket_type, default in ( ("Frame", "NodeSocketInt", 1), ("Frame Start", "NodeSocketInt", 1), ("Frames", "NodeSocketInt", 1), ("Cyclic", "NodeSocketBool", False), ): socket = new_socket(interface, name, "INPUT", socket_type, data_panel) socket.default_value = default socket.hide_in_modifier = True frame_sockets.append(socket) new_socket(interface, "Geometry", "OUTPUT", "NodeSocketGeometry") nodes = group.nodes links = group.links geometry_input = new_group_input(nodes, (-1600.0, 650.0), {"Geometry"}) controls = new_group_input( nodes, (-1600.0, 150.0), {"Subdivision", "Displacement", "Depth Scale", "Background Depth"}, ) background_depth_value = nodes.new("ShaderNodeMath") background_depth_value.operation = "MULTIPLY" background_depth_value.inputs[1].default_value = -1.0 background_depth_value.location = (-1350.0, -150.0) links.new( controls.outputs["Background Depth"], background_depth_value.inputs[0], ) images = new_group_input( nodes, (-500.0, 1100.0), {"Depth Image", "Color Image"} ) frames = new_group_input( nodes, (-500.0, 1500.0), {"Frame", "Frame Start", "Frames", "Cyclic"}, ) material_input = new_group_input(nodes, (900.0, 200.0), {"Material"}) output = nodes.new("NodeGroupOutput") output.location = (1700.0, 650.0) bounds = nodes.new("GeometryNodeBoundBox") bounds.location = (-1400.0, 650.0) links.new(geometry_input.outputs["Geometry"], bounds.inputs["Geometry"]) dimensions = nodes.new("ShaderNodeVectorMath") dimensions.operation = "SUBTRACT" dimensions.location = (-1200.0, 650.0) links.new(bounds.outputs["Max"], dimensions.inputs[0]) links.new(bounds.outputs["Min"], dimensions.inputs[1]) separate = nodes.new("ShaderNodeSeparateXYZ") separate.location = (-1000.0, 650.0) links.new(dimensions.outputs["Vector"], separate.inputs[0]) segments = nodes.new("ShaderNodeMath") segments.operation = "POWER" segments.inputs[0].default_value = 2.0 segments.location = (-1200.0, 100.0) links.new(controls.outputs["Subdivision"], segments.inputs[1]) vertices = nodes.new("ShaderNodeMath") vertices.operation = "ADD" vertices.inputs[1].default_value = 1.0 vertices.location = (-1000.0, 100.0) links.new(segments.outputs[0], vertices.inputs[0]) grid = nodes.new("GeometryNodeMeshGrid") grid.location = (-750.0, 650.0) links.new(separate.outputs["X"], grid.inputs["Size X"]) links.new(separate.outputs["Y"], grid.inputs["Size Y"]) links.new(vertices.outputs[0], grid.inputs["Vertices X"]) links.new(vertices.outputs[0], grid.inputs["Vertices Y"]) store_uv = nodes.new("GeometryNodeStoreNamedAttribute") store_uv.data_type = "FLOAT2" store_uv.domain = "CORNER" store_uv.inputs["Name"].default_value = "UVMap" store_uv.location = (-500.0, 650.0) links.new(grid.outputs["Mesh"], store_uv.inputs["Geometry"]) links.new(grid.outputs["UV Map"], store_uv.inputs["Value"]) center = nodes.new("ShaderNodeVectorMath") center.operation = "ADD" center.location = (-1000.0, 900.0) links.new(bounds.outputs["Min"], center.inputs[0]) links.new(bounds.outputs["Max"], center.inputs[1]) half = nodes.new("ShaderNodeVectorMath") half.operation = "SCALE" half.inputs[3].default_value = 0.5 half.location = (-750.0, 900.0) links.new(center.outputs["Vector"], half.inputs[0]) transform = nodes.new("GeometryNodeTransform") transform.location = (-250.0, 650.0) links.new(store_uv.outputs["Geometry"], transform.inputs["Geometry"]) links.new(half.outputs["Vector"], transform.inputs["Translation"]) depth_texture = nodes.new("GeometryNodeImageTexture") depth_texture.interpolation = "Linear" depth_texture.extension = "EXTEND" depth_texture.location = (-250.0, 1100.0) color_texture = nodes.new("GeometryNodeImageTexture") color_texture.interpolation = "Closest" color_texture.extension = "EXTEND" color_texture.location = (-250.0, 850.0) links.new(images.outputs["Depth Image"], depth_texture.inputs["Image"]) links.new(images.outputs["Color Image"], color_texture.inputs["Image"]) links.new(grid.outputs["UV Map"], depth_texture.inputs["Vector"]) links.new(grid.outputs["UV Map"], color_texture.inputs["Vector"]) frame = build_sequence_frame( nodes, links, frames.outputs["Frame"], frames, 1800.0 ) links.new(frame, depth_texture.inputs["Frame"]) links.new(frame, color_texture.inputs["Frame"]) clamp_depth = nodes.new("ShaderNodeMath") clamp_depth.operation = "MAXIMUM" clamp_depth.location = (0.0, 750.0) links.new(depth_texture.outputs["Color"], clamp_depth.inputs[0]) links.new(background_depth_value.outputs[0], clamp_depth.inputs[1]) color_mask = new_compare( nodes, "FLOAT", "GREATER_THAN", (0.0, 1000.0), 0.5, ) links.new( color_texture.outputs["Alpha"], input_socket(color_mask, "A", 0), ) masked_depth = nodes.new("ShaderNodeMix") masked_depth.data_type = "FLOAT" masked_depth.location = (200.0, 850.0) links.new(color_mask.outputs["Result"], masked_depth.inputs[0]) links.new(background_depth_value.outputs[0], masked_depth.inputs[2]) links.new(clamp_depth.outputs[0], masked_depth.inputs[3]) displacement_menu = nodes.new("GeometryNodeMenuSwitch") displacement_menu.data_type = "FLOAT" displacement_menu.enum_items.clear() displacement_menu.enum_items.new("Geometry") displacement_menu.enum_items.new("Material") displacement_menu.inputs["Geometry"].default_value = 1.0 displacement_menu.inputs["Material"].default_value = 0.0 displacement_menu.location = (0.0, 300.0) links.new(controls.outputs["Displacement"], displacement_menu.inputs["Menu"]) displacement.default_value = "Geometry" material_mode = nodes.new("ShaderNodeMath") material_mode.operation = "SUBTRACT" material_mode.inputs[0].default_value = 1.0 material_mode.location = (250.0, 250.0) links.new(displacement_menu.outputs[0], material_mode.inputs[1]) geometry_strength = nodes.new("ShaderNodeMath") geometry_strength.operation = "MULTIPLY" geometry_strength.location = (250.0, 500.0) links.new(displacement_menu.outputs[0], geometry_strength.inputs[0]) links.new(controls.outputs["Depth Scale"], geometry_strength.inputs[1]) scaled_depth = nodes.new("ShaderNodeMath") scaled_depth.operation = "MULTIPLY" scaled_depth.location = (250.0, 750.0) links.new(masked_depth.outputs["Result"], scaled_depth.inputs[0]) links.new(geometry_strength.outputs[0], scaled_depth.inputs[1]) normal = nodes.new("GeometryNodeInputNormal") normal.location = (0.0, 500.0) offset = nodes.new("ShaderNodeVectorMath") offset.operation = "SCALE" offset.location = (250.0, 700.0) links.new(normal.outputs["Normal"], offset.inputs[0]) links.new(scaled_depth.outputs[0], offset.inputs[3]) set_position = nodes.new("GeometryNodeSetPosition") set_position.location = (500.0, 650.0) links.new(transform.outputs["Geometry"], set_position.inputs["Geometry"]) links.new(offset.outputs["Vector"], set_position.inputs["Offset"]) set_material = nodes.new("GeometryNodeSetMaterial") set_material.location = (750.0, 650.0) links.new(set_position.outputs["Geometry"], set_material.inputs["Geometry"]) links.new(material_input.outputs["Material"], set_material.inputs["Material"]) smooth = nodes.new("GeometryNodeSetShadeSmooth") smooth.domain = "FACE" smooth.inputs["Shade Smooth"].default_value = True smooth.location = (1000.0, 650.0) links.new(set_material.outputs["Geometry"], smooth.inputs["Geometry"]) geometry = smooth.outputs["Geometry"] for index, (name, data_type, value) in enumerate(( (MATERIAL_DISPLACEMENT_ATTRIBUTE, "BOOLEAN", material_mode.outputs[0]), (BACKGROUND_DEPTH_ATTRIBUTE, "FLOAT", background_depth_value.outputs[0]), (DEPTH_SCALE_ATTRIBUTE, "FLOAT", controls.outputs["Depth Scale"]), )): store = nodes.new("GeometryNodeStoreNamedAttribute") store.data_type = data_type store.domain = "POINT" store.inputs["Name"].default_value = name store.location = (1200.0 + index * 200.0, 650.0) links.new(geometry, store.inputs["Geometry"]) links.new(value, store.inputs["Value"]) geometry = store.outputs["Geometry"] links.new(geometry, output.inputs["Geometry"]) group.color_tag = "GEOMETRY" group.description = "Build a MoGe-2 depth plane with geometry or material displacement." group.use_fake_user = True return group def build_shadeless_group(): group = bpy.data.node_groups.new(SHADELESS_GROUP_NAME, "ShaderNodeTree") interface = group.interface new_socket(interface, "Shader", "OUTPUT", "NodeSocketShader") new_socket(interface, "Color", "INPUT", "NodeSocketColor") nodes = group.nodes links = group.links group_input = nodes.new("NodeGroupInput") group_input.location = (-900.0, -350.0) group_output = nodes.new("NodeGroupOutput") group_output.location = (850.0, 100.0) diffuse = nodes.new("ShaderNodeBsdfDiffuse") diffuse.location = (250.0, -150.0) emission = nodes.new("ShaderNodeEmission") emission.location = (250.0, -400.0) links.new(group_input.outputs["Color"], diffuse.inputs["Color"]) links.new(group_input.outputs["Color"], emission.inputs["Color"]) light_path = nodes.new("ShaderNodeLightPath") light_path.location = (-900.0, 150.0) bounce_count = nodes.new("ShaderNodeMath") bounce_count.operation = "SUBTRACT" bounce_count.label = "Bounce Count" bounce_count.location = (-500.0, -50.0) links.new(light_path.outputs["Ray Depth"], bounce_count.inputs[0]) links.new(light_path.outputs["Transmission Depth"], bounce_count.inputs[1]) refracted = nodes.new("ShaderNodeMath") refracted.operation = "SUBTRACT" refracted.label = "Camera or Refracted" refracted.inputs[0].default_value = 1.0 refracted.location = (-250.0, -50.0) links.new(bounce_count.outputs[0], refracted.inputs[1]) reflection_limit = nodes.new("ShaderNodeMath") reflection_limit.operation = "SUBTRACT" reflection_limit.label = "Limit Reflections" reflection_limit.inputs[0].default_value = 2.0 reflection_limit.location = (-650.0, 450.0) links.new(light_path.outputs["Ray Depth"], reflection_limit.inputs[1]) camera_reflected = nodes.new("ShaderNodeMath") camera_reflected.operation = "MULTIPLY" camera_reflected.label = "Camera Ray to Glossy" camera_reflected.location = (-400.0, 400.0) links.new(reflection_limit.outputs[0], camera_reflected.inputs[0]) links.new(light_path.outputs["Is Glossy Ray"], camera_reflected.inputs[1]) shadow_or_reflect = nodes.new("ShaderNodeMath") shadow_or_reflect.operation = "MAXIMUM" shadow_or_reflect.label = "Shadow or Reflection?" shadow_or_reflect.location = (-100.0, 350.0) links.new(camera_reflected.outputs[0], shadow_or_reflect.inputs[0]) links.new(light_path.outputs["Is Shadow Ray"], shadow_or_reflect.inputs[1]) shader_factor = nodes.new("ShaderNodeMath") shader_factor.operation = "MAXIMUM" shader_factor.label = "Shadow, Reflect or Refract?" shader_factor.location = (150.0, 300.0) links.new(shadow_or_reflect.outputs[0], shader_factor.inputs[0]) links.new(refracted.outputs[0], shader_factor.inputs[1]) mix = nodes.new("ShaderNodeMixShader") mix.location = (550.0, 100.0) links.new(shader_factor.outputs[0], mix.inputs["Fac"]) links.new(diffuse.outputs["BSDF"], mix.inputs[1]) links.new(emission.outputs["Emission"], mix.inputs[2]) links.new(mix.outputs["Shader"], group_output.inputs["Shader"]) group.color_tag = "SHADER" group.description = ( "Display color without scene lighting while preserving shadows " "and reflections." ) group.use_fake_user = True return group def main(): bpy.ops.wm.read_factory_settings(use_empty=True) bpy.context.preferences.filepaths.save_version = 0 build_group() moge_surface_group = build_group( MOGE_SURFACE_GROUP_NAME, include_confidence=False, include_discontinuity=True, include_mask=True, discontinuity_name="Depth Continuity", discontinuity_attribute="o_depth_discontinuity", discontinuity_default=50.0, ) configure_moge_surface_group(moge_surface_group) build_depth_plane_group() build_moge_depth_plane_group() build_shadeless_group() OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) bpy.ops.wm.save_as_mainfile(filepath=str(OUTPUT_PATH), check_existing=False) if __name__ == "__main__": main()