Add cutout profile and teddy balloon height calculations

- Implemented cotangent Poisson field and Poisson balloon profile in cutout_profile.py.
- Added teddy balloon heights calculation and related functions in cutout_teddy.py.
- Introduced interaction utilities for image editing in interaction.py.
- Developed depth normalization and analysis functions in cutout_depth.py.
- Created a validation script for cutout assets in validate_cutout_asset.py.
This commit is contained in:
2026-08-27 22:46:57 +08:00
parent 6c8358f92f
commit 7d39fe8c5e
40 changed files with 3058 additions and 3896 deletions

View File

@@ -18,6 +18,15 @@ UPSCALE_MODEL_ITEMS = tuple(
for index, model in enumerate(UPSCALE_MODELS.values())
)
BALLOON_PROFILE_ALGORITHM_ITEMS = (
(
"POISSON",
"Poisson",
"Smooth cotangent-Laplacian Balloon profile",
),
("TEDDY", "Teddy", "Chordal-axis Balloon profile"),
)
def addon_preferences(context=None):
preference_context = (
@@ -85,6 +94,12 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
step=256,
subtype="PIXEL",
)
balloon_profile_algorithm: bpy.props.EnumProperty(
name="Balloon Profile",
description="Algorithm used to generate o_balloon for every Cutout shape",
items=BALLOON_PROFILE_ALGORITHM_ITEMS,
default="POISSON",
)
mesh_depth_model: bpy.props.EnumProperty(
name="Mesh Depth Model",
description="Depth model used by Depth Plane, Depth Balloon and Depth Surface",
@@ -114,20 +129,12 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
from .runtime import runtime
layout = self.layout
status = ai_status()
models = {model["key"]: model for model in model_catalog()}
self._draw_section(layout, "Environment")
layout.prop(self, "storage_root")
layout.prop(self, "device")
layout.prop(self, "upscale_model")
layout.prop(self, "upscale_max_output_size")
layout.separator(type="LINE")
layout.label(text="Mesh Conversion")
layout.prop(self, "mesh_depth_model")
mesh_depth_model = getattr(self, "mesh_depth_model", DEFAULT_MOGE2_MODEL_KEY)
if mesh_depth_model.startswith("MOGE2_"):
layout.prop(self, "mesh_resolution_level")
else:
layout.prop(self, "mesh_process_res_factor")
layout.separator()
status = ai_status()
layout.label(text=f"Environment: {runtime.environment_directory()}")
if not status["environment_ready"]:
layout.label(text="Environment is not installed", icon="ERROR")
@@ -148,30 +155,109 @@ class AnyImagePreferences(bpy.types.AddonPreferences):
text=ai_setup_label(status),
icon="IMPORT",
)
layout.separator(type="LINE")
layout.label(text="Models")
models = model_catalog()
for family in ("moge2", "da3", "ben2", "upscale"):
for model in models:
if model.get("family") == family and (
family != "da3" or model.get("enabled")
):
self._draw_model(layout, model)
layout.separator()
self._draw_section(layout, "Diagnostics")
layout.operator(
"anyimage.open_server_log",
text="Open Server Log",
icon="TEXT",
)
def _draw_model(self, layout, model):
section = self._draw_panel(layout, "depth_normal", "Depth & Normal")
if section is not None:
self._draw_depth_normal(
section,
models,
status["environment_ready"],
)
section = self._draw_panel(
layout,
"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,
status["environment_ready"],
)
section = self._draw_panel(layout, "cutout", "Cutout")
if section is not None:
section.prop(self, "balloon_profile_algorithm")
def _draw_depth_normal(self, layout, models, environment_ready):
layout.prop(self, "mesh_depth_model")
mesh_depth_model = getattr(
self, "mesh_depth_model", DEFAULT_MOGE2_MODEL_KEY
)
if mesh_depth_model not in models:
mesh_depth_model = DEFAULT_MOGE2_MODEL_KEY
self._draw_model(
layout,
models.get(mesh_depth_model),
environment_ready,
)
if mesh_depth_model.startswith("MOGE2_"):
layout.prop(self, "mesh_resolution_level")
else:
layout.prop(self, "mesh_process_res_factor")
layout.label(
text="DA3 models cannot generate normals",
icon="INFO",
)
def _draw_upscale(self, layout, models, environment_ready):
layout.prop(self, "upscale_model")
upscale_model = getattr(
self, "upscale_model", DEFAULT_UPSCALE_MODEL_KEY
)
if upscale_model not in models:
upscale_model = DEFAULT_UPSCALE_MODEL_KEY
self._draw_model(
layout,
models.get(upscale_model),
environment_ready,
)
layout.prop(self, "upscale_max_output_size")
@staticmethod
def _draw_section(layout, 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
if model is None:
layout.label(text="Model is unavailable", icon="ERROR")
return
row = layout.row(align=True)
row.label(text=model["label"])
if model.get("ready"):
row.label(text="Ready", icon="CHECKMARK")
return
if not environment_ready:
row.label(text="Install Environment First", icon="INFO")
return
download_row = row.row(align=True)
download_row.enabled = not runtime.server_busy()
operator = download_row.operator(