HayrettinIscan commited on
Commit
dc4ddab
·
verified ·
1 Parent(s): 34ba142

Upload code/faz3_v6/meshai_api/pipeline.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. code/faz3_v6/meshai_api/pipeline.py +296 -0
code/faz3_v6/meshai_api/pipeline.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Faz3 generate pipeline — TAM yol.
3
+
4
+ image|prompt -> Microsoft TRELLIS -> Hunyuan3D-Paint maps -> meshai_bridge -> result.glb
5
+ Hafif hybrid: sadece MESHAI_ALLOW_LEGACY_HYBRID=1
6
+ Stub: sadece MESHAI_ALLOW_TRELLIS_STUB=1
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import shutil
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from meshai_api.hunyuan import (
17
+ ensure_stub_hunyuan_maps,
18
+ hunyuan_ready,
19
+ release_hunyuan_cuda,
20
+ resolve_hunyuan_maps_dir,
21
+ run_hunyuan_paint,
22
+ write_hunyuan_maps_from_feats,
23
+ )
24
+ from meshai_api.trellis import (
25
+ allow_trellis_stub,
26
+ release_trellis_cuda,
27
+ run_trellis_geometry,
28
+ trellis_ready,
29
+ )
30
+ from meshai_api.weights import resolve_faz2_ckpt, runtime_status
31
+
32
+ PIPELINE_ID = "faz3-full-v1"
33
+
34
+
35
+ def _env_int(name: str, default: int) -> int:
36
+ raw = os.environ.get(name, "").strip()
37
+ if not raw:
38
+ return default
39
+ try:
40
+ return int(raw)
41
+ except ValueError:
42
+ return default
43
+
44
+
45
+ def _truthy(name: str) -> bool:
46
+ return os.environ.get(name, "").strip().lower() in {"1", "true", "yes"}
47
+
48
+
49
+ def _write_trellis_stub(
50
+ *,
51
+ job_dir: Path,
52
+ prompt: str,
53
+ image_path: Path | None,
54
+ log: Any,
55
+ ) -> Path:
56
+ out = job_dir / "trellis.glb"
57
+ import numpy as np
58
+ import trimesh
59
+
60
+ mesh = trimesh.creation.box(extents=(1.0, 1.0, 1.0))
61
+ seed = abs(hash(prompt or (image_path.name if image_path else "x"))) % (2**32)
62
+ rng = np.random.default_rng(seed)
63
+ color = (
64
+ int(rng.integers(80, 220)),
65
+ int(rng.integers(80, 220)),
66
+ int(rng.integers(80, 220)),
67
+ 255,
68
+ )
69
+ mesh.visual.vertex_colors = color
70
+ mesh.export(out)
71
+ log(f"[faz3] trellis STUB GLB (explicit allow): {out}")
72
+ return out
73
+
74
+
75
+ def _resolve_final_glb(bridge_result: dict[str, Any], bridged_dir: Path) -> Path:
76
+ final = Path(bridge_result.get("final_glb") or "")
77
+ if final.is_file():
78
+ return final
79
+ for name in ("final_textured.glb", "normalized.glb", "final.glb"):
80
+ candidate = bridged_dir / name
81
+ if candidate.is_file():
82
+ return candidate
83
+ raise FileNotFoundError(f"bridge final GLB yok: {bridged_dir}")
84
+
85
+
86
+ def _collect_previews(bridged_dir: Path, job_dir: Path, *, max_n: int = 4) -> list[str]:
87
+ render_dir = bridged_dir / "renders"
88
+ if not render_dir.is_dir():
89
+ return []
90
+ exts = {".png", ".jpg", ".jpeg", ".webp"}
91
+ frames = sorted(
92
+ p for p in render_dir.iterdir() if p.is_file() and p.suffix.lower() in exts
93
+ )[:max_n]
94
+ if not frames:
95
+ return []
96
+ preview_dir = job_dir / "previews"
97
+ preview_dir.mkdir(parents=True, exist_ok=True)
98
+ rels: list[str] = []
99
+ for i, src in enumerate(frames):
100
+ dest = preview_dir / f"view_{i:02d}{src.suffix.lower()}"
101
+ shutil.copy2(src, dest)
102
+ rels.append(f"previews/{dest.name}")
103
+ return rels
104
+
105
+
106
+ def run_bridge_on_trellis(
107
+ *,
108
+ trellis_glb: Path,
109
+ out_dir: Path,
110
+ hunyuan_maps_dir: Path | None = None,
111
+ log: Any = print,
112
+ ) -> tuple[Path, dict[str, Any]]:
113
+ from meshai_bridge.bridge_pipeline import run_bridge_runtime
114
+ from meshai_bridge.constants import BRIDGE_VERSION
115
+
116
+ views = _env_int("MESHAI_BRIDGE_VIEWS", 4)
117
+ render_size = _env_int("MESHAI_BRIDGE_RENDER_SIZE", 512)
118
+
119
+ out_dir.mkdir(parents=True, exist_ok=True)
120
+ maps = hunyuan_maps_dir if hunyuan_maps_dir and hunyuan_maps_dir.exists() else None
121
+ result = run_bridge_runtime(
122
+ trellis_glb,
123
+ out_dir,
124
+ hunyuan_maps_dir=maps,
125
+ views=views,
126
+ render_size=render_size,
127
+ unwrap_uv=True,
128
+ textured_renders=not _truthy("MESHAI_BRIDGE_GRAY_RENDERS"),
129
+ voxel_fn=None,
130
+ latent_fn=None,
131
+ final_glb_name="final_textured.glb",
132
+ )
133
+ result.setdefault("bridge_version", BRIDGE_VERSION)
134
+ result["hunyuan_maps_dir"] = str(maps) if maps else None
135
+ final = _resolve_final_glb(result, out_dir)
136
+ log(f"[faz3] bridge final: {final}")
137
+ return final, result
138
+
139
+
140
+ def run_generate_job(
141
+ *,
142
+ job_dir: Path,
143
+ prompt: str = "",
144
+ image_path: Path | None = None,
145
+ log: Any = print,
146
+ ) -> Path:
147
+ job_dir.mkdir(parents=True, exist_ok=True)
148
+ faz2 = resolve_faz2_ckpt(download=_truthy("MESHAI_FAZ2_AUTO_DOWNLOAD"), log=log)
149
+ meta: dict[str, Any] = {
150
+ "pipeline": PIPELINE_ID,
151
+ "prompt": prompt,
152
+ "image": str(image_path) if image_path else None,
153
+ "trellis": None,
154
+ "hunyuan_source": None,
155
+ "previews": [],
156
+ "trellis_health": trellis_ready(),
157
+ "hunyuan": hunyuan_ready(job_dir),
158
+ "runtime": runtime_status(),
159
+ "faz2_ckpt": str(faz2) if faz2 else None,
160
+ "faz2_bind": "pending_peft" if faz2 else "missing",
161
+ "note": "Faz2 ckpt cached; TRELLIS full PEFT bind henuz yok. Paint icin VRAM handoff aktif.",
162
+ }
163
+
164
+ # Onceki job Paint modeli tutuyorsa TRELLIS'e yer ac
165
+ release_hunyuan_cuda(log=log)
166
+
167
+ trellis = job_dir / "trellis.glb"
168
+ trellis_meta: dict[str, Any] = {}
169
+ if not trellis.is_file():
170
+ try:
171
+ trellis, trellis_meta = run_trellis_geometry(
172
+ job_dir=job_dir,
173
+ prompt=prompt,
174
+ image_path=image_path,
175
+ log=log,
176
+ )
177
+ meta["trellis"] = trellis_meta.get("backend") or "trellis_full"
178
+ meta["trellis_meta"] = {
179
+ k: v for k, v in trellis_meta.items() if k != "feats"
180
+ }
181
+ except Exception as exc:
182
+ if allow_trellis_stub():
183
+ log(f"[faz3] TRELLIS full fail, stub allow: {exc}")
184
+ trellis = _write_trellis_stub(
185
+ job_dir=job_dir, prompt=prompt, image_path=image_path, log=log
186
+ )
187
+ meta["trellis"] = "stub"
188
+ meta["trellis_error"] = str(exc)
189
+ else:
190
+ meta["trellis_error"] = str(exc)
191
+ (job_dir / "FAILED.txt").write_text(
192
+ f"trellis failed: {exc}", encoding="utf-8"
193
+ )
194
+ (job_dir / "meta.json").write_text(
195
+ json.dumps(meta, indent=2, default=str), encoding="utf-8"
196
+ )
197
+ raise
198
+
199
+ # TRELLIS VRAM'i birak; Paint ayni A100'e sigsin
200
+ release_trellis_cuda(log=log)
201
+
202
+ # Hunyuan full paint
203
+ hy_info: dict[str, Any] | None = None
204
+ try:
205
+ hy_info = run_hunyuan_paint(
206
+ job_dir=job_dir,
207
+ trellis_glb=trellis,
208
+ image_path=image_path,
209
+ prompt=prompt,
210
+ log=log,
211
+ )
212
+ meta["hunyuan_source"] = "hunyuan_paint"
213
+ meta["hunyuan_meta"] = hy_info
214
+ except Exception as exc:
215
+ meta["hunyuan_paint_error"] = str(exc)
216
+ log(f"[faz3] Hunyuan paint fail: {exc}")
217
+ # Legacy latent maps only if hybrid feats present
218
+ feats = trellis_meta.get("feats") if isinstance(trellis_meta, dict) else None
219
+ if isinstance(feats, dict) and _truthy("MESHAI_ALLOW_LEGACY_HYBRID"):
220
+ write_hunyuan_maps_from_feats(
221
+ job_dir,
222
+ tex_latent=feats.get("tex_latent"),
223
+ hunyuan_feat=feats.get("hunyuan_feat"),
224
+ bridge_out=feats.get("bridge_out"),
225
+ log=log,
226
+ )
227
+ meta["hunyuan_source"] = "legacy_latent"
228
+ stub = ensure_stub_hunyuan_maps(job_dir)
229
+ if stub and meta.get("hunyuan_source") != "legacy_latent":
230
+ meta["hunyuan_source"] = "stub"
231
+ log(f"[faz3] hunyuan stub maps: {stub}")
232
+
233
+ maps_dir = resolve_hunyuan_maps_dir(job_dir)
234
+ meta["hunyuan"] = hunyuan_ready(job_dir)
235
+
236
+ # Prefer Hunyuan's own textured GLB when present (correct UV bake).
237
+ # Fallback: TRELLIS mesh + extracted maps via bridge packager.
238
+ bridge_input = trellis
239
+ textured = None
240
+ if hy_info and hy_info.get("textured_glb"):
241
+ candidate = Path(hy_info["textured_glb"])
242
+ if candidate.is_file():
243
+ textured = candidate
244
+ bridge_input = textured
245
+ meta["bridge_input"] = "hunyuan_textured"
246
+ # Maps already baked into textured GLB — avoid re-packing onto TRELLIS UVs
247
+ maps_dir = None
248
+ elif maps_dir is not None:
249
+ meta["bridge_input"] = "trellis_plus_maps"
250
+
251
+ bridged_dir = job_dir / "bridged"
252
+ try:
253
+ final_glb, bridge_result = run_bridge_on_trellis(
254
+ trellis_glb=bridge_input,
255
+ out_dir=bridged_dir,
256
+ hunyuan_maps_dir=maps_dir,
257
+ log=log,
258
+ )
259
+ meta["bridge"] = {
260
+ "version": bridge_result.get("bridge_version"),
261
+ "render_mode": bridge_result.get("render_mode"),
262
+ "final_glb": str(final_glb),
263
+ "normalized_glb": bridge_result.get("normalized_glb"),
264
+ "hunyuan_maps_dir": bridge_result.get("hunyuan_maps_dir"),
265
+ "hunyuan_stage": None,
266
+ }
267
+ report_path = bridged_dir / "bridge_report.json"
268
+ if report_path.is_file():
269
+ try:
270
+ report = json.loads(report_path.read_text(encoding="utf-8"))
271
+ meta["bridge"]["hunyuan_stage"] = report.get("hunyuan_stage")
272
+ meta["bridge"]["errors"] = report.get("bridge_errors") or report.get("errors")
273
+ meta["bridge"]["pbr"] = report.get("pbr")
274
+ except Exception:
275
+ pass
276
+ meta["previews"] = _collect_previews(bridged_dir, job_dir)
277
+ except Exception as exc:
278
+ meta["bridge_error"] = str(exc)
279
+ (job_dir / "meta.json").write_text(
280
+ json.dumps(meta, indent=2, default=str), encoding="utf-8"
281
+ )
282
+ log(f"[faz3] bridge failed, falling back to input glb: {exc}")
283
+ final_glb = bridge_input
284
+
285
+ out_glb = job_dir / "result.glb"
286
+ if Path(final_glb).resolve() != out_glb.resolve():
287
+ shutil.copy2(final_glb, out_glb)
288
+ else:
289
+ out_glb = Path(final_glb)
290
+
291
+ meta["artifact_glb"] = str(out_glb)
292
+ (job_dir / "meta.json").write_text(
293
+ json.dumps(meta, indent=2, default=str), encoding="utf-8"
294
+ )
295
+ log(f"[faz3] result GLB: {out_glb}")
296
+ return out_glb