printhive/tools/convert_3mf.py
dlawler489 a37db15a3f Initial commit: Printhive 3D-print file library
Self-hosted Express + Postgres app for organizing STL/3MF/OBJ/etc. with:
- Model/file library, tags, collections, print settings
- URL import: Thingiverse (API), Printables (GraphQL), MakerWorld
  (FlareSolverr + token), cubee3d/other (metadata + manual files)
- In-browser 3D viewer; 3MF rendered via Python/trimesh GLB conversion
- Auto thumbnails, license/commercial-use tracking, in-app editing
- Settings (API tokens / FlareSolverr) stored in DB with .env fallback

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:15:17 +10:00

44 lines
1.3 KiB
Python

#!/usr/bin/env python3
"""Convert a 3MF (incl. Bambu/MakerWorld production-extension files) to GLB.
Usage: convert_3mf.py <input.3mf> <output.glb>
Prints a JSON summary {glb, volume_cm3, bbox, faces} to stdout.
"""
import sys
import json
import trimesh
def main():
if len(sys.argv) != 3:
print(json.dumps({"error": "usage: convert_3mf.py <in.3mf> <out.glb>"}))
return 2
in_path, out_path = sys.argv[1], sys.argv[2]
scene = trimesh.load(in_path, force="scene")
geoms = list(scene.geometry.values())
if not geoms:
print(json.dumps({"error": "no geometry found in 3MF"}))
return 1
scene.export(out_path, file_type="glb")
combined = trimesh.util.concatenate(geoms)
ext = combined.bounds[1] - combined.bounds[0]
summary = {
"glb": out_path,
"bbox": {"x": round(float(ext[0]), 3), "y": round(float(ext[1]), 3), "z": round(float(ext[2]), 3)},
"faces": int(sum(len(g.faces) for g in geoms)),
}
try:
# trimesh volume is in model units (mm) -> cm^3 to match the rest of the app
summary["volume_cm3"] = round(float(combined.volume) / 1000.0, 3)
except Exception:
summary["volume_cm3"] = None
print(json.dumps(summary))
return 0
if __name__ == "__main__":
sys.exit(main())