Cleans up and re-topologizes a 3D file, or converts it between formats. Given a mesh URL it can decimate the triangle count, generate quad-dominant topology, bake a silhouette-preserving low-poly, repair holes and bad normals, or just transcode between GLB / OBJ / FBX / STL / PLY / USDZ / 3MF. It backs three.ws's post-generation tools — Game-Ready export and the remesh stage of the paid 3D asset pipeline — not the primary generation lanes.
Wraps trimesh + open3d for geometry, QuadriFlow
(MIT) for quad remeshing, xatlas (MIT) for UV
re-unwrap, and headless Blender (bpy) for FBX
export. No GPU required — everything runs on CPU.
A FastAPI service on Cloud Run. Jobs are accepted immediately (202) and polled
— a quad remesh with texture re-bake runs far longer than a request should be
held open.
FBX is the only format here that needs Blender: trimesh has no FBX writer, so
every other format is written directly and FBX is bridged through a temporary
GLB handed to a one-shot headless Blender subprocess (blender_fbx.py). A plain
convert of a rigged GLB to FBX keeps its bone hierarchy, skin weights, and
blendshapes — that route (remesh_mode: "triangle", operation: "convert",
output_format: "fbx") skips the geometry pipelines entirely so the skeleton
survives. Any geometry-changing op discards the rig and yields a static FBX.
remesh_mode |
What it does |
|---|---|
triangle (default) |
Repair + quadric-error triangle decimation (open3d, trimesh fallback). Geometry only — drops materials. Honors operation. |
quad |
Field-aligned quad-dominant topology via QuadriFlow, then xatlas UV re-unwrap + source-texture re-bake. Reports a real quad_ratio. |
lowpoly |
Silhouette-preserving QEM decimation + UV re-unwrap + texture re-bake. |
operation (convert · simplify · repair · full) applies to triangle
mode; full (default) runs repair then decimate.
All routes require Authorization: Bearer $API_KEY, except /health.
curl -X POST https://$SERVICE_URL/process \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"mesh": "https://example.com/model.glb",
"remesh_mode": "quad",
"target_faces": 20000,
"texture_size": 1024,
"output_format": "glb"
}'
# → { "task_id": "…", "status": "queued", "mode": "quad" }| Field | Required | Default | Notes |
|---|---|---|---|
mesh |
yes | — | https:// URL to a GLB / GLTF / OBJ / STL / PLY / FBX / OFF / DAE (≤ 128 MB) |
remesh_mode |
no | triangle |
triangle | quad | lowpoly |
operation |
no | full |
convert | simplify | repair | full (triangle mode) |
target_faces |
no | 50000 |
1000–500000 |
texture_size |
no | 1024 |
512 | 1024 | 2048 — atlas size for the re-bake |
output_format |
no | glb |
glb | obj | stl | ply | usdz | 3mf | fbx |
Remote URLs are fetched through the SSRF guard in
worker_security.py — https-only, with private,
loopback, link-local, and cloud-metadata addresses rejected.
{
"task_id": "…",
"status": "done",
"result_url": "https://storage.googleapis.com/three-ws-avatar-reconstructions/remesh/….glb",
"texture_url": "https://storage.googleapis.com/…/remesh/….png",
"mtl_url": null,
"face_count": 19842,
"quad_ratio": 0.94,
"textured": true,
"mode": "quad",
"output_format": "glb",
"bytes": 1830112,
"elapsed_ms": 41230
}status is queued | running | done | failed. On failure the response
carries a sanitized error string; the full traceback stays in the server log.
texture_url / mtl_url are populated when a re-bake produces a sidecar PNG or
.mtl (e.g. a textured obj output).
{ "ok": true, "service": "remesh" }Unauthenticated, so Cloud Run's startup probe can reach it.
| Var | Required | Default | Notes |
|---|---|---|---|
API_KEY |
yes | — | Shared bearer secret (Secret Manager: avatar-reconstruction-key) |
GCS_BUCKET |
yes | — | Output bucket; artifacts land under the remesh/ prefix (three-ws-avatar-reconstructions) |
MAX_CONCURRENT |
no | 2 |
In-flight jobs |
QUADRIFLOW_BIN |
no | quadriflow |
Path to the QuadriFlow executable (built into the image) |
BLENDER_TIMEOUT |
no | 300 |
Seconds before a Blender FBX export is killed |
Built and deployed by Google Cloud Build from
cloudbuild.yaml to Cloud Run — service remesh-service
in us-central1 (project aerial-vehicle-466722-p5), 8 vCPU / 16 GiB, port
8080, scale-to-zero (min 0, max 3), 300 s request timeout, run as the
avatar-reconstruction-sa service account. API_KEY is mounted from the
avatar-reconstruction-key secret; GCS_BUCKET is set to
three-ws-avatar-reconstructions. There is no local run target — build the image
from Dockerfile (it compiles QuadriFlow from source and
installs the Blender bpy wheel) and run the container, or submit the build:
gcloud builds submit --config workers/remesh/cloudbuild.yaml workers/remeshThis is not a GitHub Actions job — three.ws has no Actions; all CI/CD runs on
Cloud Build. See docs/ops/gcp-model-workers.md
for how the model/post-gen workers are operated and
docs/ops/gcp-production.md for the platform
production runbook.
The platform never talks to this service directly from the browser. The GCP
provider (api/_providers/gcp.js, remesh mode) reads its URL from
GCP_REMESH_URL and the shared key from GCP_RECONSTRUCTION_KEY, maps
the tool request onto POST /process (resultKey: result_url), and polls
GET /tasks/:id until done, surfacing face_count, quad_ratio, and
textured back to the caller. It backs the free /api/forge-remesh tool and the
paid POST /api/x402/pipeline-remesh stage (api/x402/pipeline-remesh.js); a
lane missing either env var drops out cleanly and is reported
configured: false.