This is 2026.9 documentation. Coverage here is incremental: a page exists only where 2026.9
differs or is newly documented. For any other topic, switch to Current (2026.8 and earlier) in
the version selector.
.dat that the server builds from it. You never write the .dat.
Before 2026.9, as the Geometry Schema describes, geometry is a list of
displayValue objects in JSON on each DataObject. In 2026.9 it is a table of blobs keyed by a
geometry key, attached to objects through relations. This page covers the blob format, how the table
is laid out, and what the .dat is for. For which relations attach geometry to what, see Display
geometry.
Where geometry lives in a bundle
The geometry table is{versionId}.geometries.parquet, sharded into {versionId}.geometries.{n}.parquet
files once the current shard holds 1536 MiB of uncompressed content. Shard zero keeps the canonical
name, so a small model has one file and a reader always globs all shards.
Each row holds one blob, its dense integer key, and an id that is the SHA-256 of the blob bytes.
Because every producer encodes SGEO identically, the same mesh from any host gets the same id, and
readers deduplicate on it. A row also records its encoding: display meshes and centerlines are
SGEO, while a SOLID row carries the host’s own bytes, such as a 3dm body, with a type label.
Nothing in the geometry table says which object a blob belongs to. That is a relation: DISPLAY,
SOLID, or CENTERLINE from the object, or DEFINES from a definition node whose placements
reuse the blob. Each of those keeps its own ordinal per object, so an object’s first display mesh is
ordinal zero whether or not a solid was written before it.
The SGEO header
Every blob starts with 16 bytes, little-endian.
Conventions that every encoder follows:
- The body starts 8-byte aligned at
0x10, and every array of doubles stays 8-aligned. Pairs of 32-bit scalars are padded to keep it so. - Doubles are IEEE-754 binary64. Face indices and colors are signed 32-bit integers.
- Derived values such as length, area, volume, bounding box, and arc radius are not stored. Readers recompute them.
- One unit per blob. Sub-objects inside a composite lose their own units.
Bodies
The mesh body is the one every producer needs.
Curves follow one rule: the viewer has no NURBS evaluator, so a curve body leads with the display
polyline it renders and trails with the analytical definition (degree, points, weights, knots). A
polycurve or region nests each segment as its own SGEO blob, prefixed by its length and padded to
8 bytes. Arcs, circles, and ellipses are stored analytically. Points and point clouds store
coordinates plus optional colors and sizes.
The viewer .dat
After the upload completes, the server builds {versionId}.viewer.dat from the bundle and appends
it to the version’s artifact list. The version is not visible until that build finishes. The WebGPU
viewer downloads only the .dat, never the parquet files.
The .dat is one self-contained binary: a geometry region of chunk-encoded meshes and lines, an
index region of fixed-order sections (meta, primitives, chunks, placements, materials, colors), and
a 128-byte trailer that maps the file. Its current format expands reusable definitions into
placements and gives each occurrence a realization id, which is what lets the viewer select one
instance of a repeated object. That identity exists only in the .dat. The bundle keeps the compact
authored graph.
The
.dat is an internal artifact of the viewer and the server. Its layout can change without a
bundle spec change. Do not parse it, and do not write one.Read geometry from a script
Receive the version as aModel through receive3 / Receive3. The SDK decodes SGEO into the
mesh and curve types you already use and exposes them per object. If you read the parquet files
directly through the artifacts endpoint, decode the blobs with the SDK decoder or a port of it, and
read every geometry shard.
FAQ
I am not using an SDK. How do I decode the geometry?
I am not using an SDK. How do I decode the geometry?
Read the parquet geometry table, not the
.dat. Download every geometry shard from the
artifacts listing and open the files with any parquet library. Each row’s blob starts with the
16-byte header on this page: check the magic, read the primitive type and unit code, and verify
the CRC-32 over the body. Then parse the body for that primitive, starting with the mesh layout
above. Port the decoder from sgeo.py in specklepy or SgeoDecoder in Speckle.Sdk rather than
writing one from the tables alone; both are short and tested. The .dat is the viewer’s own
build of the same geometry and is not readable by anything else.Is the .dat open source? Can I use it outside the Speckle viewer?
Is the .dat open source? Can I use it outside the Speckle viewer?
No. The
.dat is built by the server and read only by the Speckle viewer. Its layout is not
published, it can change without a bundle spec change, and it carries viewer-specific
realization ids that mean nothing elsewhere. There is nothing to license because there is
nothing to build against. The portable geometry is the bundle itself: SGEO blobs in the parquet
geometry table, with open-source decoders in specklepy and Speckle.Sdk. To show a model, embed
the Speckle viewer rather than reading its artifact.Can I send only a solid and skip the display mesh?
Can I send only a solid and skip the display mesh?
No. The solid is the host’s own format and only a matching host can read it. Every consumer,
including the viewer, renders the SGEO display mesh. Ship both.
What happens if a primitive has no SGEO type?
What happens if a primitive has no SGEO type?
Skip that primitive, keep the owning object, and report the skip. If nothing renderable remains
and there is no solid, mark the object as failed in your results so the report matches the
bundle.