Initial pipeline scaffold: Hunyuan3D-2.1 + ComfyUI on Vast.ai
Browse files- .gitignore +33 -0
- LICENSE +32 -0
- README.md +125 -0
- blender/README.md +111 -0
- blender/add_estante_anchors.py +226 -0
- docs/cost_tracking.md +54 -0
- docs/troubleshooting.md +155 -0
- docs/vastai_runbook.md +172 -0
- outputs/README.md +33 -0
- prompts/README.md +58 -0
- prompts/casco_hogwarts.txt +11 -0
- prompts/estante_baroque.txt +12 -0
- setup/download_models.sh +93 -0
- setup/install_hunyuan3d.sh +104 -0
- setup/run_estante_pipeline.sh +130 -0
- setup/verify_install.sh +69 -0
- workflows/hunyuan_inference.py +84 -0
- workflows/sdxl_estante.json +19 -0
.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 |
+
}
|