Communitygithub.com

Meshy 3D — Génération + Impression

Compétences d'agent IA pour la plateforme de génération 3D Meshy AI

Qu'est-ce que Meshy 3D — Génération + Impression ?

Cette compétence permet à un agent IA (comme Claude Code, Cursor, ou Codex) d'interagir directement avec l'API Meshy AI pour générer et préparer des actifs 3D à l'impression. Elle couvre l'ensemble du cycle de vie : configuration de la clé API, création de tâches (text-to-3D, image-to-3D, multi-image-to-3D, retexturing, remaillage, conversion, rigging, animation, etc.), interrogation avec backoff exponentiel, téléchargement organisé dans des dossiers projets structurés, et pipelines multi-étapes incluant la préparation à l'impression 3D (analyse de printabilité, réparation, impression multi-couleur). La compétence intègre des règles de sécurité strictes : accès aux fichiers limité au répertoire de travail courant (fichier `.env` pour la clé, dossier `meshy_output/` pour les téléchargements), aucune donnée locale transmise en dehors des requêtes API, et journalisation sécurisée sans affichage complet des clés. Elle supporte toutes les fonctionnalités de la plateforme Meshy, incluant le contrôle du solde de crédits, les pipelines créatifs (Creative Lab) et la conversion de format, avec une présentation systématique du coût à l'utilisateur avant toute dépense de crédits.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add meshy-dev/meshy-3d-agent

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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 HTTP Authorization: Bearer uniquement. Jamais journalisé, jamais écrit dans un fichier autre que .env dans 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 : .env dans le répertoire de travail courant uniquement (recherche de clé API)
  • Écriture : .env dans 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.ai incluent le MESHY_API_KEY dans 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 dans meshy_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.json par projet, et globalement dans history.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 :

  1. Allez sur https://www.meshy.ai/settings/api
  2. Cliquez sur "Créer une clé API", nommez-la et copiez la clé (commence par msy_)
  3. 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 .env dans le répertoire de votre projet actuel. Ne commettez jamais ce fichier dans le contrôle de version. .env a é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...APIPoint de terminaisonCrédits
Modèle 3D à partir de texteText to 3DPOST /openapi/v2/text-to-3d5–20 (aperçu) + 10 (affiner)
Modèle 3D à partir d'une imageImage to 3DPOST /openapi/v1/image-to-3d5–30
Modèle 3D à partir de plusieurs imagesMulti-Image to 3DPOST /openapi/v1/multi-image-to-3d5–30
Nouvelles textures sur modèle existantRetexturePOST /openapi/v1/retexture10
Changer le format/topologie du maillageRemeshPOST /openapi/v1/remesh5
Convertir un modèle vers d'autres formats (sans remaillage)ConvertPOST /openapi/v1/convert1
Redimensionner un modèle à la taille réelleResizePOST /openapi/v1/resize1
Générer de nouvelles UVs (GLB, ≤40k faces) avant texturation externeUV UnwrapPOST /openapi/v1/uv-unwrap5
Ajouter un squelette à un personnageAuto-RiggingPOST /openapi/v1/rigging5
Animer un personnage riggéAnimationPOST /openapi/v1/animations3
Image 2D à partir de texte (étape préalable recommandée avant image-to-3d)Text to ImagePOST /openapi/v1/text-to-image3 / 6 / 9 / 9
Optimiser/modifier une image 2D (étape préalable recommandée avant image-to-3d)Image to ImagePOST /openapi/v1/image-to-image3 / 6 / 9 / 12
Photo → produit physique stylisé (figurine/lampe/porte-clés/aimant frigo)Creative LabPOST /openapi/creative-lab/{produit}/v1/prototype puis .../build6 + 30
Vérifier l'imprimabilité FDMAnalyser l'imprimabilitéPOST /openapi/v1/print/analyze0 (gratuit)
Réparer la topologie non-manifold/face dégénérée/trouRéparer l'imprimabilitéPOST /openapi/v1/print/repair10
Impression 3D multicoloreImpression Multi-CouleurPOST /openapi/v1/print/multi-color10 (+ génération)
Imprimer un modèle 3D (blanc)→ Voir section Pipeline d'impression—20
Vérifier le solde de créditsBalanceGET /openapi/v1/balance0

É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.)

Skills associés