s&box Model Importer
or
Convert Source Engine models and materials into s&box-ready assets.
Select the root asset folder so our auto-detection can match textures to materials.
Choose which mesh to use for the physics collision. Lower-poly meshes improve runtime performance.
folder into your project's Assets/ directory.
API & integration
Process 3D models programmatically via POST /api/tools/model (async, poll the returned statusUrl until status=done).
JOB=$(curl -s -X POST https://api.sboxcool.com/tools/model \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" -F "pipeline=source1" -F "lodLevels=2") # then poll the returned statusUrl until status=done
Process 3D models programmatically. Get your API key first. Rate limit: 10/min, 60/hr, 500/day. Max 5 concurrent model jobs per user.
POST /api/tools/model
Upload a 3D model to start an async processing pipeline. Returns a job ID to poll for status. Download links expire after 1 hour.
| Field | Description | |
file | required | 3D model file (FBX or OBJ, max 100MB) |
format | optional | Input format: fbx (default) or obj |
pipeline | optional | standard (default) or source1 (generates VMDL manifest) |
ratio | optional | Base decimation ratio 0.01 - 1.0 (default: 0.5). Lower = fewer faces. |
| LoD Options | ||
lodLevels | optional | Number of additional LoD levels to generate, 0-5 (default: 0) |
lodFinalRatio | optional | Final lowest-detail LoD ratio 0.001 - 1.0 (default: 0.15). Used for exponential decay curve. |
lodMaxThreshold | optional | Max switch threshold for furthest LoD, 1-500 (default: 50) |
| PBR Texture Generation | ||
generatePbr | optional | true to generate normal/roughness/AO maps from uploaded color texture |
pbrNormalStrength | optional | 0.5 - 4.0 (default: 1.5) |
pbrRoughnessBase | optional | 100 - 240 (default: 180) |
pbrAoStrength | optional | 0 - 1.0 (default: 0.3) |
| Textures | ||
tex_{name}_{slot} | optional | Texture file. name = material name, slot = color, normal, roughness, metalness, emissive, ao |
{
"ok": true,
"jobId": "a1b2c3d4e5f6",
"statusUrl": "/api/tools/model/a1b2c3d4e5f6",
"message": "Model processing started. Poll statusUrl for progress."
}
{
"jobId": "a1b2c3d4e5f6",
"status": "done", // "queued" | "running" | "done" | "error"
"progress": ["Decimating model...", "LOD0 ready: 512 KB", ...],
"result": {
"originalFaces": 15000,
"resultFaces": 7500,
"pipeline": "source1",
"lodLevels": 2,
"lods": [
{"level": 1, "ratio": 0.56, "threshold": 25, "faces": 8400, "size": 190000},
{"level": 2, "ratio": 0.15, "threshold": 50, "faces": 2250, "size": 62000}
]
},
"downloads": {
"lod0": "/uploads/temp/123/abc_model_lod0.glb",
"lod1": "/uploads/temp/123/abc_model_lod1.glb",
"lod2": "/uploads/temp/123/abc_model_lod2.glb",
"vmdl": "/uploads/temp/123/abc_model.vmdl",
"pbrTextures": "/uploads/temp/123/abc_pbr_textures.zip"
},
"expiresAt": 1711900000000
}
# 1. Submit JOB=$(curl -s -X POST https://api.sboxcool.com/tools/model \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "format=fbx" \ -F "pipeline=source1" \ -F "ratio=0.5" \ -F "lodLevels=2" \ -F "lodMaxThreshold=50" \ -F "generatePbr=true" \ -F "tex_body_color=@body_color.png") JOB_ID=$(echo $JOB | jq -r '.jobId') # 2. Poll until done while true; do STATUS=$(curl -s -H "Authorization: Bearer YOUR_API_KEY" \ https://api.sboxcool.com/tools/model/$JOB_ID) echo $STATUS | jq '.status, .progress[-1]' [ "$(echo $STATUS | jq -r '.status')" = "done" ] && break sleep 3 done # 3. Download curl -o model_lod0.glb "https://sboxcool.com$(echo $STATUS | jq -r '.downloads.lod0')"
import requests, time
headers = {"Authorization": "Bearer YOUR_API_KEY"}
# Submit
resp = requests.post("https://api.sboxcool.com/tools/model", headers=headers,
files={"file": open("character.fbx", "rb"), "tex_body_color": open("body.png", "rb")},
data={"format": "fbx", "pipeline": "source1", "ratio": "0.5",
"lodLevels": "2", "generatePbr": "true"})
job = resp.json()
# Poll
while True:
status = requests.get(f"https://sboxcool.com{job['statusUrl']}", headers=headers).json()
print(status["status"], status["progress"][-1] if status["progress"] else "")
if status["status"] in ("done", "error"):
break
time.sleep(3)
# Download all LoDs
for key, url in status["downloads"].items():
data = requests.get(f"https://sboxcool.com{url}").content
with open(f"{key}.{'zip' if 'zip' in url else 'glb'}", "wb") as f:
f.write(data)
This is a two-step async API. Step 1: POST with multipart/form-data to upload the model file and start processing (requires file upload support in your MCP/HTTP tool). Step 2: Poll the statusUrl with GET requests until status is "done", then download files from the downloads URLs with simple GET requests. Most MCP HTTP tools can handle the polling + download steps natively since they are plain JSON GET requests. If your MCP tool cannot send multipart/form-data, you will need a proxy or CLI wrapper for the initial upload.