3D 打板部署
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped

This commit is contained in:
zcr
2026-04-28 17:03:04 +08:00
parent ad4db736de
commit c73bfa7e2a
8 changed files with 303 additions and 65 deletions

View File

@@ -11,6 +11,7 @@ from app.api import api_precompute
from app.api import api_prompt_generation
from app.api import api_recommendation
from app.api import api_test
from app.api import api_sketch_to_garment
router = APIRouter()
@@ -26,6 +27,7 @@ router.include_router(api_precompute.router, tags=['api_precompute'], prefix="/a
router.include_router(api_mannequins_edit.router, tags=['api_mannequins_edit'], prefix="/api")
router.include_router(api_pose_transform.router, tags=['api_pose_transform'], prefix="/api")
router.include_router(api_clothing_seg.router, tags=['api_clothing_seg'], prefix="/api")
router.include_router(api_sketch_to_garment.router, tags=['sketch_to_garment'], prefix="/api")
"""停用"""
# from app.api import api_chat_robot

View File

@@ -0,0 +1,104 @@
import json
import logging
from fastapi import APIRouter, HTTPException
from app.schemas.response_template import ResponseModel
from app.schemas.sketch_to_garment_schemas import SketchToGarmentModel
from app.service.sketch2garment.server import submit_sketch_to_garment_task
logger = logging.getLogger()
router = APIRouter()
@router.post("/sketch_to_garment")
def sketch_to_garment_api(request_item: SketchToGarmentModel):
"""
### 接口说明:
将图片转换为3D模型异步处理。接口接收请求后立即返回任务ID后台通过 Celery 处理,处理完成后结果会通过 RabbitMQ 发送。
### 参数说明:
- **input_image_path**: 输入图片路径
- **bucket_name**: bucket name
- **user_id**: 用户id
- **callback_url**: 回调url
- **task_id**: 任务id
- **model**: 转换模式 文本和图片 ,默认只有图片
### 请求体示例:
**单张图片模式:**
```json
{
"input_image_path": "test/53d38bd5-f77b-4034-ada2-45f1e2ebe00c.png",
"bucket_name": "test",
"user_id": "string-456",
"callback_url": "http://18.167.251.121:10015/api/image/webhook/img-to-3d",
"task_id": "string12",
"model": "picture"
}
```
### 输出示例:
```json
{
"code": 200,
"msg": "OK!",
"data": {
"state": "success",
"task_id": "string12",
"message": "任务已成功提交,正在后台处理..."
}
}
```
### 错误输出
参考文档: https://platform.tripo3d.ai/docs/error-handling
```json
{
"code": 500,
"message": "You dont have enough credit to create this task",
"data": {
"status": "fail",
"task_id": "123",
"message": "You dont have enough credit to create this task",
"error": str(e)
}
}
```
回调请求参数例子:
```json
{
"task_id": "string12",
"status": "success",
"result": {
"pattern": "test/string-456/pattern_making/now_string-456_pattern.png",
"texture": "test/string-456/pattern_making/now_string-456_texture.png",
"glb": "test/string-456/pattern_making/now_string-456_sim.glb",
"texture_fabric": "test/string-456/pattern_making/now_string-456_texture_fabric.png"
}
}
```
"""
try:
logger.info(f"sketch_to_garment request item is : @@@@@@:{json.dumps(request_item.model_dump(), indent=4)}")
result = submit_sketch_to_garment_task(
task_id=request_item.task_id,
callback_url=request_item.callback_url,
bucket_name=request_item.bucket_name,
input_image_path=request_item.input_image_path,
user_id=request_item.user_id,
model=request_item.model
)
result = {
"state": "success",
"task_id": request_item.task_id,
"message": "任务已成功提交,正在后台处理...",
}
state_code = 200
return ResponseModel(data=result, code=state_code)
except Exception as e:
logger.warning(f"super_resolution Run Exception @@@@@@:{e}")
raise HTTPException(status_code=404, detail=str(e))

View File

@@ -87,6 +87,9 @@ class Settings(BaseSettings):
A6000_SERVICE_HOST: str = Field(default='', description="")
B_4_X_4090_SERVICE_HOST: str = Field(default='', description="")
# --- sketch to garment 模型url ---
SKETCH_TO_GARMENT_URL: str = Field(default='', description="")
# --- 其他配置信息 以下均为Docker容器内配置---
LOGS_PATH: str = Field(default="/logs/", description="")
CATEGORY_PATH: str = Field(default="/app/service/attribute/config/descriptor/category/category_dis.csv", description="")
@@ -97,71 +100,6 @@ class Settings(BaseSettings):
settings = Settings()
# ====================== Nacos 配置管理 ======================
client_config = (ClientConfigBuilder()
.server_address(NACOS_SERVER_ADDRESSES)
.username(NACOS_USERNAME)
.password(NACOS_PASSWORD)
.namespace_id(NACOS_NAMESPACE)
.log_level('INFO')
.grpc_config(GRPCConfig(grpc_timeout=50000))
.build())
# ====================== Nacos 配置管理 ======================
nacos_config_data: Dict[str, Any] = {}
async def load_nacos_config() -> None:
"""初始化 Nacos 配置并监听变化"""
global nacos_config_data, settings
global nacos_initialized_successfully
try:
client = await NacosConfigService.create_config_service(client_config)
# 1. 第一次获取配置
content = await client.get_config(ConfigParam(data_id=NACOS_DATA_ID, group=NACOS_GROUP))
if content:
loaded = yaml.safe_load(content) or {}
nacos_config_data = loaded
# 用 Nacos 配置覆盖 settings
for key, value in loaded.items():
if hasattr(settings, key):
setattr(settings, key, value)
logger.info(f"✅ Nacos 配置加载成功: {NACOS_DATA_ID} | 覆盖字段数量: {len(loaded)}")
else:
logger.warning("Nacos 返回配置为空,使用 .env + 默认值")
# 2. 注册动态监听器(配置变更自动刷新)
async def listener(tenant: str, data_id: str, group: str, content: str):
global nacos_config_data, settings
try:
new_config = yaml.safe_load(content) if content else {}
nacos_config_data = new_config
# 实时覆盖 settings
for key, value in new_config.items():
if hasattr(settings, key):
old_val = getattr(settings, key)
setattr(settings, key, value)
if old_val != value:
logger.info(f"🔄 配置更新 → {key}: {old_val}{value}")
logger.info(f"【Nacos 动态更新】{NACOS_DATA_ID}")
except Exception as e:
logger.error(f"Nacos 配置解析失败: {e}")
await client.add_listener(NACOS_DATA_ID, NACOS_GROUP, listener)
logger.info("✅ Nacos 配置监听器已注册(支持热更新)")
nacos_initialized_successfully = True
except Exception as e:
logger.error(f"❌ Nacos 初始化失败: {e}")
nacos_initialized_successfully = False
# 提供给 FastAPI 的依赖
def get_settings() -> Settings:
return settings
"""Design 服务"""
# 推荐服装类别映射
TABLE_CATEGORIES = {

View File

@@ -0,0 +1,12 @@
from typing import List
from pydantic import BaseModel, Field
class SketchToGarmentModel(BaseModel):
input_image_path: str = Field(..., description="输入图片路径列表")
bucket_name: str = Field(..., description="输入图片路径列表")
user_id: str = Field(..., description="用户id")
callback_url: str # 必填,客户端提供的回调地址
task_id: str = Field()
model: str = Field(default="single", description="模型类型: single 或 multi")

View File

@@ -0,0 +1,35 @@
import logging
import httpx
logger = logging.getLogger("app")
async def notify_callback(callback_url: str, task_id: str, status: str, result: dict, ):
"""
调用客户端提供的回调接口
"""
try:
payload = {
"task_id": task_id,
"status": status,
"result": result
}
logger.info(payload)
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(
str(callback_url),
json=payload,
headers={"Content-Type": "application/json"}
)
if 200 <= resp.status_code < 300:
logger.info(f"回调成功 | task_id: {task_id} | status: {status} | url: {callback_url}")
return True
else:
logger.warning(f"回调返回非2xx状态码 | task_id: {task_id} | status: {resp.status_code} | url: {callback_url}")
return False
except Exception as e:
logger.error(f"回调失败 | task_id: {task_id} | url: {callback_url} | error: {e}", exc_info=True)
return False

View File

@@ -0,0 +1,46 @@
from celery import Celery
from kombu import Queue, Exchange
from app.core.config import settings
celery_app = Celery(
"sketch_to_garment",
broker=f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/2",
backend=f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/{settings.REDIS_DB}",
include=["app.service.sketch2garment.tasks"]
)
print(f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/3")
print(f"celery_app: {celery_app}")
celery_app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
timezone="Asia/Hong_Kong",
enable_utc=True,
task_track_started=True,
task_time_limit=300, # 单个任务最长 5 分钟
task_soft_time_limit=280,
# 定义队列
task_queues=(
Queue("sketch_to_garment_queue",
exchange=Exchange("sketch_to_garment_exchange", type="direct"),
durable=True),
),
task_routes={
'app.service.sketch2garment.tasks.sketch_to_garment':
{
'queue': 'sketch_to_garment_queue',
'exchange': 'sketch_to_garment_exchange', # ← 修改这里
},
},
task_default_queue="sketch_to_garment_queue",
worker_concurrency=1,
worker_prefetch_multiplier=1,
worker_max_tasks_per_child=1,
task_acks_late=True,
task_reject_on_worker_lost=True,
)

View File

@@ -0,0 +1,44 @@
import logging
from app.service.sketch2garment.tasks import sketch_to_garment
logger = logging.getLogger(__name__)
def submit_sketch_to_garment_task(model: str = "single", task_id: str = "", callback_url: str = "", bucket_name: str = "test", user_id: str = "123", input_image_path: str = ""):
"""提交 img_to_3D 任务(带队列长度限制)"""
queue_name = "img_to_3d_queue"
max_queue_length = 10
try:
# current_length = get_queue_length(queue_name)
# if current_length >= max_queue_length:
# return {
# "state": "queue_full",
# "message": "当前 3D 生成请求较多,请稍后重试。",
# "queue_length": current_length,
# "max_length": max_queue_length
# }
# 提交任务
task = sketch_to_garment.apply_async(
args=(task_id, callback_url, bucket_name, input_image_path, user_id, model),
task_id=task_id,
queue="sketch_to_garment_queue")
# logger.info(f"img_to_3d_task 已提交 | task_id: {task_id} | 当前队列长度: {current_length}")
return {
"state": "success",
"task_id": task_id,
"message": "任务已成功提交,正在后台处理...",
}
except Exception as e:
logger.error(f"提交 img_to_3d_task 失败: {e}", exc_info=True)
return {
"state": "fail",
"message": "提交失败,请稍后重试。",
"error": str(e)
}

View File

@@ -0,0 +1,57 @@
import asyncio
import logging
from app.core.config import settings
from app.service.sketch2garment.callback import notify_callback
import httpx
from app.service.sketch2garment.celery_app import celery_app
logger = logging.getLogger(__name__)
@celery_app.task(bind=True, queue="sketch_to_garment_queue", max_retries=3, name='app.service.sketch2garment.tasks.sketch_to_garment')
def sketch_to_garment(self, task_id: str, callback_url: str, bucket_name: str, input_image_path: str, user_id: str, category: str = None):
payload = {
"bucket_name": bucket_name,
"category": category or settings.DEFAULT_CATEGORY,
"input_image_path": input_image_path,
"user_id": user_id
}
logger.info(f"payload: {payload}")
try:
with httpx.Client(timeout=300.0) as client: # 注意这里用 AsyncClient 配合 Celery
# 如果你的 LitServe 是同步 endpoint也可以用 httpx.Client()
response = client.post(settings.SKETCH_TO_GARMENT_URL, json=payload)
if response.status_code == 200:
result = response.json()
result_json = {
"pattern": result[1],
"texture": result[2],
"glb": result[3],
"texture_fabric": result[4]
}
asyncio.run(
notify_callback(callback_url=callback_url, task_id=task_id, result=result_json, status="success")
)
else:
asyncio.run(
notify_callback(
callback_url=callback_url,
task_id=task_id,
result={
"status": "fail",
"task_id": task_id,
"message": "fail",
"error": "fail"
},
status="fail")
)
except Exception as e:
return {
"status": "failed",
"task_id": task_id,
"input": payload,
"error": str(e)
}