CommunityImagengithub.com

TraX22/modelado-en-blender

Construye y edita modelos 3D en el Blender del usuario a través de una conexión Blender (el servidor oficial de BlenderLab): inspeccionando la escena, escribiendo código bpy en pequeños pasos verificados, escala del mundo real, nombres limpios, modificadores y verificando el resultado con capturas de pantalla de la ventana gráfica. Úsalo cuando el usuario pida modelar, construir, crear, arreglar o cambiar un objeto o una escena en Blender, o para hacer un modelo 3D a partir de una descripción o una imagen de referencia.

¿Qué es modelado-en-blender?

Esta habilidad permite a un agente de IA trabajar directamente dentro de Blender del usuario a través de la conexión BlenderLab, actuando como un asistente de modelado 3D. El agente puede inspeccionar la escena, crear y editar objetos, aplicar modificadores, y verificar resultados visualmente a través de capturas de la ventana gráfica. Está diseñada para plataformas como Claude Code, Cursor o Codex, y funciona interactuando con la API de Python de Blender (bpy) en pequeños pasos verificados. La habilidad enfatiza el trabajo a escala real, el nombrado limpio de objetos, la preferencia por datos sobre operadores, y un riguroso ciclo de verificación que incluye capturas de pantalla y un análisis final automatizado que compara el renderizado con la imagen de referencia. El agente gestiona su presupuesto de pasos, evitando acciones destructivas y manteniendo una colección organizada para su trabajo. Es útil para cualquier tarea de modelado, desde objetos simples hasta escenas complejas, asegurando que los resultados sean precisos y estén alineados con las instrucciones del usuario.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/TraX22/HydraOps-Skills/tree/HEAD/blender-modeling

Installed? Explore more Imagen skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Modelado en Blender

Trabajas dentro del Blender en ejecución del usuario a través de la conexión BlenderLab. No puedes ver la ventana gráfica a menos que solicites una captura, y cada cambio que realizas es real: se aloja en el archivo que el usuario tiene abierto.

Tu conexión Blender

Trabajas a través de BlenderLab, el servidor oficial de Blender Lab; sus herramientas son blenderlab_…:

  • ¿Qué hay en la escena?: get_objects_summary; un objeto en detalle: get_object_detail_summary (por name). El archivo, guardado o no, y su ruta: get_blendfile_summary_path_info.
  • Ejecutar Python: execute_blender_code. Nada sobrevive entre llamadas (lleva tus ayudantes en cada script); lo que el script imprime regresa, y un diccionario asignado a result regresa como datos. Su guardia rechaza algunos operadores (salir de Blender, restablecimientos de fábrica).
  • Ver la ventana gráfica 3D: get_screenshot_of_area_as_image con area_ui_type: "VIEW_3D" (la ventana completa: get_screenshot_of_window_as_image); apúntala a un objeto: jump_to_view3d_object_by_name.
  • Consultar la API de Python: get_python_api_docs (un identificador como bpy.types.BevelModifier), search_api_docs; el manual: search_manual_docs.
  • Renderizado rápido: render_thumbnail_to_path, render_viewport_to_path; la imagen va a la carpeta temporal de Blender y el resultado da la ruta real.
  • Exportar: código (bpy.ops.export_scene.gltf, bpy.ops.export_scene.fbx).
  • Bibliotecas de activos y generadores 3D: ninguna. Construye el objeto, o pide al usuario un archivo para importar.

Si estas herramientas no están disponibles, detente y dímelo: el usuario debe instalar la conexión BlenderLab (Tools → Connections), dársela a este agente, y tener Blender abierto con el servidor del complemento iniciado. No describas el trabajo como hecho cuando no pudiste hacerlo.

El bucle

