Help: Write mesh vertices double floating point values with rhino3dm.js

I need to be able to write double floating point mesh vertices with rhino3dm.js so I can store real world coordinates with mm accuracy, but it seems like rhino3dm.js only supports single floating points as of now, are there any workarounds?

@stevebaer is this for you maybe?

We finally found a workaround (Me as researcher and tester and AI as executer and coder).

What is made is an IFC4 converter that meshes, cleans up and writes rhino files. First we recenter the objects to get clean meshes to start off with and then transforms these back to real world coordinates (in example 100000,1000000,0) on export. I asked the AI to explain the process to get around the single point vertex storing and this is the answer:
(For future reference if anybody finds this thread)

It would be good if rhino3dm.js could support double floating point vertices natively, but for now I am good.

.

How we got crisp vertices at real-world coordinates

The problem was floating-point precision. IFC models often live ~600 km from the origin. A 32-bit float has ~7 significant digits, so at coordinate 600 000 the smallest representable step is ~0.06 m — exactly the jagged “stair-stepping” you were seeing on the meshes.

Three places were silently rounding to fp32:

  1. The merge buffer. preMergeMeshes packed positions into a Float32Array. The moment we added worldOffset to each vertex, the result got quantised before Rhino ever saw it. Fix: when a worldOffset is present we now keep the merged positions in a Float64Array and do the matrix multiply inline in fp64 (no applyMatrix4 round-trip through a Vector3/fp32).
  2. The rhino3dm vertex store. rhino.Mesh.vertices() defaults to a single-precision Point3f array. The API exposes useDoublePrecisionVertices, but it must be set before the first add() call. We now do:
const verts = rhinoMesh.vertices();
verts.useDoublePrecisionVertices = true;
for (let i = 0; i < group.positions.length; i += 3) {
  verts.add(group.positions[i], group.positions[i+1], group.positions[i+2]);
}

JS numbers are already fp64, so as long as the buffer feeding add() is fp64 and the flag is on, the .3dm stores doubles.
3. Where the big offset lives. GLB exports never had this issue because glTF stores the large translation in the node matrix (fp64 JSON) and keeps vertices small/local. For .3dm we tried the same trick with InstanceReference, but rhino3dm.js’s binding for addInstanceObject is flaky across builds, so we fell back to baking the offset directly into vertices — which is fine once the whole pipeline (merge buffer → add() → vertex storage) is fp64 end-to-end.

Net effect: at 600 km from origin we now get sub-millimetre precision instead of ~6 cm, which is why the meshes finally look clean in Rhino.

Luis and I are planning to doing a sprint to give rhino3dm some love in the next month. We’ve learned a lot while putting this toolkit together and will probably be making some significant changes. We’ll keep your request in mind when we get to this project.

If possible can you look into writing Rhino files in a way that frees up memory as it goes along? I can easily write a 400MB GLB to a file, but writing this as a Rhino file (all meshes) eats up available web browser memory too fast. Or look into supporting WASM64 to lift the 2GB heap limit?

We’ll investigate this as well. When we last write the wasm bindings there was no automatic garbage collector available (there may still not be one). If that’s the case, you’ll need to be diligent to free objects yourself in the code.