Add ONNX model inference implementations for MoGe-2, BEN2, and DA3
- Introduced `onnx_moge2.py` for MoGe-2 ONNX inference with NumPy post-processing. - Added `onnx_ben2.py` for BEN2 ONNX inference with Pillow and NumPy preprocessing. - Created `onnx_da3.py` for Depth Anything 3 inference with ONNX Runtime and NumPy. - Implemented shared ONNX Runtime session helpers in `onnx_runtime.py`. - Developed unit tests for server functionalities in `test_server.py`, including model caching and job management. - Enhanced job server to handle frame extraction without requiring models.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
@@ -53,7 +54,7 @@ VIDEO_NAME_SUFFIXES = (
|
||||
|
||||
|
||||
def image_depth_model_items(owner, context):
|
||||
from ..models import depth_plane_model_enum_items
|
||||
from ..properties import depth_plane_model_enum_items
|
||||
|
||||
return depth_plane_model_enum_items(owner, context)
|
||||
|
||||
@@ -218,19 +219,17 @@ def process_resolution_scale_property():
|
||||
def draw_process_resolution(layout, owner, image):
|
||||
process_res = scaled_process_resolution(
|
||||
image,
|
||||
owner.snapform_process_res_factor,
|
||||
owner.process_res_factor,
|
||||
)
|
||||
layout.prop(
|
||||
owner,
|
||||
"snapform_process_res_factor",
|
||||
"process_res_factor",
|
||||
text=f"Resolution Scale ({process_res} px)",
|
||||
)
|
||||
|
||||
|
||||
def save_packed_image(image, scene):
|
||||
from ..client import temporary_input_path
|
||||
|
||||
output_path = temporary_input_path()
|
||||
output_path = Path(tempfile.mkdtemp(prefix="snapform-input-")) / "input.png"
|
||||
image_settings = scene.render.image_settings
|
||||
original_file_format = image_settings.file_format
|
||||
try:
|
||||
@@ -286,9 +285,7 @@ def prepare_image_input(image, scene):
|
||||
return source_path, False
|
||||
sequence_frames = collect_sequence_frames(image)
|
||||
if sequence_frames:
|
||||
from ..client import temporary_input_directory
|
||||
|
||||
temporary_directory = Path(temporary_input_directory())
|
||||
temporary_directory = Path(tempfile.mkdtemp(prefix="snapform-input-"))
|
||||
try:
|
||||
for index, frame_path in enumerate(sequence_frames):
|
||||
suffix = frame_path.suffix.lower() or ".jpg"
|
||||
@@ -1868,90 +1865,6 @@ def build_depth_plane(
|
||||
)
|
||||
|
||||
|
||||
class BuildDepthPlane(bpy.types.Operator):
|
||||
bl_idname = "snapform.build_depth_plane"
|
||||
bl_label = "Build Depth Plane"
|
||||
bl_options = {"INTERNAL"}
|
||||
|
||||
snapform_source_object_name: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_npz_path: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_depth_data_name: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_background_path: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_color_frames_dir: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_depth_frames_dir: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_delete_source: bpy.props.BoolProperty(
|
||||
default=True,
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_unshaded: bpy.props.BoolProperty(
|
||||
default=False,
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
source_object = bpy.data.objects.get(self.snapform_source_object_name)
|
||||
if not is_image_empty(source_object):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"The source image empty is no longer available",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
npz_path = Path(self.snapform_npz_path)
|
||||
if not npz_path.is_file():
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Depth result does not exist: {npz_path}",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
foreground_image = None
|
||||
if self.snapform_background_path:
|
||||
foreground_image = create_removed_background_image(
|
||||
source_object,
|
||||
self.snapform_background_path,
|
||||
assign_to_source=False,
|
||||
)
|
||||
color_image = None
|
||||
# The foreground result already carries the color, so the original
|
||||
# frame sequence is unused when background removal is available.
|
||||
if self.snapform_color_frames_dir and foreground_image is None:
|
||||
color_image = create_sequence_image(
|
||||
f"{image_base_name(source_object.data)}.frame",
|
||||
self.snapform_color_frames_dir,
|
||||
)
|
||||
build_depth_plane(
|
||||
context,
|
||||
source_object,
|
||||
npz_path,
|
||||
self.snapform_delete_source,
|
||||
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
|
||||
),
|
||||
)
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def build_moge2_depth_plane(
|
||||
context,
|
||||
source_object,
|
||||
@@ -2048,177 +1961,6 @@ def build_moge2_depth_plane(
|
||||
)
|
||||
|
||||
|
||||
class BuildMoge2DepthPlane(bpy.types.Operator):
|
||||
bl_idname = "snapform.build_moge2_depth_plane"
|
||||
bl_label = "Build MoGe-2 Depth Plane"
|
||||
bl_options = {"INTERNAL"}
|
||||
|
||||
snapform_source_object_name: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
snapform_npz_path: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
snapform_background_path: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
snapform_color_frames_dir: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
snapform_data_frames_dir: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
snapform_delete_source: bpy.props.BoolProperty(default=True, options={"HIDDEN"})
|
||||
snapform_unshaded: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
source_object = bpy.data.objects.get(self.snapform_source_object_name)
|
||||
if not is_image_empty(source_object):
|
||||
self.report({"ERROR"}, "The source image empty is no longer available")
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
foreground_image = None
|
||||
if self.snapform_background_path:
|
||||
foreground_image = create_removed_background_image(
|
||||
source_object,
|
||||
self.snapform_background_path,
|
||||
assign_to_source=False,
|
||||
)
|
||||
color_image = None
|
||||
# The foreground result already carries the color, so the original
|
||||
# frame sequence is unused when background removal is available.
|
||||
if self.snapform_color_frames_dir and foreground_image is None:
|
||||
color_image = create_sequence_image(
|
||||
f"{image_base_name(source_object.data)}.frame",
|
||||
self.snapform_color_frames_dir,
|
||||
)
|
||||
build_moge2_depth_plane(
|
||||
context,
|
||||
source_object,
|
||||
Path(self.snapform_npz_path),
|
||||
self.snapform_delete_source,
|
||||
unshaded=self.snapform_unshaded,
|
||||
color_image=color_image,
|
||||
foreground_image=foreground_image,
|
||||
data_frames_directory=(
|
||||
self.snapform_data_frames_dir or None
|
||||
),
|
||||
)
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BuildOriginalDepthPlane(bpy.types.Operator):
|
||||
bl_idname = "snapform.build_original_depth_plane"
|
||||
bl_label = "Build Original Depth Plane"
|
||||
bl_options = {"INTERNAL"}
|
||||
|
||||
snapform_source_object_name: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_background_path: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_color_frames_dir: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_depth_frames_dir: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_depth_npz_path: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_delete_source: bpy.props.BoolProperty(
|
||||
default=True,
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_unshaded: bpy.props.BoolProperty(
|
||||
default=False,
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
source_object = bpy.data.objects.get(self.snapform_source_object_name)
|
||||
if not is_image_empty(source_object):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"The source image empty is no longer available",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
foreground_image = None
|
||||
if self.snapform_background_path:
|
||||
foreground_image = create_removed_background_image(
|
||||
source_object,
|
||||
self.snapform_background_path,
|
||||
assign_to_source=False,
|
||||
)
|
||||
color_image = None
|
||||
# The foreground result already carries the color, so the original
|
||||
# frame sequence is unused when background removal is available.
|
||||
if self.snapform_color_frames_dir and foreground_image is None:
|
||||
color_image = create_sequence_image(
|
||||
f"{image_base_name(source_object.data)}.frame",
|
||||
self.snapform_color_frames_dir,
|
||||
)
|
||||
depth_image = None
|
||||
if self.snapform_depth_npz_path:
|
||||
depth_arrays = extract_depth_arrays(
|
||||
self.snapform_depth_npz_path
|
||||
)
|
||||
if len(depth_arrays) == 1:
|
||||
depth_image = create_depth_image(
|
||||
depth_data_name(source_object),
|
||||
depth_arrays[0],
|
||||
)
|
||||
else:
|
||||
depth_image = create_depth_sequence_image(
|
||||
depth_data_name(source_object),
|
||||
depth_arrays,
|
||||
context.scene,
|
||||
self.snapform_depth_frames_dir,
|
||||
)
|
||||
build_image_depth_plane(
|
||||
context,
|
||||
source_object,
|
||||
self.snapform_delete_source,
|
||||
unshaded=self.snapform_unshaded,
|
||||
color_image=color_image,
|
||||
foreground_image=foreground_image,
|
||||
depth_frames_directory=(
|
||||
self.snapform_depth_frames_dir or None
|
||||
),
|
||||
depth_image=depth_image,
|
||||
)
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyRemovedBackground(bpy.types.Operator):
|
||||
bl_idname = "snapform.apply_removed_background"
|
||||
bl_label = "Apply Removed Background"
|
||||
bl_options = {"INTERNAL"}
|
||||
|
||||
snapform_source_object_name: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_output_path: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, _context):
|
||||
source_object = bpy.data.objects.get(self.snapform_source_object_name)
|
||||
if not is_image_empty(source_object):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"The source image empty is no longer available",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
create_removed_background_image(
|
||||
source_object,
|
||||
self.snapform_output_path,
|
||||
)
|
||||
except RuntimeError as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveImageBackground(bpy.types.Operator):
|
||||
bl_idname = "snapform.remove_image_background"
|
||||
bl_label = "Remove Background"
|
||||
@@ -2227,15 +1969,13 @@ class RemoveImageBackground(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
from ..operators import active_job
|
||||
from ..client import service_busy, model_ready
|
||||
from ..models import BEN2_MODEL
|
||||
from ..job_runtime import runtime
|
||||
from ..properties import BEN2_MODEL_KEY, model_ready
|
||||
|
||||
return (
|
||||
is_image_empty(context.object)
|
||||
and active_job() is None
|
||||
and not service_busy()
|
||||
and model_ready(BEN2_MODEL.key)
|
||||
and not runtime.server_busy()
|
||||
and model_ready(BEN2_MODEL_KEY)
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -2255,9 +1995,9 @@ class RemoveImageBackground(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
result = bpy.ops.snapform.process_image_background(
|
||||
snapform_input_path=str(source_path),
|
||||
snapform_source_object_name=source_object.name,
|
||||
snapform_delete_input=delete_input,
|
||||
input_path=str(source_path),
|
||||
source_object_name=source_object.name,
|
||||
delete_input=delete_input,
|
||||
)
|
||||
if "CANCELLED" not in result:
|
||||
return {"FINISHED"}
|
||||
@@ -2266,42 +2006,42 @@ class RemoveImageBackground(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
bl_idname = "snapform.convert_image_to_depth_plane"
|
||||
class ConvertToDepthPlane(bpy.types.Operator):
|
||||
bl_idname = "snapform.convert_to_depth_plane"
|
||||
bl_label = "Convert to Depth Plane"
|
||||
bl_description = "Convert an image to a displaced depth plane"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
snapform_unshaded: bpy.props.BoolProperty(
|
||||
unshaded: bpy.props.BoolProperty(
|
||||
name="Unshaded",
|
||||
description="Display the source color through an Emission shader",
|
||||
default=False,
|
||||
)
|
||||
snapform_remove_background: bpy.props.BoolProperty(
|
||||
remove_background: bpy.props.BoolProperty(
|
||||
name="Remove Background",
|
||||
description="Remove the background during conversion with BEN2",
|
||||
default=False,
|
||||
)
|
||||
snapform_model: bpy.props.EnumProperty(
|
||||
model: bpy.props.EnumProperty(
|
||||
name="Depth From",
|
||||
description="Model or original image used to build depth",
|
||||
items=image_depth_model_items,
|
||||
default=0,
|
||||
)
|
||||
snapform_use_ray_pose: bpy.props.BoolProperty(
|
||||
use_ray_pose: bpy.props.BoolProperty(
|
||||
name="Use Ray Pose",
|
||||
description="Legacy task compatibility; ONNX uses the camera decoder",
|
||||
default=False,
|
||||
)
|
||||
snapform_process_res_factor: process_resolution_scale_property()
|
||||
snapform_moge_resolution_level: bpy.props.IntProperty(
|
||||
process_res_factor: process_resolution_scale_property()
|
||||
resolution_level: bpy.props.IntProperty(
|
||||
name="Resolution Level",
|
||||
description="MoGe-2 inference resolution level; higher is finer and slower",
|
||||
default=5,
|
||||
min=0,
|
||||
max=9,
|
||||
)
|
||||
snapform_keep_original: bpy.props.BoolProperty(
|
||||
keep_original: bpy.props.BoolProperty(
|
||||
name="Keep Original",
|
||||
description="Keep the source Image Empty after conversion",
|
||||
default=False,
|
||||
@@ -2309,13 +2049,11 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
from ..operators import active_job
|
||||
from ..client import service_busy
|
||||
from ..job_runtime import runtime
|
||||
|
||||
return (
|
||||
is_image_empty(context.object)
|
||||
and active_job() is None
|
||||
and not service_busy()
|
||||
and not runtime.server_busy()
|
||||
)
|
||||
|
||||
def invoke(self, context, _event):
|
||||
@@ -2323,15 +2061,15 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "snapform_model")
|
||||
if self.snapform_model in {"SMALL", "BASE"}:
|
||||
layout.prop(self, "model")
|
||||
if self.model in {"SMALL", "BASE"}:
|
||||
draw_process_resolution(layout, self, context.object.data)
|
||||
elif self.snapform_model.startswith("MOGE2_"):
|
||||
layout.prop(self, "snapform_moge_resolution_level")
|
||||
layout.prop(self, "snapform_remove_background")
|
||||
layout.prop(self, "snapform_unshaded")
|
||||
elif self.model.startswith("MOGE2_"):
|
||||
layout.prop(self, "resolution_level")
|
||||
layout.prop(self, "remove_background")
|
||||
layout.prop(self, "unshaded")
|
||||
layout.separator(type="LINE")
|
||||
layout.prop(self, "snapform_keep_original")
|
||||
layout.prop(self, "keep_original")
|
||||
|
||||
def execute(self, context):
|
||||
source_object = context.object
|
||||
@@ -2339,7 +2077,7 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
context.scene.snapform_settings.video_frames = (
|
||||
default_extract_frames(source_object)
|
||||
)
|
||||
if self.snapform_model == "ORIGINAL":
|
||||
if self.model == "ORIGINAL":
|
||||
try:
|
||||
source_path, delete_input = prepare_image_input(
|
||||
source_object.data,
|
||||
@@ -2350,15 +2088,15 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
run_original = (
|
||||
bpy.ops.snapform.generate_original_background
|
||||
if self.snapform_remove_background
|
||||
if self.remove_background
|
||||
else bpy.ops.snapform.extract_original_frames
|
||||
)
|
||||
result = run_original(
|
||||
snapform_input_path=str(source_path),
|
||||
snapform_source_object_name=source_object.name,
|
||||
snapform_delete_source=not self.snapform_keep_original,
|
||||
snapform_delete_input=delete_input,
|
||||
snapform_unshaded=self.snapform_unshaded,
|
||||
input_path=str(source_path),
|
||||
source_object_name=source_object.name,
|
||||
delete_source=not self.keep_original,
|
||||
delete_input=delete_input,
|
||||
unshaded=self.unshaded,
|
||||
)
|
||||
if "CANCELLED" not in result:
|
||||
return {"FINISHED"}
|
||||
@@ -2366,7 +2104,7 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
remove_temporary_input(source_path)
|
||||
return {"CANCELLED"}
|
||||
|
||||
if self.snapform_model.startswith("MOGE2_"):
|
||||
if self.model.startswith("MOGE2_"):
|
||||
try:
|
||||
source_path, delete_input = prepare_image_input(
|
||||
source_object.data,
|
||||
@@ -2376,16 +2114,14 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
self.report({"ERROR"}, str(error))
|
||||
return {"CANCELLED"}
|
||||
result = bpy.ops.snapform.generate_moge2_depth(
|
||||
snapform_input_path=str(source_path),
|
||||
snapform_source_object_name=source_object.name,
|
||||
snapform_moge_resolution_level=(
|
||||
self.snapform_moge_resolution_level
|
||||
),
|
||||
snapform_model_key=self.snapform_model,
|
||||
snapform_delete_source=not self.snapform_keep_original,
|
||||
snapform_delete_input=delete_input,
|
||||
snapform_unshaded=self.snapform_unshaded,
|
||||
snapform_remove_background=self.snapform_remove_background,
|
||||
input_path=str(source_path),
|
||||
source_object_name=source_object.name,
|
||||
resolution_level=self.resolution_level,
|
||||
model=self.model,
|
||||
delete_source=not self.keep_original,
|
||||
delete_input=delete_input,
|
||||
unshaded=self.unshaded,
|
||||
remove_background=self.remove_background,
|
||||
)
|
||||
if "CANCELLED" not in result:
|
||||
return {"FINISHED"}
|
||||
@@ -2397,7 +2133,7 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
try:
|
||||
process_res = scaled_process_resolution(
|
||||
image,
|
||||
self.snapform_process_res_factor,
|
||||
self.process_res_factor,
|
||||
)
|
||||
except ValueError as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
@@ -2412,16 +2148,16 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
result = bpy.ops.snapform.generate_da3_depth(
|
||||
snapform_input_path=str(source_path),
|
||||
snapform_source_object_name=source_object.name,
|
||||
snapform_depth_data_name=depth_data_name(source_object),
|
||||
snapform_model_key=self.snapform_model,
|
||||
snapform_use_ray_pose=self.snapform_use_ray_pose,
|
||||
snapform_process_res=process_res,
|
||||
snapform_delete_source=not self.snapform_keep_original,
|
||||
snapform_delete_input=delete_input,
|
||||
snapform_unshaded=self.snapform_unshaded,
|
||||
snapform_remove_background=self.snapform_remove_background,
|
||||
input_path=str(source_path),
|
||||
source_object_name=source_object.name,
|
||||
depth_data_name=depth_data_name(source_object),
|
||||
model=self.model,
|
||||
use_ray_pose=self.use_ray_pose,
|
||||
process_res=process_res,
|
||||
delete_source=not self.keep_original,
|
||||
delete_input=delete_input,
|
||||
unshaded=self.unshaded,
|
||||
remove_background=self.remove_background,
|
||||
)
|
||||
if "CANCELLED" in result:
|
||||
if delete_input:
|
||||
@@ -2435,11 +2171,11 @@ class SnapFormImageMenu(bpy.types.Menu):
|
||||
bl_label = "SnapForm"
|
||||
|
||||
def draw(self, _context):
|
||||
from ..client import env_installed
|
||||
from ..job_runtime import runtime
|
||||
|
||||
layout = self.layout
|
||||
layout.operator_context = "INVOKE_DEFAULT"
|
||||
if not env_installed():
|
||||
if not runtime.environment_ready():
|
||||
layout.operator(
|
||||
"snapform.install_environment",
|
||||
text="Install Environment",
|
||||
@@ -2447,12 +2183,12 @@ class SnapFormImageMenu(bpy.types.Menu):
|
||||
)
|
||||
return
|
||||
layout.operator(
|
||||
"snapform.convert_image_to_depth_plane",
|
||||
"snapform.convert_to_depth_plane",
|
||||
text="Convert to Depth Plane",
|
||||
icon="MOD_DISPLACE",
|
||||
)
|
||||
layout.operator(
|
||||
"snapform.convert_image_to_mesh",
|
||||
"snapform.convert_to_surface",
|
||||
text="Convert to Surface",
|
||||
icon="MESH_GRID",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user