Reverb commited on
Commit
f9271f9
·
1 Parent(s): c6d0961

Fix pymeshlab pipeline crash + add post-process progress streaming

Browse files

- Re-add pymeshlab to requirements.txt: hy3dshape/postprocessors.py
imports it at module level, so removing it broke _get_hy_pipeline().
The import itself works fine; only the OpenGL-dependent filter plugins
print warnings (which we no longer call from our own code).
- Replace inline pymeshlab decimation in generate_hunyuan with
fast_simplification to match stage2_decimate.py
- run_post_process() is now a streaming generator: yields cumulative
status markdown at each step so the UI updates live (⏳ before, ✅/⚠️
after each stage completes)

Files changed (3) hide show
  1. app.py +55 -43
  2. requirements.txt +5 -3
  3. src/stages/stage1_generate.py +11 -16
app.py CHANGED
@@ -103,153 +103,165 @@ def run_post_process(
103
  do_albedo, do_material, do_ao, ao_quality,
104
  do_inpaint, do_lods, do_collision, pivot, scale_m,
105
  ):
106
- state = workspace.get_state() # rebuilt from disk by get_state()
 
107
  current_glb = state.raw_gen_glb or state.high_poly_glb
108
  if not current_glb or not current_glb.exists():
109
- return "❌ No generated asset found. Run Stage 1 (Generate) first."
 
110
 
111
  log = []
112
 
 
 
 
 
113
  if do_repair:
 
114
  try:
115
  from src.stages.stage2_repair import repair_mesh
116
  current_glb, msg = repair_mesh(current_glb)
117
- log.append(f"✅ {msg}")
118
  except Exception as e:
119
- log.append(f"⚠️ Repair error: {e}")
120
 
121
  if do_cleanup:
 
122
  try:
123
  from src.stages.stage2_cleanup import cleanup_mesh
124
  current_glb, msg = cleanup_mesh(current_glb)
125
- log.append(f"✅ {msg}")
126
  except Exception as e:
127
- log.append(f"⚠️ Cleanup error: {e}")
128
 
129
  if do_decimate:
 
130
  try:
131
  from src.stages.stage2_decimate import decimate_mesh_final
132
  current_glb, msg = decimate_mesh_final(current_glb, int(target_faces))
133
- log.append(f"✅ {msg}")
134
  except Exception as e:
135
- log.append(f"⚠️ Decimation error: {e}")
136
 
137
  if do_symmetry:
 
138
  try:
139
  from src.stages.stage2_symmetry import apply_symmetry
140
- sym_axis = "bilateral-X" # default; exposed via Advanced in M4 polish
141
- current_glb, msg = apply_symmetry(current_glb, sym_axis)
142
- log.append(f"✅ {msg}")
143
  except Exception as e:
144
- log.append(f"⚠️ Symmetry error: {e}")
145
 
146
  if do_unwrap:
 
147
  try:
148
  from src.stages.stage2_uv import unwrap_uvs
149
  current_glb, msg = unwrap_uvs(current_glb)
150
- log.append(f"✅ {msg}")
151
  except Exception as e:
152
- log.append(f"⚠️ UV unwrap error: {e}")
153
 
154
  if do_normal_bake:
 
155
  try:
156
  from src.stages.stage2_bake_normal import bake_normal_map
157
  st = workspace.get_state()
158
  hp = st.high_poly_glb
159
  lo = st.unwrapped_glb or current_glb
160
  if not hp or not hp.exists():
161
- log.append("⚠️ Normal bake: no high-poly GLB. Generate first.")
162
  else:
163
  _gl, _dx, msg = bake_normal_map(hp, lo, map_size=2048, dx_format=True)
164
- log.append(f"✅ {msg}")
165
  except Exception as e:
166
- log.append(f"⚠️ Normal bake error: {e}")
167
 
168
  st = workspace.get_state()
169
  hp = st.high_poly_glb
170
  lo = st.unwrapped_glb or current_glb
171
 
172
  if do_albedo:
 
173
  try:
174
  from src.stages.stage2_bake_albedo import bake_albedo
175
  if not hp or not hp.exists():
176
- log.append("⚠️ Albedo bake: no high-poly. Generate first.")
177
  else:
178
  _, msg = bake_albedo(hp, lo, map_size=2048)
179
- log.append(f"✅ {msg}")
180
  except Exception as e:
181
- log.append(f"⚠️ Albedo bake error: {e}")
182
 
183
  if do_material:
 
184
  try:
185
  from src.stages.stage2_bake_albedo import bake_material
186
  if not hp or not hp.exists():
187
- log.append("⚠️ Material bake: no high-poly. Generate first.")
188
  else:
