Que fait Meshy 3D — Génération + Impression ?
Communiquez directement avec l'API Meshy AI pour générer et imprimer des actifs 3D. Couvre le cycle de vie complet : configuration de la clé API, création de tâches, interrogation avec backoff exponentiel, téléchargement, pipelines multi-étapes et préparation à l'impression 3D avec intégration du slicer.
MANIFESTE DE SÉCURITÉ
Variables d'environnement consultées :
MESHY_API_KEY— Jeton d'authentification API envoyé dans l'en-tête HTTPAuthorization: Beareruniquement. Jamais journalisé, jamais écrit dans un fichier autre que.envdans le répertoire de travail courant, sauf demande explicite de l'utilisateur.
Points de terminaison réseau externes :
https://api.meshy.ai— API Meshy AI (création de tâches, interrogation de statut, téléchargements de modèles/images)
Accès au système de fichiers :
- Lecture :
.envdans le répertoire de travail courant uniquement (recherche de clé API) - Écriture :
.envdans le répertoire de travail courant uniquement (stockage de clé API, uniquement sur demande de l'utilisateur) - Écriture :
./meshy_output/dans le répertoire de travail courant (fichiers de modèles téléchargés, métadonnées) - Lecture : fichiers fournis explicitement par l'utilisateur (par exemple, images locales passées pour la conversion image vers 3D), consultés uniquement au chemin exact spécifié par l'utilisateur
- Aucun accès aux répertoires personnels, profils shell ou tout chemin en dehors de ce qui précède
Données quittant cette machine :
- Les requêtes API vers
api.meshy.aiincluent leMESHY_API_KEYdans l'en-tête Authorization ainsi que les invites textuelles ou URL d'images fournies par l'utilisateur. Aucune autre donnée locale n'est transmise. Les fichiers de modèles téléchargés sont enregistrés localement uniquement.
IMPORTANT : Avis de première utilisation de la session
Lorsque cette compétence est activée pour la première fois dans une session, informez l'utilisateur :
Tous les fichiers générés seront enregistrés dans
meshy_output/dans le répertoire de travail courant. Chaque projet reçoit son propre dossier ({YYYYMMDD_HHmmss}_{invite}_{id}/) avec fichiers de modèles, textures, vignettes et métadonnées. L'historique est suivi dansmeshy_output/history.json.
Cela ne doit être dit qu'une fois par session.
IMPORTANT : Organisation des fichiers
Tous les fichiers téléchargés DOIVENT aller dans un répertoire structuré meshy_output/ dans le répertoire de travail courant. Ne dispersez PAS les fichiers au hasard.
- Chaque projet :
meshy_output/{YYYYMMDD_HHmmss}_{slug_invite}_{préfixe_tâche}/ - Les tâches chaînées (aperçu → affiner → rigger) réutilisent le même
project_dir - Suivez les tâches dans
metadata.jsonpar projet, et globalement danshistory.json - Téléchargez automatiquement les vignettes avec les modèles
IMPORTANT : Règles pour les commandes shell
Utilisez uniquement les outils POSIX standards. N'utilisez PAS rg, fd, bat, exa/eza.
IMPORTANT : Exécuter correctement les tâches longues
La génération Meshy prend 1 à 5 minutes. Écrivez le flux complet création → interrogation → téléchargement en UN script Python et exécutez-le en un seul appel Bash. Utilisez python3 -u script.py pour une sortie non tamponnée. Les tâches bloquées à 99% pendant 30 à 120 s sont une finalisation normale — n'interrompez PAS.
Étape 0 : Détection de la clé API (TOUJOURS À EXÉCUTER EN PREMIER)
Vérifiez uniquement les variables d'environnement de la session en cours et le fichier .env dans le répertoire de travail courant. Ne parcourez PAS les répertoires personnels ou les fichiers de profil shell.
echo "=== Détection de la clé API Meshy ==="
# 1. Vérifier la variable d'environnement actuelle
if [ -n "$MESHY_API_KEY" ]; then
echo "ENV_VAR: TROUVÉE (${MESHY_API_KEY:0:8}...)"
else
echo "ENV_VAR: NON_TROUVÉE"
fi
# 2. Vérifier .env dans le répertoire de travail courant uniquement
if [ -f ".env" ] && grep -q "MESHY_API_KEY" ".env" 2>/dev/null; then
echo "DOTENV(.env): TROUVÉE"
export MESHY_API_KEY=$(grep "^MESHY_API_KEY=" ".env" | head -1 | cut -d'=' -f2- | tr -d '"'"'"' )
fi
# 3. Statut final
if [ -n "$MESHY_API_KEY" ]; then
echo "PRÊT : clé=${MESHY_API_KEY:0:8}..."
else
echo "PRÊT : AUCUNE_CLÉ_TROUVÉE"
fi
# 4. Vérification des requêtes Python
python3 -c "import requests; print('PYTHON_REQUESTS: OK')" 2>/dev/null || echo "PYTHON_REQUESTS: MANQUANT (exécutez : pip install requests)"
echo "=== Détection terminée ==="
Décision après détection
- Clé trouvée → Passez à l'Étape 1.
- Clé NON trouvée → Allez à l'Étape 0a.
- Requêtes Python manquantes → Exécutez
pip install requests.
Étape 0a : Configuration de la clé API (uniquement si aucune clé trouvée)
Dites à l'utilisateur :
Pour utiliser l'API Meshy, vous avez besoin d'une clé API :
- Allez sur https://www.meshy.ai/settings/api
- Cliquez sur "Créer une clé API", nommez-la et copiez la clé (commence par
msy_)- La clé n'est affichée qu'une seule fois — conservez-la en lieu sûr
Remarque : L'accès à l'API nécessite un plan Pro ou supérieur. Les comptes gratuits ne peuvent pas créer de clés API.
Une fois que l'utilisateur fournit la clé, définissez-la pour la session en cours et éventuellement persistez-la dans .env :
# Définir pour la session en cours uniquement
export MESHY_API_KEY="msy_COLLEZ_LA_CLÉ_ICI"
# Vérifier la clé
STATUT=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $MESHY_API_KEY" \
https://api.meshy.ai/openapi/v1/balance)
if [ "$STATUT" = "200" ]; then
SOLDE=$(curl -s -H "Authorization: Bearer $MESHY_API_KEY" https://api.meshy.ai/openapi/v1/balance)
echo "Clé valide. $SOLDE"
else
echo "Clé invalide (HTTP $STATUT). Veuillez vérifier la clé et réessayer."
fi
Pour persister la clé (projet actuel uniquement) :
# Écrire dans .env dans le répertoire de travail courant
echo 'MESHY_API_KEY=msy_COLLEZ_LA_CLÉ_ICI' >> .env
echo "Sauvegardée dans .env"
# IMPORTANT : ajouter .env à .gitignore pour éviter de fuiter la clé
grep -q "^\.env" .gitignore 2>/dev/null || echo ".env" >> .gitignore
echo ".env ajouté à .gitignore"
Rappel de sécurité : La clé est stockée uniquement dans
.envdans le répertoire de votre projet actuel. Ne commettez jamais ce fichier dans le contrôle de version..enva été automatiquement ajouté à.gitignore.
Étape 1 : Confirmer le plan avec l'utilisateur avant de dépenser des crédits
CRITIQUE : Avant de créer une tâche, présentez à l'utilisateur un résumé des coûts et attendez la confirmation :
Je vais générer un modèle 3D de "<invite>" en utilisant le plan suivant :
1. Aperçu (génération de maillage) — 20 crédits
2. Affinage (texturation avec PBR) — 10 crédits
3. Téléchargement en .glb
Coût total : 30 crédits
Solde actuel : <N> crédits
Dois-je procéder ?
Pour les pipelines multi-étapes (text-to-3d → rigger → animer), montrez le coût TOTAL du pipeline dès le départ.
Remarque : Le rigging inclut automatiquement les animations de marche + course sans frais supplémentaires. Ajoutez
Animer(3 crédits) uniquement pour les animations personnalisées au-delà de celles-ci.
Correspondance intention → API
| L'utilisateur veut... | API | Point de terminaison | Crédits |
|---|---|---|---|
| Modèle 3D à partir de texte | Text to 3D | POST /openapi/v2/text-to-3d | 5–20 (aperçu) + 10 (affiner) |
| Modèle 3D à partir d'une image | Image to 3D | POST /openapi/v1/image-to-3d | 5–30 |
| Modèle 3D à partir de plusieurs images | Multi-Image to 3D | POST /openapi/v1/multi-image-to-3d | 5–30 |
| Nouvelles textures sur modèle existant | Retexture | POST /openapi/v1/retexture | 10 |
| Changer le format/topologie du maillage | Remesh | POST /openapi/v1/remesh | 5 |
| Convertir un modèle vers d'autres formats (sans remaillage) | Convert | POST /openapi/v1/convert | 1 |
| Redimensionner un modèle à la taille réelle | Resize | POST /openapi/v1/resize | 1 |
| Générer de nouvelles UVs (GLB, ≤40k faces) avant texturation externe | UV Unwrap | POST /openapi/v1/uv-unwrap | 5 |
| Ajouter un squelette à un personnage | Auto-Rigging | POST /openapi/v1/rigging | 5 |
| Animer un personnage riggé | Animation | POST /openapi/v1/animations | 3 |
| Image 2D à partir de texte (étape préalable recommandée avant image-to-3d) | Text to Image | POST /openapi/v1/text-to-image | 3 / 6 / 9 / 9 |
| Optimiser/modifier une image 2D (étape préalable recommandée avant image-to-3d) | Image to Image | POST /openapi/v1/image-to-image | 3 / 6 / 9 / 12 |
| Photo → produit physique stylisé (figurine/lampe/porte-clés/aimant frigo) | Creative Lab | POST /openapi/creative-lab/{produit}/v1/prototype puis .../build | 6 + 30 |
| Vérifier l'imprimabilité FDM | Analyser l'imprimabilité | POST /openapi/v1/print/analyze | 0 (gratuit) |
| Réparer la topologie non-manifold/face dégénérée/trou | Réparer l'imprimabilité | POST /openapi/v1/print/repair | 10 |
| Impression 3D multicolore | Impression Multi-Couleur | POST /openapi/v1/print/multi-color | 10 (+ génération) |
| Imprimer un modèle 3D (blanc) | → Voir section Pipeline d'impression | — | 20 |
| Vérifier le solde de crédits | Balance | GET /openapi/v1/balance | 0 |
Étape 2 : Exécuter le workflow
Modèle de script réutilisable
Utilisez ceci comme base pour TOUS les workflows. Il charge la clé API de manière sécurisée depuis l'environnement ou .env dans le répertoire courant uniquement :
#!/usr/bin/env python3
"""Exécuteur de tâches API Meshy. Gère créer → interroger → télécharger."""
import requests, time, os, sys, re, json
from datetime import datetime
# --- Chargement sécurisé de la clé API ---
def load_api_key():
"""Charge MESHY_API_KEY depuis l'environnement, puis .env dans le cwd uniquement."""
clé = os.environ.get("MESHY_API_KEY", "").strip()
if clé:
return clé
chemin_env = os.path.join(os.getcwd(), ".env")
if os.path.exists(chemin_env):
with open(chemin_env) as f:
for ligne in f:
ligne = ligne.strip()
if ligne.startswith("MESHY_API_KEY=") and not ligne.startswith("#"):
val = ligne.split("=", 1)[1].strip().strip('"').strip("'")
if val:
return val
return ""
API_KEY = load_api_key()
if not API_KEY:
sys.exit("ERREUR : MESHY_API_KEY non définie. Exécutez l'Étape 0a pour la configurer.")
# Ne jamais journaliser la clé complète — seulement les 8 premiers caractères pour la traçabilité
print(f"Clé API chargée : {API_KEY[:8]}...")
BASE = "https://api.meshy.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
SESSION = requests.Session()
SESSION.trust_env = False # ignorer les paramètres de proxy système
def create_task(endpoint, payload):
resp = SESSION.post(f"{BASE}{endpoint}", headers=HEADERS, json=payload, timeout=30)
if resp.status_code == 401:
sys.exit("ERREUR : Clé API invalide (401). Réexécutez l'Étape 0a.")
if resp.status_code == 402:
try:
solde = SESSION.get(f"{BASE}/openapi/v1/balance", headers=HEADERS, timeout=10)
balance = solde.json().get("balance", "inconnu")
sys.exit(f"ERREUR : Crédits insuffisants (402). Solde : {balance}. Rechargez sur https://www.meshy.ai/pricing")
except Exception:
sys.exit("ERREUR : Crédits insuffisants (402). Vérifiez le solde sur https://www.meshy.ai/pricing")
if resp.status_code == 429:
sys.exit("ERREUR : Limite de débit atteinte (429). Attendez et réessayez.")
resp.raise_for_status()
task_id = resp.json()["result"]
print(f"TÂCHE_CRÉÉE : {task_id}")
return task_id
def poll_task(endpoint, task_id, timeout=300):
"""Interroge avec backoff exponentiel (5s→30s, fixe 15s à 95%+)."""
écoulé, délai, délai_max, backoff, délai_final, compteur = 0, 5, 30, 1.5, 15, 0
while écoulé < timeout:
compteur += 1
resp = SESSION.get(f"{BASE}{endpoint}/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
tâche = resp.json()
statut = tâche["status"]
progrès = tâche.get("progress", 0)
barre = f"[{'█' * int(progrès/5)}{'░' * (20 - int(progrès/5))}] {progrès}%"
print(f" {barre} — {statut} ({écoulé}s, requête #{compteur})", flush=True)
if statut == "SUCCEEDED":
return tâche
if statut in ("FAILED", "CANCELED"):
msg = tâche.get("task_error", {}).get("message", "Inconnu")
sys.exit(f"TÂCHE_{statut} : {msg}")
délai_actuel = délai_final if progrès >= 95 else délai
time.sleep(délai_actuel)
écoulé += délai_actuel
if progrès < 95:
délai = min(délai * backoff, délai_max)
sys.exit(f"TIMEOUT après {timeout}s ({compteur} requêtes)")
def download(url, chemin_fichier):
"""Télécharge un fichier dans un répertoire projet (dans cwd/meshy_output/)."""
os.makedirs(os.path.dirname(chemin_fichier), exist_ok=True)
print(f"Téléchargement de {chemin_fichier}...", flush=True)
resp = SESSION.get(url, timeout=300, stream=True)
resp.raise_for_status()
with open(chemin_fichier, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
print(f"TÉLÉCHARGÉ : {chemin_fichier} ({os.path.getsize(chemin_fichier)/1024/1024:.1f} Mo)")
# --- Aides à l'organisation des fichiers ---
RACINE_SORTIE = os.path.join(os.getcwd(), "meshy_output")
os.makedirs(RACINE_SORTIE, exist_ok=True)
FICHIER_HISTORIQUE = os.path.join(RACINE_SORTIE, "history.json")
def get_project_dir(task_id, invite="", type_tâche="model"):
slug = re.sub(r'[^a-z0-9]+', '-', (invite or type_tâche).lower())[:30].strip('-')
dossier = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{slug}_{task_id[:8]}"
répertoire_projet = os.path.join(RACINE_SORTIE, dossier)
os.makedirs(répertoire_projet, exist_ok=True)
return répertoire_projet
def record_task(répertoire_projet, task_id, type_tâche, étape, invite="", fichiers=None):
chemin_meta = os.path.join(répertoire_projet, "metadata.json")
meta = json.load(open(chemin_meta)) if os.path.exists(chemin_meta) else {
"nom_projet": invite or type_tâche, "dossier": os.path.basename(répertoire_projet),
"tâche_racine_id": task_id, "créé_le": datetime.now().isoformat(), "tâches": []
}
meta["tâches"].append({"tâche_id": task_id, "type_tâche": type_tâche, "étape": étape,
"fichiers": fichiers or [], "créé_le": datetime.now().isoformat()})
meta["mis_à_jour_le"] = datetime.now().isoformat()
json.dump(meta, open(chemin_meta, "w"), indent=2)
historique = json.load(open(FICHIER_HISTORIQUE)) if os.path.exists(FICHIER_HISTORIQUE) else {"version": 1, "projets": []}
dossier = os.path.basename(répertoire_projet)
entrée = next((p for p in historique["projets"] if p["dossier"] == dossier), None)
if entrée:
entrée.update({"nombre_tâches": len(meta["tâches"]), "mis_à_jour_le": meta["mis_à_jour_le"]})
else:
historique["projets"].append({"dossier": dossier, "invite": invite, "type_tâche": type_tâche,
"tâche_racine_id": task_id, "créé_le": meta["créé_le"],
"mis_à_jour_le": meta["mis_à_jour_le"], "nombre_tâches": len(meta["tâches"])})
json.dump(historique, open(FICHIER_HISTORIQUE, "w"), indent=2)
def save_thumbnail(répertoire_projet, url):
chemin = os.path.join(répertoire_projet, "thumbnail.png")
if os.path.exists(chemin): return
try:
r = SESSION.get(url, timeout=15); r.raise_for_status()
with open(chemin, "wb") as f: f.write(r.content)
print(f"VIGNETTE : {chemin}")
except Exception as e:
print(f"AVERTISSEMENT : échec du téléchargement de la vignette : {e}")
(Le script complet continue avec les workflows spécifiques pour chaque type de tâche. Voir le référentiel pour le code complet.)