Convert a single mesh file to GLB.
(input_path: Path, output_path: Path)
| 67 | |
| 68 | |
| 69 | def convert_mesh(input_path: Path, output_path: Path) -> bool: |
| 70 | """Convert a single mesh file to GLB.""" |
| 71 | print(f"\n Converting: {input_path.name} -> {output_path.name}") |
| 72 | |
| 73 | try: |
| 74 | mesh = trimesh.load(str(input_path), force='mesh') |
| 75 | |
| 76 | if not isinstance(mesh, trimesh.Trimesh): |
| 77 | print(f" x Not a valid mesh: {input_path.name}") |
| 78 | return False |
| 79 | |
| 80 | cleaned_mesh = clean_mesh(mesh) |
| 81 | |
| 82 | # Strip embedded materials/textures to prevent Unreal's Interchange importer |
| 83 | # from creating a Texture2D instead of a StaticMesh. |
| 84 | # Preserve UV coordinates so textures can be applied via material instances. |
| 85 | if hasattr(cleaned_mesh.visual, 'uv') and cleaned_mesh.visual.uv is not None: |
| 86 | uv = cleaned_mesh.visual.uv.copy() |
| 87 | cleaned_mesh.visual = trimesh.visual.TextureVisuals(uv=uv) |
| 88 | else: |
| 89 | cleaned_mesh.visual = trimesh.visual.ColorVisuals() |
| 90 | |
| 91 | if len(cleaned_mesh.vertices) == 0 or len(cleaned_mesh.faces) == 0: |
| 92 | print(f" x Mesh became empty after cleaning") |
| 93 | return False |
| 94 | |
| 95 | bounds = cleaned_mesh.bounds |
| 96 | size = bounds[1] - bounds[0] |
| 97 | print(f" Bounds: {size}") |
| 98 | |
| 99 | if np.allclose(size, 0): |
| 100 | print(f" Warning: Mesh has zero size!") |
| 101 | |
| 102 | cleaned_mesh.export(str(output_path)) |
| 103 | print(f" -> Saved: {output_path.name}") |
| 104 | return True |
| 105 | |
| 106 | except Exception as e: |
| 107 | print(f" x Error: {e}") |
| 108 | return False |
| 109 | |
| 110 | |
| 111 | def _parse_floats(s: str) -> list: |
no test coverage detected