feat: Add MoGe-2 surface generation and export functionality
- Introduced new enum items for depth plane and surface models with caching. - Added MOGE2_MESH_JOB to operators for converting images to surfaces. - Implemented surface mesh building and exporting in RunJob operator. - Enhanced UI to include export format options for MoGe-2. - Created validation tools for MoGe-2 surface assets and depth planes. - Updated geometry nodes to support new surface generation features. - Refactored existing tests to accommodate new functionality and ensure stability.
This commit is contained in:
@@ -4,6 +4,7 @@ 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"
|
||||
MATERIAL_DISPLACEMENT_ATTRIBUTE = "o_material_displacement"
|
||||
@@ -357,8 +358,13 @@ def add_uv_reprojection(group, source, geometry, location):
|
||||
return store_uv.outputs["Geometry"]
|
||||
|
||||
|
||||
def build_group():
|
||||
group = bpy.data.node_groups.new(GROUP_NAME, "GeometryNodeTree")
|
||||
def build_group(
|
||||
group_name=GROUP_NAME,
|
||||
include_confidence=True,
|
||||
include_stretch=True,
|
||||
include_mask=False,
|
||||
):
|
||||
group = bpy.data.node_groups.new(group_name, "GeometryNodeTree")
|
||||
group.is_modifier = True
|
||||
interface = group.interface
|
||||
geometry_input_socket = new_socket(
|
||||
@@ -393,37 +399,50 @@ def build_group():
|
||||
"INPUT",
|
||||
"NodeSocketFloat",
|
||||
)
|
||||
smooth_amount.default_value = 1.0
|
||||
smooth_amount.default_value = 0.5
|
||||
smooth_amount.min_value = 0.0
|
||||
smooth_amount.max_value = 1.0
|
||||
smooth_amount.subtype = "FACTOR"
|
||||
if include_mask:
|
||||
mask_socket = new_socket(
|
||||
interface,
|
||||
"Mask",
|
||||
"INPUT",
|
||||
"NodeSocketBool",
|
||||
)
|
||||
mask_socket.default_value = True
|
||||
mask_socket.structure_type = "SINGLE"
|
||||
|
||||
culling_panel = interface.new_panel(
|
||||
name="Culling",
|
||||
default_closed=False,
|
||||
)
|
||||
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"
|
||||
edge = new_socket(
|
||||
interface,
|
||||
"Stretch",
|
||||
"INPUT",
|
||||
"NodeSocketFloat",
|
||||
parent=culling_panel,
|
||||
)
|
||||
edge.default_value = 10.0
|
||||
edge.min_value = 0.0
|
||||
edge.max_value = 100.0
|
||||
edge.subtype = "PERCENTAGE"
|
||||
culling_panel = None
|
||||
if include_confidence or include_stretch:
|
||||
culling_panel = interface.new_panel(
|
||||
name="Culling",
|
||||
default_closed=False,
|
||||
)
|
||||
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_stretch:
|
||||
edge = new_socket(
|
||||
interface,
|
||||
"Stretch",
|
||||
"INPUT",
|
||||
"NodeSocketFloat",
|
||||
parent=culling_panel,
|
||||
)
|
||||
edge.default_value = 10.0
|
||||
edge.min_value = 0.0
|
||||
edge.max_value = 100.0
|
||||
edge.subtype = "PERCENTAGE"
|
||||
|
||||
options_panel = interface.new_panel(
|
||||
name="Options",
|
||||
@@ -444,8 +463,16 @@ def build_group():
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
geometry_input = new_group_input(nodes, (-650.0, 50.0), {"Geometry"})
|
||||
confidence_input = new_group_input(nodes, (-1800.0, 800.0), {"Confidence"})
|
||||
stretch_input = new_group_input(nodes, (-1800.0, 50.0), {"Stretch"})
|
||||
confidence_input = None
|
||||
if include_confidence:
|
||||
confidence_input = new_group_input(
|
||||
nodes,
|
||||
(-1800.0, 800.0),
|
||||
{"Confidence"},
|
||||
)
|
||||
stretch_input = None
|
||||
if include_stretch:
|
||||
stretch_input = new_group_input(nodes, (-1800.0, 50.0), {"Stretch"})
|
||||
remesh_input = new_group_input(
|
||||
nodes,
|
||||
(-100.0, -500.0),
|
||||
@@ -457,89 +484,71 @@ def build_group():
|
||||
group_output = nodes.new("NodeGroupOutput")
|
||||
group_output.location = (5650.0, 100.0)
|
||||
|
||||
confidence_attribute = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
confidence_attribute.data_type = "FLOAT"
|
||||
confidence_attribute.inputs["Name"].default_value = "o_face_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),
|
||||
)
|
||||
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_face_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"]
|
||||
|
||||
edge_attribute = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
edge_attribute.data_type = "FLOAT"
|
||||
edge_attribute.inputs["Name"].default_value = "o_edge_stretch"
|
||||
edge_attribute.location = (-1800.0, 350.0)
|
||||
edge_scale = nodes.new("ShaderNodeMath")
|
||||
edge_scale.operation = "MULTIPLY"
|
||||
edge_scale.inputs[1].default_value = 0.01
|
||||
edge_scale.location = (-1600.0, -50.0)
|
||||
edge_maximum = nodes.new("ShaderNodeMath")
|
||||
edge_maximum.operation = "MAXIMUM"
|
||||
edge_maximum.inputs[1].default_value = 0.0001
|
||||
edge_maximum.location = (-1600.0, 100.0)
|
||||
edge_reciprocal = nodes.new("ShaderNodeMath")
|
||||
edge_reciprocal.operation = "DIVIDE"
|
||||
edge_reciprocal.inputs[0].default_value = 1.0
|
||||
edge_reciprocal.location = (-1400.0, 100.0)
|
||||
edge_compare = new_compare(
|
||||
nodes,
|
||||
"FLOAT",
|
||||
"GREATER_EQUAL",
|
||||
(-1200.0, 350.0),
|
||||
1.0,
|
||||
)
|
||||
links.new(stretch_input.outputs["Stretch"], edge_scale.inputs[0])
|
||||
links.new(edge_scale.outputs[0], edge_maximum.inputs[0])
|
||||
links.new(edge_maximum.outputs[0], edge_reciprocal.inputs[1])
|
||||
links.new(edge_attribute.outputs["Attribute"], input_socket(edge_compare, "A", 0))
|
||||
links.new(edge_reciprocal.outputs[0], input_socket(edge_compare, "B", 1))
|
||||
|
||||
first_or = nodes.new("FunctionNodeBooleanMath")
|
||||
first_or.operation = "OR"
|
||||
first_or.location = (-950.0, 450.0)
|
||||
links.new(confidence_compare.outputs["Result"], first_or.inputs[0])
|
||||
links.new(edge_compare.outputs["Result"], first_or.inputs[1])
|
||||
full_stretch = new_compare(
|
||||
nodes,
|
||||
"FLOAT",
|
||||
"GREATER_EQUAL",
|
||||
(-950.0, -20.0),
|
||||
0.999999,
|
||||
)
|
||||
links.new(
|
||||
edge_scale.outputs[0],
|
||||
input_socket(full_stretch, "A", 0),
|
||||
)
|
||||
culling_selection = nodes.new("FunctionNodeBooleanMath")
|
||||
culling_selection.operation = "OR"
|
||||
culling_selection.location = (-550.0, 300.0)
|
||||
links.new(first_or.outputs[0], culling_selection.inputs[0])
|
||||
links.new(full_stretch.outputs["Result"], culling_selection.inputs[1])
|
||||
delete_geometry = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete_geometry.domain = "FACE"
|
||||
delete_geometry.location = (-350.0, 100.0)
|
||||
links.new(geometry_input.outputs["Geometry"], delete_geometry.inputs["Geometry"])
|
||||
links.new(culling_selection.outputs[0], delete_geometry.inputs["Selection"])
|
||||
culled_geometry = geometry
|
||||
if include_stretch:
|
||||
edge_attribute = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
edge_attribute.data_type = "FLOAT"
|
||||
edge_attribute.inputs["Name"].default_value = "o_edge_stretch"
|
||||
edge_attribute.location = (-1800.0, 350.0)
|
||||
edge_scale = nodes.new("ShaderNodeMath")
|
||||
edge_scale.operation = "MULTIPLY"
|
||||
edge_scale.inputs[1].default_value = 0.01
|
||||
edge_scale.location = (-1600.0, -50.0)
|
||||
edge_compare = new_compare(
|
||||
nodes,
|
||||
"FLOAT",
|
||||
"GREATER_THAN",
|
||||
(-1200.0, 350.0),
|
||||
0.0,
|
||||
)
|
||||
links.new(stretch_input.outputs["Stretch"], edge_scale.inputs[0])
|
||||
links.new(edge_attribute.outputs["Attribute"], input_socket(edge_compare, "A", 0))
|
||||
links.new(edge_scale.outputs[0], input_socket(edge_compare, "B", 1))
|
||||
delete_geometry = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete_geometry.domain = "EDGE"
|
||||
delete_geometry.location = (-350.0, 100.0)
|
||||
links.new(geometry, delete_geometry.inputs["Geometry"])
|
||||
links.new(edge_compare.outputs["Result"], delete_geometry.inputs["Selection"])
|
||||
culled_geometry = delete_geometry.outputs["Geometry"]
|
||||
|
||||
vertex_neighbors = nodes.new("GeometryNodeInputMeshVertexNeighbors")
|
||||
vertex_neighbors.location = (-550.0, -320.0)
|
||||
@@ -557,7 +566,7 @@ def build_group():
|
||||
delete_orphans = nodes.new("GeometryNodeDeleteGeometry")
|
||||
delete_orphans.domain = "POINT"
|
||||
delete_orphans.location = (-300.0, 100.0)
|
||||
links.new(delete_geometry.outputs["Geometry"], delete_orphans.inputs["Geometry"])
|
||||
links.new(culled_geometry, delete_orphans.inputs["Geometry"])
|
||||
links.new(orphan_vertex.outputs["Result"], delete_orphans.inputs["Selection"])
|
||||
|
||||
remeshed = add_remesh_zone(
|
||||
@@ -622,6 +631,49 @@ def build_group():
|
||||
return group
|
||||
|
||||
|
||||
def configure_moge_surface_group(group):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
mask_input = 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(mask_input.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 stretched 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"
|
||||
@@ -1246,6 +1298,13 @@ 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_stretch=False,
|
||||
include_mask=True,
|
||||
)
|
||||
configure_moge_surface_group(moge_surface_group)
|
||||
build_depth_plane_group()
|
||||
build_moge_depth_plane_group()
|
||||
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user