Implement AI Host functionality with model caching and job management

- Added ai_host.py to handle AI tasks with FastAPI integration.
- Introduced HostState and ModelCache classes for managing client connections and model loading.
- Implemented job submission and cancellation endpoints in the AI host.
- Enhanced tests for AI host functionality, including client tracking and job cancellation.
- Updated existing tests to accommodate changes in the operator and command structure.
- Refactored runtime commands to use request-based architecture instead of command-line arguments.
- Improved model loading logic to handle CUDA memory management and reuse models efficiently.
This commit is contained in:
2026-08-04 07:35:40 +08:00
parent d694980da7
commit 838af0ee96
16 changed files with 2112 additions and 225 deletions

View File

@@ -26,8 +26,13 @@ IMAGE_NAME_SUFFIXES = (
def image_depth_model_items(owner, context):
from .models import model_enum_items
from .runtime import model_ready
return model_enum_items(owner, context)
return [
item
for item in model_enum_items(owner, context)
if model_ready(item[0])
]
_last_depth_plane_model = "BASE"
@@ -921,8 +926,13 @@ class RemoveImageBackground(bpy.types.Operator):
@classmethod
def poll(cls, context):
from .operators import active_job
from .runtime import host_busy
return is_image_empty(context.object) and active_job() is None
return (
is_image_empty(context.object)
and active_job() is None
and not host_busy()
)
def execute(self, context):
from .operators import IMAGE_BACKGROUND_JOB
@@ -1001,7 +1011,7 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
snapform_process_res_factor: bpy.props.FloatProperty(
name="Resolution Scale",
description="Scale applied to the source image long-edge resolution",
default=0.5,
default=1.0,
min=0.1,
max=1.0,
step=10,
@@ -1019,18 +1029,30 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
min=0,
max=10,
)
@classmethod
def poll(cls, context):
from .operators import active_job
from .runtime import host_busy
return is_image_empty(context.object) and active_job() is None
return (
is_image_empty(context.object)
and active_job() is None
and not host_busy()
)
def invoke(self, context, _event):
global _last_depth_plane_model
if not self.properties.is_property_set("snapform_model"):
self.snapform_model = _last_depth_plane_model
available_keys = [
item[0] for item in image_depth_model_items(self, context)
]
if available_keys:
self.snapform_model = (
_last_depth_plane_model
if _last_depth_plane_model in available_keys
else available_keys[0]
)
return context.window_manager.invoke_props_dialog(self, width=340)
def draw(self, context):
@@ -1038,21 +1060,32 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
layout.prop(self, "snapform_depth_source")
if self.snapform_depth_source == "ESTIMATE":
estimate_box = layout.box()
estimate_box.prop(self, "snapform_model")
estimate_box.prop(self, "snapform_use_ray_pose")
process_res = scaled_process_resolution(
context.object.data,
self.snapform_process_res_factor,
)
estimate_box.prop(
self,
"snapform_process_res_factor",
text=f"Resolution Scale ({process_res} px)",
)
layout.prop(self, "snapform_remove_background")
layout.prop(self, "snapform_displacement_via")
layout.prop(self, "snapform_unshaded")
layout.prop(self, "snapform_subdivision_level")
available_models = image_depth_model_items(self, context)
if not available_models:
estimate_box.label(text="No DA3 models installed", icon="ERROR")
estimate_box.label(text="Download one in Host > Models")
else:
estimate_box.prop(self, "snapform_model")
estimate_box.prop(self, "snapform_use_ray_pose")
process_res = scaled_process_resolution(
context.object.data,
self.snapform_process_res_factor,
)
estimate_box.prop(
self,
"snapform_process_res_factor",
text=f"Resolution Scale ({process_res} px)",
)
settings_header, settings_body = layout.panel(
"snapform_depth_plane_options",
default_closed=True,
)
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")
settings_body.prop(self, "snapform_remove_background")
settings_body.prop(self, "snapform_unshaded")
layout.prop(self, "snapform_delete_original")
def execute(self, context):