- Implemented a comprehensive test suite for the clipboard image module, covering various functionalities including operator registration, context handling, and image processing. - Created a new mesh plane asset with customizable parameters and integrated it into the geometry node system. - Developed a validation script to ensure the correct behavior of the mesh plane asset, including input handling and output verification.
1451 lines
55 KiB
Python
1451 lines
55 KiB
Python
from pathlib import Path
|
|
|
|
import bpy
|
|
|
|
from mesh_plane_asset import build_mesh_plane_group
|
|
|
|
|
|
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"
|
|
/ "anyimage"
|
|
/ "assets"
|
|
/ "O_AnyImage.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_mesh_plane_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()
|