Add standalone Huoyi training project
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create lightweight visual STL meshes by deterministic vertex clustering.
|
||||
|
||||
The source meshes are left untouched. This optimization is intended only for
|
||||
URDF ``visual`` geometry; collision geometry and all physical properties stay
|
||||
in the URDF. Vertices are snapped to a regular grid, so the maximum movement
|
||||
of any source vertex is bounded by ``sqrt(3) * grid_mm / 2``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import trimesh
|
||||
|
||||
|
||||
def optimize_mesh(source: Path, destination: Path, grid_mm: float) -> dict[str, object]:
|
||||
mesh = trimesh.load_mesh(source, process=True)
|
||||
source_vertices = len(mesh.vertices)
|
||||
source_faces = len(mesh.faces)
|
||||
|
||||
# Round each vertex to its nearest grid point. The rounding makes the
|
||||
# geometric error bounded and gives deterministic output across runs.
|
||||
clustered_vertices = np.rint(mesh.vertices / grid_mm).astype(np.int64)
|
||||
unique_grid_vertices, inverse = np.unique(clustered_vertices, axis=0, return_inverse=True)
|
||||
faces = inverse[mesh.faces]
|
||||
|
||||
# Remove triangles collapsed by clustering.
|
||||
valid = (
|
||||
(faces[:, 0] != faces[:, 1])
|
||||
& (faces[:, 1] != faces[:, 2])
|
||||
& (faces[:, 2] != faces[:, 0])
|
||||
)
|
||||
faces = faces[valid]
|
||||
|
||||
# Remove duplicate triangles while preserving the winding of the first
|
||||
# occurrence. Sorting is used only to detect duplicates.
|
||||
canonical_faces = np.sort(faces, axis=1)
|
||||
_, first_indices = np.unique(canonical_faces, axis=0, return_index=True)
|
||||
faces = faces[np.sort(first_indices)]
|
||||
|
||||
vertices = unique_grid_vertices.astype(np.float64) * grid_mm
|
||||
result = trimesh.Trimesh(vertices=vertices, faces=faces, process=False)
|
||||
result.remove_unreferenced_vertices()
|
||||
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
result.export(destination, file_type="stl")
|
||||
|
||||
return {
|
||||
"file": source.name,
|
||||
"source_vertices": source_vertices,
|
||||
"source_faces": source_faces,
|
||||
"optimized_vertices": len(result.vertices),
|
||||
"optimized_faces": len(result.faces),
|
||||
"source_bytes": source.stat().st_size,
|
||||
"optimized_bytes": destination.stat().st_size,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source_dir", type=Path)
|
||||
parser.add_argument("output_dir", type=Path)
|
||||
parser.add_argument("--grid-mm", type=float, default=1.0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.grid_mm <= 0.0:
|
||||
raise ValueError("--grid-mm must be positive")
|
||||
|
||||
source_dir = args.source_dir.resolve()
|
||||
output_dir = args.output_dir.resolve()
|
||||
records = [
|
||||
optimize_mesh(source, output_dir / source.name, args.grid_mm)
|
||||
for source in sorted(source_dir.glob("*.STL"))
|
||||
]
|
||||
if not records:
|
||||
raise RuntimeError(f"No STL files found in {source_dir}")
|
||||
|
||||
manifest = {
|
||||
"source_dir": str(source_dir),
|
||||
"grid_mm": args.grid_mm,
|
||||
"maximum_vertex_displacement_mm": math.sqrt(3.0) * args.grid_mm / 2.0,
|
||||
"source_faces": sum(int(record["source_faces"]) for record in records),
|
||||
"optimized_faces": sum(int(record["optimized_faces"]) for record in records),
|
||||
"source_bytes": sum(int(record["source_bytes"]) for record in records),
|
||||
"optimized_bytes": sum(int(record["optimized_bytes"]) for record in records),
|
||||
"meshes": records,
|
||||
}
|
||||
(output_dir / "optimization_manifest.json").write_text(
|
||||
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
print(json.dumps(manifest, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user