Trabaja en este orden, cada vez. Saltarse los pasos de mirar primero y después es cómo los modelos terminan mal sin que nadie lo note.

  1. Mira. La herramienta de escena para lo que existe (colecciones, objetos, cámara activa). Para un objeto que tocarás, la herramienta de detalle del objeto. Nunca asumas una escena vacía.
  2. Planifica en partes. Divide el modelo en partes nombradas con tamaños en metros y una posición cada una ("asiento 0.45 × 0.45 × 0.04 en z = 0.45"). Expresa el plan en una lista corta antes de construir algo más grande que un par de objetos.
  3. Construye en etapas. Dos o tres partes relacionadas por llamada execute_blender_code (paredes, plinto y techo; puerta y ventanas), 30-90 líneas. Una sola parte por llamada cuando sea complicada (un booleano, una superficie curva) o justo después de un fallo: un traceback en un script de 200 líneas te dice poco y deja la escena a medio cambiar.
  4. Verifica en los hitos. Captura la ventana gráfica 3D una vez que los volúmenes principales estén en pie, una vez que los detalles estén puestos, y al final, y compárala realmente con el plan: proporciones, partes tocándose donde deberían, nada flotando, nada dentro de otra cosa. Entre capturas, verifica con los números que tu código imprimió. Arregla antes de continuar. Si la captura regresa marcada como no visible para ti, tu modelo no puede ver imágenes: verifica con números (dimensiones, posiciones, conteos), y di que no pudiste mirar.
  5. Reporta. Di qué construiste, los nombres de los objetos, las dimensiones, y cualquier cosa que difiera de lo que se pidió.

Tu presupuesto de pasos

Una tarea te da un número limitado de rondas de herramientas (15 a menos que el usuario lo aumente), y cada llamada gasta una: abrir una habilidad, un resumen de escena, una llamada de construcción, una captura.

  • Abre las habilidades que necesites una vez, juntas, y sus archivos de referencia solo si los usarás. No reabras una que ya abriste en esta conversación.
  • Planifica las etapas según el presupuesto antes de la primera llamada de construcción: aproximadamente 1 ronda para mirar, 8-9 para construir, 3 capturas, 1 de repuesto para un arreglo. Ordénalas para que las partes más visibles vengan primero.
  • Cuando una captura y un resumen no dependan el uno del otro, pide ambos en una ronda.
  • Si la solicitud no cabe, dilo en el plan, termina bien una parte coherente de ella y termina con la lista exacta de lo que queda, para que el usuario solo tenga que decir "continúa". Nunca te detengas en medio de una parte.

Reglas que previenen la mayoría de los fallos

  • No elimines o sobrescribas lo que no creaste a menos que el usuario lo haya pedido. No "seleccionar todo, eliminar" para obtener una escena limpia; no read_factory_settings. Si el cubo por defecto está en medio, pregunta o trabaja a su lado.
  • Pon tu trabajo en su propia colección, con el nombre del modelo, y da a cada objeto y malla un nombre real (Silla_Pata_DT, no Cubo.004). Pasos posteriores encuentran objetos por nombre.
  • Escala real. 1 unidad = 1 metro. Una puerta mide 2.0 m de alto, una taza 0.10 m. Verifica obj.dimensions después de construir en lugar de confiar en los números que escribiste.
  • Transformaciones: establece location, rotation_euler (radianes: usa math.radians) y scale directamente; aplica la escala antes de biselados o exportaciones (consulta la referencia).
  • Prefiere datos sobre operadores. bpy.data y bmesh funcionan independientemente de lo que esté seleccionado; bpy.ops depende del contexto y falla de formas difíciles de leer. Cuando debas usar un operador, selecciona y activa el objeto explícitamente primero.
  • Haz que los scripts sean re-ejecutables. Busca un objeto por nombre y reutilízalo o reemplázalo, en lugar de crear Pata.001, Pata.002 en cada reintento.
  • Envía de vuelta lo que necesites. Termina una llamada con un print corto de nombres, dimensiones o conteos (en BlenderLab, o un diccionario asignado a result) para que el siguiente paso comience a partir de hechos.
  • ¿No estás seguro de un nombre de API? La API de Python de Blender cambia entre versiones. Verifícalo con la herramienta API de tu conexión antes de adivinar, y lee la versión de bpy.app.version_string una vez al principio.

Los patrones para primitivas, bmesh, modificadores, suavizado, curvas, arreglos y herencia están en referencias/patrones-bpy.md. Ábrelo antes de escribir el primer script de una tarea.

Cómo elegir construir una forma

FormaEnfoque
Cajas, cilindros, muebles, arquitectura, accesoriosPrimitivas, escaladas y posicionadas; modificador Bisel para bordes
Cualquier cosa repetida (valla, escaleras, dientes de un engranaje)Una parte + modificador Matriz, o un bucle que enlace la misma malla
Objetos redondos o torneados (jarrón, vidrio, botella, rueda)Un perfil + modificador Tornillo, o una curva con bisel
Tuberías, cables, mangos, rielesUna curva con bevel_depth
Cosas simétricas (vehículo, bloqueo de personaje)Mitad del modelo + modificador Espejo
Texto, logotipos, monedas, placasObjeto de texto o curva, extruido, luego convertido a malla
Formas suaves y orgánicasBloqueo a partir de primitivas + Subdivisión de Superficie; di honestamente que el detalle esculpido es limitado de esta manera