189
  _, _, msg = bake_material(hp, lo, map_size=2048)
190
- log.append(f"✅ {msg}")
191
  except Exception as e:
192
- log.append(f"⚠️ Material bake error: {e}")
193
 
194
  if do_ao:
 
195
  try:
196
  from src.stages.stage2_bake_ao import bake_ao
197
  _, msg = bake_ao(current_glb, lo, map_size=2048, quality=ao_quality)
198
- log.append(f"✅ {msg}")
199
  except Exception as e:
200
- log.append(f"⚠️ AO bake error: {e}")
201
 
202
- # Pack ORM if we have the component maps
203
  st2 = workspace.get_state()
 
204
  if do_albedo or do_material or do_ao:
 
205
  try:
206
  from src.stages.stage2_finalize import pack_orm
207
  _, msg = pack_orm(st2.ao_png, st2.roughness_png, st2.metallic_png)
208
- log.append(f"✅ {msg}")
209
  except Exception as e:
210
- log.append(f"⚠️ ORM pack error: {e}")
211
 
212
  if do_lods:
 
213
  try:
214
  from src.stages.stage2_finalize import generate_lods
215
  lod_src = st2.final_glb or st2.low_poly_glb or current_glb
216
  _, msg = generate_lods(lod_src)
217
- log.append(f"✅ {msg}")
218
  except Exception as e:
219
- log.append(f"⚠️ LOD error: {e}")
220
 
221
  if do_collision:
 
222
  try:
223
  from src.stages.stage2_finalize import generate_collision
224
  col_src = st2.low_poly_glb or current_glb
225
  _, msg = generate_collision(col_src)
226
- log.append(f"✅ {msg}")
227
  except Exception as e:
228
- log.append(f"⚠️ Collision error: {e}")
229
 
230
- # Pivot + scale (always run if we have a final mesh)
231
  try:
232
  from src.stages.stage2_finalize import set_pivot, validate_scale
233
  piv_src = st2.low_poly_glb or current_glb
234
  piv_src, msg = set_pivot(piv_src, pivot)
235
- log.append(f"✅ {msg}")
236
  _, msg = validate_scale(piv_src, float(scale_m))
237
- log.append(f"✅ {msg}")
238
  except Exception as e:
239
- log.append(f"⚠️ Pivot/scale error: {e}")
240
 
241
  if do_inpaint:
242
- log.append("🚧 SDXL inpaint — Milestone 7 (not yet implemented)")
243
-
244
- if not log:
245
- return "No processing steps selected."
246
 
247
  final = workspace.get_state()
248
  out = final.final_glb or final.low_poly_glb or final.cleaned_glb or final.repaired_glb
249
  if out and out.exists():
250
- log.append(f"\n**Output:** `{out.name}`")
251
-
252
- return "\n".join(log)
253
 
254
 
255
  def handle_auto_rig(rig_type, seed, spring, fmt):
 
103
  do_albedo, do_material, do_ao, ao_quality,
104
  do_inpaint, do_lods, do_collision, pivot, scale_m,
105
  ):
106
+ """Run post-processing pipeline. Yields cumulative status markdown for streaming."""
107
+ state = workspace.get_state()
108
  current_glb = state.raw_gen_glb or state.high_poly_glb
109
  if not current_glb or not current_glb.exists():
110
+ yield "❌ No generated asset found. Run Stage 1 (Generate) first."
111
+ return
112
 
113
  log = []
114
 
115
+ def _emit(line: str):
116
+ log.append(line)
117
+ return "\n".join(log)
118
+
119
  if do_repair:
120
+ yield _emit("⏳ Repairing mesh (pymeshfix)...")
121
  try:
122
  from src.stages.stage2_repair import repair_mesh
123
  current_glb, msg = repair_mesh(current_glb)
124
+ yield _emit(f"✅ {msg}")
125
  except Exception as e:
126
+ yield _emit(f"⚠️ Repair error: {e}")
127
 
128
  if do_cleanup:
129
+ yield _emit("⏳ Cleaning geometry (trimesh)...")
130
  try:
131
  from src.stages.stage2_cleanup import cleanup_mesh
132
  current_glb, msg = cleanup_mesh(current_glb)
133
+ yield _emit(f"✅ {msg}")
134
  except Exception as e:
135
+ yield _emit(f"⚠️ Cleanup error: {e}")
136
 
137
  if do_decimate:
138
+ yield _emit(f"⏳ Decimating to {int(target_faces):,} faces...")
139
  try:
140
  from src.stages.stage2_decimate import decimate_mesh_final
