andersonabs commited on
Commit
c3b2552
·
verified ·
1 Parent(s): 5345a9d

Initial pipeline scaffold: Hunyuan3D-2.1 + ComfyUI on Vast.ai

Browse files
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Outputs (GLBs/FBXs gerados — não commitar; copiar manualmente pra Unity)
2
+ outputs/*.glb
3
+ outputs/*.fbx
4
+ outputs/*.obj
5
+ outputs/*.png
6
+ outputs/*.jpg
7
+ outputs/runs/
8
+
9
+ # Model weights baixados em setup/download_models.sh — NUNCA commitar (~30GB)
10
+ weights/
11
+ models/
12
+ *.safetensors
13
+ *.ckpt
14
+ *.bin
15
+
16
+ # ComfyUI / Hunyuan caches
17
+ __pycache__/
18
+ *.pyc
19
+ *.pyo
20
+ .cache/
21
+ .venv/
22
+ venv/
23
+ 3d-env/
24
+
25
+ # IDE / system
26
+ .DS_Store
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+
31
+ # Logs (manter cost_tracking.md, ignorar runtime)
32
+ logs/
33
+ *.log
LICENSE ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anderson Melo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ NOTE: This MIT license covers ONLY the scripts/code in this repository.
26
+
27
+ Third-party components used by this pipeline have their own licenses:
28
+ - Hunyuan3D-2.1 (Tencent): see https://github.com/Tencent/Hunyuan3D-2/blob/main/LICENSE
29
+ - Stable Diffusion XL: CreativeML Open RAIL-M
30
+ - ComfyUI: GPL-3.0
31
+ - Generated 3D assets: license depends on Hunyuan3D Tencent terms — verify
32
+ for commercial use before deploying in shipped EDou Unity build.
README.md ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EDou 3D Pipeline — Hunyuan3D + ComfyUI on Vast.ai
2
+
3
+ Pipeline pré-organizada pra geração de assets 3D do projeto e-DOU VR Library
4
+ (Unity 6.4.4f1 / Quest 3) via **Hunyuan3D-2.1** (Tencent, open-source) +
5
+ **ComfyUI/SDXL** rodando em GPU instance Vast.ai.
6
+
7
+ > **Por que open-source self-hosted?** Anderson optou pelo learning vs APIs
8
+ > blackbox (Tripo/Meshy). Stack inteiro fica no controle: PyTorch + CUDA +
9
+ > ComfyUI + Hunyuan + Blender post-processing → skills transferem pra C5
10
+ > (Voice SDK + busca semântica) e qualquer ML deployment futuro.
11
+
12
+ ## Quick start (após instance Vast.ai pronta)
13
+
14
+ ```bash
15
+ # 1. SSH no Vast.ai instance + clone este repo
16
+ git clone https://huggingface.co/<your-username>/edou-3d.git
17
+ cd edou-3d
18
+
19
+ # 2. Setup (~30min, ~$0.20 num RTX 3090)
20
+ bash setup/install_hunyuan3d.sh
21
+ bash setup/download_models.sh
22
+ bash setup/verify_install.sh
23
+
24
+ # 3. Gera estante hero piece
25
+ bash setup/run_estante_pipeline.sh
26
+
27
+ # 4. Download GLB pra local + post-process
28
+ # (no laptop)
29
+ scp <vastai-ip>:~/work/outputs/estante_v1.glb ./outputs/
30
+ blender --background --python blender/add_estante_anchors.py -- ./outputs/estante_v1.glb
31
+ ```
32
+
33
+ ## Estrutura
34
+
35
+ ```
36
+ 3d-pipeline/
37
+ ├── README.md este arquivo
38
+ ├── .gitignore ignora outputs/, weights/, cache/
39
+ ├── setup/
40
+ │ ├── install_hunyuan3d.sh Hunyuan3D-2.1 + ComfyUI deps em GPU instance
41
+ │ ├── download_models.sh baixa weights Hunyuan + SDXL (~30GB)
42
+ │ ├── verify_install.sh smoke test (gera 1 cubo de teste)
43
+ │ └── run_estante_pipeline.sh pipeline completa: SDXL→imagem→Hunyuan→GLB
44
+ ├── prompts/
45
+ │ ├── estante_baroque.txt SDXL prompt pra imagem de referência da estante
46
+ │ ├── casco_hogwarts.txt SDXL prompt pro casco (paredes, teto, vitrais)
47
+ │ └── README.md guia de prompt engineering (lições + iteração)
48
+ ├── workflows/
49
+ │ ├── sdxl_estante.json ComfyUI workflow JSON (SDXL → 1024×1024)
50
+ │ └── hunyuan_inference.py script Python: imagem → mesh + textura
51
+ ├── blender/
52
+ │ ├── add_estante_anchors.py Blender headless: importa GLB, adiciona empties
53
+ │ │ Prateleira_0..3 + Plaqueta_Anchor, exporta FBX
54
+ │ └── README.md passo-a-passo Blender post-processing manual
55
+ ├── docs/
56
+ │ ├── vastai_runbook.md SSH + tmux + cost monitoring + destruir instance
57
+ │ ├── troubleshooting.md erros comuns + soluções
58
+ │ └── cost_tracking.md log de runs com custo real
59
+ └── outputs/ gitignored — GLBs/FBXs gerados
60
+ └── README.md
61
+ ```
62
+
63
+ ## Pipeline conceitual
64
+
65
+ ```
66
+ TEXT (Anderson)
67
+ ↓ prompt engineering em prompts/
68
+ IMAGE (SDXL via ComfyUI, ~30s no instance)
69
+ ↓ workflows/sdxl_estante.json
70
+ 3D SHAPE + TEXTURE (Hunyuan3D-2.1, ~2-5min no instance)
71
+ ↓ workflows/hunyuan_inference.py
72
+ GLB (download local)
73
+ ↓ scp
74
+ BLENDER POST-PROCESS (local, gratuito)
75
+ ↓ blender/add_estante_anchors.py
76
+ FBX com empties anchors
77
+ ↓ cp pra Unity Assets/_Project/Models/Furniture/
78
+ UNITY EstanteSpawner (existente, sem mudanças)
79
+ ✓ 12 estantes + 800 livros funcionando
80
+ ```
81
+
82
+ ## Custo esperado
83
+
84
+ | Item | Quanto | Custo |
85
+ |---|---|---|
86
+ | Vast.ai RTX 3090 setup inicial | ~3h primeira vez | ~$1.00 |
87
+ | Vast.ai gen estante (3-5 iter) | ~1.5h | ~$0.50 |
88
+ | Vast.ai gen casco (futuro C2) | ~1h | ~$0.30 |
89
+ | **Total estante completa** | — | **<$2** |
90
+
91
+ Pré-aprovação Anderson: <$1/instância sem perguntar. Com tmux + sessões
92
+ descartáveis isso bate certinho.
93
+
94
+ ## Requisitos
95
+
96
+ - Vast.ai conta com saldo
97
+ - GPU mínimo: 24GB VRAM (RTX 3090, 4090, A5000, A40, A100)
98
+ - Storage: ~50GB no instance (15GB Hunyuan + 7GB SDXL + 10GB cache + 18GB working)
99
+ - Local: Blender 5.x + ~5GB livre pros GLB/FBX
100
+
101
+ ## Status
102
+
103
+ - 🟢 Estrutura local organizada (este repo)
104
+ - 🟡 Aguardando primeiro run real em Vast.ai (smoke test pendente)
105
+ - 🔴 Todos os scripts são **best-effort sem testes em GPU instance** — esperar
106
+ ajustes na primeira execução. Documentar troubleshooting em `docs/`.
107
+
108
+ ## Workflow de aprendizado sugerido
109
+
110
+ 1. Lê `docs/vastai_runbook.md` pra entender o lifecycle do instance
111
+ 2. Spin um RTX 3090 cheap (~$0.20/hr), abre SSH + tmux
112
+ 3. Roda `install_hunyuan3d.sh` — observa cada etapa, anota o que falha
113
+ 4. Roda `download_models.sh` (vai dormir, ~30min download)
114
+ 5. Roda `verify_install.sh` — primeiro mesh teste
115
+ 6. Itera o `run_estante_pipeline.sh` 3-5 vezes ajustando prompts em `prompts/estante_baroque.txt`
116
+ 7. Quando GLB ficar bom, baixa local + Blender post-process
117
+ 8. Atualiza `docs/cost_tracking.md` com $ gasto + resultados
118
+ 9. Destrói instance Vast.ai (CRITICAL — esquecer = $$/hr indo)
119
+
120
+ ## Licença
121
+
122
+ Scripts deste repo: MIT.
123
+ Hunyuan3D-2.1: Tencent license (verificar uso comercial em `LICENSE` do repo deles).
124
+ SDXL: CreativeML Open RAIL-M.
125
+ Geometria gerada pelo Hunyuan: livre conforme licença Tencent (verificar caso a caso).
blender/README.md ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Blender Post-Processing — EDou 3D
2
+
3
+ Após Hunyuan3D gerar o GLB no Vast.ai, baixe localmente e rode esse passo
4
+ no teu Blender (5.x). Adiciona os empties que o Unity precisa pra spawn
5
+ de livros + plaqueta de mês/ano.
6
+
7
+ ## Quick start (headless / batch)
8
+
9
+ ```bash
10
+ # Do diretório 3d-pipeline/
11
+ blender --background --python blender/add_estante_anchors.py -- \
12
+ --input outputs/estante_v1.glb \
13
+ --output-fbx outputs/estante_hero_ai.fbx \
14
+ --scale-target 2.6 \
15
+ --shelf-count 4
16
+
17
+ # Verifica abrindo no Blender GUI:
18
+ blender outputs/estante_hero_ai.fbx
19
+ # Outliner deve mostrar: EstanteHeroAI + Prateleira_0..3 + Plaqueta_Anchor
20
+
21
+ # Copia pro Unity:
22
+ cp outputs/estante_hero_ai.fbx \
23
+ ../unity-library/EDouLibrary/Assets/_Project/Models/Furniture/
24
+ ```
25
+
26
+ ## Quick start (manual UI)
27
+
28
+ Se preferir Blender GUI pra ajustar antes:
29
+
30
+ 1. **File → Import → glTF 2.0** → selecionar `outputs/estante_v1.glb`
31
+ 2. Selecionar mesh → **Object → Set Origin → Origin to Geometry (Median)**
32
+ 3. Verificar bounding box no N-panel → escalar pra altura ~2.6m com **S**
33
+ 4. Mover mesh pra Z=0 base no chão
34
+ 5. **Add → Empty → Plain Axes**, nomear `Prateleira_0`
35
+ 6. Posicionar dentro do mesh na altura da prateleira topo
36
+ 7. Ctrl+drag pra duplicar 3 vezes, distribuir verticalmente, renomear `Prateleira_1..3`
37
+ 8. Add empty `Plaqueta_Anchor` no centro do frontão (face -Y)
38
+ 9. Selecionar todos os empties + mesh pai
39
+ 10. Parent → Object (Keep Transform) — empties viram filhos do mesh
40
+ 11. **File → Export → FBX (.fbx)**:
41
+ - Selected Objects ✓
42
+ - Object Types: Mesh + Empty
43
+ - Apply Scalings: FBX All
44
+ - Forward: -Z Forward
45
+ - Up: Y Up
46
+ - Bake Space Transform ✓
47
+ - Path Mode: Copy + Embed Textures ✓
48
+ 12. Salvar em `Assets/_Project/Models/Furniture/estante_hero_ai.fbx`
49
+
50
+ ## Validação no Unity
51
+
52
+ Após copiar o FBX pro Unity:
53
+
54
+ ```
55
+ Project view → estante_hero_ai.fbx → Inspector
56
+ - Materials tab: 1 slot "MadeiraMogno" → External Material assigned (do FBX)
57
+ - Apply
58
+
59
+ Menu: EDou → Estante Hero - PolyHaven Materials
60
+ → Reaplica madeira + bronze nos slots externos
61
+
62
+ Menu: EDou → Estante Hero - Preview Scene
63
+ → Cria cena com 1 estante AI rotacionando
64
+
65
+ Play ▶
66
+ → Click + WASD pra walkthrough
67
+ ```
68
+
69
+ ## Convenções de empties (CRITICAL pro spawn de livros funcionar)
70
+
71
+ | Empty name | Posição esperada | Função |
72
+ |---|---|---|
73
+ | `Prateleira_0` | mais alta (próxima ao frontão) | EstanteInfo.GetPrateleira(1) → Sec1 |
74
+ | `Prateleira_1` | meio-alto | Sec2 |
75
+ | `Prateleira_2` | meio-baixo | Sec3 |
76
+ | `Prateleira_3` | mais baixa (próxima à base) | Extra |
77
+ | `Plaqueta_Anchor` | centro frontão, face frontal | EstantePrefabBuilder.CriarPlaqueta posiciona texto |
78
+
79
+ Empties sem mesh, parented ao root mesh do estante, exportados via
80
+ `object_types={"MESH", "EMPTY"}` no FBX export.
81
+
82
+ ## Troubleshooting
83
+
84
+ **Hunyuan exportou mesh com escala enorme/minúscula** → `--scale-target 2.6`
85
+ normaliza pra altura padrão do EDou (2.60m).
86
+
87
+ **Mesh tá deitado / rotacionado** → adicionar passo de rotação 90° no
88
+ script. Hunyuan às vezes exporta com Y up mas Z forward (Blender
89
+ convention diferente). Editar `add_estante_anchors.py` linha de import
90
+ gltf, adicionar `bpy.ops.transform.rotate()` apropriado.
91
+
92
+ **Texturas vazaram no FBX** → `path_mode="COPY"` + `embed_textures=True`
93
+ empacota textures dentro do FBX. Unity ignora — vamos remap pra
94
+ PolyHaven mesmo. Sem problema.
95
+
96
+ **Verts > 50K** → Hunyuan output é high-poly por default. Decimate
97
+ modifier antes de export pode reduzir pra ~10K mantendo silhueta.
98
+ Adicionar ao script:
99
+ ```python
100
+ mod = estante.modifiers.new(name="Decimate", type="DECIMATE")
101
+ mod.ratio = 0.3 # mantém 30% dos polys
102
+ bpy.ops.object.modifier_apply(modifier="Decimate")
103
+ ```
104
+
105
+ **Prateleiras posicionadas erradas** → script distribui uniformemente
106
+ no terço superior-inferior. Se mesh AI tem prateleiras mais densas no
107
+ topo, ajustar margins manualmente:
108
+ ```bash
109
+ --shelf-count 4 # padrão
110
+ # OU editar margem em add_estante_anchors.py linhas margin_top/margin_base
111
+ ```
blender/add_estante_anchors.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ add_estante_anchors.py — pós-processa GLB do Hunyuan3D pra integrar no
3
+ pipeline do EDou. Importa GLB, calcula bounding box, gera 4 empties
4
+ Prateleira_0..3 distribuídos verticalmente na metade dianteira do mesh,
5
+ adiciona Plaqueta_Anchor no topo do mesh, e exporta FBX pronto pro
6
+ Unity Assets/_Project/Models/Furniture/.
7
+
8
+ Uso (Blender headless):
9
+ blender --background --python add_estante_anchors.py -- \\
10
+ --input ./outputs/estante_v1.glb \\
11
+ --output-fbx ../unity-library/EDouLibrary/Assets/_Project/Models/Furniture/estante_hero_ai.fbx \\
12
+ --scale-target 2.6
13
+
14
+ O '--' separa args do Blender vs args do script.
15
+
16
+ Args:
17
+ --input path do GLB gerado pelo Hunyuan
18
+ --output-fbx path destino do FBX (default: ./outputs/estante_hero_ai.fbx)
19
+ --scale-target altura desejada em metros (default: 2.6)
20
+ --shelf-count número de prateleiras (default: 4)
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import os
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ # Blender args separator
30
+ if "--" in sys.argv:
31
+ argv = sys.argv[sys.argv.index("--") + 1:]
32
+ else:
33
+ argv = []
34
+
35
+
36
+ def parse_args():
37
+ parser = argparse.ArgumentParser(description="Hunyuan GLB → Unity FBX com anchors")
38
+ parser.add_argument("--input", required=True, type=Path, help="GLB gerado pelo Hunyuan")
39
+ parser.add_argument("--output-fbx", type=Path,
40
+ default=Path("./outputs/estante_hero_ai.fbx"))
41
+ parser.add_argument("--scale-target", type=float, default=2.6,
42
+ help="Altura em metros (estantes do projeto = 2.60m)")
43
+ parser.add_argument("--shelf-count", type=int, default=4)
44
+ return parser.parse_args(argv)
45
+
46
+
47
+ def main() -> int:
48
+ args = parse_args()
49
+
50
+ if not args.input.exists():
51
+ print(f"ERRO: input não encontrado: {args.input}", file=sys.stderr)
52
+ return 1
53
+
54
+ args.output_fbx.parent.mkdir(parents=True, exist_ok=True)
55
+
56
+ import bpy
57
+
58
+ # 1. Cena vazia
59
+ bpy.ops.object.select_all(action="SELECT")
60
+ bpy.ops.object.delete(use_global=False)
61
+
62
+ # 2. Import GLB
63
+ print(f"[blender] Importando {args.input}...")
64
+ bpy.ops.import_scene.gltf(filepath=str(args.input))
65
+
66
+ # Pega objeto raiz importado (Hunyuan geralmente exporta 1 mesh top-level)
67
+ imported = [obj for obj in bpy.data.objects if obj.type == "MESH"]
68
+ if not imported:
69
+ print("ERRO: nenhum mesh importado", file=sys.stderr)
70
+ return 1
71
+
72
+ # Joina meshes (caso Hunyuan exporte vários submeshes)
73
+ if len(imported) > 1:
74
+ bpy.ops.object.select_all(action="DESELECT")
75
+ for o in imported:
76
+ o.select_set(True)
77
+ bpy.context.view_layer.objects.active = imported[0]
78
+ bpy.ops.object.join()
79
+ estante = bpy.context.view_layer.objects.active
80
+ estante.name = "EstanteHeroAI"
81
+
82
+ # 3. Centralizar pivot na origem + apoiar base no chão (Z=0)
83
+ print("[blender] Centering + grounding pivot...")
84
+ bpy.ops.object.select_all(action="DESELECT")
85
+ estante.select_set(True)
86
+ bpy.context.view_layer.objects.active = estante
87
+ bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN")
88
+
89
+ # Calcular bounding box em world
90
+ bpy.context.view_layer.update()
91
+ bbox_corners = [estante.matrix_world @ Mathutils.Vector(corner) if False else None for corner in estante.bound_box]
92
+ # Workaround: usar bound_box em local space + estante.location
93
+ bbox_local = [tuple(c) for c in estante.bound_box]
94
+ min_z_local = min(c[2] for c in bbox_local)
95
+ max_z_local = max(c[2] for c in bbox_local)
96
+ height_local = max_z_local - min_z_local
97
+ print(f" Local bbox height: {height_local:.3f}")
98
+
99
+ # 4. Scale uniformly to target height
100
+ print(f"[blender] Scaling para altura {args.scale_target}m...")
101
+ if height_local > 0:
102
+ scale_factor = args.scale_target / height_local
103
+ estante.scale = (scale_factor, scale_factor, scale_factor)
104
+ bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
105
+
106
+ # 5. Mover pra base ficar em Z=0
107
+ bpy.context.view_layer.update()
108
+ bbox_after = [tuple(c) for c in estante.bound_box]
109
+ min_z_after = min(c[2] for c in bbox_after)
110
+ estante.location.z -= min_z_after
111
+ bpy.ops.object.transform_apply(location=True, rotation=False, scale=False)
112
+
113
+ # 6. Calcular dimensions finais (depois do scale e ground)
114
+ bpy.context.view_layer.update()
115
+ bbox = estante.bound_box
116
+ xs = [c[0] for c in bbox]; ys = [c[1] for c in bbox]; zs = [c[2] for c in bbox]
117
+ min_x, max_x = min(xs), max(xs)
118
+ min_y, max_y = min(ys), max(ys)
119
+ min_z, max_z = min(zs), max(zs)
120
+ w = max_x - min_x # width X
121
+ d = max_y - min_y # depth Y
122
+ h = max_z - min_z # height Z
123
+ print(f" Final bbox: W={w:.3f} D={d:.3f} H={h:.3f}m")
124
+
125
+ # 7. Adicionar empties Prateleira_0..3 distribuídos verticalmente
126
+ # Convenção do EDou: índice 0 = mais alta (Sec1), índice N-1 = mais baixa (Extra)
127
+ # Distribui no terço superior ao terço inferior do mesh, deixando margens
128
+ # pra base/topo. shelf_z_positions vai do TOPO pra BASE (decreasing Z).
129
+ print(f"[blender] Adicionando {args.shelf_count} empties Prateleira_*...")
130
+ margin_top = h * 0.10 # 10% top margin (pediment area)
131
+ margin_base = h * 0.05 # 5% base margin
132
+ usable_height = h - margin_top - margin_base
133
+ if args.shelf_count > 1:
134
+ spacing = usable_height / args.shelf_count
135
+ first_z = max_z - margin_top - spacing / 2.0
136
+ else:
137
+ spacing = 0
138
+ first_z = (max_z + min_z) / 2.0
139
+
140
+ # Y position: meio do mesh em Y (front-back)
141
+ shelf_y = (min_y + max_y) / 2.0
142
+ # X position: centro
143
+ shelf_x = (min_x + max_x) / 2.0
144
+
145
+ for i in range(args.shelf_count):
146
+ z = first_z - i * spacing
147
+ empty = bpy.data.objects.new(f"Prateleira_{i}", None)
148
+ empty.empty_display_type = "PLAIN_AXES"
149
+ empty.empty_display_size = 0.10
150
+ bpy.context.collection.objects.link(empty)
151
+ empty.parent = estante
152
+ # Posição em coords LOCAL do estante (after parenting, location is local)
153
+ empty.location = (shelf_x, shelf_y, z)
154
+ print(f" Prateleira_{i}: ({shelf_x:.2f}, {shelf_y:.2f}, {z:.2f})")
155
+
156
+ # 8. Plaqueta_Anchor — no topo do mesh, na face frontal (Y mais negativo)
157
+ # Convenção EDou: frontal face em Blender -Y direction.
158
+ print("[blender] Adicionando Plaqueta_Anchor no topo frontal...")
159
+ plaqueta_z = max_z - h * 0.13 # ~13% abaixo do topo (área do frontão)
160
+ plaqueta_y = min_y - 0.005 # 5mm pra fora da face frontal
161
+ plaqueta_x = (min_x + max_x) / 2.0 # centro
162
+ anchor = bpy.data.objects.new("Plaqueta_Anchor", None)
163
+ anchor.empty_display_type = "PLAIN_AXES"
164
+ anchor.empty_display_size = 0.05
165
+ bpy.context.collection.objects.link(anchor)
166
+ anchor.parent = estante
167
+ anchor.location = (plaqueta_x, plaqueta_y, plaqueta_z)
168
+ print(f" Plaqueta_Anchor: ({plaqueta_x:.2f}, {plaqueta_y:.2f}, {plaqueta_z:.2f})")
169
+
170
+ # 9. Renomear material do mesh pra "MadeiraMogno" (compatibilidade com
171
+ # EstantePolyHavenSetup que faz remap por nome). Hunyuan exporta material
172
+ # com nome arbitrário; renomear pra slot 0.
173
+ if estante.data.materials:
174
+ mat = estante.data.materials[0]
175
+ mat.name = "MadeiraMogno"
176
+ print(f" Material slot 0 renomeado pra MadeiraMogno")
177
+ else:
178
+ print(" ⚠ Mesh sem material — Unity vai aplicar default URP/Lit")
179
+
180
+ # 10. Export FBX
181
+ print(f"[blender] Export FBX → {args.output_fbx}")
182
+ bpy.ops.object.select_all(action="DESELECT")
183
+ estante.select_set(True)
184
+ for child in estante.children:
185
+ child.select_set(True)
186
+ bpy.context.view_layer.objects.active = estante
187
+
188
+ bpy.ops.export_scene.fbx(
189
+ filepath=str(args.output_fbx),
190
+ use_selection=True,
191
+ object_types={"MESH", "EMPTY"},
192
+ bake_space_transform=True,
193
+ axis_forward="-Z",
194
+ axis_up="Y",
195
+ apply_scale_options="FBX_SCALE_NONE",
196
+ use_mesh_modifiers=True,
197
+ path_mode="COPY", # textura empacotada junto
198
+ embed_textures=True,
199
+ )
200
+ print(f" ✅ FBX salvo: {args.output_fbx}")
201
+ print(f" Size: {args.output_fbx.stat().st_size / 1024:.1f} KB")
202
+
203
+ print("")
204
+ print("============================================================")
205
+ print("✅ Post-processing completo. FBX pronto pro Unity.")
206
+ print("============================================================")
207
+ print("Próximos passos:")
208
+ print(f" 1. Verificar FBX em Blender GUI: blender {args.output_fbx}")
209
+ print(f" 2. Copiar pro Unity Assets:")
210
+ print(f" cp {args.output_fbx} ../unity-library/EDouLibrary/Assets/_Project/Models/Furniture/")
211
+ print(f" 3. Em Unity: EDou → Estante Hero - PolyHaven Materials (re-aplica wood+bronze)")
212
+ print(f" 4. EDou → Estante Hero - Preview Scene → Play")
213
+ return 0
214
+
215
+
216
+ # Mathutils import fix (Blender API)
217
+ try:
218
+ import mathutils as Mathutils
219
+ except ImportError:
220
+ Mathutils = None
221
+
222
+
223
+ if __name__ == "__main__":
224
+ rc = main()
225
+ if rc != 0:
226
+ sys.exit(rc)
docs/cost_tracking.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cost Tracking — EDou 3D Pipeline
2
+
3
+ Log de runs Vast.ai pra controle de gastos + aprendizado sobre o que
4
+ funciona / não funciona em cada iteração.
5
+
6
+ ## Template de entrada
7
+
8
+ ```markdown
9
+ ### YYYY-MM-DD — <descrição curta>
10
+
11
+ - **Instance**: RTX 3090 / 4090 / etc
12
+ - **$/hr**: $0.XX
13
+ - **Duração**: Xh YYmin
14
+ - **Total**: $X.XX
15
+ - **Output**: arquivo gerado (ou "rejeitado" / "failed")
16
+ - **Lições**:
17
+ - O que funcionou
18
+ - O que falhou
19
+ - Próxima iteração: o que mudar
20
+
21
+ ```
22
+
23
+ ## Histórico
24
+
25
+ ### 2026-04-29 — Setup inicial (planejado)
26
+
27
+ - **Instance**: RTX 3090 (a definir)
28
+ - **$/hr**: ~$0.25 (esperado)
29
+ - **Duração estimada**: 2-3h (install + downloads + smoke + 1ª estante)
30
+ - **Total esperado**: ~$0.60-0.90
31
+ - **Output esperado**: 1 estante GLB + smoke test + runbook validado
32
+ - **Lições**: PRIMEIRA EXECUÇÃO — o que documentar:
33
+ - Tempo real de cada etapa do install_hunyuan3d.sh
34
+ - Tamanho real download de weights
35
+ - GPU memory pico durante texture generation
36
+ - Velocidade rede do host (afeta download)
37
+
38
+ (Atualizar após primeiro run real)
39
+
40
+ ## Cumulativo
41
+
42
+ | Mês | Total gasto | Outputs aceitos |
43
+ |---|---|---|
44
+ | 2026-04 | $0.00 | 0 (zero ainda — pipeline preparada mas não rodada) |
45
+
46
+ ## Budget cap
47
+
48
+ Pré-aprovação Anderson: **$1/instância sem perguntar, sempre destruir
49
+ pós-uso**. Acima de $1 → perguntar antes.
50
+
51
+ Limite de queima recomendado por sessão de iteração: **$2** (descontando
52
+ setup amortizado). Se passar disso sem output usável → reavaliar
53
+ abordagem (talvez a estante via IA não está funcionando, voltar pra
54
+ procedural turbinado round 4 ou pivotar pra Tripo/Meshy API).
docs/troubleshooting.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Troubleshooting — EDou 3D Pipeline
2
+
3
+ Erros comuns + soluções. Atualizar conforme bater novos.
4
+
5
+ ## Setup phase
6
+
7
+ ### `nvidia-smi: command not found`
8
+ Instance Vast.ai sem GPU ou template wrong. Re-spin escolhendo template
9
+ PyTorch/CUDA explicitamente.
10
+
11
+ ### `CUDA out of memory` no install
12
+ Algum script de exemplo carregou modelo antes de tu rodar inferência.
13
+ ```bash
14
+ nvidia-smi # ver processos
15
+ # kill PID se necessário
16
+ ```
17
+
18
+ ### `pip install -e .` falha com C++ build error
19
+ Falta dependência de sistema:
20
+ ```bash
21
+ apt-get install build-essential cmake python3-dev libgl1-mesa-glx
22
+ ```
23
+
24
+ ### `huggingface-cli` não autentica
25
+ Modelos públicos do Tencent não precisam login. Se pedir token:
26
+ ```bash
27
+ huggingface-cli login
28
+ # Cole token de huggingface.co/settings/tokens (read scope)
29
+ ```
30
+
31
+ ### Download de weights muito lento
32
+ Vast.ai a velocidade varia bastante por host. Se <10MB/s, considera:
33
+ - Re-spin em outro host (filtrar Inet > 500Mbps)
34
+ - Usar `aria2c` em vez de `huggingface-cli`:
35
+ ```bash
36
+ apt-get install aria2
37
+ aria2c -x 16 -s 16 <url-direta>
38
+ ```
39
+
40
+ ## Verify phase
41
+
42
+ ### `from hy3dgen.shapegen import ...` → ModuleNotFoundError
43
+ ```bash
44
+ cd ~/work/hunyuan
45
+ pip install -e .
46
+ # Re-tenta
47
+ ```
48
+
49
+ ### Hunyuan generation produces all-zero mesh
50
+ Imagem de input ruim ou modelo não baixou completamente.
51
+ ```bash
52
+ # Verifica weights
53
+ ls -la ~/work/hunyuan/weights/
54
+ # Deve ter ~5-10GB de arquivos .safetensors / .bin
55
+ ```
56
+
57
+ ## Pipeline run phase
58
+
59
+ ### SDXL geração: imagem com pessoa/texto/watermark indesejado
60
+ Negative prompt insuficiente. Adicionar em `prompts/estante_baroque.txt`
61
+ (positive já filtra; negative é hard-coded em `run_estante_pipeline.sh`).
62
+ Se persistir, editar negative no script.
63
+
64
+ ### Hunyuan: mesh gerado é só uma esfera/cubo
65
+ Imagem de referência sem silhueta clara. Re-gerar imagem com:
66
+ - Fundo branco/neutro
67
+ - Iluminação plana sem sombras duras
68
+ - Vista frontal (não 3/4 nem perspectiva extrema)
69
+
70
+ ### Hunyuan: textura aplica errado (esticada/distorcida)
71
+ UV unwrap do Hunyuan é hit-or-miss. Soluções:
72
+ - Skip texture step: `--no-texture` flag → usa só shape, aplica
73
+ PolyHaven materials no Unity (recomendado pro EDou)
74
+ - Refinar imagem de input (mais detalhe = melhor UV)
75
+
76
+ ### Hunyuan: texture step falha com `paint pipeline error`
77
+ ```bash
78
+ # Verifica que texture deps compilaram corretamente
79
+ cd ~/work/hunyuan/hy3dgen/texgen/custom_rasterizer
80
+ pip install -e . --force-reinstall
81
+ cd ../differentiable_renderer
82
+ pip install -e . --force-reinstall
83
+ ```
84
+
85
+ ## Blender post-process phase
86
+
87
+ ### `blender: command not found`
88
+ Local: instalar Blender 5.x via brew (macOS) ou apt (Linux):
89
+ ```bash
90
+ brew install --cask blender # macOS
91
+ # ou
92
+ sudo snap install blender --classic # Ubuntu
93
+ ```
94
+
95
+ ### Script add_estante_anchors.py: "import bpy" falha
96
+ Tem que rodar via blender, não python direto:
97
+ ```bash
98
+ # ERRADO:
99
+ python add_estante_anchors.py
100
+ # CERTO:
101
+ blender --background --python add_estante_anchors.py -- --input ...
102
+ ```
103
+
104
+ ### FBX gerado não importa no Unity
105
+ Verificar:
106
+ 1. Unity Console por erros de import (ProjectSettings → Console)
107
+ 2. FBX size > 0 KB (script falhou silenciosamente?)
108
+ 3. Re-export Blender com `axis_forward=-Z, axis_up=Y, bake_space_transform=True`
109
+
110
+ ### Empties não aparecem no Unity após import FBX
111
+ Inspector do FBX:
112
+ - Model tab: Import Cameras + Import Lights ✗ (não usar)
113
+ - Animation tab: Import Animation ✗
114
+ - Rig tab: Animation Type = None
115
+ - Re-import via "Apply" → Empties devem virar GameObjects vazios children
116
+ do mesh root
117
+
118
+ ### EstantePolyHavenSetup não remap material no FBX gerado
119
+ O slot de material precisa se chamar `MadeiraMogno`. Verificar:
120
+ ```bash
121
+ # Em Blender com o FBX aberto:
122
+ # Outliner → mesh → Material → slot 0 name?
123
+ ```
124
+ Se diferente, editar `add_estante_anchors.py` linha que renomeia material.
125
+
126
+ ## Cost overruns
127
+
128
+ ### Esqueci de destruir instance, $$ rolando
129
+ Acessa imediatamente: vast.ai/console → Instances → Destroy.
130
+ Cobrança continua até confirmar destroy. Snapshot disk se quiser
131
+ preservar work.
132
+
133
+ ### Custo total > $1 sem produzir output útil
134
+ Diagnóstico:
135
+ 1. tmux session ainda ativa? (work em progresso?)
136
+ 2. Setup falhou meio do caminho? (parou de funcionar mas instance ligado)
137
+ 3. Pra cortar perdas: download que tiver pronto + destroy.
138
+
139
+ ## Unity integration phase
140
+
141
+ ### Estante FBX importado mas materiais magenta
142
+ URP shader missing. `EDou → Configurar URP Pipeline` (já existe no projeto).
143
+
144
+ ### Estante visível mas livros não spawnam nas prateleiras
145
+ Empties não estão presentes ou nomeados errado. Em Unity:
146
+ ```
147
+ Hierarchy → estante prefab → expand
148
+ Children precisam: Prateleira_0, Prateleira_1, Prateleira_2, Prateleira_3
149
+ ```
150
+ Se ausentes, voltar pro Blender + re-export com empties.
151
+
152
+ ### Plaqueta texto não aparece
153
+ EstantePrefabBuilder.CriarPlaqueta busca `Plaqueta_Anchor`. Se não
154
+ encontrar, fallback é lateral direita. Verificar empty Plaqueta_Anchor
155
+ existe no FBX.
docs/vastai_runbook.md ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vast.ai Runbook — EDou 3D Pipeline
2
+
3
+ Walkthrough completo de spin → setup → uso → destruir instance Vast.ai
4
+ pra rodar Hunyuan3D-2.1.
5
+
6
+ > **Pré-aprovação Anderson**: <$1/instância sem perguntar, sempre destruir
7
+ > pós-uso. Acima disso, perguntar.
8
+
9
+ ## 1. Setup conta Vast.ai (uma vez)
10
+
11
+ 1. Cadastro em [vast.ai](https://vast.ai/console/create/)
12
+ 2. Add credits ($5-10 inicial — sobra muito)
13
+ 3. Gera SSH key local se ainda não tem:
14
+ ```bash
15
+ ssh-keygen -t ed25519 -C "andersonabsmelo@gmail.com" -f ~/.ssh/edou_vastai
16
+ ```
17
+ 4. Copia public key pro Vast.ai: Console → Account → SSH keys → Add
18
+ ```bash
19
+ cat ~/.ssh/edou_vastai.pub | pbcopy # macOS
20
+ ```
21
+
22
+ ## 2. Spin up instance
23
+
24
+ Vai pro [Vast.ai search console](https://cloud.vast.ai/create/) e filtra:
25
+
26
+ | Filtro | Valor recomendado |
27
+ |---|---|
28
+ | GPU | RTX 3090 ou 4090 (24GB) |
29
+ | Disk | 50+ GB |
30
+ | Reliability | >95% |
31
+ | Verified | ✓ |
32
+ | Inet up/down | >100 Mbps (download de weights) |
33
+ | Image template | `pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel` (ou CUDA 12.1+ similar) |
34
+ | Price | <$0.40/hr (3090), <$0.80/hr (4090) |
35
+
36
+ **Exemplo realista**: RTX 3090 a $0.25/hr, 60GB disk, 99% reliability.
37
+
38
+ Click **Rent** → instance starting.
39
+
40
+ ## 3. SSH no instance
41
+
42
+ Após status "Running":
43
+
44
+ ```bash
45
+ # Pega SSH command do Vast.ai console (instance details → Connect → SSH)
46
+ ssh -i ~/.ssh/edou_vastai -p <PORT> root@<IP> -L 8188:localhost:8188
47
+
48
+ # -L 8188:localhost:8188 abre túnel pra ComfyUI no browser local
49
+ ```
50
+
51
+ ## 4. tmux pra sessões persistentes
52
+
53
+ **SEMPRE rode dentro de tmux**. Se SSH cair, instance continua rodando o
54
+ trabalho. Sem tmux + SSH cair = setup interrompido + créditos queimados.
55
+
56
+ ```bash
57
+ tmux new -s edou
58
+ # Dentro do tmux: roda os scripts
59
+
60
+ # Detach: Ctrl+B depois D
61
+ # Reattach (mesma SSH ou nova): tmux attach -t edou
62
+ # Listar sessões: tmux ls
63
+ # Kill sessão: tmux kill-session -t edou
64
+ ```
65
+
66
+ ## 5. Clone repo + setup
67
+
68
+ ```bash
69
+ # Dentro do tmux:
70
+ cd ~
71
+ # Clona seu repo HF (depois de tu pushar o 3d-pipeline lá)
72
+ git clone https://huggingface.co/<seu-username>/edou-3d.git
73
+ cd edou-3d
74
+
75
+ # Setup (~30-45min)
76
+ bash setup/install_hunyuan3d.sh
77
+
78
+ # Download models (~30-60min, ~30GB) — vai dormir, deixa rodando no tmux
79
+ bash setup/download_models.sh
80
+
81
+ # Smoke test (~5min) — confirma que tudo funciona
82
+ bash setup/verify_install.sh
83
+ ```
84
+
85
+ ## 6. Uso — gerar estante
86
+
87
+ ```bash
88
+ # Edita prompt se quiser:
89
+ nano prompts/estante_baroque.txt
90
+
91
+ # Roda pipeline (~5-10min: SDXL ~30s + Hunyuan shape ~3min + texture ~3min)
92
+ bash setup/run_estante_pipeline.sh --output-name estante_v1
93
+
94
+ # Resultado em ~/work/outputs/runs/estante_v1/
95
+ ls -la ~/work/outputs/runs/estante_v1/
96
+ # - reference.png (imagem SDXL)
97
+ # - shape_only.glb (Hunyuan sem textura)
98
+ # - final.glb (Hunyuan com PBR — usar este)
99
+ ```
100
+
101
+ ## 7. Download local + Blender post
102
+
103
+ **Antes de destruir o instance**, baixa os GLBs:
104
+
105
+ ```bash
106
+ # No laptop (não no instance):
107
+ cd ~/Desktop/Projetos/edou/3d-pipeline/outputs
108
+ scp -i ~/.ssh/edou_vastai -P <PORT> \
109
+ root@<IP>:~/work/outputs/runs/estante_v1/final.glb \
110
+ ./estante_v1.glb
111
+
112
+ # Blender post-processing (local, gratuito)
113
+ cd ..
114
+ blender --background --python blender/add_estante_anchors.py -- \
115
+ --input outputs/estante_v1.glb \
116
+ --output-fbx outputs/estante_hero_ai.fbx
117
+
118
+ # Copia pro Unity
119
+ cp outputs/estante_hero_ai.fbx \
120
+ ../unity-library/EDouLibrary/Assets/_Project/Models/Furniture/
121
+ ```
122
+
123
+ ## 8. Destruir instance ⚠️ CRITICAL
124
+
125
+ **Esquecer = $$/hr indo continuamente.** Após baixar os GLBs:
126
+
127
+ 1. Vast.ai console → instance → **DESTROY**
128
+ 2. Confirma: "Yes, destroy"
129
+ 3. Verifica em "Instances" que o estado virou "Stopped"
130
+ 4. Storage permanente NÃO é cobrado após destroy (a menos que tu tenha
131
+ alocado volume persistente — pra Hunyuan não precisa)
132
+
133
+ Custo estimado primeiro run completo:
134
+ - Setup + downloads + smoke + 1 estante: ~3-4 horas RTX 3090 a $0.25/hr = **~$1**
135
+
136
+ ## 9. Monitoring durante uso
137
+
138
+ ```bash
139
+ # GPU usage
140
+ watch -n1 nvidia-smi
141
+
142
+ # Disk space
143
+ df -h /
144
+
145
+ # Network (download progress)
146
+ iftop # se instalado
147
+
148
+ # Cost tracking
149
+ # Vast.ai console mostra $/hr e total acumulado por instance
150
+ ```
151
+
152
+ ## 10. Re-uso instance (sessões futuras)
153
+
154
+ Após destruir, próxima sessão precisa novo instance. Pra acelerar:
155
+ - **Snapshot persistent storage** — opcional, custa ~$0.01/GB/mês
156
+ (50GB × $0.01 = $0.50/mês). Permite reuso sem re-download.
157
+ - **Sem snapshot**: cada sessão re-baixa weights (~30min). Aceitável
158
+ pra uso esporádico.
159
+
160
+ Se for usar Hunyuan >2x/semana → snapshot vale a pena.
161
+ Se for esporádico → re-download cada vez.
162
+
163
+ ## Cost log template
164
+
165
+ Manter em `docs/cost_tracking.md`:
166
+
167
+ ```
168
+ | Data | Duração | GPU | $/hr | Total | Output |
169
+ |---|---|---|---|---|---|
170
+ | 2026-04-29 | 2h45 | RTX 3090 | $0.25 | $0.69 | estante_v1.glb (rejected) |
171
+ | 2026-04-30 | 1h20 | RTX 3090 | $0.27 | $0.36 | estante_v3.glb (✓ aprovado) |
172
+ ```
outputs/README.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Outputs/ — gerados pelos scripts (gitignored)
2
+
3
+ Diretório onde os artefatos de cada run são salvos. Os arquivos
4
+ `*.glb`, `*.fbx`, `*.png`, etc. são gitignored (.gitignore root) porque:
5
+
6
+ - Ficam grandes (10-100MB por GLB)
7
+ - Mudam frequentemente entre iterações
8
+ - Versão final aprovada vai pro Unity Assets/Furniture/, não pra cá
9
+
10
+ ## Estrutura esperada
11
+
12
+ ```
13
+ outputs/
14
+ ├── runs/ # cada run em pasta separada com timestamp
15
+ │ ├── estante_v1/
16
+ │ │ ├── reference.png # SDXL output
17
+ │ │ ├── shape_only.glb # Hunyuan shape (sem texture)
18
+ │ │ └── final.glb # Hunyuan textured (USAR ESTE)
19
+ │ ├── estante_v2/
20
+ │ │ └── ...
21
+ │ └── casco_v1/
22
+ │ └── ...
23
+ └── estante_hero_ai.fbx # output final do Blender post-process
24
+ ```
25
+
26
+ ## Workflow
27
+
28
+ 1. `bash setup/run_estante_pipeline.sh` cria `runs/estante_<timestamp>/`
29
+ 2. Anderson avalia visualmente (abre `final.glb` em viewer GLB ou Blender)
30
+ 3. Se bom → roda `add_estante_anchors.py` → produz `estante_hero_ai.fbx`
31
+ 4. Copia FBX pro Unity: `cp estante_hero_ai.fbx ../unity-library/.../Furniture/`
32
+ 5. Mantém `runs/` localmente pra histórico (ocasional limpeza manual quando
33
+ ficar grande demais)
prompts/README.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prompt Engineering — EDou 3D
2
+
3
+ Lições e iterações sobre prompts pra Hunyuan3D-2.1 (via SDXL → image → 3D).
4
+
5
+ ## Princípios para Hunyuan3D image-to-3D
6
+
7
+ Hunyuan3D não recebe texto direto — ele recebe IMAGEM e gera 3D. Logo,
8
+ **toda a engenharia de prompt vai pro SDXL** que cria a imagem de
9
+ referência. Características críticas da imagem que geram bom 3D:
10
+
11
+ 1. **Objeto isolado, fundo neutro** — fundo complexo confunde o modelo de shape
12
+ 2. **Iluminação suave + contraste claro** — sombras duras enganam o modelo de profundidade
13
+ 3. **Vista frontal direta** — perspectivas extremas distorcem proporções
14
+ 4. **Sem texto/letras** — Hunyuan tenta extrudar texto como geometria, vira ruído
15
+ 5. **Sem pessoas/personagens** — modelo otimizado pra objetos
16
+ 6. **Detalhes nítidos com sharpness alta** — blur reduz definição da silhueta gerada
17
+ 7. **1024×1024** mínimo (SDXL native), idealmente 1536 com refiner
18
+
19
+ ## Negative prompts padrão (sempre incluir)
20
+
21
+ ```
22
+ person, people, human, character, face,
23
+ text, letters, watermark, signature, logo,
24
+ blurry, distorted, low quality, deformed,
25
+ sketch, drawing, painting, cartoon,
26
+ multiple objects, cluttered scene
27
+ ```
28
+
29
+ ## Iteração esperada
30
+
31
+ Hunyuan tira ~3-5 imagens diferentes pra encontrar uma que gera bom 3D.
32
+ Comum: imagem que parece linda em 2D mas gera 3D ruim porque tem
33
+ ambiguidade de profundidade (ex: ornamentos muito densos = textura
34
+ plana em vez de relevo).
35
+
36
+ ## Arquivos
37
+
38
+ - `estante_baroque.txt` — prompt pra estante hero piece (uma estante isolada)
39
+ - `casco_hogwarts.txt` — prompt pro corredor da biblioteca (interior arquitetural)
40
+
41
+ ## Dicas iteração estante
42
+
43
+ Se primeira gen ficar com proporções erradas:
44
+ - **Muito largo**: adicionar "narrow tall vertical cabinet"
45
+ - **Muito raso**: adicionar "deep sturdy bookcase, 50cm depth"
46
+ - **Sem prateleiras visíveis**: adicionar "with 4 horizontal wooden shelves clearly visible"
47
+ - **Demasiado ornamentado**: remover "ornate" e "baroque", adicionar "elegant simple"
48
+ - **Pouco ornamentado**: adicionar "richly carved", "highly decorated"
49
+
50
+ ## Dicas iteração casco
51
+
52
+ Casco é texturizado mais como AMBIENTE que como objeto. Pode dar 3D
53
+ ruim mesmo com imagem boa (interiores são notoriamente difíceis pra
54
+ text-to-3D atual). Se Hunyuan falhar com casco:
55
+ - Plano B: gerar PAREDES individuais (1 imagem de painel de parede,
56
+ Hunyuan cria 1 mesh, replica em Unity)
57
+ - Plano C: cair em casco procedural (Blender + create_arch_pointed
58
+ existente em geometry.py + texture PolyHaven)
prompts/casco_hogwarts.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Hogwarts library interior corridor, gothic medieval architecture,
2
+ tall stone walls with vaulted ribbed ceiling,
3
+ stained glass windows with colored panels casting warm light,
4
+ dark wooden floor with worn texture,
5
+ gothic pointed arches and stone columns flanking the corridor,
6
+ moody atmospheric lighting from candles and natural daylight,
7
+ inspired by Trinity College Long Room Dublin and Strahov Monastery Library,
8
+ photorealistic interior architecture, no people, no furniture,
9
+ empty room ready for decoration,
10
+ front-facing perspective showing depth into corridor,
11
+ cinematic 16:9 aspect, high detail, sharp focus
prompts/estante_baroque.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Ornate baroque library bookshelf, single tall vertical cabinet,
2
+ made of dark mahogany wood with rich aged patina,
3
+ gold leaf gilded ornaments and bronze decorative brackets at corners,
4
+ gothic arched pediment top with carved volutes flanking the sides,
5
+ turned baluster columns with composite capitals on all four corners,
6
+ four horizontal shelves visible inside, ornate central shield-shaped plaque on the pediment,
7
+ double cornice with dentil molding and ovolo band,
8
+ inspired by Strahov Monastery Library Prague,
9
+ photorealistic, museum quality woodworking,
10
+ studio product photography lighting, neutral background,
11
+ front view, full body shot, isolated object,
12
+ high detail, sharp focus, no text, no people
setup/download_models.sh ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # download_models.sh
3
+ # Baixa weights do Hunyuan3D-2.1 + SDXL pra ComfyUI.
4
+ # Total ~30GB. Roda em background com tmux pra não travar SSH.
5
+ #
6
+ # Uso:
7
+ # bash download_models.sh
8
+ #
9
+ # IMPORTANTE: tem que rodar APÓS install_hunyuan3d.sh.
10
+
11
+ set -euo pipefail
12
+
13
+ WORK_DIR="${WORK_DIR:-$HOME/work}"
14
+ VENV_DIR="${VENV_DIR:-$HOME/3d-env}"
15
+
16
+ source "$VENV_DIR/bin/activate"
17
+
18
+ echo "============================================================"
19
+ echo "Download de model weights (~30GB total)"
20
+ echo "============================================================"
21
+
22
+ # Hunyuan3D-2.1 weights — a HF repo do Tencent oficial
23
+ # (verificar repo name — Tencent reorg pode renomear; ajustar se erro 404)
24
+ HF_HUNYUAN_REPO="${HF_HUNYUAN_REPO:-tencent/Hunyuan3D-2.1}"
25
+ HUNYUAN_WEIGHTS_DIR="$WORK_DIR/hunyuan/weights"
26
+
27
+ echo ""
28
+ echo "[1/2] Hunyuan3D-2.1 weights de $HF_HUNYUAN_REPO..."
29
+ mkdir -p "$HUNYUAN_WEIGHTS_DIR"
30
+ huggingface-cli download "$HF_HUNYUAN_REPO" \
31
+ --local-dir "$HUNYUAN_WEIGHTS_DIR" \
32
+ --local-dir-use-symlinks False \
33
+ || {
34
+ echo " ⚠ Falhou. Tentar fallback Hunyuan3D-2 (versão 2.0)..."
35
+ huggingface-cli download "tencent/Hunyuan3D-2" \
36
+ --local-dir "$HUNYUAN_WEIGHTS_DIR" \
37
+ --local-dir-use-symlinks False
38
+ }
39
+
40
+ # SDXL pra ComfyUI (geração de imagem de referência)
41
+ SDXL_REPO="${SDXL_REPO:-stabilityai/stable-diffusion-xl-base-1.0}"
42
+ SDXL_FILE="sd_xl_base_1.0.safetensors"
43
+ COMFY_CKPT_DIR="$WORK_DIR/comfyui/models/checkpoints"
44
+
45
+ echo ""
46
+ echo "[2/2] SDXL base 1.0 pra ComfyUI..."
47
+ mkdir -p "$COMFY_CKPT_DIR"
48
+ if [ ! -f "$COMFY_CKPT_DIR/$SDXL_FILE" ]; then
49
+ huggingface-cli download "$SDXL_REPO" "$SDXL_FILE" \
50
+ --local-dir "$COMFY_CKPT_DIR" \
51
+ --local-dir-use-symlinks False
52
+ else
53
+ echo " $SDXL_FILE já existe, skip."
54
+ fi
55
+
56
+ # Refiner SDXL (opcional mas melhora qualidade da imagem de referência)
57
+ SDXL_REFINER_FILE="sd_xl_refiner_1.0.safetensors"
58
+ SDXL_REFINER_REPO="stabilityai/stable-diffusion-xl-refiner-1.0"
59
+ echo ""
60
+ echo "[bonus] SDXL refiner 1.0 (opcional — melhora detalhes)..."
61
+ if [ ! -f "$COMFY_CKPT_DIR/$SDXL_REFINER_FILE" ]; then
62
+ huggingface-cli download "$SDXL_REFINER_REPO" "$SDXL_REFINER_FILE" \
63
+ --local-dir "$COMFY_CKPT_DIR" \
64
+ --local-dir-use-symlinks False \
65
+ || echo " refiner skip (falhou — não bloqueia pipeline)"
66
+ else
67
+ echo " refiner já existe, skip."
68
+ fi
69
+
70
+ # VAE (opcional, melhor cor)
71
+ VAE_FILE="sdxl_vae.safetensors"
72
+ VAE_REPO="madebyollin/sdxl-vae-fp16-fix"
73
+ COMFY_VAE_DIR="$WORK_DIR/comfyui/models/vae"
74
+ mkdir -p "$COMFY_VAE_DIR"
75
+ echo ""
76
+ echo "[bonus] VAE FP16 fix..."
77
+ if [ ! -f "$COMFY_VAE_DIR/$VAE_FILE" ]; then
78
+ huggingface-cli download "$VAE_REPO" \
79
+ --local-dir "$COMFY_VAE_DIR" \
80
+ --local-dir-use-symlinks False \
81
+ || echo " vae skip (não bloqueia)"
82
+ else
83
+ echo " vae já existe, skip."
84
+ fi
85
+
86
+ echo ""
87
+ echo "============================================================"
88
+ echo "✅ Models baixados em:"
89
+ echo " Hunyuan: $HUNYUAN_WEIGHTS_DIR ($(du -sh $HUNYUAN_WEIGHTS_DIR 2>/dev/null | cut -f1))"
90
+ echo " SDXL: $COMFY_CKPT_DIR ($(du -sh $COMFY_CKPT_DIR 2>/dev/null | cut -f1))"
91
+ echo "============================================================"
92
+ echo ""
93
+ echo "Próximo: bash verify_install.sh"
setup/install_hunyuan3d.sh ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # install_hunyuan3d.sh
3
+ # Setup Hunyuan3D-2.1 + ComfyUI numa GPU instance Vast.ai (Ubuntu 22.04 + CUDA 12.1).
4
+ # Tested target: RTX 3090 (24GB VRAM) ou superior.
5
+ #
6
+ # Uso:
7
+ # bash install_hunyuan3d.sh
8
+ #
9
+ # Tempo esperado: ~30-45min (sem download de weights — esses vão em download_models.sh).
10
+ # IMPORTANTE: rode dentro de tmux! Conexão SSH cair = setup interrompido.
11
+
12
+ set -euo pipefail
13
+
14
+ WORK_DIR="${WORK_DIR:-$HOME/work}"
15
+ VENV_DIR="${VENV_DIR:-$HOME/3d-env}"
16
+
17
+ echo "============================================================"
18
+ echo "EDou 3D Pipeline — Hunyuan3D-2.1 + ComfyUI install"
19
+ echo "WORK_DIR=$WORK_DIR VENV_DIR=$VENV_DIR"
20
+ echo "============================================================"
21
+
22
+ # 0. Sanity check — GPU presente
23
+ if ! command -v nvidia-smi >/dev/null 2>&1; then
24
+ echo "ERRO: nvidia-smi não encontrado. Instance sem GPU?"
25
+ exit 1
26
+ fi
27
+ nvidia-smi | head -20
28
+
29
+ # 1. Sistema (sudo varia por instance — Vast.ai geralmente já é root)
30
+ echo ""
31
+ echo "[1/7] System dependencies..."
32
+ if command -v sudo >/dev/null 2>&1; then SUDO="sudo"; else SUDO=""; fi
33
+ $SUDO apt-get update -qq
34
+ $SUDO apt-get install -y -qq \
35
+ git wget curl tmux htop \
36
+ python3-pip python3-venv python3-dev \
37
+ build-essential cmake \
38
+ libgl1-mesa-glx libglib2.0-0 \
39
+ libsm6 libxext6 libxrender-dev libgomp1
40
+
41
+ # 2. Python venv (isola dependências)
42
+ echo ""
43
+ echo "[2/7] Python virtual env at $VENV_DIR..."
44
+ python3 -m venv "$VENV_DIR"
45
+ source "$VENV_DIR/bin/activate"
46
+ pip install --upgrade pip wheel setuptools
47
+
48
+ # 3. PyTorch matching CUDA 12.1
49
+ echo ""
50
+ echo "[3/7] PyTorch (CUDA 12.1)..."
51
+ pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
52
+
53
+ # 4. HuggingFace CLI (pra download de weights depois)
54
+ echo ""
55
+ echo "[4/7] HuggingFace Hub CLI..."
56
+ pip install -U "huggingface_hub[cli]"
57
+
58
+ # 5. Hunyuan3D-2.1
59
+ echo ""
60
+ echo "[5/7] Hunyuan3D-2.1 clone + install..."
61
+ mkdir -p "$WORK_DIR" && cd "$WORK_DIR"
62
+ if [ ! -d "hunyuan" ]; then
63
+ git clone https://github.com/Tencent/Hunyuan3D-2.git hunyuan
64
+ fi
65
+ cd hunyuan
66
+ git pull origin main
67
+ pip install -r requirements.txt
68
+ pip install -e .
69
+
70
+ # Texture custom rasterizer + differentiable renderer (compilam C++/CUDA — pode demorar)
71
+ echo ""
72
+ echo " → Building custom rasterizer (C++/CUDA, ~3-5min)..."
73
+ if [ -d "hy3dgen/texgen/custom_rasterizer" ]; then
74
+ cd hy3dgen/texgen/custom_rasterizer && pip install -e . && cd "$WORK_DIR/hunyuan"
75
+ fi
76
+ echo " → Building differentiable renderer..."
77
+ if [ -d "hy3dgen/texgen/differentiable_renderer" ]; then
78
+ cd hy3dgen/texgen/differentiable_renderer && pip install -e . && cd "$WORK_DIR/hunyuan"
79
+ fi
80
+
81
+ # 6. ComfyUI (pra geração de imagem via SDXL)
82
+ echo ""
83
+ echo "[6/7] ComfyUI clone + install..."
84
+ cd "$WORK_DIR"
85
+ if [ ! -d "comfyui" ]; then
86
+ git clone https://github.com/comfyanonymous/ComfyUI.git comfyui
87
+ fi
88
+ cd comfyui
89
+ git pull origin master
90
+ pip install -r requirements.txt
91
+
92
+ # 7. Final check
93
+ echo ""
94
+ echo "[7/7] Verificações finais..."
95
+ python3 -c "import torch; print(f' PyTorch: {torch.__version__} | CUDA: {torch.cuda.is_available()} | Device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"CPU\"}')"
96
+ python3 -c "import sys; sys.path.insert(0, '$WORK_DIR/hunyuan'); from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline; print(' Hunyuan3D import OK')" || echo " ⚠ Hunyuan3D import falhou — checar logs"
97
+
98
+ echo ""
99
+ echo "============================================================"
100
+ echo "✅ Install completo. Próximo passo: bash download_models.sh"
101
+ echo "============================================================"
102
+ echo ""
103
+ echo "Lembrete: ative o venv em sessões SSH novas:"
104
+ echo " source $VENV_DIR/bin/activate"
setup/run_estante_pipeline.sh ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # run_estante_pipeline.sh
3
+ # Pipeline ponta-a-ponta: SDXL gera imagem de referência → Hunyuan3D
4
+ # converte em GLB com PBR textures.
5
+ #
6
+ # Uso:
7
+ # bash run_estante_pipeline.sh [--prompt-file prompts/estante_baroque.txt]
8
+ # [--output-name estante_v1]
9
+
10
+ set -euo pipefail
11
+
12
+ WORK_DIR="${WORK_DIR:-$HOME/work}"
13
+ VENV_DIR="${VENV_DIR:-$HOME/3d-env}"
14
+ REPO_DIR="${REPO_DIR:-$(dirname "$(realpath "$0")")/..}"
15
+ OUTPUT_DIR="${OUTPUT_DIR:-$WORK_DIR/outputs}"
16
+
17
+ # Args
18
+ PROMPT_FILE="$REPO_DIR/prompts/estante_baroque.txt"
19
+ OUTPUT_NAME="estante_$(date +%Y%m%d_%H%M%S)"
20
+ while [[ $# -gt 0 ]]; do
21
+ case $1 in
22
+ --prompt-file) PROMPT_FILE="$2"; shift 2 ;;
23
+ --output-name) OUTPUT_NAME="$2"; shift 2 ;;
24
+ *) echo "Arg desconhecido: $1"; exit 1 ;;
25
+ esac
26
+ done
27
+
28
+ source "$VENV_DIR/bin/activate"
29
+ mkdir -p "$OUTPUT_DIR/runs/$OUTPUT_NAME"
30
+
31
+ PROMPT=$(cat "$PROMPT_FILE")
32
+ echo "============================================================"
33
+ echo "Pipeline estante hero"
34
+ echo "Prompt file: $PROMPT_FILE"
35
+ echo "Output: $OUTPUT_DIR/runs/$OUTPUT_NAME"
36
+ echo "============================================================"
37
+ echo ""
38
+ echo "Prompt:"
39
+ echo "$PROMPT" | head -5
40
+ echo "..."
41
+ echo ""
42
+
43
+ # Etapa 1: SDXL gera imagem de referência
44
+ # Usa script Python standalone com pipeline diffusers (mais simples que ComfyUI headless).
45
+ REF_IMG="$OUTPUT_DIR/runs/$OUTPUT_NAME/reference.png"
46
+ echo "[1/2] SDXL gerando imagem de referência..."
47
+ python3 - <<PYEOF
48
+ from diffusers import StableDiffusionXLPipeline
49
+ import torch
50
+
51
+ prompt = """$PROMPT"""
52
+
53
+ pipe = StableDiffusionXLPipeline.from_single_file(
54
+ "$WORK_DIR/comfyui/models/checkpoints/sd_xl_base_1.0.safetensors",
55
+ torch_dtype=torch.float16,
56
+ use_safetensors=True,
57
+ )
58
+ pipe = pipe.to("cuda")
59
+ pipe.enable_attention_slicing()
60
+
61
+ # Negative prompt: evitar pessoas, texto, watermarks
62
+ negative = "person, people, human, text, watermark, signature, blurry, distorted, low quality, sketch, drawing"
63
+
64
+ print(" Gerando 1024x1024 (steps=30, guidance=7.5)...")
65
+ image = pipe(
66
+ prompt=prompt,
67
+ negative_prompt=negative,
68
+ width=1024,
69
+ height=1024,
70
+ num_inference_steps=30,
71
+ guidance_scale=7.5,
72
+ ).images[0]
73
+
74
+ image.save("$REF_IMG")
75
+ print(f" ✅ Reference image: $REF_IMG")
76
+ PYEOF
77
+
78
+ echo ""
79
+ echo "[2/2] Hunyuan3D convertendo imagem em mesh + textura..."
80
+ python3 - <<PYEOF
81
+ import os, sys, time
82
+ sys.path.insert(0, "$WORK_DIR/hunyuan")
83
+
84
+ from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline
85
+ from hy3dgen.texgen import Hunyuan3DPaintPipeline
86
+
87
+ print(" Carregando shape pipeline...")
88
+ t0 = time.time()
89
+ shape_pipe = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained("$WORK_DIR/hunyuan/weights")
90
+ print(f" {time.time()-t0:.1f}s")
91
+
92
+ print(" Gerando shape (Hunyuan3D-2.1, ~1-3min)...")
93
+ t0 = time.time()
94
+ mesh = shape_pipe(image="$REF_IMG")[0]
95
+ print(f" {time.time()-t0:.1f}s — verts={len(mesh.vertices)}, faces={len(mesh.faces)}")
96
+
97
+ mesh_path = "$OUTPUT_DIR/runs/$OUTPUT_NAME/shape_only.glb"
98
+ mesh.export(mesh_path)
99
+ print(f" ✅ Shape: $OUTPUT_DIR/runs/$OUTPUT_NAME/shape_only.glb")
100
+
101
+ print(" Carregando texture pipeline...")
102
+ t0 = time.time()
103
+ try:
104
+ texgen = Hunyuan3DPaintPipeline.from_pretrained("$WORK_DIR/hunyuan/weights")
105
+ print(f" {time.time()-t0:.1f}s")
106
+
107
+ print(" Gerando textures PBR (~2-4min)...")
108
+ t0 = time.time()
109
+ textured = texgen(mesh, image="$REF_IMG")
110
+ print(f" {time.time()-t0:.1f}s")
111
+
112
+ final_path = "$OUTPUT_DIR/runs/$OUTPUT_NAME/final.glb"
113
+ textured.export(final_path)
114
+ print(f" ✅ Final (textured): {final_path}")
115
+ except Exception as e:
116
+ print(f" ⚠ Texture step falhou ({e}). Mesh sem textura disponível em shape_only.glb.")
117
+ PYEOF
118
+
119
+ echo ""
120
+ echo "============================================================"
121
+ echo "✅ Pipeline completo. Resultados em:"
122
+ echo " $OUTPUT_DIR/runs/$OUTPUT_NAME/"
123
+ echo ""
124
+ echo "Files:"
125
+ ls -la "$OUTPUT_DIR/runs/$OUTPUT_NAME/"
126
+ echo "============================================================"
127
+ echo ""
128
+ echo "Próximo: scp pra local + Blender post-process:"
129
+ echo " scp <vastai-ip>:$OUTPUT_DIR/runs/$OUTPUT_NAME/final.glb ./outputs/"
130
+ echo " blender --background --python blender/add_estante_anchors.py -- ./outputs/final.glb"
setup/verify_install.sh ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # verify_install.sh
3
+ # Smoke test: gera 1 mesh teste com Hunyuan3D pra confirmar que tudo
4
+ # funciona ANTES de gastar tempo iterando prompts.
5
+ #
6
+ # Uso:
7
+ # bash verify_install.sh
8
+
9
+ set -euo pipefail
10
+
11
+ WORK_DIR="${WORK_DIR:-$HOME/work}"
12
+ VENV_DIR="${VENV_DIR:-$HOME/3d-env}"
13
+ OUTPUT_DIR="${OUTPUT_DIR:-$WORK_DIR/outputs}"
14
+
15
+ source "$VENV_DIR/bin/activate"
16
+
17
+ mkdir -p "$OUTPUT_DIR"
18
+
19
+ echo "============================================================"
20
+ echo "Smoke test Hunyuan3D-2.1"
21
+ echo "============================================================"
22
+
23
+ # Verifica imagem de teste — usa uma sample do próprio repo Hunyuan,
24
+ # ou baixa rapida de um asset clássico (esfera, cubo, etc).
25
+ TEST_IMG="$WORK_DIR/hunyuan/assets/example_images/004.png"
26
+ if [ ! -f "$TEST_IMG" ]; then
27
+ # Fallback: baixar uma imagem test simples
28
+ TEST_IMG="$OUTPUT_DIR/test_input.png"
29
+ echo "Sample do repo não encontrado, baixando test image..."
30
+ wget -O "$TEST_IMG" \
31
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/Banana-Single.jpg/640px-Banana-Single.jpg" \
32
+ || { echo "ERRO: falha no download da test image"; exit 1; }
33
+ fi
34
+
35
+ echo "Test image: $TEST_IMG"
36
+ echo ""
37
+
38
+ # Inferência simples Hunyuan3D
39
+ python3 - <<PYEOF
40
+ import os, sys, time
41
+ sys.path.insert(0, os.path.expanduser("$WORK_DIR/hunyuan"))
42
+
43
+ from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline
44
+
45
+ print("Carregando Hunyuan3D pipeline (pode demorar ~1min na primeira vez)...")
46
+ t0 = time.time()
47
+ pipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
48
+ "$WORK_DIR/hunyuan/weights",
49
+ )
50
+ print(f" Loaded em {time.time()-t0:.1f}s")
51
+
52
+ print("Gerando mesh do test image...")
53
+ t0 = time.time()
54
+ mesh = pipeline(image="$TEST_IMG")[0]
55
+ print(f" Generated em {time.time()-t0:.1f}s")
56
+ print(f" Verts: {len(mesh.vertices)} Faces: {len(mesh.faces)}")
57
+
58
+ out_glb = "$OUTPUT_DIR/smoke_test.glb"
59
+ mesh.export(out_glb)
60
+ print(f"✅ Salvo em {out_glb}")
61
+ PYEOF
62
+
63
+ echo ""
64
+ echo "============================================================"
65
+ echo "✅ Smoke test passou. Hunyuan3D operacional."
66
+ echo "Resultado: $OUTPUT_DIR/smoke_test.glb (${$(du -h $OUTPUT_DIR/smoke_test.glb | cut -f1)})"
67
+ echo "============================================================"
68
+ echo ""
69
+ echo "Pronto pra gerar a estante: bash run_estante_pipeline.sh"
workflows/hunyuan_inference.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ hunyuan_inference.py — script Python standalone pra rodar Hunyuan3D-2.1
4
+ em qualquer imagem de input. Usado pelo run_estante_pipeline.sh mas pode
5
+ ser executado manualmente também.
6
+
7
+ Uso:
8
+ python3 hunyuan_inference.py --image path/to/ref.png --output out.glb [--no-texture]
9
+
10
+ Requer venv ativo + weights baixados em $WORK_DIR/hunyuan/weights.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import os
16
+ import sys
17
+ import time
18
+ from pathlib import Path
19
+
20
+
21
+ def main() -> int:
22
+ parser = argparse.ArgumentParser(description="Hunyuan3D-2.1 inference")
23
+ parser.add_argument("--image", required=True, type=Path, help="Reference image (PNG/JPG)")
24
+ parser.add_argument("--output", required=True, type=Path, help="Output GLB path")
25
+ parser.add_argument("--weights", default=os.path.expanduser("~/work/hunyuan/weights"),
26
+ help="Hunyuan weights dir")
27
+ parser.add_argument("--no-texture", action="store_true",
28
+ help="Skip texture generation (faster, mesh sem PBR)")
29
+ parser.add_argument("--seed", type=int, default=42)
30
+ args = parser.parse_args()
31
+
32
+ if not args.image.exists():
33
+ print(f"ERRO: imagem não encontrada: {args.image}", file=sys.stderr)
34
+ return 1
35
+
36
+ args.output.parent.mkdir(parents=True, exist_ok=True)
37
+
38
+ # Add Hunyuan repo to path
39
+ repo_path = Path(args.weights).parent
40
+ sys.path.insert(0, str(repo_path))
41
+
42
+ from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline
43
+
44
+ print(f"[hunyuan] Image: {args.image}")
45
+ print(f"[hunyuan] Output: {args.output}")
46
+ print(f"[hunyuan] Weights: {args.weights}")
47
+
48
+ print("[hunyuan] Loading shape pipeline...")
49
+ t0 = time.time()
50
+ shape_pipe = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(args.weights)
51
+ print(f" loaded em {time.time()-t0:.1f}s")
52
+
53
+ print("[hunyuan] Generating shape...")
54
+ t0 = time.time()
55
+ mesh = shape_pipe(image=str(args.image), seed=args.seed)[0]
56
+ print(f" generated em {time.time()-t0:.1f}s")
57
+ print(f" verts={len(mesh.vertices)} faces={len(mesh.faces)}")
58
+
59
+ if not args.no_texture:
60
+ print("[hunyuan] Loading texture pipeline...")
61
+ try:
62
+ from hy3dgen.texgen import Hunyuan3DPaintPipeline
63
+ t0 = time.time()
64
+ texgen = Hunyuan3DPaintPipeline.from_pretrained(args.weights)
65
+ print(f" loaded em {time.time()-t0:.1f}s")
66
+
67
+ print("[hunyuan] Painting texture...")
68
+ t0 = time.time()
69
+ mesh = texgen(mesh, image=str(args.image))
70
+ print(f" painted em {time.time()-t0:.1f}s")
71
+ except ImportError as e:
72
+ print(f" ⚠ texgen import falhou ({e}). Mesh sem textura.", file=sys.stderr)
73
+ except Exception as e:
74
+ print(f" ⚠ texgen erro ({e}). Salvando mesh untextured.", file=sys.stderr)
75
+
76
+ print(f"[hunyuan] Exporting {args.output}...")
77
+ mesh.export(str(args.output))
78
+ size_mb = args.output.stat().st_size / 1024 / 1024
79
+ print(f" ✅ {args.output} ({size_mb:.1f} MB)")
80
+ return 0
81
+
82
+
83
+ if __name__ == "__main__":
84
+ sys.exit(main())
workflows/sdxl_estante.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_comment": "ComfyUI workflow JSON (SDXL base + refiner) pra gerar imagem de referência da estante. Importar via ComfyUI web UI: Load → este arquivo. Pode também ser feito programaticamente via run_estante_pipeline.sh que usa diffusers diretamente (não ComfyUI). Mantido aqui pra quem preferir UI manual.",
3
+ "_workflow_version": "1.0",
4
+ "_target_resolution": "1024x1024",
5
+ "_steps": 30,
6
+ "_guidance_scale": 7.5,
7
+ "_notes": "Anderson — quando rodar ComfyUI no Vast.ai instance, abra túnel SSH na porta 8188:\nssh -L 8188:localhost:8188 root@<vastai-ip>\nhttp://localhost:8188 no browser local.",
8
+ "nodes": {
9
+ "_skeleton": "Workflow real será exportado da UI ComfyUI numa primeira sessão de uso. Esse JSON é placeholder pra estrutura do repo. Substituir pelo export real quando tiver workflow validado.",
10
+ "1_loader": "CheckpointLoaderSimple → sd_xl_base_1.0.safetensors",
11
+ "2_positive": "CLIPTextEncode (positive prompt do prompts/estante_baroque.txt)",
12
+ "3_negative": "CLIPTextEncode (negative prompt — pessoas/texto/blur)",
13
+ "4_latent": "EmptyLatentImage 1024x1024",
14
+ "5_sampler": "KSampler (steps=30, cfg=7.5, sampler=dpmpp_2m, scheduler=karras)",
15
+ "6_decode": "VAEDecode",
16
+ "7_save": "SaveImage → outputs/runs/<timestamp>/reference.png"
17
+ },
18
+ "_alternative": "Pra automação via Python: usar diffusers StableDiffusionXLPipeline.from_single_file() — código já implementado em setup/run_estante_pipeline.sh."
19
+ }
Free AI Image Generator No sign-up. Instant results. Open Now