| import os |
| import threading |
| from pathlib import Path |
| from typing import Any |
|
|
| import torch |
| from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.staticfiles import StaticFiles |
| from pydantic import BaseModel |
|
|
| from trellis_api_service import GenerationParameters, TMP_DIR, TrellisAPIService |
|
|
|
|
| def _parse_allowed_origins(value: str) -> list[str]: |
| value = value.strip() |
| if not value: |
| return ["*"] |
| if value == "*": |
| return ["*"] |
| return [origin.strip() for origin in value.split(",") if origin.strip()] |
|
|
|
|
| service = TrellisAPIService() |
| app = FastAPI( |
| title="TRELLIS.2 API", |
| version="1.0.0", |
| description="API service for TRELLIS.2 image-to-3D generation and GLB export.", |
| ) |
|
|
| allowed_origins = _parse_allowed_origins(os.environ.get("TRELLIS_ALLOWED_ORIGINS", "*")) |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=allowed_origins, |
| allow_credentials=allowed_origins != ["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
| Path(TMP_DIR).mkdir(parents=True, exist_ok=True) |
| app.mount("/artifacts", StaticFiles(directory=str(TMP_DIR)), name="artifacts") |
|
|
|
|
| class GenerateResponse(BaseModel): |
| request_id: str |
| seed: int |
| resolution: str |
| glb_url: str |
| request_url: str |
| preview_urls: dict[str, list[str]] |
|
|
|
|
| @app.on_event("startup") |
| def preload_model() -> None: |
| preload_setting = os.environ.get("TRELLIS_PRELOAD_MODEL", "auto").lower() |
| preload = preload_setting in {"1", "true", "yes", "on"} or ( |
| preload_setting == "auto" and torch.cuda.is_available() |
| ) |
| if preload: |
| threading.Thread(target=service.ensure_initialized, daemon=True).start() |
|
|
|
|
| @app.get("/") |
| def root() -> dict[str, Any]: |
| return { |
| "service": "TRELLIS.2 API", |
| "docs_url": "/docs", |
| "health_url": "/healthz", |
| "parameters_url": "/v1/parameters", |
| "generate_url": "/v1/generate", |
| } |
|
|
|
|
| @app.get("/healthz") |
| def healthz() -> dict[str, Any]: |
| return service.health() |
|
|
|
|
| @app.get("/v1/parameters") |
| def get_parameters() -> dict[str, Any]: |
| return service.get_parameter_schema() |
|
|
|
|
| @app.post("/v1/generate", response_model=GenerateResponse) |
| def generate( |
| request: Request, |
| image: UploadFile = File(...), |
| resolution: str = Form("1024"), |
| seed: int = Form(0), |
| randomize_seed: bool = Form(True), |
| preprocess_image: bool = Form(True), |
| decimation_target: int = Form(300000), |
| texture_size: int = Form(2048), |
| include_previews: bool = Form(True), |
| ss_guidance_strength: float = Form(7.5), |
| ss_guidance_rescale: float = Form(0.7), |
| ss_sampling_steps: int = Form(12), |
| ss_rescale_t: float = Form(5.0), |
| shape_slat_guidance_strength: float = Form(7.5), |
| shape_slat_guidance_rescale: float = Form(0.5), |
| shape_slat_sampling_steps: int = Form(12), |
| shape_slat_rescale_t: float = Form(3.0), |
| tex_slat_guidance_strength: float = Form(1.0), |
| tex_slat_guidance_rescale: float = Form(0.0), |
| tex_slat_sampling_steps: int = Form(12), |
| tex_slat_rescale_t: float = Form(3.0), |
| ) -> GenerateResponse: |
| valid_resolutions = {"512", "1024", "1536"} |
| if resolution not in valid_resolutions: |
| raise HTTPException(status_code=400, detail=f"Unsupported resolution '{resolution}'. Choose from {sorted(valid_resolutions)}.") |
|
|
| params = GenerationParameters( |
| resolution=resolution, |
| seed=seed, |
| randomize_seed=randomize_seed, |
| preprocess_image=preprocess_image, |
| decimation_target=decimation_target, |
| texture_size=texture_size, |
| include_previews=include_previews, |
| ss_guidance_strength=ss_guidance_strength, |
| ss_guidance_rescale=ss_guidance_rescale, |
| ss_sampling_steps=ss_sampling_steps, |
| ss_rescale_t=ss_rescale_t, |
| shape_slat_guidance_strength=shape_slat_guidance_strength, |
| shape_slat_guidance_rescale=shape_slat_guidance_rescale, |
| shape_slat_sampling_steps=shape_slat_sampling_steps, |
| shape_slat_rescale_t=shape_slat_rescale_t, |
| tex_slat_guidance_strength=tex_slat_guidance_strength, |
| tex_slat_guidance_rescale=tex_slat_guidance_rescale, |
| tex_slat_sampling_steps=tex_slat_sampling_steps, |
| tex_slat_rescale_t=tex_slat_rescale_t, |
| ) |
| image_bytes = image.file.read() |
| result = service.generate(image_bytes, image.filename or "upload.png", params) |
|
|
| glb_path = Path(result["glb_path"]) |
| request_path = Path(result["request_path"]) |
| request_id = result["request_id"] |
|
|
| preview_urls = { |
| mode: [ |
| str(request.url_for("artifacts", path=f"{request_id}/previews/{Path(path).name}")) |
| for path in paths |
| ] |
| for mode, paths in result["preview_paths"].items() |
| } |
|
|
| return GenerateResponse( |
| request_id=request_id, |
| seed=result["seed"], |
| resolution=result["resolution"], |
| glb_url=str(request.url_for("artifacts", path=f"{request_id}/{glb_path.name}")), |
| request_url=str(request.url_for("artifacts", path=f"{request_id}/{request_path.name}")), |
| preview_urls=preview_urls, |
| ) |
|
|