141
  current_glb, msg = decimate_mesh_final(current_glb, int(target_faces))
142
+ yield _emit(f"✅ {msg}")
143
  except Exception as e:
144
+ yield _emit(f"⚠️ Decimation error: {e}")
145
 
146
  if do_symmetry:
147
+ yield _emit("⏳ Enforcing bilateral symmetry...")
148
  try:
149
  from src.stages.stage2_symmetry import apply_symmetry
150
+ current_glb, msg = apply_symmetry(current_glb, "bilateral-X")
151
+ yield _emit(f"✅ {msg}")
 
152
  except Exception as e:
153
+ yield _emit(f"⚠️ Symmetry error: {e}")
154
 
155
  if do_unwrap:
156
+ yield _emit("⏳ UV unwrapping (xatlas)...")
157
  try:
158
  from src.stages.stage2_uv import unwrap_uvs
159
  current_glb, msg = unwrap_uvs(current_glb)
160
+ yield _emit(f"✅ {msg}")
161
  except Exception as e:
162
+ yield _emit(f"⚠️ UV unwrap error: {e}")
163
 
164
  if do_normal_bake:
165
+ yield _emit("⏳ Baking normal map (GPU)...")
166
  try:
167
  from src.stages.stage2_bake_normal import bake_normal_map
168
  st = workspace.get_state()
169
  hp = st.high_poly_glb
170
  lo = st.unwrapped_glb or current_glb
171
  if not hp or not hp.exists():
172
+ yield _emit("⚠️ Normal bake: no high-poly GLB. Generate first.")
173
  else:
174
  _gl, _dx, msg = bake_normal_map(hp, lo, map_size=2048, dx_format=True)
175
+ yield _emit(f"✅ {msg}")
176
  except Exception as e:
177
+ yield _emit(f"⚠️ Normal bake error: {e}")
178
 
179
  st = workspace.get_state()
180
  hp = st.high_poly_glb
181
  lo = st.unwrapped_glb or current_glb
182
 
183
  if do_albedo:
184
+ yield _emit("⏳ Baking albedo map (GPU)...")
185
  try:
186
  from src.stages.stage2_bake_albedo import bake_albedo
187
  if not hp or not hp.exists():
188
+ yield _emit("⚠️ Albedo bake: no high-poly. Generate first.")
189
  else:
190
  _, msg = bake_albedo(hp, lo, map_size=2048)
191
+ yield _emit(f"✅ {msg}")
192
  except Exception as e:
193
+ yield _emit(f"⚠️ Albedo bake error: {e}")
194
 
195
  if do_material:
196
+ yield _emit("⏳ Baking material maps (GPU)...")
197
  try:
198
  from src.stages.stage2_bake_albedo import bake_material
199
  if not hp or not hp.exists():
200
+ yield _emit("⚠️ Material bake: no high-poly. Generate first.")
201
  else:
202
  _, _, msg = bake_material(hp, lo, map_size=2048)
203
+ yield _emit(f"✅ {msg}")
204
  except Exception as e:
205
+ yield _emit(f"⚠️ Material bake error: {e}")
206
 
207
  if do_ao:
208
+ yield _emit(f"⏳ Baking AO ({ao_quality}, ray casting)...")
209
  try:
210
  from src.stages.stage2_bake_ao import bake_ao
211
  _, msg = bake_ao(current_glb, lo, map_size=2048, quality=ao_quality)
212
+ yield _emit(f"✅ {msg}")
213
  except Exception as e:
214
+ yield _emit(f"⚠️ AO bake error: {e}")
215
 
 
216
  st2 = workspace.get_state()
217
+
218
  if do_albedo or do_material or do_ao:
219
+ yield _emit("⏳ Packing ORM texture...")
220
  try:
221
  from src.stages.stage2_finalize import pack_orm
222
  _, msg = pack_orm(st2.ao_png, st2.roughness_png, st2.metallic_png)
223
+ yield _emit(f"✅ {msg}")
224
  except Exception as e:
225
+ yield _emit(f"⚠️ ORM pack error: {e}")
226
 
227
  if do_lods:
228
+ yield _emit("⏳ Generating LODs...")
229
  try:
230
  from src.stages.stage2_finalize import generate_lods
231
  lod_src = st2.final_glb or st2.low_poly_glb or current_glb
232
  _, msg = generate_lods(lod_src)
233
+ yield _emit(f"✅ {msg}")
234
  except Exception as e:
235
+ yield _emit(f"⚠️ LOD error: {e}")
236
 
237
  if do_collision:
238
+ yield _emit("⏳ Generating collision mesh (CoACD)...")
239
  try:
240
  from src.stages.stage2_finalize import generate_collision
