¿Qué hace Meshy 3D — Generación + Impresión?
Comunícate directamente con la API de Meshy AI para generar e imprimir activos 3D. Cubre el ciclo de vida completo: configuración de clave API, creación de tareas, sondeo con retroceso exponencial, descarga, pipelines de múltiples pasos y preparación para impresión 3D con integración de laminador.
MANIFIESTO DE SEGURIDAD
Variables de entorno accedidas:
MESHY_API_KEY: Token de autenticación de API enviado solo en el encabezado HTTPAuthorization: Bearer. Nunca se registra, nunca se escribe en ningún archivo excepto.enven el directorio de trabajo actual cuando el usuario lo solicita explícitamente.
Puntos finales de red externos:
https://api.meshy.ai: API de Meshy AI (creación de tareas, sondeo de estado, descarga de modelos/imágenes)
Acceso al sistema de archivos:
- Lectura:
.enven el directorio de trabajo actual solamente (búsqueda de clave API) - Escritura:
.enven el directorio de trabajo actual solamente (almacenamiento de clave API, solo a petición del usuario) - Escritura:
./meshy_output/en el directorio de trabajo actual (archivos de modelo descargados, metadatos) - Lectura: archivos proporcionados explícitamente por el usuario (por ejemplo, imágenes locales para conversión de imagen a 3D), accedidos solo en la ruta exacta que el usuario especifica
- Sin acceso a directorios de inicio, perfiles de shell ni ninguna ruta fuera de lo anterior
Datos que salen de esta máquina:
- Las solicitudes de API a
api.meshy.aiincluyen laMESHY_API_KEYen el encabezado de Autorización y las indicaciones de texto o URL de imágenes proporcionadas por el usuario. No se transmite ningún otro dato local. Los archivos de modelo descargados se guardan solo localmente.
IMPORTANTE: Aviso de sesión de primer uso
Cuando este skill se active por primera vez en una sesión, informa al usuario:
Todos los archivos generados se guardarán en
meshy_output/en el directorio de trabajo actual. Cada proyecto obtiene su propia carpeta ({YYYYMMDD_HHmmss}_{prompt}_{id}/) con archivos de modelo, texturas, miniaturas y metadatos. El historial se rastrea enmeshy_output/history.json.
Esto solo necesita decirse una vez por sesión.
IMPORTANTE: Organización de archivos
Todos los archivos descargados DEBEN ir a un directorio estructurado meshy_output/ en el directorio de trabajo actual. NO disperses archivos al azar.
- Cada proyecto:
meshy_output/{YYYYMMDD_HHmmss}_{prompt_slug}_{task_id_prefix}/ - Las tareas encadenadas (vista previa → refinar → rigging) reutilizan el mismo
project_dir - Rastrea las tareas en
metadata.jsonpor proyecto, y globalmente enhistory.json - Descarga automáticamente las miniaturas junto con los modelos
IMPORTANTE: Reglas de comandos de shell
Usa solo herramientas POSIX estándar. NO uses rg, fd, bat, exa/eza.
IMPORTANTE: Ejecutar tareas largas correctamente
La generación de Meshy toma de 1 a 5 minutos. Escribe todo el flujo de crear → sondear → descargar como UN script de Python y ejecútalo en una sola llamada de Bash. Usa python3 -u script.py para una salida sin búfer. Las tareas que permanecen al 99% durante 30-120 segundos es una finalización normal: NO interrumpas.
Paso 0: Detección de clave API (SIEMPRE EJECUTAR PRIMERO)
Solo verifica el entorno de la sesión actual y el archivo .env en el directorio de trabajo actual. NO escanees directorios de inicio ni archivos de perfil de shell.
echo "=== Detección de clave API de Meshy ==="
# 1. Verificar variable de entorno actual
if [ -n "$MESHY_API_KEY" ]; then
echo "ENV_VAR: ENCONTRADA (${MESHY_API_KEY:0:8}...)"
else
echo "ENV_VAR: NO_ENCONTRADA"
fi
# 2. Verificar .env en el directorio de trabajo actual solamente
if [ -f ".env" ] && grep -q "MESHY_API_KEY" ".env" 2>/dev/null; then
echo "DOTENV(.env): ENCONTRADA"
export MESHY_API_KEY=$(grep "^MESHY_API_KEY=" ".env" | head -1 | cut -d'=' -f2- | tr -d '"'"'" )
fi
# 3. Estado final
if [ -n "$MESHY_API_KEY" ]; then
echo "LISTO: clave=${MESHY_API_KEY:0:8}..."
else
echo "LISTO: NO_SE_ENCONTRÓ_CLAVE"
fi
# 4. Verificación de requests de Python
python3 -c "import requests; print('PYTHON_REQUESTS: OK')" 2>/dev/null || echo "PYTHON_REQUESTS: FALTANTE (ejecuta: pip install requests)"
echo "=== Detección completa ==="
Decisión después de la detección
- Clave encontrada → Procede al Paso 1.
- Clave NO encontrada → Ve al Paso 0a.
- Faltan requests de Python → Ejecuta
pip install requests.
Paso 0a: Configuración de clave API (Solo si no se encuentra clave)
Dile al usuario:
Para usar la API de Meshy, necesitas una clave API:
- Ve a https://www.meshy.ai/settings/api
- Haz clic en "Create API Key", asígnale un nombre y copia la clave (comienza con
msy_)- La clave se muestra solo una vez: guárdala en un lugar seguro
Nota: El acceso a la API requiere un plan Pro o superior. Las cuentas de nivel gratuito no pueden crear claves API.
Una vez que el usuario proporcione la clave, configúrala para la sesión actual y, opcionalmente, persístela en .env:
# Configurar solo para la sesión actual
export MESHY_API_KEY="msy_PEGA_CLAVE_AQUÍ"
# Verificar la clave
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $MESHY_API_KEY" \
https://api.meshy.ai/openapi/v1/balance)
if [ "$STATUS" = "200" ]; then
BALANCE=$(curl -s -H "Authorization: Bearer $MESHY_API_KEY" https://api.meshy.ai/openapi/v1/balance)
echo "Clave válida. $BALANCE"
else
echo "Clave inválida (HTTP $STATUS). Verifica la clave e inténtalo de nuevo."
fi
Para persistir la clave (solo proyecto actual):
# Escribir en .env en el directorio de trabajo actual
echo 'MESHY_API_KEY=msy_PEGA_CLAVE_AQUÍ' >> .env
echo "Guardada en .env"
# IMPORTANTE: agregar .env a .gitignore para evitar filtrar la clave
grep -q "^\.env" .gitignore 2>/dev/null || echo ".env" >> .gitignore
echo ".env agregado a .gitignore"
Recordatorio de seguridad: La clave se almacena solo en
.enven el directorio de tu proyecto actual. Nunca confirmes este archivo en el control de versiones..envse ha agregado automáticamente a.gitignore.
Paso 1: Confirmar plan con el usuario antes de gastar créditos
CRÍTICO: Antes de crear cualquier tarea, presenta al usuario un resumen de costos y espera confirmación:
Generaré un modelo 3D de "<indicación>" usando el siguiente plan:
1. Vista previa (generación de malla) — 20 créditos
2. Refinar (texturizado con PBR) — 10 créditos
3. Descargar como .glb
Costo total: 30 créditos
Saldo actual: <N> créditos
¿Procedo?
Para pipelines de múltiples pasos (texto a 3D → rigging → animación), muestra el costo TOTAL del pipeline por adelantado.
Nota: El rigging incluye automáticamente animaciones de caminar y correr sin costo adicional. Solo agrega
Animar(3 créditos) para animaciones personalizadas más allá de esas.
Mapeo de intención a API
| El usuario quiere... | API | Endpoint | Créditos |
|---|---|---|---|
| Modelo 3D desde texto | Texto a 3D | POST /openapi/v2/text-to-3d | 5–20 (vista previa) + 10 (refinar) |
| Modelo 3D desde una imagen | Imagen a 3D | POST /openapi/v1/image-to-3d | 5–30 |
| Modelo 3D desde múltiples imágenes | Multi-Imagen a 3D | POST /openapi/v1/multi-image-to-3d | 5–30 |
| Nuevas texturas en modelo existente | Retexturizar | POST /openapi/v1/retexture | 10 |
| Cambiar formato/topología de malla | Remallar | POST /openapi/v1/remesh | 5 |
| Convertir un modelo a otros formatos (sin remallar) | Convertir | POST /openapi/v1/convert | 1 |
| Reescalar un modelo al tamaño del mundo real | Redimensionar | POST /openapi/v1/resize | 1 |
| Generar UVs nuevas (GLB, ≤40k caras) antes de texturizado externo | Desenvolver UV | POST /openapi/v1/uv-unwrap | 5 |
| Agregar esqueleto a personaje | Rigging automático | POST /openapi/v1/rigging | 5 |
| Animar un personaje con rigging | Animación | POST /openapi/v1/animations | 3 |
| Imagen 2D desde texto (paso previo recomendado antes de imagen a 3D) | Texto a Imagen | POST /openapi/v1/text-to-image | 3 / 6 / 9 / 9 |
| Optimizar/editar una imagen 2D (paso previo recomendado antes de imagen a 3D) | Imagen a Imagen | POST /openapi/v1/image-to-image | 3 / 6 / 9 / 12 |
| Foto → producto físico estilizado (figura/lámpara/llavero/imán de nevera) | Laboratorio Creativo | POST /openapi/creative-lab/{producto}/v1/prototype luego .../build | 6 + 30 |
| Verificar imprimibilidad FDM | Analizar Imprimibilidad | POST /openapi/v1/print/analyze | 0 (gratis) |
| Reparar topología no múltiple/caras degeneradas/agujeros | Reparar Imprimibilidad | POST /openapi/v1/print/repair | 10 |
| Impresión 3D multicolor | Impresión Multicolor | POST /openapi/v1/print/multi-color | 10 (+ generación) |
| Impresión 3D de un modelo (blanco) | → Ver sección Pipeline de Impresión | — | 20 |
| Verificar saldo de créditos | Saldo | GET /openapi/v1/balance | 0 |
Paso 2: Ejecutar el flujo de trabajo
Plantilla de script reutilizable
Usa esto como base para TODOS los flujos de trabajo. Carga la clave API de forma segura desde el entorno o .env solo en el directorio actual:
#!/usr/bin/env python3
"""Ejecutor de tareas de la API de Meshy. Maneja crear → sondear → descargar."""
import requests, time, os, sys, re, json
from datetime import datetime
# --- Carga segura de clave API ---
def load_api_key():
"""Carga MESHY_API_KEY del entorno, luego de .env en el cwd únicamente."""
key = os.environ.get("MESHY_API_KEY", "").strip()
if key:
return key
env_path = os.path.join(os.getcwd(), ".env")
if os.path.exists(env_path):
with open(env_path) as f:
for line in f:
line = line.strip()
if line.startswith("MESHY_API_KEY=") and not line.startswith("#"):
val = line.split("=", 1)[1].strip().strip('"').strip("'")
if val:
return val
return ""
API_KEY = load_api_key()
if not API_KEY:
sys.exit("ERROR: MESHY_API_KEY no configurada. Ejecuta el Paso 0a para configurarla.")
# Nunca registrar la clave completa — solo los primeros 8 caracteres para trazabilidad
print(f"Clave API cargada: {API_KEY[:8]}...")
BASE = "https://api.meshy.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
SESSION = requests.Session()
SESSION.trust_env = False # omitir cualquier configuración de proxy del sistema
def create_task(endpoint, payload):
resp = SESSION.post(f"{BASE}{endpoint}", headers=HEADERS, json=payload, timeout=30)
if resp.status_code == 401:
sys.exit("ERROR: Clave API inválida (401). Re-ejecuta el Paso 0a.")
if resp.status_code == 402:
try:
bal = SESSION.get(f"{BASE}/openapi/v1/balance", headers=HEADERS, timeout=10)
balance = bal.json().get("balance", "desconocido")
sys.exit(f"ERROR: Créditos insuficientes (402). Saldo: {balance}. Recarga en https://www.meshy.ai/pricing")
except Exception:
sys.exit("ERROR: Créditos insuficientes (402). Verifica saldo en https://www.meshy.ai/pricing")
if resp.status_code == 429:
sys.exit("ERROR: Límite de tasa excedido (429). Espera y reintenta.")
resp.raise_for_status()
task_id = resp.json()["result"]
print(f"TAREA_CREADA: {task_id}")
return task_id
def poll_task(endpoint, task_id, timeout=300):
"""Sondea con retroceso exponencial (5s→30s, fijo 15s al 95%+)."""
elapsed, delay, max_delay, backoff, finalize_delay, poll_count = 0, 5, 30, 1.5, 15, 0
while elapsed < timeout:
poll_count += 1
resp = SESSION.get(f"{BASE}{endpoint}/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
task = resp.json()
status = task["status"]
progress = task.get("progress", 0)
bar = f"[{'█' * int(progress/5)}{'░' * (20 - int(progress/5))}] {progress}%"
print(f" {bar} — {status} ({elapsed}s, sondeo #{poll_count})", flush=True)
if status == "SUCCEEDED":
return task
if status in ("FAILED", "CANCELED"):
msg = task.get("task_error", {}).get("message", "Desconocido")
sys.exit(f"TAREA_{status}: {msg}")
current_delay = finalize_delay if progress >= 95 else delay
time.sleep(current_delay)
elapsed += current_delay
if progress < 95:
delay = min(delay * backoff, max_delay)
sys.exit(f"TIEMPO_AGOTADO después de {timeout}s ({poll_count} sondeos)")
def download(url, filepath):
"""Descarga un archivo en un directorio de proyecto (dentro de cwd/meshy_output/)."""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
print(f"Descargando {filepath}...", flush=True)
resp = SESSION.get(url, timeout=300, stream=True)
resp.raise_for_status()
with open(filepath, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
print(f"DESCARGADO: {filepath} ({os.path.getsize(filepath)/1024/1024:.1f} MB)")
# --- Ayudantes de organización de archivos ---
OUTPUT_ROOT = os.path.join(os.getcwd(), "meshy_output")
os.makedirs(OUTPUT_ROOT, exist_ok=True)
HISTORY_FILE = os.path.join(OUTPUT_ROOT, "history.json")
def get_project_dir(task_id, prompt="", task_type="model"):
slug = re.sub(r'[^a-z0-9]+', '-', (prompt or task_type).lower())[:30].strip('-')
folder = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{slug}_{task_id[:8]}"
project_dir = os.path.join(OUTPUT_ROOT, folder)
os.makedirs(project_dir, exist_ok=True)
return project_dir
def record_task(project_dir, task_id, task_type, stage, prompt="", files=None):
meta_path = os.path.join(project_dir, "metadata.json")
meta = json.load(open(meta_path)) if os.path.exists(meta_path) else {
"project_name": prompt or task_type, "folder": os.path.basename(project_dir),
"root_task_id": task_id, "created_at": datetime.now().isoformat(), "tasks": []
}
meta["tasks"].append({"task_id": task_id, "task_type": task_type, "stage": stage,
"files": files or [], "created_at": datetime.now().isoformat()})
meta["updated_at"] = datetime.now().isoformat()
json.dump(meta, open(meta_path, "w"), indent=2)
history = json.load(open(HISTORY_FILE)) if os.path.exists(HISTORY_FILE) else {"version": 1, "projects": []}
folder = os.path.basename(project_dir)
entry = next((p for p in history["projects"] if p["folder"] == folder), None)
if entry:
entry.update({"task_count": len(meta["tasks"]), "updated_at": meta["updated_at"]})
else:
history["projects"].append({"folder": folder, "prompt": prompt, "task_type": task_type,
"root_task_id": task_id, "created_at": meta["created_at"],
"updated_at": meta["updated_at"], "task_count": len(meta["tasks"])})
json.dump(history, open(HISTORY_FILE, "w"), indent=2)
def save_thumbnail(project_dir, url):
path = os.path.join(project_dir, "thumbnail.png")
if os.path.exists(path): return
try:
r = SESSION.get(url, timeout=15); r
...
[truncado para traducción]