(input_path: str, target_faces: int, tmp_dir: str)
| 112 | |
| 113 | |
| 114 | def _decimate(input_path: str, target_faces: int, tmp_dir: str) -> trimesh.Trimesh: |
| 115 | loaded = trimesh.load(input_path) |
| 116 | if isinstance(loaded, trimesh.Scene): |
| 117 | geoms = list(loaded.geometry.values()) |
| 118 | geom = trimesh.util.concatenate(geoms) if len(geoms) > 1 else geoms[0] |
| 119 | else: |
| 120 | geom = loaded |
| 121 | |
| 122 | ms = _pymeshlab.MeshSet() |
| 123 | |
| 124 | if _has_texture(geom): |
| 125 | # ── Textured path: OBJ intermediate to preserve UV coordinates ────── |
| 126 | obj_in = os.path.join(tmp_dir, "input.obj") |
| 127 | mtl_in = os.path.join(tmp_dir, "input.mtl") |
| 128 | tex_in = os.path.join(tmp_dir, "texture.png") |
| 129 | obj_out = os.path.join(tmp_dir, "output.obj") |
| 130 | |
| 131 | # Save texture image under a known filename (handles PBR and simple materials) |
| 132 | _get_texture_image(geom).save(tex_in) |
| 133 | |
| 134 | # Export OBJ (trimesh writes UV coords + MTL) |
| 135 | geom.export(obj_in) |
| 136 | |
| 137 | # Patch MTL so any map_Kd points to our known texture filename |
| 138 | if os.path.exists(mtl_in): |
| 139 | mtl = open(mtl_in).read() |
| 140 | mtl = re.sub(r"map_Kd\s+\S+", "map_Kd texture.png", mtl) |
| 141 | open(mtl_in, "w").write(mtl) |
| 142 | |
| 143 | ms.load_new_mesh(obj_in) |
| 144 | ms.meshing_decimation_quadric_edge_collapse( |
| 145 | targetfacenum=target_faces, |
| 146 | preservetexcoord=True, # ← keeps UV coordinates intact |
| 147 | preservenormal=True, |
| 148 | preservetopology=True, |
| 149 | autoclean=True, |
| 150 | ) |
| 151 | ms.save_current_mesh(obj_out) |
| 152 | |
| 153 | # Patch output MTL too, so trimesh can find the texture on load |
| 154 | mtl_out = obj_out.replace(".obj", ".mtl") |
| 155 | if os.path.exists(mtl_out): |
| 156 | mtl = open(mtl_out).read() |
| 157 | mtl = re.sub(r"map_Kd\s+\S+", "map_Kd texture.png", mtl) |
| 158 | open(mtl_out, "w").write(mtl) |
| 159 | |
| 160 | return trimesh.load(obj_out) |
| 161 | |
| 162 | else: |
| 163 | # ── Geometry-only path: PLY (fast, no texture to worry about) ──────── |
| 164 | ply_in = os.path.join(tmp_dir, "input.ply") |
| 165 | ply_out = os.path.join(tmp_dir, "output.ply") |
| 166 | |
| 167 | geom.export(ply_in) |
| 168 | ms.load_new_mesh(ply_in) |
| 169 | ms.meshing_decimation_quadric_edge_collapse( |
| 170 | targetfacenum=target_faces, |
| 171 | preservenormal=True, |
no test coverage detected