241
  col_src = st2.low_poly_glb or current_glb
242
  _, msg = generate_collision(col_src)
243
+ yield _emit(f"✅ {msg}")
244
  except Exception as e:
245
+ yield _emit(f"⚠️ Collision error: {e}")
246
 
247
+ yield _emit("⏳ Setting pivot and scale...")
248
  try:
249
  from src.stages.stage2_finalize import set_pivot, validate_scale
250
  piv_src = st2.low_poly_glb or current_glb
251
  piv_src, msg = set_pivot(piv_src, pivot)
252
+ yield _emit(f"✅ {msg}")
253
  _, msg = validate_scale(piv_src, float(scale_m))
254
+ yield _emit(f"✅ {msg}")
255
  except Exception as e:
256
+ yield _emit(f"⚠️ Pivot/scale error: {e}")
257
 
258
  if do_inpaint:
259
+ yield _emit("🚧 SDXL inpaint — not yet implemented")
 
 
 
260
 
261
  final = workspace.get_state()
262
  out = final.final_glb or final.low_poly_glb or final.cleaned_glb or final.repaired_glb
263
  if out and out.exists():
264
+ yield _emit(f"\n**Output:** `{out.name}` · ready for Stage 3 or Export.")
 
 
265
 
266
 
267
  def handle_auto_rig(rig_type, seed, spring, fmt):
requirements.txt CHANGED
@@ -38,9 +38,11 @@ onnxruntime
38
  # ===== Milestone 3: Stage 2A-2C Mesh Cleanup =====
39
  trimesh[easy]
40
  pymeshfix
41
- # pymeshlab excluded: its mesh-filter plugins need libOpenGL.so.0 which is
42
- # absent on ZeroGPU Spaces (headless, no display server). Decimation and
43
- # LOD generation use fast-simplification (VTK, no OpenGL) instead.
 
 
44
  fast-simplification
45
 
46
  # ===== Milestone 4: Stage 2D-2E UV + Symmetry =====
 
38
  # ===== Milestone 3: Stage 2A-2C Mesh Cleanup =====
39
  trimesh[easy]
40
  pymeshfix
41
+ # pymeshlab is required by hy3dshape/postprocessors.py (imported at module
42
+ # level, so must be installable). Its OpenGL-dependent filter plugins print
43
+ # warnings on ZeroGPU headless but do NOT crash on import. Our own pipeline
44
+ # stages use fast-simplification for decimation/LODs to avoid those filters.
45
+ pymeshlab
46
  fast-simplification
47
 
48
  # ===== Milestone 4: Stage 2D-2E UV + Symmetry =====
src/stages/stage1_generate.py CHANGED
@@ -339,23 +339,18 @@ def generate_hunyuan(
339
  mesh.export(str(high_poly_path))
340
 
341
  try:
342
- import pymeshlab
343
  import trimesh as _tm
344
- with tempfile.NamedTemporaryFile(suffix=".ply", delete=False) as tmp:
345
- ply_in = tmp.name
346
- mesh.export(ply_in)
347
- ms = pymeshlab.MeshSet()
348
- ms.load_new_mesh(ply_in)
349
- ms.apply_filter(
350
- "meshing_decimation_quadric_edge_collapse",
351
- targetfacenum=max(100, preset["decimation_target"]),
352
- preservenormal=True,
353
- autoclean=True,
354
- )
355
- with tempfile.NamedTemporaryFile(suffix=".ply", delete=False) as tmp2:
356
- ply_out = tmp2.name
357
- ms.save_current_mesh(ply_out)
358
- working = _tm.load(ply_out, force="mesh")
359
  working.export(str(raw_gen_path))
360
  except Exception:
361
  shutil.copy(str(high_poly_path), str(raw_gen_path))
 
339
  mesh.export(str(high_poly_path))
340
 
341
  try:
342
+ import fast_simplification
343
  import trimesh as _tm
344
+ target = max(100, preset["decimation_target"])
345
+ base_faces = len(mesh.faces)
346
+ reduction = max(0.0, min(1.0 - target / base_faces, 0.99)) if base_faces > target else 0.0
347
+ if reduction > 0.0:
348
+ pts_out, faces_out = fast_simplification.simplify(
349
+ mesh.vertices, mesh.faces, target_reduction=reduction
350
+ )
351
+ working = _tm.Trimesh(vertices=pts_out, faces=faces_out, process=False)
352
+ else:
353
+ working = mesh
 
 
 
 
 
354
  working.export(str(raw_gen_path))
355
  except Exception:
356
  shutil.copy(str(high_poly_path), str(raw_gen_path))