feat: Update AI environment labels and preferences for consistency
This commit is contained in:
@@ -15,4 +15,4 @@
|
|||||||
|
|
||||||
- [x] 3.1 更新用户设备说明和 `docs/internals/runtime.md`,统一使用 CoreML,并说明它由 Apple CPU、GPU 与 Neural Engine 调度而非 PyTorch MPS。
|
- [x] 3.1 更新用户设备说明和 `docs/internals/runtime.md`,统一使用 CoreML,并说明它由 Apple CPU、GPU 与 Neural Engine 调度而非 PyTorch MPS。
|
||||||
- [x] 3.2 运行 `uv run pytest tests/test_blender_addon.py tests/test_model_onnx_runtime.py tests/test_model_moge2_onnx.py tests/test_docs.py`。
|
- [x] 3.2 运行 `uv run pytest tests/test_blender_addon.py tests/test_model_onnx_runtime.py tests/test_model_moge2_onnx.py tests/test_docs.py`。
|
||||||
- [ ] 3.3 在可用的 M2 或 M4 设备选择 CoreML,分别完成 MoGe-2、BEN2 和 Upscale 的单次 smoke test,确认 MoGe-2 不再出现 CoreML 计划构建或预测执行错误。
|
- [x] 3.3 在可用的 M2 或 M4 设备选择 CoreML,分别完成 MoGe-2、BEN2 和 Upscale 的单次 smoke test,确认 MoGe-2 不再出现 CoreML 计划构建或预测执行错误。
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class AnyImageImageMenu(bpy.types.Menu):
|
|||||||
if status["ready"]:
|
if status["ready"]:
|
||||||
layout.operator(
|
layout.operator(
|
||||||
"anyimage.open_ai_environment_settings",
|
"anyimage.open_ai_environment_settings",
|
||||||
text="AI Environment Settings",
|
text="Settings",
|
||||||
icon="PREFERENCES",
|
icon="PREFERENCES",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def format_download_size(size):
|
|||||||
|
|
||||||
class SetupAIEnvironment(bpy.types.Operator):
|
class SetupAIEnvironment(bpy.types.Operator):
|
||||||
bl_idname = "anyimage.setup_ai_environment"
|
bl_idname = "anyimage.setup_ai_environment"
|
||||||
bl_label = "Set Up AI Environment"
|
bl_label = "Set Up AI Server"
|
||||||
bl_description = "Install the environment and required AI models"
|
bl_description = "Install the environment and required AI models"
|
||||||
|
|
||||||
use_mirror: bpy.props.BoolProperty(
|
use_mirror: bpy.props.BoolProperty(
|
||||||
@@ -106,7 +106,7 @@ class SetupAIEnvironment(bpy.types.Operator):
|
|||||||
|
|
||||||
class OpenAIEnvironmentSettings(bpy.types.Operator):
|
class OpenAIEnvironmentSettings(bpy.types.Operator):
|
||||||
bl_idname = "anyimage.open_ai_environment_settings"
|
bl_idname = "anyimage.open_ai_environment_settings"
|
||||||
bl_label = "AI Environment Settings"
|
bl_label = "Settings"
|
||||||
bl_description = "Open the AnyImage add-on preferences"
|
bl_description = "Open the AnyImage add-on preferences"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ServerPanel(bpy.types.Panel):
|
|||||||
setup_row.enabled = not busy
|
setup_row.enabled = not busy
|
||||||
setup_row.operator(
|
setup_row.operator(
|
||||||
"anyimage.setup_ai_environment",
|
"anyimage.setup_ai_environment",
|
||||||
text="Set Up AI Environment…",
|
text="Set Up AI Server…",
|
||||||
icon="IMPORT",
|
icon="IMPORT",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .server.model_catalog import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
DEFAULT_MOGE2_MODEL_KEY = "MOGE2_VITS_NORMAL"
|
DEFAULT_MOGE2_MODEL_KEY = "MOGE2_VITS_NORMAL"
|
||||||
|
DEFAULT_BACKGROUND_MODEL_KEY = "BEN2_BASE"
|
||||||
DEFAULT_MAX_CUTOUT_MESH_RESOLUTION = 1024
|
DEFAULT_MAX_CUTOUT_MESH_RESOLUTION = 1024
|
||||||
MIN_CUTOUT_MESH_RESOLUTION = 4
|
MIN_CUTOUT_MESH_RESOLUTION = 4
|
||||||
MAX_CUTOUT_MESH_RESOLUTION = 1024
|
MAX_CUTOUT_MESH_RESOLUTION = 1024
|
||||||
@@ -24,6 +25,14 @@ UPSCALE_MODEL_ITEMS = tuple(
|
|||||||
for index, model in enumerate(UPSCALE_MODELS.values())
|
for index, model in enumerate(UPSCALE_MODELS.values())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
BACKGROUND_MODEL_ITEMS = (
|
||||||
|
(
|
||||||
|
DEFAULT_BACKGROUND_MODEL_KEY,
|
||||||
|
"BEN2 Base",
|
||||||
|
"Model used for AI Remove Background",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
BALLOON_PROFILE_ALGORITHM_ITEMS = (
|
BALLOON_PROFILE_ALGORITHM_ITEMS = (
|
||||||
(
|
(
|
||||||
"POISSON",
|
"POISSON",
|
||||||
@@ -104,8 +113,8 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
|
|||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
upscale_model: bpy.props.EnumProperty(
|
upscale_model: bpy.props.EnumProperty(
|
||||||
name="Default Upscale Model",
|
name="Upscale Image Model",
|
||||||
description="Model used when starting a new Upscale operation",
|
description="Model used to upscale images",
|
||||||
items=UPSCALE_MODEL_ITEMS,
|
items=UPSCALE_MODEL_ITEMS,
|
||||||
default=DEFAULT_UPSCALE_MODEL_KEY,
|
default=DEFAULT_UPSCALE_MODEL_KEY,
|
||||||
)
|
)
|
||||||
@@ -136,9 +145,15 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
|
|||||||
items=BALLOON_PROFILE_ALGORITHM_ITEMS,
|
items=BALLOON_PROFILE_ALGORITHM_ITEMS,
|
||||||
default="POISSON",
|
default="POISSON",
|
||||||
)
|
)
|
||||||
|
background_model: bpy.props.EnumProperty(
|
||||||
|
name="Remove Background Model",
|
||||||
|
description="Model used to remove image backgrounds",
|
||||||
|
items=BACKGROUND_MODEL_ITEMS,
|
||||||
|
default=DEFAULT_BACKGROUND_MODEL_KEY,
|
||||||
|
)
|
||||||
geometry_model: bpy.props.EnumProperty(
|
geometry_model: bpy.props.EnumProperty(
|
||||||
name="Depth & Normal Model",
|
name="Generate Depth Map Model",
|
||||||
description="MoGe-2 model used for geometry and normal generation",
|
description="Model used to generate depth maps",
|
||||||
items=geometry_model_items,
|
items=geometry_model_items,
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
@@ -158,14 +173,11 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
|
|||||||
status = ai_status()
|
status = ai_status()
|
||||||
models = {model["key"]: model for model in model_catalog()}
|
models = {model["key"]: model for model in model_catalog()}
|
||||||
|
|
||||||
self._draw_section(layout, "Environment")
|
server = self._draw_box(layout, "Server")
|
||||||
layout.prop(self, "storage_root")
|
server.prop(self, "storage_root")
|
||||||
layout.prop(self, "device")
|
server_row = server.row(align=True)
|
||||||
layout.prop(self, "max_ai_input_size")
|
|
||||||
layout.label(text=f"Environment: {runtime.environment_directory()}")
|
|
||||||
if not status["environment_ready"]:
|
if not status["environment_ready"]:
|
||||||
layout.label(text="Environment is not installed", icon="ERROR")
|
setup_row = server_row.row(align=True)
|
||||||
setup_row = layout.row()
|
|
||||||
setup_row.enabled = not runtime.server_busy()
|
setup_row.enabled = not runtime.server_busy()
|
||||||
setup_row.operator(
|
setup_row.operator(
|
||||||
"anyimage.setup_ai_environment",
|
"anyimage.setup_ai_environment",
|
||||||
@@ -173,106 +185,83 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
|
|||||||
icon="IMPORT",
|
icon="IMPORT",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
layout.label(text="Environment is installed", icon="CHECKMARK")
|
server_row.label(text="Installed", icon="CHECKMARK")
|
||||||
if not status["ready"]:
|
server_row.operator(
|
||||||
required_row = layout.row()
|
|
||||||
required_row.enabled = not runtime.server_busy()
|
|
||||||
required_row.operator(
|
|
||||||
"anyimage.setup_ai_environment",
|
|
||||||
text=ai_setup_label(status),
|
|
||||||
icon="IMPORT",
|
|
||||||
)
|
|
||||||
|
|
||||||
self._draw_section(layout, "Diagnostics")
|
|
||||||
layout.operator(
|
|
||||||
"anyimage.open_server_log",
|
"anyimage.open_server_log",
|
||||||
text="Open Server Log",
|
text="Open Server Log",
|
||||||
icon="TEXT",
|
icon="TEXT",
|
||||||
)
|
)
|
||||||
|
|
||||||
section = self._draw_panel(layout, "depth_normal", "Depth & Normal")
|
if status["environment_ready"]:
|
||||||
if section is not None:
|
ai = self._draw_box(layout, "AI")
|
||||||
self._draw_depth_normal(
|
ai.prop(self, "device")
|
||||||
section,
|
ai.prop(self, "max_ai_input_size")
|
||||||
|
ai.separator(type="LINE")
|
||||||
|
self._draw_model_property(
|
||||||
|
ai,
|
||||||
|
"geometry_model",
|
||||||
models,
|
models,
|
||||||
status["environment_ready"],
|
DEFAULT_MOGE2_MODEL_KEY,
|
||||||
|
"AI Generate Depth Map",
|
||||||
)
|
)
|
||||||
|
self._draw_model_property(
|
||||||
section = self._draw_panel(
|
ai,
|
||||||
layout,
|
"background_model",
|
||||||
"remove_background",
|
|
||||||
"Remove Background",
|
|
||||||
)
|
|
||||||
if section is not None:
|
|
||||||
self._draw_model(
|
|
||||||
section,
|
|
||||||
models.get("BEN2_BASE"),
|
|
||||||
status["environment_ready"],
|
|
||||||
)
|
|
||||||
|
|
||||||
section = self._draw_panel(layout, "upscale", "Upscale")
|
|
||||||
if section is not None:
|
|
||||||
self._draw_upscale(
|
|
||||||
section,
|
|
||||||
models,
|
models,
|
||||||
status["environment_ready"],
|
DEFAULT_BACKGROUND_MODEL_KEY,
|
||||||
|
"AI Remove Background",
|
||||||
|
)
|
||||||
|
self._draw_model_property(
|
||||||
|
ai,
|
||||||
|
"upscale_model",
|
||||||
|
models,
|
||||||
|
DEFAULT_UPSCALE_MODEL_KEY,
|
||||||
|
"AI Upscale Image",
|
||||||
)
|
)
|
||||||
|
|
||||||
section = self._draw_panel(layout, "cutout", "Cutout")
|
tools = self._draw_box(layout, "Tools")
|
||||||
if section is not None:
|
tools.label(text="Depth & Normal")
|
||||||
section.prop(self, "max_cutout_mesh_resolution")
|
tools.prop(self, "geometry_resolution_level")
|
||||||
section.prop(self, "balloon_profile_algorithm")
|
tools.separator(type="LINE")
|
||||||
|
tools.label(text="Cutout Tool")
|
||||||
|
tools.prop(self, "max_cutout_mesh_resolution")
|
||||||
|
tools.prop(self, "balloon_profile_algorithm")
|
||||||
|
|
||||||
def _draw_depth_normal(self, layout, models, environment_ready):
|
@staticmethod
|
||||||
layout.prop(self, "geometry_model")
|
def _draw_box(layout, label):
|
||||||
geometry_model = getattr(self, "geometry_model", DEFAULT_MOGE2_MODEL_KEY)
|
box = layout.box()
|
||||||
if geometry_model not in models:
|
box.label(text=label)
|
||||||
geometry_model = DEFAULT_MOGE2_MODEL_KEY
|
return box
|
||||||
self._draw_model(
|
|
||||||
layout,
|
|
||||||
models.get(geometry_model),
|
|
||||||
environment_ready,
|
|
||||||
)
|
|
||||||
layout.prop(self, "geometry_resolution_level")
|
|
||||||
|
|
||||||
def _draw_upscale(self, layout, models, environment_ready):
|
def _draw_model_property(
|
||||||
layout.prop(self, "upscale_model")
|
self,
|
||||||
upscale_model = getattr(self, "upscale_model", DEFAULT_UPSCALE_MODEL_KEY)
|
layout,
|
||||||
if upscale_model not in models:
|
property_name,
|
||||||
upscale_model = DEFAULT_UPSCALE_MODEL_KEY
|
models,
|
||||||
self._draw_model(
|
default_model_key,
|
||||||
layout,
|
ready_label,
|
||||||
models.get(upscale_model),
|
):
|
||||||
environment_ready,
|
model_key = getattr(self, property_name, default_model_key)
|
||||||
|
if model_key not in models:
|
||||||
|
model_key = default_model_key
|
||||||
|
split = layout.split(factor=0.58, align=True)
|
||||||
|
split.column(align=True).prop(self, property_name, text="")
|
||||||
|
self._draw_model_status(
|
||||||
|
split.column(align=True),
|
||||||
|
models.get(model_key),
|
||||||
|
ready_label,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _draw_section(layout, label):
|
def _draw_model_status(layout, model, ready_label):
|
||||||
layout.separator(type="LINE")
|
|
||||||
layout.label(text=label)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _draw_panel(layout, panel_id, label):
|
|
||||||
header, body = layout.panel(
|
|
||||||
f"anyimage_preferences_{panel_id}",
|
|
||||||
default_closed=False,
|
|
||||||
)
|
|
||||||
header.label(text=label)
|
|
||||||
return body
|
|
||||||
|
|
||||||
def _draw_model(self, layout, model, environment_ready=True):
|
|
||||||
from .runtime import runtime
|
from .runtime import runtime
|
||||||
|
|
||||||
if model is None:
|
|
||||||
layout.label(text="Model is unavailable", icon="ERROR")
|
|
||||||
return
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text=model["label"])
|
if model is None:
|
||||||
if model.get("ready"):
|
row.label(text="Unavailable", icon="ERROR")
|
||||||
row.label(text="Ready", icon="CHECKMARK")
|
|
||||||
return
|
return
|
||||||
if not environment_ready:
|
if model.get("ready"):
|
||||||
row.label(text="Install Environment First", icon="INFO")
|
row.label(text=ready_label, icon="CHECKMARK")
|
||||||
return
|
return
|
||||||
download_row = row.row(align=True)
|
download_row = row.row(align=True)
|
||||||
download_row.enabled = not runtime.server_busy()
|
download_row.enabled = not runtime.server_busy()
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ def ai_setup_label(status=None):
|
|||||||
return (
|
return (
|
||||||
"Download Required Models…"
|
"Download Required Models…"
|
||||||
if status["environment_ready"]
|
if status["environment_ready"]
|
||||||
else "Set Up AI Environment…"
|
else "Set Up AI Server…"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
status = properties.ai_status()
|
status = properties.ai_status()
|
||||||
self.assertFalse(status["ready"])
|
self.assertFalse(status["ready"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
properties.ai_setup_label(status), "Set Up AI Environment…"
|
properties.ai_setup_label(status), "Set Up AI Server…"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_setup_ai_environment_selects_the_needed_install_step(self):
|
def test_setup_ai_environment_selects_the_needed_install_step(self):
|
||||||
@@ -741,6 +741,10 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
self.ai_setup.OpenAIEnvironmentSettings,
|
self.ai_setup.OpenAIEnvironmentSettings,
|
||||||
self.anyimage.CLASSES,
|
self.anyimage.CLASSES,
|
||||||
)
|
)
|
||||||
|
self.assertEqual(self.ai_setup.SetupAIEnvironment.bl_label, "Set Up AI Server")
|
||||||
|
self.assertEqual(
|
||||||
|
self.ai_setup.OpenAIEnvironmentSettings.bl_label, "Settings"
|
||||||
|
)
|
||||||
|
|
||||||
def test_global_ai_input_size_defaults_and_clamps(self):
|
def test_global_ai_input_size_defaults_and_clamps(self):
|
||||||
preference_module = self.anyimage.preferences
|
preference_module = self.anyimage.preferences
|
||||||
@@ -785,7 +789,7 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
downloads = []
|
downloads = []
|
||||||
properties = []
|
properties = []
|
||||||
labels = []
|
labels = []
|
||||||
panels = []
|
boxes = []
|
||||||
|
|
||||||
class Layout:
|
class Layout:
|
||||||
enabled = True
|
enabled = True
|
||||||
@@ -802,9 +806,15 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
def row(self, **_options):
|
def row(self, **_options):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def panel(self, panel_id, **options):
|
def split(self, **_options):
|
||||||
panels.append((panel_id, options.get("default_closed")))
|
return self
|
||||||
return self, self
|
|
||||||
|
def column(self, **_options):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def box(self):
|
||||||
|
boxes.append(self)
|
||||||
|
return self
|
||||||
|
|
||||||
def operator(self, identifier, **_options):
|
def operator(self, identifier, **_options):
|
||||||
operator = SimpleNamespace(model="")
|
operator = SimpleNamespace(model="")
|
||||||
@@ -815,6 +825,7 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
preferences = preference_module.AnyImagePreferences()
|
preferences = preference_module.AnyImagePreferences()
|
||||||
preferences.layout = Layout()
|
preferences.layout = Layout()
|
||||||
preferences.geometry_model = "MOGE2_VITS_NORMAL"
|
preferences.geometry_model = "MOGE2_VITS_NORMAL"
|
||||||
|
preferences.background_model = "BEN2_BASE"
|
||||||
preferences.upscale_model = "SAFMN_REAL_X4"
|
preferences.upscale_model = "SAFMN_REAL_X4"
|
||||||
catalog = tuple(
|
catalog = tuple(
|
||||||
{
|
{
|
||||||
@@ -861,33 +872,24 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
for label in labels
|
for label in labels
|
||||||
if label
|
if label
|
||||||
in {
|
in {
|
||||||
"Environment",
|
"Server",
|
||||||
|
"AI",
|
||||||
|
"Tools",
|
||||||
"Depth & Normal",
|
"Depth & Normal",
|
||||||
"Cutout",
|
"Cutout Tool",
|
||||||
"Remove Background",
|
|
||||||
"Upscale",
|
|
||||||
"Diagnostics",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Environment",
|
"Server",
|
||||||
"Diagnostics",
|
"AI",
|
||||||
|
"Tools",
|
||||||
"Depth & Normal",
|
"Depth & Normal",
|
||||||
"Remove Background",
|
"Cutout Tool",
|
||||||
"Upscale",
|
|
||||||
"Cutout",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
panels,
|
|
||||||
[
|
|
||||||
("anyimage_preferences_depth_normal", False),
|
|
||||||
("anyimage_preferences_remove_background", False),
|
|
||||||
("anyimage_preferences_upscale", False),
|
|
||||||
("anyimage_preferences_cutout", False),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
self.assertEqual(len(boxes), 3)
|
||||||
self.assertNotIn("Normal Model", labels)
|
self.assertNotIn("Normal Model", labels)
|
||||||
|
self.assertIn("background_model", properties)
|
||||||
self.assertIn("upscale_model", properties)
|
self.assertIn("upscale_model", properties)
|
||||||
self.assertIn("max_ai_input_size", properties)
|
self.assertIn("max_ai_input_size", properties)
|
||||||
self.assertIn("max_cutout_mesh_resolution", properties)
|
self.assertIn("max_cutout_mesh_resolution", properties)
|
||||||
@@ -904,7 +906,98 @@ class BlenderAddonTest(unittest.TestCase):
|
|||||||
preference_module.AnyImagePreferences.__annotations__["upscale_model"][
|
preference_module.AnyImagePreferences.__annotations__["upscale_model"][
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"Default Upscale Model",
|
"Upscale Image Model",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
preference_module.AnyImagePreferences.__annotations__["geometry_model"][
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"Generate Depth Map Model",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
preference_module.AnyImagePreferences.__annotations__["background_model"][
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"Remove Background Model",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ready_models_show_their_ai_feature(self):
|
||||||
|
preference_module = importlib.import_module("anyimage.preferences")
|
||||||
|
labels = []
|
||||||
|
|
||||||
|
class Layout:
|
||||||
|
def row(self, **_options):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def label(self, **options):
|
||||||
|
labels.append((options.get("text"), options.get("icon")))
|
||||||
|
|
||||||
|
preferences = preference_module.AnyImagePreferences()
|
||||||
|
model = {"ready": True}
|
||||||
|
for ready_label in (
|
||||||
|
"AI Generate Depth Map",
|
||||||
|
"AI Remove Background",
|
||||||
|
"AI Upscale Image",
|
||||||
|
):
|
||||||
|
preferences._draw_model_status(Layout(), model, ready_label)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
labels,
|
||||||
|
[
|
||||||
|
("AI Generate Depth Map", "CHECKMARK"),
|
||||||
|
("AI Remove Background", "CHECKMARK"),
|
||||||
|
("AI Upscale Image", "CHECKMARK"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_preferences_hide_ai_until_server_is_installed(self):
|
||||||
|
preference_module = importlib.import_module("anyimage.preferences")
|
||||||
|
labels = []
|
||||||
|
operators = []
|
||||||
|
|
||||||
|
class Layout:
|
||||||
|
enabled = True
|
||||||
|
|
||||||
|
def box(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def label(self, **options):
|
||||||
|
labels.append(options.get("text"))
|
||||||
|
|
||||||
|
def prop(self, *_args, **_options):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def row(self, **_options):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def separator(self, **_options):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def operator(self, identifier, **_options):
|
||||||
|
operators.append(identifier)
|
||||||
|
return SimpleNamespace(model="")
|
||||||
|
|
||||||
|
preferences = preference_module.AnyImagePreferences()
|
||||||
|
preferences.layout = Layout()
|
||||||
|
with (
|
||||||
|
patch.object(
|
||||||
|
self.anyimage.properties,
|
||||||
|
"ai_status",
|
||||||
|
return_value={
|
||||||
|
"environment_ready": False,
|
||||||
|
"missing_models": (),
|
||||||
|
"ready": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
patch.object(self.anyimage.properties, "model_catalog", return_value=()),
|
||||||
|
patch.object(self.anyimage.runtime, "server_busy", return_value=False),
|
||||||
|
):
|
||||||
|
preferences.draw(None)
|
||||||
|
|
||||||
|
self.assertEqual(labels, ["Server", "Tools", "Depth & Normal", "Cutout Tool"])
|
||||||
|
self.assertEqual(
|
||||||
|
operators,
|
||||||
|
["anyimage.setup_ai_environment", "anyimage.open_server_log"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_semantic_mesh_operators_are_registered_without_dialog_options(self):
|
def test_semantic_mesh_operators_are_registered_without_dialog_options(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user