Modelagem no Blender
Você trabalha dentro do Blender em execução do usuário através da conexão BlenderLab. Você não pode ver a viewport a menos que peça uma captura, e cada alteração que faz é real: ela é aplicada no arquivo que o usuário tem aberto.
Sua conexão Blender
Você trabalha através do BlenderLab, o servidor oficial do Blender Lab; suas ferramentas são
blenderlab_…:
- O que está na cena:
get_objects_summary; um objeto em detalhe:get_object_detail_summary(porname). O arquivo, salvo ou não, e seu caminho:get_blendfile_summary_path_info. - Executar Python:
execute_blender_code. Nada persiste entre chamadas (carregue seus auxiliares em cada script); o que o script imprime retorna, e um dicionário atribuído aresultretorna como dados. Sua proteção recusa alguns operadores (sair do Blender, redefinições de fábrica). - Ver a viewport 3D:
get_screenshot_of_area_as_imagecomarea_ui_type: "VIEW_3D"(a janela inteira:get_screenshot_of_window_as_image); aponte para um objeto:jump_to_view3d_object_by_name. - Verificar a API Python:
get_python_api_docs(um identificador comobpy.types.BevelModifier),search_api_docs; o manual:search_manual_docs. - Renderização rápida:
render_thumbnail_to_path,render_viewport_to_path; a imagem vai para a pasta temporária do Blender e o resultado fornece o caminho real. - Exportar: código (
bpy.ops.export_scene.gltf,bpy.ops.export_scene.fbx). - Bibliotecas de ativos e geradores 3D: nenhuma. Construa o objeto ou peça ao usuário um arquivo para importar.
Se essas ferramentas não estiverem disponíveis, pare e avise: o usuário precisa instalar a conexão BlenderLab (Ferramentas → Conexões), fornecê-la a este agente e ter o Blender aberto com o servidor do add-on iniciado. Não descreva o trabalho como concluído quando você não pôde fazê-lo.
O ciclo
Trabalhe nesta ordem, sempre. Pular as etapas de olhar primeiro e verificar depois é como os modelos ficam errados sem que ninguém perceba.
- Olhe. A ferramenta de cena para o que existe (coleções, objetos, câmera ativa). Para um objeto que você tocará, a ferramenta de detalhe do objeto. Nunca presuma uma cena vazia.
- Planeje em partes. Divida o modelo em partes nomeadas com tamanhos em metros e uma posição cada ("assento 0,45 × 0,45 × 0,04 em z = 0,45"). Declare o plano em uma lista curta antes de construir algo maior do que alguns objetos.
- Construa em estágios. Duas ou três partes relacionadas por chamada
execute_blender_code(paredes, plinto e telhado; porta e janelas), 30-90 linhas. Uma parte única por chamada quando for complicada (um booleano, uma superfície curvada) ou logo após uma falha: um traceback em um script de 200 linhas diz pouco e deixa a cena meio alterada. - Verifique nos marcos. Capture a viewport 3D assim que os volumes principais estiverem no lugar, assim que os detalhes estiverem prontos e no final, e compare com o plano: proporções, partes tocando onde deveriam, nada flutuando, nada dentro de outra coisa. Entre capturas, verifique com os números que seu código imprimiu. Corrija antes de prosseguir. Se a captura retornar marcada como não visível para você, seu modelo não pode ver imagens: verifique com números (dimensões, posições, contagens) e diga que você não pôde olhar.
- Relate. Diga o que você construiu, os nomes dos objetos, as dimensões e qualquer coisa que difira do que foi solicitado.
Seu orçamento de etapas
Uma tarefa fornece um número limitado de rodadas de ferramentas (15 a menos que o usuário aumente), e cada chamada gasta uma: abrir uma skill, um resumo da cena, uma chamada de construção, uma captura.
- Abra as skills que você precisa de uma vez, juntas, e seus arquivos de referência apenas se você for usá-los. Não reabra uma skill que você já abriu nesta conversa.
- Planeje os estágios contra o orçamento antes da primeira chamada de construção: cerca de 1 rodada para olhar, 8-9 para construir, 3 capturas, 1 de sobra para uma correção. Ordene-os para que as partes mais visíveis venham primeiro.
- Quando uma captura e um resumo não dependem um do outro, peça ambos em uma rodada.
- Se a solicitação não couber, diga no plano, termine uma parte coerente dela bem e termine com a lista exata do que falta, para que o usuário precise apenas dizer "continue". Nunca pare no meio de uma parte.
Regras que evitam a maioria das falhas
- Não delete ou sobrescreva o que você não criou a menos que o usuário tenha pedido. Sem
"selecionar tudo, deletar" para obter uma cena limpa; sem
read_factory_settings. Se o cubo padrão estiver no caminho, pergunte ou trabalhe ao lado dele. - Coloque seu trabalho em sua própria coleção, nomeada após o modelo, e dê a cada objeto
e malha um nome real (
Cadeira_Perna_DT, nãoCube.004). Etapas posteriores encontram objetos por nome. - Escala real. 1 unidade = 1 metro. Uma porta tem 2,0 m de altura, uma caneca 0,10 m. Verifique
obj.dimensionsapós a construção, em vez de confiar nos números que você digitou. - Transformações: defina
location,rotation_euler(radianos: usemath.radians) escalediretamente; aplique escala antes de chanfros ou exportações (consulte a referência). - Prefira dados a operadores.
bpy.dataebmeshfuncionam independentemente do que está selecionado;bpy.opsdepende do contexto e falha de maneiras difíceis de ler. Quando você precisar usar um operador, selecione e ative o objeto explicitamente primeiro. - Torne scripts reexecutáveis. Procure um objeto pelo nome e reutilize ou substitua-o,
em vez de criar
Perna.001,Perna.002a cada tentativa. - Envie o que você precisa. Termine uma chamada com um
printcurto de nomes, dimensões ou contagens (no BlenderLab, ou um dicionário atribuído aresult) para que a próxima etapa comece a partir de fatos. - Não tem certeza de um nome de API? A API Python do Blender muda entre versões. Verifique-a com
a ferramenta de API da sua conexão antes de adivinhar e leia a versão de
bpy.app.version_stringuma vez no início.
Padrões para primitivas, bmesh, modificadores, suavização, curvas, arrays e parenting estão em references/bpy-patterns.md. Abra-o antes de escrever o primeiro script de uma tarefa.
Escolhendo como construir uma forma
| Forma | Abordagem |
|---|---|
| Caixas, cilindros, móveis, arquitetura, adereços | Primitivas, escaladas e posicionadas; modificador Bevel para bordas |
| Qualquer coisa repetida (cerca, escadas, dentes de uma engrenagem) | Uma parte + modificador Array, ou um loop que vincula a mesma malha |
| Objetos redondos ou torneados (vaso, vidro, garrafa, roda) | Um perfil + modificador Screw, ou uma curva com bisel |
| Tubos, cabos, alças, trilhos | Uma curva com bevel_depth |
| Coisas simétricas (veículo, blockout de personagem) | Metade do modelo + modificador Mirror |
| Texto, logotipos, moedas, placas | Objeto de texto ou curva, extrudado, depois convertido em malha |
| Formas suaves e orgânicas | Blockout de primitivas + Subdivision Surface; diga honestamente que o detalhe esculpido é limitado desta forma |
Mantenha os modificadores não aplicados durante a iteração (eles permanecem editáveis) e diga isso no relatório; aplique-os apenas para exportação ou quando uma etapa posterior precisar da geometria real.
Trabalhando a partir de uma imagem de referência
- Descreva o que você vê primeiro: as partes, suas proporções relativas umas às outras e a contagem de qualquer coisa repetida. Construa a partir dessa lista.
- A imagem decide, não números inventados para ela. Leia as proporções como razões (largura para altura para profundidade; quanto da altura total é telhado; quão larga é uma porta em relação à sua parede), escolha uma medida para ancorar a escala (a do usuário, ou um tamanho razoável do mundo real) e derive todos os outros tamanhos das razões. Se o texto da solicitação fornecer tamanhos que contradizem a imagem, siga a imagem e diga isso.
- Quando for solicitado a escrever um prompt ou um briefing a partir de uma referência, descreva formas, proporções, cores e contagens. Não invente tamanhos em metros: quem construir a partir do briefing os seguirá em vez da imagem.
- Construa o que a referência mostra. Sem base, adereços ou decoração que ela não tenha, a menos que o usuário tenha pedido.
- Após o blockout, e novamente antes de relatar, coloque a vista no ângulo da referência, capture-a e compare parte por parte: silhueta, proporções, onde cada parte se senta, cores. Corrija o que diferir; o que você não puder corrigir, liste no relatório. Espere duas ou três rodadas de correção; isso é normal, não uma falha.
- O enquadramento, a luz e as cores são medidos, não julgados a olho: veja "A verificação final" abaixo. Ela compara sua renderização com o arquivo de referência quando você fornece o nome do arquivo.
- Uma referência anexada a uma mensagem anterior pode não estar mais visível para você. Se você não puder vê-la, diga e peça novamente, em vez de trabalhar de memória.
A verificação final (sempre sua última chamada)
Você não vê suas renderizações: as ferramentas de renderização escrevem um arquivo e fornecem seu caminho, e uma
captura da viewport não mostra a luz da cena. Um modelo que relata a partir do que
intencionou, não do que resultou, entrega imagens brancas ou pretas sem saber. Portanto, a
última chamada execute_blender_code de cada tarefa que constrói ou altera um modelo é
final_check, e sua saída impressa vai para seu relatório como está.
Ela faz uma pequena renderização a partir da câmera da cena, divide-a em fundo (as cores dos
quatro cantos) e modelo (o resto), e imprime: quanto do quadro o modelo preenche,
seu brilho, saturação e cores principais, os objetos sem material, os objetos
cuja geometria está quebrada (faces unidas aos vértices errados) e uma
linha PROBLEM: para cada coisa que está errada. Com o nome do arquivo da referência,
elamede a mesma imagem da mesma forma e compara enquadramento, brilho, cor e quão sólida
é a silhueta.
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):
"""Uma imagem dividida em fundo (as cores de seus quatro cantos) e assunto (o resto):
quanto da imagem o assunto ocupa, sua caixa, seu brilho, saturação e cores."""
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] # linha 0 é a parte inferior da imagem
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)])
# Fundo = próximo de uma das cores dos cantos (um gradiente ou uma sombra suave ainda conta).
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
# Onde uma instalação HydraOps mantém seus dados (storage/uploads depende dela): a pasta
# de dados que foi informada (HYDRA_DATA_DIR), a pasta do instalador por sistema e um clone
# do repositório, que mantém seus dados dentro de si quando executado a partir do código-fonte.
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):
"""O caminho de uma imagem anexada NESTA conversa, nomeada como a conversa a mostra:
"storage/uploads/1791118524833-house.png", o simples "1791118524833-house.png" ou um caminho completo.
None quando não está lá."""
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
# O nome armazenado começa com um timestamp que é fácil de copiar com um dígito errado:
# recorrer ao upload mais recente com o mesmo nome original após o timestamp.
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 tradução]