Add tests for foreground preservation and refactor geometry node asset

- Implement test to ensure foreground preserves source RGB and Ben2 alpha in WorkerTest.
- Remove outdated tests related to background distance and original background depth.
- Update geometry node asset to reflect new naming conventions and structure.
- Introduce validation for depth plane asset, ensuring correct input/output configurations and attribute handling.
- Adjust confidence and edge parameters in geometry node asset to support a wider range of values.
This commit is contained in:
2026-08-11 23:25:51 +08:00
parent 07d836d9ad
commit 7cd5681a8b
15 changed files with 1296 additions and 660 deletions

View File

@@ -13,6 +13,14 @@ REFERENCE_DEPTH_KEY = "snapform_reference_depth"
ORIGINAL_DISPLACEMENT_STRENGTH = 1.0
ORIGINAL_DISPLACEMENT_MIDLEVEL = 0.0
BUMP_BASE_DISTANCE = 0.001
DEPTH_PLANE_NODE_GROUP_NAME = "O DA3 Depth Plane"
NODE_ASSET_NAME = "O_SnapForm.blend"
REMOVE_BACKGROUND_ATTRIBUTE = "o_remove_background"
MATERIAL_DISPLACEMENT_ATTRIBUTE = "o_material_displacement"
BACKGROUND_DEPTH_ATTRIBUTE = "o_background_depth"
DEPTH_SCALE_ATTRIBUTE = "o_depth_scale"
SURFACE_MATERIAL_NODE_GROUP_NAME = "O DA3 Surface"
DEPTH_PLANE_MATERIAL_NODE_GROUP_NAME = "O DA3 Depth Plane Material"
IMAGE_NAME_SUFFIXES = (
".jpeg",
".jpg",
@@ -430,6 +438,7 @@ def create_removed_background_image(
str(foreground_paths[0]),
check_existing=False,
)
result_image.alpha_mode = "CHANNEL_PACKED"
force_sequence_source(result_image)
except (OSError, RuntimeError) as error:
if "result_image" in locals():
@@ -471,6 +480,7 @@ def create_removed_background_image(
str(output_path),
check_existing=False,
)
result_image.alpha_mode = "CHANNEL_PACKED"
except RuntimeError as error:
raise RuntimeError("Unable to load the background result") from error
source_image = source_object.data
@@ -794,10 +804,13 @@ def create_plane_mesh(source_object, bounds=None, subdivision_level=5):
x_min, x_max, y_min, y_max = (
bounds if bounds is not None else plane_bounds(source_object)
)
x_segments, y_segments = grid_segments(
(x_min, x_max, y_min, y_max),
subdivision_level,
)
if subdivision_level is None:
x_segments, y_segments = 1, 1
else:
x_segments, y_segments = grid_segments(
(x_min, x_max, y_min, y_max),
subdivision_level,
)
vertices = []
uv_coordinates = []
for y_index in range(y_segments + 1):
@@ -836,11 +849,171 @@ def create_plane_mesh(source_object, bounds=None, subdivision_level=5):
return mesh
def material_node_group(depth_plane=False):
group_name = (
DEPTH_PLANE_MATERIAL_NODE_GROUP_NAME
if depth_plane
else SURFACE_MATERIAL_NODE_GROUP_NAME
)
existing = bpy.data.node_groups.get(group_name)
if existing is not None:
return existing
group = bpy.data.node_groups.new(group_name, "ShaderNodeTree")
interface = group.interface
for name, socket_type in (
("Color", "NodeSocketColor"),
("Alpha", "NodeSocketFloat"),
("Depth", "NodeSocketFloat"),
("Bump Scale", "NodeSocketFloat"),
):
socket = interface.new_socket(
name=name,
in_out="INPUT",
socket_type=socket_type,
)
if name == "Alpha":
socket.default_value = 1.0
socket.min_value = 0.0
socket.max_value = 1.0
socket.subtype = "FACTOR"
elif name == "Bump Scale":
socket.default_value = 1.0
socket.min_value = -10000.0
socket.max_value = 10000.0
for name, socket_type in (
("Color", "NodeSocketColor"),
("Roughness", "NodeSocketFloat"),
("Alpha", "NodeSocketFloat"),
("Normal", "NodeSocketVector"),
("Displacement", "NodeSocketVector"),
):
interface.new_socket(
name=name,
in_out="OUTPUT",
socket_type=socket_type,
)
nodes = group.nodes
links = group.links
group_input = nodes.new("NodeGroupInput")
group_input.location = (-500.0, 100.0)
group_output = nodes.new("NodeGroupOutput")
group_output.location = (300.0, 100.0)
roughness_curve = nodes.new("ShaderNodeFloatCurve")
roughness_curve.mapping.initialize()
curve_points = roughness_curve.mapping.curves[0].points
curve_points[0].location = (0.0, 0.5)
curve_points[-1].location = (1.0, 0.1)
roughness_curve.mapping.update()
roughness_curve.location = (-250.0, 350.0)
bump = nodes.new("ShaderNodeBump")
bump.location = (-250.0, 0.0)
displacement = nodes.new("ShaderNodeDisplacement")
displacement.inputs["Midlevel"].default_value = 0.0
displacement.inputs["Scale"].default_value = 1.0
displacement.location = (-250.0, -300.0)
processed_alpha = group_input.outputs["Alpha"]
processed_depth = group_input.outputs["Depth"]
if depth_plane:
group_input.location = (-1200.0, 100.0)
group_output.location = (1050.0, 100.0)
roughness_curve.location = (-300.0, 450.0)
bump.location = (750.0, 50.0)
displacement.location = (750.0, -300.0)
remove_attribute = nodes.new("ShaderNodeAttribute")
remove_attribute.attribute_type = "GEOMETRY"
remove_attribute.attribute_name = REMOVE_BACKGROUND_ATTRIBUTE
remove_attribute.location = (-950.0, 0.0)
background_attribute = nodes.new("ShaderNodeAttribute")
background_attribute.attribute_type = "GEOMETRY"
background_attribute.attribute_name = BACKGROUND_DEPTH_ATTRIBUTE
background_attribute.location = (-950.0, -500.0)
material_attribute = nodes.new("ShaderNodeAttribute")
material_attribute.attribute_type = "GEOMETRY"
material_attribute.attribute_name = MATERIAL_DISPLACEMENT_ATTRIBUTE
material_attribute.location = (-50.0, -650.0)
strength_attribute = nodes.new("ShaderNodeAttribute")
strength_attribute.attribute_type = "GEOMETRY"
strength_attribute.attribute_name = DEPTH_SCALE_ATTRIBUTE
strength_attribute.location = (200.0, -650.0)
alpha_mix = nodes.new("ShaderNodeMix")
alpha_mix.data_type = "FLOAT"
alpha_mix.inputs[2].default_value = 1.0
alpha_mix.location = (-650.0, 50.0)
foreground_mask = nodes.new("ShaderNodeMath")
foreground_mask.operation = "GREATER_THAN"
foreground_mask.inputs[1].default_value = 0.5
foreground_mask.location = (-950.0, -250.0)
clamp_foreground = nodes.new("ShaderNodeMath")
clamp_foreground.operation = "MAXIMUM"
clamp_foreground.location = (-650.0, -400.0)
masked_depth = nodes.new("ShaderNodeMix")
masked_depth.data_type = "FLOAT"
masked_depth.location = (-350.0, -350.0)
background_mix = nodes.new("ShaderNodeMix")
background_mix.data_type = "FLOAT"
background_mix.location = (-50.0, -350.0)
enabled_depth = nodes.new("ShaderNodeMath")
enabled_depth.operation = "MULTIPLY"
enabled_depth.location = (250.0, -350.0)
scaled_depth = nodes.new("ShaderNodeMath")
scaled_depth.operation = "MULTIPLY"
scaled_depth.location = (450.0, -350.0)
links.new(remove_attribute.outputs["Fac"], alpha_mix.inputs[0])
links.new(group_input.outputs["Alpha"], alpha_mix.inputs[3])
links.new(group_input.outputs["Alpha"], foreground_mask.inputs[0])
links.new(group_input.outputs["Depth"], clamp_foreground.inputs[0])
links.new(background_attribute.outputs["Fac"], clamp_foreground.inputs[1])
links.new(foreground_mask.outputs[0], masked_depth.inputs[0])
links.new(background_attribute.outputs["Fac"], masked_depth.inputs[2])
links.new(clamp_foreground.outputs[0], masked_depth.inputs[3])
links.new(remove_attribute.outputs["Fac"], background_mix.inputs[0])
links.new(group_input.outputs["Depth"], background_mix.inputs[2])
links.new(masked_depth.outputs["Result"], background_mix.inputs[3])
links.new(background_mix.outputs["Result"], enabled_depth.inputs[0])
links.new(material_attribute.outputs["Fac"], enabled_depth.inputs[1])
links.new(enabled_depth.outputs[0], scaled_depth.inputs[0])
links.new(strength_attribute.outputs["Fac"], scaled_depth.inputs[1])
processed_alpha = alpha_mix.outputs["Result"]
processed_depth = scaled_depth.outputs[0]
links.new(group_input.outputs["Color"], group_output.inputs["Color"])
links.new(group_input.outputs["Color"], roughness_curve.inputs["Value"])
links.new(
roughness_curve.outputs["Value"],
group_output.inputs["Roughness"],
)
links.new(processed_alpha, group_output.inputs["Alpha"])
links.new(processed_depth, bump.inputs["Height"])
bump_distance = nodes.new("ShaderNodeMath")
bump_distance.operation = "MULTIPLY"
bump_distance.inputs[1].default_value = BUMP_BASE_DISTANCE
bump_distance.location = (500.0, 100.0) if depth_plane else (-250.0, 150.0)
links.new(group_input.outputs["Bump Scale"], bump_distance.inputs[0])
links.new(bump_distance.outputs[0], bump.inputs["Distance"])
links.new(bump.outputs["Normal"], group_output.inputs["Normal"])
links.new(processed_depth, displacement.inputs["Height"])
links.new(
displacement.outputs["Displacement"],
group_output.inputs["Displacement"],
)
group.color_tag = "SHADER"
group.description = (
"Prepare DA3 depth-plane material outputs."
if depth_plane
else "Prepare DA3 surface material outputs."
)
group.use_fake_user = True
return group
def create_material(
name,
color_image,
depth_scale,
displacement_image=None,
foreground_image=None,
displacement_midlevel=0.0,
displacement_scale=1.0,
unshaded=False,
@@ -848,6 +1021,8 @@ def create_material(
frame_start=1,
color_frame_offset=0,
color_frame_duration=None,
foreground_frame_offset=0,
foreground_frame_duration=None,
depth_frame_offset=0,
depth_frame_duration=None,
use_cyclic=None,
@@ -860,13 +1035,17 @@ def create_material(
nodes.clear()
color_texture = nodes.new("ShaderNodeTexImage")
uv_map = nodes.new("ShaderNodeUVMap")
output = nodes.new("ShaderNodeOutputMaterial")
color_texture.image = color_image
uv_map.uv_map = "UVMap"
uv_map.location = (-1250.0, 350.0)
material_image = foreground_image or color_image
color_texture.image = material_image
color_texture.interpolation = "Linear"
color_texture.extension = "EXTEND"
color_texture.location = (-640.0, 180.0)
output.location = (460.0, 180.0)
color_texture.location = (-1000.0, 350.0)
output.location = (850.0, 350.0)
extracted_frames = frame_count if frame_count > 1 else None
color_frames = (
color_frame_duration
@@ -878,29 +1057,51 @@ def create_material(
if depth_frame_duration is not None
else extracted_frames
)
if frame_count > 1 or is_animated_image(color_image):
material_frames = color_frames
material_frame_offset = color_frame_offset
if foreground_image is not None:
material_frames = (
foreground_frame_duration
if foreground_frame_duration is not None
else extracted_frames
)
material_frame_offset = foreground_frame_offset
if frame_count > 1 or is_animated_image(material_image):
configure_image_user(
color_texture.image_user,
color_frames,
material_frames,
frame_start=frame_start,
frame_offset=color_frame_offset,
frame_offset=material_frame_offset,
use_cyclic=use_cyclic,
)
links = material.node_tree.links
links.new(uv_map.outputs["UV"], color_texture.inputs["Vector"])
alpha = color_texture.outputs["Alpha"]
material_inputs = nodes.new("ShaderNodeGroup")
is_depth_plane = displacement_image is not None
material_inputs.node_tree = material_node_group(is_depth_plane)
material_inputs.name = (
"O DA3 Depth Plane" if is_depth_plane else "O DA3 Surface"
)
material_inputs.label = material_inputs.name
material_inputs.location = (-300.0, 350.0)
material_inputs.inputs["Bump Scale"].default_value = depth_scale
links.new(color_texture.outputs["Color"], material_inputs.inputs["Color"])
links.new(alpha, material_inputs.inputs["Alpha"])
if unshaded:
emission = nodes.new("ShaderNodeEmission")
transparent = nodes.new("ShaderNodeBsdfTransparent")
mix_shader = nodes.new("ShaderNodeMixShader")
emission.location = (-100.0, 260.0)
transparent.location = (-100.0, 20.0)
mix_shader.location = (200.0, 180.0)
emission.location = (0.0, 550.0)
transparent.location = (0.0, 150.0)
mix_shader.location = (300.0, 350.0)
links.new(
color_texture.outputs["Color"],
material_inputs.outputs["Color"],
emission.inputs["Color"],
)
links.new(
color_texture.outputs["Alpha"],
material_inputs.outputs["Alpha"],
mix_shader.inputs["Fac"],
)
links.new(
@@ -917,39 +1118,22 @@ def create_material(
)
else:
shader = nodes.new("ShaderNodeBsdfPrincipled")
bump = nodes.new("ShaderNodeBump")
roughness_curve = nodes.new("ShaderNodeFloatCurve")
bump.inputs["Distance"].default_value = BUMP_BASE_DISTANCE * depth_scale
roughness_curve.mapping.initialize()
curve_points = roughness_curve.mapping.curves[0].points
curve_points[0].location = (0.0, 0.5)
curve_points[-1].location = (1.0, 0.1)
roughness_curve.mapping.update()
roughness_curve.location = (-300.0, 320.0)
bump.location = (-300.0, -80.0)
shader.location = (80.0, 180.0)
shader.location = (250.0, 400.0)
links.new(
color_texture.outputs["Color"],
material_inputs.outputs["Color"],
shader.inputs["Base Color"],
)
links.new(color_texture.outputs["Alpha"], shader.inputs["Alpha"])
links.new(color_texture.outputs["Color"], bump.inputs["Height"])
links.new(bump.outputs["Normal"], shader.inputs["Normal"])
links.new(material_inputs.outputs["Alpha"], shader.inputs["Alpha"])
links.new(material_inputs.outputs["Normal"], shader.inputs["Normal"])
links.new(
color_texture.outputs["Color"],
roughness_curve.inputs["Value"],
)
links.new(
roughness_curve.outputs["Value"],
material_inputs.outputs["Roughness"],
shader.inputs["Roughness"],
)
links.new(shader.outputs["BSDF"], output.inputs["Surface"])
if displacement_image is not None:
depth_texture = nodes.new("ShaderNodeTexImage")
displacement = nodes.new("ShaderNodeDisplacement")
depth_texture.image = displacement_image
depth_texture.interpolation = "Linear"
depth_texture.extension = "EXTEND"
@@ -961,21 +1145,30 @@ def create_material(
frame_offset=depth_frame_offset,
use_cyclic=use_cyclic,
)
displacement.inputs["Midlevel"].default_value = (
displacement_midlevel
)
displacement.inputs["Scale"].default_value = displacement_scale
depth_texture.location = (-300.0, -360.0)
displacement.location = (80.0, -260.0)
depth_texture.location = (-1000.0, -400.0)
links.new(uv_map.outputs["UV"], depth_texture.inputs["Vector"])
displacement_height = depth_texture.outputs["Color"]
if displacement_midlevel != 0.0 or displacement_scale != 1.0:
depth_adjust = nodes.new("ShaderNodeMath")
depth_adjust.operation = "SUBTRACT"
depth_adjust.inputs[1].default_value = displacement_midlevel
depth_adjust.location = (500.0, -550.0)
depth_scale_node = nodes.new("ShaderNodeMath")
depth_scale_node.operation = "MULTIPLY"
depth_scale_node.inputs[1].default_value = displacement_scale
depth_scale_node.location = (700.0, -550.0)
links.new(displacement_height, depth_adjust.inputs[0])
links.new(depth_adjust.outputs[0], depth_scale_node.inputs[0])
links.new(depth_scale_node.outputs[0], material_inputs.inputs["Depth"])
else:
links.new(displacement_height, material_inputs.inputs["Depth"])
links.new(
depth_texture.outputs["Color"],
displacement.inputs["Height"],
)
links.new(
displacement.outputs["Displacement"],
material_inputs.outputs["Displacement"],
output.inputs["Displacement"],
)
set_displacement_only(material)
else:
links.new(color_texture.outputs["Color"], material_inputs.inputs["Depth"])
return material
@@ -990,6 +1183,84 @@ def set_displacement_only(material):
return
def node_asset_path():
return Path(__file__).with_name("assets") / NODE_ASSET_NAME
def load_depth_plane_node_group():
existing = bpy.data.node_groups.get(DEPTH_PLANE_NODE_GROUP_NAME)
if existing is not None:
return existing
asset_path = node_asset_path()
if not asset_path.is_file():
raise RuntimeError(f"Missing Geometry Nodes asset: {asset_path}")
use_pack = bpy.app.version >= (5, 0, 0)
if not use_pack:
directory = str(asset_path) + "/NodeTree/"
result = bpy.ops.wm.append(
filepath=directory + DEPTH_PLANE_NODE_GROUP_NAME,
directory=directory,
filename=DEPTH_PLANE_NODE_GROUP_NAME,
link=False,
do_reuse_local_id=True,
)
if "FINISHED" not in result:
raise RuntimeError("Unable to append the Geometry Nodes asset")
node_group = bpy.data.node_groups.get(DEPTH_PLANE_NODE_GROUP_NAME)
if node_group is None:
raise RuntimeError("Appended Geometry Nodes asset is unavailable")
node_group.use_fake_user = True
return node_group
with bpy.data.libraries.load(str(asset_path), link=True) as (source, target):
if DEPTH_PLANE_NODE_GROUP_NAME not in source.node_groups:
raise RuntimeError(
"Geometry Nodes asset is missing "
f"{DEPTH_PLANE_NODE_GROUP_NAME}"
)
target.node_groups = [DEPTH_PLANE_NODE_GROUP_NAME]
linked_group = target.node_groups[0]
node_group = bpy.data.pack_linked_ids_hierarchy(root_id=linked_group)
if linked_group != node_group and linked_group.users == 0:
bpy.data.node_groups.remove(linked_group)
node_group.use_fake_user = True
return node_group
def modifier_input_identifier(node_group, name):
for item in node_group.interface.items_tree:
if item.item_type != "SOCKET" or item.in_out != "INPUT":
continue
if item.name == name:
return item.identifier
raise RuntimeError(f"Geometry Nodes input is missing: {name}")
def add_depth_plane_processing(
plane_object,
depth_image,
foreground_image,
material,
frame_start=1,
frame_duration=1,
use_cyclic=False,
):
node_group = load_depth_plane_node_group()
modifier = plane_object.modifiers.new(name=node_group.name, type="NODES")
modifier.node_group = node_group
modifier.show_group_selector = False
for name, value in (
("Depth Image", depth_image),
("Foreground Image", foreground_image),
("Material", material),
("Frame Start", frame_start),
("Frames", max(int(frame_duration or 1), 1)),
("Cyclic", use_cyclic),
):
identifier = modifier_input_identifier(node_group, name)
modifier[identifier] = value
return modifier
def finish_depth_plane(
context,
source_object,
@@ -1010,11 +1281,10 @@ def finish_depth_plane(
def build_image_depth_plane(
context,
source_object,
subdivision_level,
delete_source,
displacement_via="MODIFIER",
unshaded=False,
color_image=None,
foreground_image=None,
depth_frames_directory=None,
depth_image=None,
):
@@ -1046,48 +1316,21 @@ def build_image_depth_plane(
depth_source_image = frames_image
elif not is_animated_image(source_object.data):
depth_source_image = copy_depth_image(source_object)
mesh = create_plane_mesh(
source_object,
subdivision_level=subdivision_level,
foreground_image = (
foreground_image or color_image or source_object.data
)
mesh = create_plane_mesh(source_object, subdivision_level=None)
plane_object = bpy.data.objects.new(source_name, mesh)
context.collection.objects.link(plane_object)
plane_object.matrix_world = source_object.matrix_world.copy()
subdivision = plane_object.modifiers.new("Subdivision", "SUBSURF")
subdivision.subdivision_type = "SIMPLE"
subdivision.levels = 1
subdivision.render_levels = 2
if displacement_via == "MODIFIER":
texture = bpy.data.textures.new(
depth_data_name(source_object),
type="IMAGE",
)
texture.image = depth_source_image
texture.extension = "EXTEND"
if uses_extracted or is_animated_image(source_object.data):
configure_image_user(
texture.image_user,
texture_frame_count,
frame_start=source_frame_start,
frame_offset=texture_frame_offset,
use_cyclic=source_use_cyclic,
)
displace = plane_object.modifiers.new("Displace", "DISPLACE")
displace.texture = texture
displace.texture_coords = "UV"
displace.mid_level = ORIGINAL_DISPLACEMENT_MIDLEVEL
depth_scale = abs(float(plane_object.scale.z))
displacement_image = (
depth_source_image if displacement_via == "MATERIAL" else None
)
material = create_material(
source_name,
color_image if color_image is not None else source_object.data,
depth_scale,
displacement_image=displacement_image,
displacement_image=depth_source_image,
foreground_image=foreground_image,
displacement_midlevel=ORIGINAL_DISPLACEMENT_MIDLEVEL,
displacement_scale=ORIGINAL_DISPLACEMENT_STRENGTH,
unshaded=unshaded,
@@ -1095,11 +1338,22 @@ def build_image_depth_plane(
frame_start=source_frame_start,
color_frame_offset=texture_frame_offset,
color_frame_duration=texture_frame_count,
foreground_frame_offset=0,
foreground_frame_duration=frame_count,
depth_frame_offset=texture_frame_offset,
depth_frame_duration=texture_frame_count,
use_cyclic=source_use_cyclic,
)
mesh.materials.append(material)
add_depth_plane_processing(
plane_object,
depth_source_image,
foreground_image,
material,
frame_start=source_frame_start,
frame_duration=frame_count,
use_cyclic=source_use_cyclic,
)
plane_object["snapform_frame_count"] = frame_count
return finish_depth_plane(
context,
@@ -1113,12 +1367,11 @@ def build_depth_plane(
context,
source_object,
npz_path,
subdivision_level,
delete_source,
displacement_via="MODIFIER",
unshaded=False,
depth_name="",
color_image=None,
foreground_image=None,
depth_frames_directory=None,
):
source_name = source_object.name
@@ -1136,13 +1389,16 @@ def build_depth_plane(
)
depth_name = depth_name or depth_data_name(source_object)
frame_count = len(depth_arrays)
foreground_image = (
foreground_image or color_image or source_object.data
)
(
source_frame_start,
source_frame_offset,
source_frame_duration,
source_use_cyclic,
) = image_user_settings(source_object)
mesh = create_plane_mesh(source_object, bounds, subdivision_level)
mesh = create_plane_mesh(source_object, bounds, None)
plane_object = bpy.data.objects.new(
source_name,
mesh,
@@ -1170,39 +1426,12 @@ def build_depth_plane(
depth_frames_directory,
)
subdivision = plane_object.modifiers.new("Subdivision", "SUBSURF")
subdivision.subdivision_type = "SIMPLE"
subdivision.levels = 1
subdivision.render_levels = 2
if displacement_via == "MODIFIER":
texture = bpy.data.textures.new(
depth_name,
type="IMAGE",
)
texture.image = depth_image
texture.extension = "EXTEND"
if frame_count > 1:
configure_image_user(
texture.image_user,
frame_count,
frame_start=source_frame_start,
use_cyclic=source_use_cyclic,
)
displace = plane_object.modifiers.new("Displace", "DISPLACE")
displace.texture = texture
displace.texture_coords = "UV"
displace.direction = "NORMAL"
displace.strength = DISPLACEMENT_STRENGTH
displace.mid_level = DISPLACEMENT_MIDLEVEL
material = create_material(
source_name,
color_image if color_image is not None else source_object.data,
depth_scale,
displacement_image=(
depth_image if displacement_via == "MATERIAL" else None
),
displacement_image=depth_image,
foreground_image=foreground_image,
displacement_midlevel=DISPLACEMENT_MIDLEVEL,
displacement_scale=DISPLACEMENT_STRENGTH,
unshaded=unshaded,
@@ -1212,11 +1441,22 @@ def build_depth_plane(
color_frame_duration=(
frame_count if color_image is not None else source_frame_duration
),
foreground_frame_offset=0,
foreground_frame_duration=frame_count,
depth_frame_offset=0,
depth_frame_duration=frame_count,
use_cyclic=source_use_cyclic,
)
mesh.materials.append(material)
add_depth_plane_processing(
plane_object,
depth_image,
foreground_image,
material,
frame_start=source_frame_start,
frame_duration=frame_count,
use_cyclic=source_use_cyclic,
)
plane_object["snapform_reference_depth"] = reference_depth
plane_object["snapform_camera_plane_width"] = bounds[1] - bounds[0]
plane_object["snapform_camera_plane_height"] = bounds[3] - bounds[2]
@@ -1254,24 +1494,10 @@ class BuildDepthPlane(bpy.types.Operator):
snapform_depth_frames_dir: bpy.props.StringProperty(
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_subdivision_level: bpy.props.IntProperty(
default=5,
min=0,
max=10,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_delete_source: bpy.props.BoolProperty(
default=True,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_displacement_via: bpy.props.EnumProperty(
items=(
("MODIFIER", "Modifier", "Use a Displace modifier"),
("MATERIAL", "Material", "Use material displacement"),
),
default="MODIFIER",
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_unshaded: bpy.props.BoolProperty(
default=False,
options={"HIDDEN", "SKIP_SAVE"},
@@ -1295,14 +1521,15 @@ class BuildDepthPlane(bpy.types.Operator):
return {"CANCELLED"}
try:
color_image = None
foreground_image = None
if self.snapform_background_path:
color_image = create_removed_background_image(
foreground_image = create_removed_background_image(
source_object,
self.snapform_background_path,
assign_to_source=False,
)
elif self.snapform_color_frames_dir:
color_image = None
if self.snapform_color_frames_dir:
color_image = create_sequence_image(
f"{image_base_name(source_object.data)}.frame",
self.snapform_color_frames_dir,
@@ -1311,12 +1538,11 @@ class BuildDepthPlane(bpy.types.Operator):
context,
source_object,
npz_path,
self.snapform_subdivision_level,
self.snapform_delete_source,
displacement_via=self.snapform_displacement_via,
unshaded=self.snapform_unshaded,
depth_name=self.snapform_depth_data_name,
color_image=color_image,
foreground_image=foreground_image,
depth_frames_directory=(
self.snapform_depth_frames_dir or None
),
@@ -1347,24 +1573,10 @@ class BuildOriginalDepthPlane(bpy.types.Operator):
snapform_depth_npz_path: bpy.props.StringProperty(
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_subdivision_level: bpy.props.IntProperty(
default=5,
min=0,
max=10,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_delete_source: bpy.props.BoolProperty(
default=True,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_displacement_via: bpy.props.EnumProperty(
items=(
("MODIFIER", "Modifier", "Use a Displace modifier"),
("MATERIAL", "Material", "Use material displacement"),
),
default="MODIFIER",
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_unshaded: bpy.props.BoolProperty(
default=False,
options={"HIDDEN", "SKIP_SAVE"},
@@ -1379,14 +1591,15 @@ class BuildOriginalDepthPlane(bpy.types.Operator):
)
return {"CANCELLED"}
try:
color_image = None
foreground_image = None
if self.snapform_background_path:
color_image = create_removed_background_image(
foreground_image = create_removed_background_image(
source_object,
self.snapform_background_path,
assign_to_source=False,
)
elif self.snapform_color_frames_dir:
color_image = None
if self.snapform_color_frames_dir:
color_image = create_sequence_image(
f"{image_base_name(source_object.data)}.frame",
self.snapform_color_frames_dir,
@@ -1411,11 +1624,10 @@ class BuildOriginalDepthPlane(bpy.types.Operator):
build_image_depth_plane(
context,
source_object,
self.snapform_subdivision_level,
self.snapform_delete_source,
displacement_via=self.snapform_displacement_via,
unshaded=self.snapform_unshaded,
color_image=color_image,
foreground_image=foreground_image,
depth_frames_directory=(
self.snapform_depth_frames_dir or None
),
@@ -1527,27 +1739,11 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
),
default="ORIGINAL",
)
snapform_displacement_via: bpy.props.EnumProperty(
name="Displace via",
description="How the depth displacement is presented",
items=(
("MODIFIER", "Modifier", "Use a Displace modifier"),
("MATERIAL", "Material", "Use material displacement"),
),
default="MODIFIER",
)
snapform_unshaded: bpy.props.BoolProperty(
name="Unshaded",
description="Display the source color through an Emission shader",
default=False,
)
snapform_background_depth_distance: bpy.props.FloatProperty(
name="Background Depth Distance",
description="Set excluded background to this fixed depth distance; zero disables background exclusion",
default=0.0,
min=0.0,
soft_max=10.0,
)
snapform_model: bpy.props.EnumProperty(
name="Model",
description="Model used to estimate image depth",
@@ -1572,24 +1768,19 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
description="Delete the source Image Empty after conversion",
default=True,
)
snapform_subdivision_level: bpy.props.IntProperty(
name="Subdivision",
description="Power of two for the short-edge segment count; the long edge follows the image aspect ratio",
default=5,
min=0,
max=10,
)
@classmethod
def poll(cls, context):
from .operators import active_job
from .runtime import host_busy, model_ready
from .models import MODELS
from .models import BEN2_MODEL, MODELS
return (
is_image_empty(context.object)
and active_job() is None
and not host_busy()
and any(model_ready(model_key) for model_key in MODELS)
and model_ready(BEN2_MODEL.key)
)
def invoke(self, context, _event):
@@ -1608,9 +1799,6 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
return context.window_manager.invoke_props_dialog(self, width=340)
def draw(self, context):
from .models import BEN2_MODEL
from .runtime import model_ready
layout = self.layout
layout.prop(self, "snapform_depth_source")
if self.snapform_depth_source == "ESTIMATE":
@@ -1637,14 +1825,6 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
)
settings_header.label(text="Options")
if settings_body is not None:
settings_body.prop(self, "snapform_displacement_via")
settings_body.prop(self, "snapform_subdivision_level")
remove_background_row = settings_body.row()
remove_background_row.enabled = model_ready(BEN2_MODEL.key)
remove_background_row.prop(
self,
"snapform_background_depth_distance",
)
settings_body.prop(self, "snapform_unshaded")
layout.prop(self, "snapform_delete_original")
@@ -1652,7 +1832,6 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
from .operators import (
IMAGE_DEPTH_JOB,
ORIGINAL_BACKGROUND_JOB,
ORIGINAL_FRAMES_JOB,
)
source_object = context.object
@@ -1660,57 +1839,6 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
context.scene.snapform_settings.video_frames = (
default_extract_frames(source_object)
)
if (
self.snapform_depth_source == "ORIGINAL"
and self.snapform_background_depth_distance <= 0.0
):
if video_total_frames(source_object.data) > 1:
try:
source_path, delete_input = prepare_image_input(
source_object.data,
context.scene,
)
except RuntimeError as error:
self.report({"ERROR"}, str(error))
return {"CANCELLED"}
result = bpy.ops.snapform.run_job(
job_type=ORIGINAL_FRAMES_JOB,
snapform_input_path=str(source_path),
snapform_source_object_name=source_object.name,
snapform_subdivision_level=self.snapform_subdivision_level,
snapform_delete_source=self.snapform_delete_original,
snapform_delete_input=delete_input,
snapform_displacement_via=self.snapform_displacement_via,
snapform_unshaded=self.snapform_unshaded,
)
if "CANCELLED" not in result:
return {"FINISHED"}
if delete_input:
remove_temporary_input(source_path)
return {"CANCELLED"}
try:
build_image_depth_plane(
context,
source_object,
self.snapform_subdivision_level,
self.snapform_delete_original,
self.snapform_displacement_via,
self.snapform_unshaded,
)
except (OSError, RuntimeError, ValueError) as error:
self.report({"ERROR"}, str(error))
return {"CANCELLED"}
undo_result = bpy.ops.ed.undo_push(
message="Convert to Depth Plane",
)
if "FINISHED" not in undo_result:
self.report(
{"ERROR"},
"Unable to create the depth plane Undo step",
)
return {"CANCELLED"}
return {"FINISHED"}
if self.snapform_depth_source == "ORIGINAL":
try:
source_path, delete_input = prepare_image_input(
@@ -1724,14 +1852,9 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
job_type=ORIGINAL_BACKGROUND_JOB,
snapform_input_path=str(source_path),
snapform_source_object_name=source_object.name,
snapform_subdivision_level=self.snapform_subdivision_level,
snapform_delete_source=self.snapform_delete_original,
snapform_delete_input=delete_input,
snapform_displacement_via=self.snapform_displacement_via,
snapform_unshaded=self.snapform_unshaded,
snapform_background_depth_distance=(
self.snapform_background_depth_distance
),
)
if "CANCELLED" not in result:
return {"FINISHED"}
@@ -1765,14 +1888,10 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
snapform_model_key=self.snapform_model,
snapform_use_ray_pose=self.snapform_use_ray_pose,
snapform_process_res=process_res,
snapform_subdivision_level=self.snapform_subdivision_level,
snapform_delete_source=self.snapform_delete_original,
snapform_delete_input=delete_input,
snapform_displacement_via=self.snapform_displacement_via,
snapform_unshaded=self.snapform_unshaded,
snapform_background_depth_distance=(
self.snapform_background_depth_distance
),
snapform_remove_background=True,
)
if "CANCELLED" in result:
if delete_input:
@@ -1797,7 +1916,7 @@ class SnapFormImageMenu(bpy.types.Menu):
)
layout.operator(
"snapform.convert_image_to_mesh",
text="Convert to Mesh",
text="Convert to Surface",
icon="MESH_GRID",
)
layout.separator()