Files
AnyImage/src/anyimage/properties.py
nan 938d95db0f Add new functionality for image rectification and depth metadata handling
- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`.
- Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`.
- Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration.
- Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency.
- Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments.
- Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
2026-09-12 19:20:06 +08:00

268 lines
7.6 KiB
Python

import platform
import sys
import bpy
from .runtime import runtime
from .server import model_catalog as shared_model_catalog
DEVICE_LABELS = {
"cuda": ("CUDA", "NVIDIA CUDA"),
"directml": ("DirectML", "Windows GPU (DirectML)"),
"coreml": ("CoreML", "Apple CoreML"),
"cpu": ("CPU", "CPU"),
}
def supports_coreml():
return sys.platform == "darwin" and platform.machine().lower() in {
"arm64",
"aarch64",
}
def supported_devices():
devices = runtime.environment_manifest().get("devices")
if isinstance(devices, list) and devices:
names = [
name
for device in devices
if (name := str(device).lower()) in DEVICE_LABELS
]
if not supports_coreml():
names = [name for name in names if name != "coreml"]
elif "cpu" in names:
names = ["cpu", *(name for name in names if name != "cpu")]
return names or ["cpu"]
if sys.platform == "win32":
return ["directml", "cpu"]
return ["cpu", "coreml"] if supports_coreml() else ["cpu"]
def device_enum_items(_owner, _context):
return [
(name.upper(), *DEVICE_LABELS[name], index)
for index, name in enumerate(supported_devices())
]
def models_status(refresh=False):
status = runtime.server_status().get("resources", {}).get("model_manager", {})
return {
"catalog": tuple(
shared_model_catalog.model_record(
model,
_model_files_ready(model),
)
for model in shared_model_catalog.DOWNLOADABLE_MODELS.values()
),
"loaded": status.get("loaded", {}),
}
def _model_files_ready(model):
directory = runtime.storage_root() / "models" / model.directory_name
return all(any(directory.glob(pattern)) for pattern in model.ready_patterns)
def model_ready(model_key):
return any(
model.get("key") == model_key and model.get("ready")
for model in models_status().get("catalog", ())
)
def ai_status():
environment_ready = runtime.environment_ready()
ready_models = {model["key"] for model in model_catalog() if model.get("ready")}
missing_models = tuple(
model_key for model_key in shared_model_catalog.DEFAULT_MODEL_KEYS if model_key not in ready_models
)
return {
"environment_ready": environment_ready,
"missing_models": missing_models,
"ready": environment_ready and not missing_models,
}
def ai_ready():
return ai_status()["ready"]
def ai_setup_label(status=None):
if status is None:
status = ai_status()
return (
"Download Required Models…"
if status["environment_ready"]
else "Set Up AI Server…"
)
def model_download_size(model_key):
model = shared_model_catalog.get_downloadable_model(model_key)
return sum(size for _path, size, _checksum in model.r2_files)
def model_catalog():
return models_status().get("catalog", ())
def model_record(model_key):
return next(
(model for model in model_catalog() if model.get("key") == model_key),
None,
)
def _model_items(models):
numbers = {"MOGE2_VITS_NORMAL": 0, "MOGE2_VITB_NORMAL": 1, "MOGE3_VITL": 3}
return [
(
model["key"],
f"{tier} | {model['label']}",
f"{model['description']}; license: {model['license']}",
numbers[model["key"]],
)
for model, tier in zip(models, ("Fast", "Base", "Pro"))
]
def geometry_model_enum_items(_owner, _context):
models = [model for model in model_catalog() if model.get("family") == "moge"]
return _model_items(models)
def upscale_model_label(model_key):
model = shared_model_catalog.UPSCALE_MODELS.get(model_key)
return model.label if model is not None else model_key
def cutout_gesture_property():
return bpy.props.EnumProperty(
name="Gesture",
description="Shape used to select the Cutout area",
items=(
("LASSO", "Lasso", "Draw a closed freehand area"),
("POLYLINE", "Polyline", "Click points to define a closed area"),
),
default="LASSO",
)
def mask_gesture_property():
return bpy.props.EnumProperty(
name="Gesture",
description="Shape used to edit image Alpha",
items=(
("LASSO", "Lasso", "Draw a closed freehand area"),
("BRUSH", "Brush", "Paint with a circular brush"),
("POLYLINE", "Polyline", "Click points to define a closed area"),
),
default="LASSO",
)
def mask_mode_property():
return bpy.props.EnumProperty(
name="Mode",
description="Choose how the selected or painted area changes image visibility.",
items=(
(
"SET",
"Set",
"Keep visible content inside the selected or painted area and hide everything outside.",
"SELECT_SET",
0,
),
(
"EXTEND",
"Extend",
"Reveal the selected or painted area while keeping other areas unchanged.",
"SELECT_EXTEND",
1,
),
(
"SUBTRACT",
"Subtract",
"Hide the selected or painted area while keeping other areas unchanged.",
"SELECT_SUBTRACT",
2,
),
),
default="SET",
)
def mask_radius_property():
return bpy.props.IntProperty(
name="Radius",
description="Brush radius in Viewport pixels",
default=25,
min=1,
soft_max=500,
subtype="PIXEL",
)
def cutout_generate_normal_property():
return bpy.props.BoolProperty(
name="Normal Map",
description=(
"Generate a normal map to add surface detail under lighting for Flat and Solid shapes. "
"Depth Solid and Depth Balloon always generate normal maps."
),
default=False,
)
def cutout_alpha_threshold_property():
return bpy.props.FloatProperty(
name="Alpha Threshold",
description=(
"Exclude areas below this opacity from the Cutout outline. "
"Higher values exclude more semi-transparent edges."
),
default=0.9,
min=0.0,
max=1.0,
soft_min=0.1,
soft_max=0.9,
subtype="FACTOR",
)
def cutout_fine_outline_property():
return bpy.props.BoolProperty(
name="Fine Outline",
description="Fit detailed outlines and support thin parts; disable for simpler, faster meshes",
default=False,
)
def cutout_mesh_detail_property():
return bpy.props.EnumProperty(
name="Mesh Detail",
description="Control mesh density. Higher levels use more vertices to represent finer shapes.",
items=(
("LOW", "Low", "32 source image pixels between neighboring mesh points"),
("MEDIUM", "Medium", "16 source image pixels between neighboring mesh points"),
("HIGH", "High", "8 source image pixels between neighboring mesh points"),
("ULTRA", "Ultra", "4 source image pixels between neighboring mesh points"),
),
default="LOW",
)
class AnyImageSettings(bpy.types.PropertyGroup):
cutout_gesture: cutout_gesture_property()
cutout_alpha_threshold: cutout_alpha_threshold_property()
cutout_mesh_detail: cutout_mesh_detail_property()
cutout_fine_outline: cutout_fine_outline_property()
cutout_generate_normal: cutout_generate_normal_property()
mask_gesture: mask_gesture_property()
mask_mode: mask_mode_property()
mask_radius: mask_radius_property()