Mantén los modificadores sin aplicar mientras iteras (permanecen editables) y dilo en el informe; aplícalos solo para exportación o cuando un paso posterior necesite la geometría real.

Trabajar a partir de una imagen de referencia

  • Describe lo que ves primero: las partes, sus proporciones relativas entre sí, y el conteo de cualquier cosa repetida. Construye a partir de esa lista.
  • La imagen decide, no los números inventados para ella. Lee las proporciones como razones (ancho a alto a fondo; cuánto de la altura total es el techo; qué ancho tiene una puerta contra su pared), elige una medida para anclar la escala (la del usuario, o un tamaño sensato del mundo real) y deriva todos los demás tamaños de las razones. Si el texto de la solicitud da tamaños que contradicen la imagen, sigue la imagen y dilo.
  • Cuando te pidan escribir un prompt o un resumen a partir de una referencia, describe formas, proporciones, colores y conteos. No inventes tamaños en metros: quien construya a partir del resumen los seguirá en lugar de la imagen.
  • Construye lo que muestra la referencia. Sin base, accesorios o decoración que no tenga, a menos que el usuario lo haya pedido.
  • Después del bloqueo, y de nuevo antes de reportar, coloca la vista en el ángulo de la referencia, captúrala y compárala parte por parte: silueta, proporciones, dónde se sienta cada parte, colores. Arregla lo que difiera; lo que no puedas arreglar, enuméralo en el informe. Espera dos o tres rondas de corrección; eso es normal, no un fallo.
  • El encuadre, la luz y los colores se miden, no se juzgan a ojo: consulta "La verificación final" a continuación. Compara tu render con el archivo de referencia cuando le des el nombre del archivo.
  • Una referencia adjunta a un mensaje anterior puede que ya no sea visible para ti. Si no puedes verla, dilo y pídela de nuevo en lugar de trabajar desde la memoria.

La verificación final (siempre tu última llamada)

No ves tus renders: las herramientas de render escriben un archivo y te dan su ruta, y una captura de la ventana gráfica no muestra la luz de la escena. Un modelo que reporta desde lo que pretendió, no desde lo que resultó, entrega imágenes blancas o negras sin saberlo. Así que la última llamada execute_blender_code de cada tarea que construye o cambia un modelo es final_check, y su salida impresa va a tu informe tal como está.

Hace un pequeño render desde la cámara de la escena, lo divide en fondo (los colores de las cuatro esquinas) y modelo (el resto), e imprime: cuánto del encuadre llena el modelo, su brillo, saturación y colores principales, los objetos sin material, los objetos cuya geometría está rota (caras unidas a los vértices incorrectos), y una línea PROBLEM: por cada cosa que está mal. Con el nombre del archivo de la referencia mide esa imagen de la misma manera y compara el encuadre, el brillo, el color y qué tan sólida está la silueta.

import bpy, os, re
import numpy as np
from mathutils import Vector
from bpy_extras.object_utils import world_to_camera_view

BACKDROP_WORDS = ("ground", "backdrop", "floor", "sky", "shadow")

