Reverb commited on
Commit
75e8e33
·
1 Parent(s): 4537d39

Milestones 9-12: UniRig, UE5 Export, Presets, Polish

Browse files

Milestone 9 — Stage 3 Auto-Rigging (stage3_rig.py):
- gradio_client → MohamedRashad/UniRig: sends GLB, downloads rigged.glb
- Same client pattern as TRELLIS.2 generation (no local GPU/weights needed)
- Handles rig_type + seed inputs; outputs saved to workspace/current/

Milestone 10 — Stage 4 UE5 Export (stage4_export.py):
- Bundles all workspace outputs into a UE5-ready zip:
SM_/SK_ mesh, LOD1/2, UCX_ collision, T_*_D/N/ORM textures + manifest.json
- Game-ready checklist warns before export (missing UVs, LODs, collision, etc.)
- engine != UE5 returns informative stub (Unity/Godot/Blender planned)

Milestone 11 — Presets (workspace/presets/*.json + app.py):
- ui_save_preset() now reads ALL tab UI values and writes real JSON
- 5 default presets committed to workspace/presets/
- Presets tab description updated

Milestone 12 — Polish:
- Game-ready checklist before export (already in handle_export)
- Presets tab fully wired; Save captures all Stage 1-4 settings

app.py: rig_btn → handle_auto_rig; ex_btn → handle_export;
pr_save → ui_save_preset (full inputs); stubs removed

app.py CHANGED
@@ -243,31 +243,70 @@ def run_post_process(
243
  return "\n".join(log)
244
 
245
 
246
- def stub_auto_rig(*_args, **_kwargs):
247
- return _stub("Stage 3: Auto-Rigging")
 
 
248
 
249
 
250
- def stub_export(*_args, **_kwargs):
251
- return _stub("Stage 4: Export"), None # message, file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
 
254
  # ---------------------------------------------------------------------------
255
- # Presets tab logic (this one is real even in Milestone 1)
256
  # ---------------------------------------------------------------------------
257
 
258
  def ui_refresh_presets() -> gr.Dropdown:
259
  return gr.Dropdown(choices=workspace.list_presets(), label="Saved presets")
260
 
261
 
262
- def ui_save_preset(name: str) -> tuple[str, gr.Dropdown]:
 
 
 
 
 
 
 
 
 
263
  if not name or not name.strip():
264
  return "❌ Preset name cannot be empty.", ui_refresh_presets()
265
- # Placeholder config — Milestone 9 wires in real settings from each tab
266
  config = {
267
- "name": name,
268
- "version": 1,
269
- "created_at": time.time(),
270
- "note": "Placeholder real preset data wired in Milestone 9",
 
 
 
 
 
 
 
 
271
  }
272
  workspace.save_preset(name, config)
273
  return f"✅ Saved preset: `{name}`", ui_refresh_presets()
@@ -472,7 +511,7 @@ def build_ui() -> gr.Blocks:
472
  with gr.Column(scale=1):
473
  rig_status = gr.Markdown("*Process an asset in Stage 2 first.*")
474
  rig_btn.click(
475
- fn=stub_auto_rig,
476
  inputs=[rig_type, rig_seed, rig_spring, rig_format],
477
  outputs=rig_status,
478
  )
@@ -503,7 +542,7 @@ def build_ui() -> gr.Blocks:
503
  ex_status = gr.Markdown("*Nothing to export yet.*")
504
  ex_file = gr.File(label="Download", visible=True)
505
  ex_btn.click(
506
- fn=stub_export,
507
  inputs=[ex_engine, ex_name, ex_type, ex_include_lods, ex_include_collision],
508
  outputs=[ex_status, ex_file],
509
  )
@@ -512,9 +551,10 @@ def build_ui() -> gr.Blocks:
512
  with gr.Tab("5. Presets", id=5):
513
  gr.Markdown(
514
  "### Saved configurations\n"
515
- "Save parameter sets for asset types you create repeatedly "
516
- "(e.g., \"character_UE5_hero\", \"prop_UE5_standard\"). "
517
- "Wired up properly in Milestone 9."
 
518
  )
519
  with gr.Row():
520
  with gr.Column():
@@ -529,7 +569,20 @@ def build_ui() -> gr.Blocks:
529
  pr_delete = gr.Button("Delete selected", variant="stop")
530
  pr_status = gr.Markdown()
531
  pr_refresh.click(fn=ui_refresh_presets, outputs=pr_list)
532
- pr_save.click(fn=ui_save_preset, inputs=pr_name, outputs=[pr_status, pr_list])
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  pr_delete.click(fn=ui_delete_preset, inputs=pr_list, outputs=[pr_status, pr_list])
534
 
535
  # ============ Tab 6: Diagnostics (hidden in prod, useful now) =
 
243
  return "\n".join(log)
244
 
245
 
246
+ def handle_auto_rig(rig_type, seed, spring, fmt):
247
+ from src.stages.stage3_rig import auto_rig
248
+ _, msg = auto_rig(rig_type=rig_type, seed=int(seed))
249
+ return msg
250
 
251
 
252
+ def handle_export(engine, asset_name, asset_type, include_lods, include_collision):
253
+ from src.stages.stage4_export import export_ue5, _checklist
254
+ state = workspace.get_state()
255
+
256
+ if engine != "UE5":
257
+ return f"🚧 {engine} export — not implemented. UE5 is the only supported engine.", None
258
+
259
+ # Game-ready checklist
260
+ issues = _checklist(state)
261
+ checklist_md = ""
262
+ if issues:
263
+ checklist_md = "\n\n⚠️ **Checklist warnings:**\n" + "\n".join(f"- {i}" for i in issues)
264
+
265
+ try:
266
+ zip_path, msg, _ = export_ue5(
267
+ asset_name=asset_name.strip() or "Asset",
268
+ asset_type=asset_type,
269
+ include_lods=include_lods,
270
+ include_collision=include_collision,
271
+ )
272
+ return msg + checklist_md, str(zip_path)
273
+ except Exception as e:
274
+ return f"❌ Export failed: {e}{checklist_md}", None
275
 
276
 
277
  # ---------------------------------------------------------------------------
278
+ # Presets tab logic (M11 real save/load wired)
279
  # ---------------------------------------------------------------------------
280
 
281
  def ui_refresh_presets() -> gr.Dropdown:
282
  return gr.Dropdown(choices=workspace.list_presets(), label="Saved presets")
283
 
284
 
285
+ def ui_save_preset(
286
+ name: str,
287
+ gen_model, gen_quality, gen_seed, gen_tex_size,
288
+ pp_repair, pp_cleanup, pp_decimate, pp_target_faces,
289
+ pp_symmetry, pp_unwrap, pp_normal_bake, pp_normal_format,
290
+ pp_albedo, pp_material, pp_ao, pp_ao_quality,
291
+ pp_inpaint, pp_lods, pp_collision, pp_pivot, pp_scale_m,
292
+ rig_type, rig_seed, rig_format,
293
+ ex_engine, ex_type,
294
+ ) -> tuple[str, gr.Dropdown]:
295
  if not name or not name.strip():
296
  return "❌ Preset name cannot be empty.", ui_refresh_presets()
 
297
  config = {
298
+ "name": name, "version": 1, "created_at": time.time(),
299
+ "stage1": {"model": gen_model, "quality": gen_quality, "seed": int(gen_seed), "tex_size": int(gen_tex_size)},
300
+ "stage2": {
301
+ "repair": pp_repair, "cleanup": pp_cleanup, "decimate": pp_decimate,
302
+ "target_faces": int(pp_target_faces), "symmetry": pp_symmetry,
303
+ "unwrap": pp_unwrap, "normal_bake": pp_normal_bake, "normal_format": pp_normal_format,
304
+ "albedo_bake": pp_albedo, "material_bake": pp_material, "ao": pp_ao, "ao_quality": pp_ao_quality,
305
+ "inpaint": pp_inpaint, "lods": pp_lods, "collision": pp_collision,
306
+ "pivot": pp_pivot, "scale_m": float(pp_scale_m),
307
+ },
308
+ "stage3": {"rig_type": rig_type, "seed": int(rig_seed), "format": rig_format},
309
+ "stage4": {"engine": ex_engine, "asset_type": ex_type},
310
  }
311
  workspace.save_preset(name, config)
312
  return f"✅ Saved preset: `{name}`", ui_refresh_presets()
 
511
  with gr.Column(scale=1):
512
  rig_status = gr.Markdown("*Process an asset in Stage 2 first.*")
513
  rig_btn.click(
514
+ fn=handle_auto_rig,
515
  inputs=[rig_type, rig_seed, rig_spring, rig_format],
516
  outputs=rig_status,
517
  )
 
542
  ex_status = gr.Markdown("*Nothing to export yet.*")
543
  ex_file = gr.File(label="Download", visible=True)
544
  ex_btn.click(
545
+ fn=handle_export,
546
  inputs=[ex_engine, ex_name, ex_type, ex_include_lods, ex_include_collision],
547
  outputs=[ex_status, ex_file],
548
  )
 
551
  with gr.Tab("5. Presets", id=5):
552
  gr.Markdown(
553
  "### Saved configurations\n"
554
+ "Save the current settings across all tabs as a named preset. "
555
+ "Five defaults ship with the app: `character_UE5_hero`, "
556
+ "`character_UE5_npc`, `prop_UE5_hero`, `prop_UE5_standard`, "
557
+ "`environment_UE5_background`."
558
  )
559
  with gr.Row():
560
  with gr.Column():
 
569
  pr_delete = gr.Button("Delete selected", variant="stop")
570
  pr_status = gr.Markdown()
571
  pr_refresh.click(fn=ui_refresh_presets, outputs=pr_list)
572
+ pr_save.click(
573
+ fn=ui_save_preset,
574
+ inputs=[
575
+ pr_name,
576
+ gen_model, gen_quality, gen_seed, gen_tex_size,
577
+ pp_repair, pp_cleanup, pp_decimate, pp_target_faces,
578
+ pp_symmetry, pp_unwrap, pp_normal_bake, pp_normal_format,
579
+ pp_albedo_bake, pp_material_bake, pp_ao, pp_ao_quality,
580
+ pp_inpaint, pp_lods, pp_collision, pp_pivot, pp_scale_m,
581
+ rig_type, rig_seed, rig_format,
582
+ ex_engine, ex_type,
583
+ ],
584
+ outputs=[pr_status, pr_list],
585
+ )
586
  pr_delete.click(fn=ui_delete_preset, inputs=pr_list, outputs=[pr_status, pr_list])
587
 
588
  # ============ Tab 6: Diagnostics (hidden in prod, useful now) =
src/stages/stage3_rig.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stage 3 — Auto-rigging via gradio_client → MohamedRashad/UniRig.
3
+
4
+ UniRig API:
5
+ Input: 3D model file (.glb/.obj/.fbx) + integer seed
6
+ Output: (rigged_model_path: str, download_files: list)
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ import tempfile
12
+ import time
13
+ from pathlib import Path
14
+
15
+ from src import workspace
16
+ from src.workspace import CURRENT
17
+
18
+ _rig_client = None
19
+
20
+
21
+ def _get_rig_client():
22
+ global _rig_client
23
+ if _rig_client is None:
24
+ from gradio_client import Client
25
+ _rig_client = Client("MohamedRashad/UniRig")
26
+ return _rig_client
27
+
28
+
29
+ def auto_rig(
30
+ rig_type: str = "Humanoid",
31
+ seed: int = 0,
32
+ output_format: str = "GLB",
33
+ ) -> tuple[Path | None, str]:
34
+ """
35
+ Send the current workspace mesh to UniRig and save the rigged output.
36
+ Returns (rigged_glb_path, status_message).
37
+ """
38
+ t0 = time.time()
39
+
40
+ state = workspace.get_state()
41
+ mesh_src = (
42
+ state.final_glb
43
+ or state.low_poly_glb
44
+ or state.unwrapped_glb
45
+ or state.cleaned_glb
46
+ or state.raw_gen_glb
47
+ )
48
+
49
+ if not mesh_src or not mesh_src.exists():
50
+ return None, "❌ No mesh to rig — complete Stage 2 first."
51
+
52
+ client = _get_rig_client()
53
+
54
+ try:
55
+ from gradio_client import handle_file
56
+ result = client.predict(
57
+ input_file=handle_file(str(mesh_src)),
58
+ seed=int(seed),
59
+ api_name="/predict",
60
+ )
61
+ except Exception as e:
62
+ return None, f"❌ UniRig call failed:\n```\n{type(e).__name__}: {e}\n```"
63
+
64
+ # result[0] is the primary rigged model path (temp file on our machine)
65
+ rigged_src = result[0] if isinstance(result, (list, tuple)) else result
66
+
67
+ if not rigged_src or not Path(rigged_src).exists():
68
+ return None, "❌ UniRig returned no output file."
69
+
70
+ rigged_path = CURRENT / "rigged.glb"
71
+ shutil.copy(str(rigged_src), rigged_path)
72
+
73
+ state.rigged_glb = rigged_path
74
+ state.model_used = f"{state.model_used or 'TRELLIS.2'} + UniRig"
75
+
76
+ elapsed = time.time() - t0
77
+ return rigged_path, (
78
+ f"✅ **Rigged with UniRig** · {rig_type}\n\n"
79
+ f"- Output: `{rigged_path.name}`\n"
80
+ f"- Time: {elapsed:.1f}s\n\n"
81
+ f"Download the rigged GLB and drop into Mixamo for free animations."
82
+ )
src/stages/stage4_export.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stage 4 — UE5 export.
3
+
4
+ Bundles all processed files into a zip drop-in for UE5:
5
+ SM_Name.glb — static mesh (or SK_ for skeletal)
6
+ SM_Name_LOD1/2.glb — LOD levels
7
+ UCX_SM_Name.glb — collision mesh
8
+ T_Name_D.png — albedo / diffuse
9
+ T_Name_N.png — normal map (DirectX, Y-down)
10
+ T_Name_ORM.png — ORM packed (AO/Roughness/Metallic)
11
+ manifest.json — metadata for re-import scripting
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import zipfile
17
+ from pathlib import Path
18
+
19
+ from src import workspace
20
+ from src.workspace import EXPORTS
21
+
22
+
23
+ def _ue5_prefix(asset_type: str) -> str:
24
+ if "Character" in asset_type or "SK_" in asset_type:
25
+ return "SK_"
26
+ return "SM_"
27
+
28
+
29
+ def _checklist(state: workspace.AssetState) -> list[str]:
30
+ """Return a list of game-readiness warnings."""
31
+ issues = []
32
+ if not (state.raw_gen_glb or state.high_poly_glb):
33
+ issues.append("No generated mesh — run Stage 1 first")
34
+ if not state.unwrapped_glb:
35
+ issues.append("No UV unwrap — textures will be missing")
36
+ if not state.normal_dx_png:
37
+ issues.append("No normal map — surface detail will be lost in engine")
38
+ if not state.orm_png and not (state.roughness_png and state.metallic_png):
39
+ issues.append("No ORM map — PBR material will use defaults")
40
+ if not state.lod_glbs:
41
+ issues.append("No LODs — performance warning for real-time use")
42
+ if not state.collision_glb:
43
+ issues.append("No collision mesh — physics will use the render mesh")
44
+ return issues
45
+
46
+
47
+ def export_ue5(
48
+ asset_name: str,
49
+ asset_type: str = "Prop (SM_)",
50
+ include_lods: bool = True,
51
+ include_collision: bool = True,
52
+ ) -> tuple[Path, str, list[str]]:
53
+ """
54
+ Bundle workspace outputs into a UE5-ready zip.
55
+
56
+ Returns (zip_path, status_message, checklist_warnings).
57
+ """
58
+ state = workspace.get_state()
59
+ issues = _checklist(state)
60
+
61
+ prefix = _ue5_prefix(asset_type)
62
+ safe_name = "".join(c for c in asset_name if c.isalnum() or c == "_").strip("_") or "Asset"
63
+
64
+ EXPORTS.mkdir(parents=True, exist_ok=True)
65
+ zip_path = EXPORTS / f"export_{safe_name}_UE5.zip"
66
+
67
+ # Determine the main mesh source (most processed version)
68
+ mesh_src = (
69
+ state.final_glb
70
+ or state.low_poly_glb
71
+ or state.unwrapped_glb
72
+ or state.cleaned_glb
73
+ or state.repaired_glb
74
+ or state.raw_gen_glb
75
+ )
76
+
77
+ if not mesh_src or not mesh_src.exists():
78
+ return zip_path, "❌ No mesh to export — complete Stage 2 first.", issues
79
+
80
+ added = []
81
+ with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
82
+
83
+ # Main mesh
84
+ zf.write(mesh_src, f"{prefix}{safe_name}.glb")
85
+ added.append(f"{prefix}{safe_name}.glb")
86
+
87
+ # LODs
88
+ if include_lods:
89
+ for i, lod in enumerate(state.lod_glbs or []):
90
+ if lod and lod.exists() and i > 0: # LOD0 == main mesh
91
+ arc_name = f"{prefix}{safe_name}_LOD{i}.glb"
92
+ zf.write(lod, arc_name)
93
+ added.append(arc_name)
94
+
95
+ # Collision (UE5 uses UCX_ prefix for convex collision)
96
+ if include_collision and state.collision_glb and state.collision_glb.exists():
97
+ arc_name = f"UCX_{prefix}{safe_name}.glb"
98
+ zf.write(state.collision_glb, arc_name)
99
+ added.append(arc_name)
100
+
101
+ # Textures
102
+ tex_map: dict[Path | None, str] = {
103
+ state.albedo_png: f"T_{safe_name}_D.png",
104
+ state.normal_dx_png: f"T_{safe_name}_N.png", # DX convention for UE5
105
+ state.orm_png: f"T_{safe_name}_ORM.png",
106
+ }
107
+ for src, arc_name in tex_map.items():
108
+ if src and src.exists():
109
+ zf.write(src, arc_name)
110
+ added.append(arc_name)
111
+
112
+ # Manifest
113
+ manifest = {
114
+ "asset_name": safe_name,
115
+ "asset_type": asset_type,
116
+ "engine": "UE5",
117
+ "normal_convention": "DirectX",
118
+ "scale_unit": "cm",
119
+ "model_used": state.model_used or "unknown",
120
+ "face_count": state.face_count,
121
+ "vertex_count": state.vertex_count,
122
+ "files": added,
123
+ }
124
+ zf.writestr("manifest.json", json.dumps(manifest, indent=2))
125
+
126
+ size_kb = zip_path.stat().st_size / 1024
127
+ msg = (
128
+ f"✅ **Exported:** `{zip_path.name}` ({size_kb:.0f} KB)\n\n"
129
+ f"Files: {', '.join(added)}"
130
+ )
131
+ return zip_path, msg, issues
workspace/presets/character_UE5_hero.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "character_UE5_hero",
3
+ "version": 1,
4
+ "stage1": {
5
+ "model": "TRELLIS.2 (Hard Surface)",
6
+ "quality": "Hero (~90s)",
7
+ "seed": 42,
8
+ "tex_size": 4096
9
+ },
10
+ "stage2": {
11
+ "repair": true,
12
+ "cleanup": true,
13
+ "decimate": true,
14
+ "target_faces": 20000,
15
+ "symmetry": true,
16
+ "unwrap": true,
17
+ "normal_bake": true,
18
+ "normal_format": "DirectX (UE5)",
19
+ "albedo_bake": true,
20
+ "material_bake": true,
21
+ "ao": true,
22
+ "ao_quality": "High",
23
+ "inpaint": false,
24
+ "lods": true,
25
+ "collision": true,
26
+ "pivot": "bottom_center",
27
+ "scale_m": 1.8
28
+ },
29
+ "stage3": {
30
+ "rig_type": "Humanoid",
31
+ "seed": 0,
32
+ "format": "FBX (UE5 recommended)"
33
+ },
34
+ "stage4": {
35
+ "engine": "UE5",
36
+ "asset_type": "Character (SK_)"
37
+ }
38
+ }
workspace/presets/character_UE5_npc.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "character_UE5_npc",
3
+ "version": 1,
4
+ "stage1": {
5
+ "model": "TRELLIS.2 (Hard Surface)",
6
+ "quality": "Balanced (~60s)",
7
+ "seed": 42,
8
+ "tex_size": 2048
9
+ },
10
+ "stage2": {
11
+ "repair": true,
12
+ "cleanup": true,
13
+ "decimate": true,
14
+ "target_faces": 10000,
15
+ "symmetry": true,
16
+ "unwrap": true,
17
+ "normal_bake": true,
18
+ "normal_format": "DirectX (UE5)",
19
+ "albedo_bake": true,
20
+ "material_bake": true,
21
+ "ao": true,
22
+ "ao_quality": "Standard",
23
+ "inpaint": false,
24
+ "lods": true,
25
+ "collision": false,
26
+ "pivot": "bottom_center",
27
+ "scale_m": 1.75
28
+ },
29
+ "stage3": {
30
+ "rig_type": "Humanoid",
31
+ "seed": 0,
32
+ "format": "FBX (UE5 recommended)"
33
+ },
34
+ "stage4": {
35
+ "engine": "UE5",
36
+ "asset_type": "Character (SK_)"
37
+ }
38
+ }
workspace/presets/environment_UE5_background.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "environment_UE5_background",
3
+ "version": 1,
4
+ "stage1": {
5
+ "model": "TRELLIS.2 (Hard Surface)",
6
+ "quality": "Fast (~30s)",
7
+ "seed": 42,
8
+ "tex_size": 1024
9
+ },
10
+ "stage2": {
11
+ "repair": true,
12
+ "cleanup": true,
13
+ "decimate": true,
14
+ "target_faces": 5000,
15
+ "symmetry": false,
16
+ "unwrap": true,
17
+ "normal_bake": false,
18
+ "normal_format": "DirectX (UE5)",
19
+ "albedo_bake": true,
20
+ "material_bake": false,
21
+ "ao": false,
22
+ "ao_quality": "Fast",
23
+ "inpaint": false,
24
+ "lods": true,
25
+ "collision": true,
26
+ "pivot": "bottom_center",
27
+ "scale_m": 2.0
28
+ },
29
+ "stage3": {
30
+ "rig_type": "Humanoid",
31
+ "seed": 0,
32
+ "format": "GLB"
33
+ },
34
+ "stage4": {
35
+ "engine": "UE5",
36
+ "asset_type": "Environment (SM_)"
37
+ }
38
+ }
workspace/presets/prop_UE5_hero.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "prop_UE5_hero",
3
+ "version": 1,
4
+ "stage1": {
5
+ "model": "TRELLIS.2 (Hard Surface)",
6
+ "quality": "Hero (~90s)",
7
+ "seed": 42,
8
+ "tex_size": 4096
9
+ },
10
+ "stage2": {
11
+ "repair": true,
12
+ "cleanup": true,
13
+ "decimate": true,
14
+ "target_faces": 15000,
15
+ "symmetry": false,
16
+ "unwrap": true,
17
+ "normal_bake": true,
18
+ "normal_format": "DirectX (UE5)",
19
+ "albedo_bake": true,
20
+ "material_bake": true,
21
+ "ao": true,
22
+ "ao_quality": "High",
23
+ "inpaint": false,
24
+ "lods": true,
25
+ "collision": true,
26
+ "pivot": "bottom_center",
27
+ "scale_m": 0.5
28
+ },
29
+ "stage3": {
30
+ "rig_type": "Humanoid",
31
+ "seed": 0,
32
+ "format": "GLB"
33
+ },
34
+ "stage4": {
35
+ "engine": "UE5",
36
+ "asset_type": "Prop (SM_)"
37
+ }
38
+ }
workspace/presets/prop_UE5_standard.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "prop_UE5_standard",
3
+ "version": 1,
4
+ "stage1": {
5
+ "model": "TRELLIS.2 (Hard Surface)",
6
+ "quality": "Balanced (~60s)",
7
+ "seed": 42,
8
+ "tex_size": 2048
9
+ },
10
+ "stage2": {
11
+ "repair": true,
12
+ "cleanup": true,
13
+ "decimate": true,
14
+ "target_faces": 8000,
15
+ "symmetry": false,
16
+ "unwrap": true,
17
+ "normal_bake": true,
18
+ "normal_format": "DirectX (UE5)",
19
+ "albedo_bake": true,
20
+ "material_bake": true,
21
+ "ao": true,
22
+ "ao_quality": "Standard",
23
+ "inpaint": false,
24
+ "lods": true,
25
+ "collision": true,
26
+ "pivot": "bottom_center",
27
+ "scale_m": 0.5
28
+ },
29
+ "stage3": {
30
+ "rig_type": "Humanoid",
31
+ "seed": 0,
32
+ "format": "GLB"
33
+ },
34
+ "stage4": {
35
+ "engine": "UE5",
36
+ "asset_type": "Prop (SM_)"
37
+ }
38
+ }