"""Fingerprint a .3dm (run inside Rhino 8 via runner). Globals: MODEL_PATH, OUT_PATH, optional FAST. Opens MODEL_PATH headless (the active doc is untouched) and writes, per object id (user string `id`, falling back to the object name): layer path, geometry type, visibility, colour, bbox (0.01 mm), brep face count / solidity / volume (mm3, 1 decimal), curve length (0.001 mm) and all user strings. Hidden and locked objects are included. """ import json import Rhino import Rhino.Geometry as rg from Rhino.DocObjects import ObjectEnumeratorSettings FAST = globals().get("FAST", False) d = Rhino.RhinoDoc.OpenHeadless(MODEL_PATH) if d is None: raise RuntimeError(f"cannot open {MODEL_PATH}") try: s = ObjectEnumeratorSettings() s.HiddenObjects = True s.LockedObjects = True objs, layers = {}, {} for o in d.Objects.GetObjectList(s): a = o.Attributes oid = a.GetUserString("id") or a.Name or str(o.Id) lay = d.Layers[a.LayerIndex] layers[lay.FullPath] = [lay.Color.R, lay.Color.G, lay.Color.B, lay.IsVisible] g = o.Geometry bb = g.GetBoundingBox(True) rec = {"layer": lay.FullPath, "type": type(g).__name__, "hidden": o.IsHidden, "locked": o.IsLocked, "colour_source": int(a.ColorSource), "colour": [a.ObjectColor.R, a.ObjectColor.G, a.ObjectColor.B], "bbox": [round(v, 2) for v in (bb.Min.X, bb.Min.Y, bb.Min.Z, bb.Max.X, bb.Max.Y, bb.Max.Z)], "user": {k: a.GetUserString(k) for k in sorted(a.GetUserStrings().AllKeys)}} b = g if isinstance(g, rg.Brep) else (rg.Brep.TryConvertBrep(g) if isinstance(g, rg.Extrusion) else None) if b is not None: rec["faces"], rec["solid"] = b.Faces.Count, b.IsSolid if FAST and "-R-" in oid: # rebar pipe: bbox + face count only (volume dominates run time) rec["volume"] = "skipped" else: mp = rg.VolumeMassProperties.Compute(b) if b.IsSolid else None rec["volume"] = round(mp.Volume, 1) if mp else None elif isinstance(g, rg.Curve): rec["length"] = round(g.GetLength(), 3) if oid in objs: oid = f"{oid}#dup{sum(k.startswith(oid) for k in objs)}" objs[oid] = rec out = {"model": MODEL_PATH, "units": str(d.ModelUnitSystem), "tolerance": d.ModelAbsoluteTolerance, "count": len(objs), "layers": layers, "objects": objs} json.dump(out, open(OUT_PATH, "w", encoding="utf-8"), ensure_ascii=False) print(json.dumps({"count": len(objs), "units": out["units"], "tolerance": out["tolerance"]})) finally: d.Dispose()