def subject_stats(path, size=128):
    """Una imagen dividida en fondo (los colores de sus cuatro esquinas) y sujeto (el resto):
    cuánto ocupa el sujeto de la imagen, su caja, su brillo, saturación y colores."""
    img = bpy.data.images.load(path, check_existing=False)
    w, h = img.size
    sw, sh = (size, max(8, round(size * h / w))) if w >= h else (max(8, round(size * w / h)), size)
    img.scale(sw, sh)
    px = np.empty(len(img.pixels), dtype=np.float32)
    img.pixels.foreach_get(px)
    bpy.data.images.remove(img)
    rgb = px.reshape(sh, sw, 4)[:, :, :3]                       # la fila 0 es la parte inferior de la imagen
    cy, cx = max(2, sh // 8), max(2, sw // 8)
    corners = np.concatenate([rgb[:cy, :cx].reshape(-1, 3), rgb[:cy, -cx:].reshape(-1, 3),
                              rgb[-cy:, :cx].reshape(-1, 3), rgb[-cy:, -cx:].reshape(-1, 3)])
    # Fondo = cercano a uno de los colores de las esquinas (un degradado o una sombra suave aún cuenta).
    q = np.unique(np.round(corners * 8).astype(int), axis=0)
    flat = rgb.reshape(-1, 3)
    dist = np.min(np.abs(flat[:, None, :] * 8 - q[None, :, :]).max(axis=2), axis=1)
    subject = (dist > 1.0).reshape(sh, sw)
    share = float(subject.mean())
    whole = flat @ np.array([0.2126, 0.7152, 0.0722], dtype=np.float32)
    out = {"size": (w, h), "image_brightness": round(float(whole.mean()), 3), "subject_pct": round(100 * share, 1)}
    if share < 0.005:
        return out
    ys, xs = np.nonzero(subject)
    out["box"] = {"width": round(float(xs.max() - xs.min() + 1) / sw, 2), "height": round(float(ys.max() - ys.min() + 1) / sh, 2),
                  "center": (round(float(xs.max() + xs.min() + 1) / 2 / sw, 2), round(float(ys.max() + ys.min() + 1) / 2 / sh, 2))}
    sub = rgb[subject]
    lum = sub @ np.array([0.2126, 0.7152, 0.0722], dtype=np.float32)
    mx, mn = sub.max(axis=1), sub.min(axis=1)
    out["brightness"] = round(float(lum.mean()), 3)
    out["saturation"] = round(float(np.where(mx > 1e-4, (mx - mn) / np.maximum(mx, 1e-4), 0.0).mean()), 3)
    out["burnt_pct"] = round(100 * float((lum > 0.97).mean()), 1)
    keys = (np.clip((sub * 5.999).astype(int), 0, 5) * np.array([36, 6, 1])).sum(axis=1)
    counts = np.bincount(keys, minlength=216)
    out["colors"] = [("#%02x%02x%02x" % tuple(int(round(float(c) * 255)) for c in sub[keys == k].mean(axis=0)),
                      int(round(100 * counts[k] / len(keys)))) for k in counts.argsort()[::-1][:5] if counts[k]]
    return out

# Dónde una instalación de HydraOps guarda sus datos (storage/uploads cuelga de él): la carpeta
# de datos que se le indicó (HYDRA_DATA_DIR), la carpeta del instalador por sistema, y un clon
# del repositorio, que mantiene sus datos dentro de sí mismo cuando se ejecuta desde el código fuente.
UPLOAD_ROOTS = [os.environ.get("HYDRA_DATA_DIR", ""),
                os.path.join(os.environ.get("APPDATA", ""), "HydraOps", "data"),
                os.path.expanduser("~/Library/Application Support/HydraOps/data"),
                os.path.expanduser("~/.config/HydraOps/data"),
                os.path.expanduser("~/HydraOps")]
UPLOAD_FOLDERS = [os.path.join(r, "storage", "uploads") for r in UPLOAD_ROOTS if r]

def find_reference(file_name):
    """La ruta de una imagen adjunta en ESTA conversación, nombrada como la conversación la muestra:
    "storage/uploads/1791118524833-casa.png", la simple "1791118524833-casa.png", o una ruta completa.
    None cuando no está allí."""
    raw = str(file_name or "").replace("\\", "/").strip()
    name = os.path.basename(raw)
    if not name or os.path.splitext(name)[1].lower() not in (".png", ".jpg", ".jpeg", ".webp"):
        return None
    if os.path.isabs(raw) and os.path.isfile(raw):
        return os.path.normpath(raw)
    for folder in UPLOAD_FOLDERS:
        path = os.path.join(folder, name)
        if os.path.isfile(path):
            return path
    # El nombre almacenado comienza con una marca de tiempo que es fácil de copiar con un dígito incorrecto:
    # recurre a la subida más reciente con el mismo nombre original después de la marca de tiempo.
    m = re.match(r"^\d{10,}-(.+)$", name)
    if m:
        pattern = re.compile(r"^\d{10,}-" + re.escape(m.group(1)) + "$")
        newest = None
        for folder in UPLOAD_FOLDERS:
            try:
                entries = os.listdir(folder)
            except OSError:
                continue
            for entry in entries:

...

[truncado para la traducción]

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Skills relacionados