.dat that the server builds from it. You never write the .dat.
In a bundle, geometry is a table of blobs keyed by a geometry key, attached to objects through
relations. In versions published before 2026.9, as the Geometry
Schema describes, geometry is a list of displayValue
objects in JSON on each DataObject. 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. The writer starts a new shard,
{versionId}.geometries.{n}.parquet, before a blob would push the current one past 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 carries a type label: the SGEO primitive name (mesh,
line, curve, and so on) for display meshes and centerlines, or the host format, such as 3dm,
for a SOLID row that carries the host’s own bytes.
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. DISPLAY, SOLID, and CENTERLINE each keep their own ordinal per object, so an
object’s first display mesh is ordinal zero whether or not a solid was written before it. DEFINES
uses the definition’s member ordinal instead, shared with DEFINES_MEMBER and DEFINES_INSTANCE.
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 behind an 8-byte prefix (a
u32 length
and a reserved u32), 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,
realizations), an optional extension table, 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
(GET /api/v2/projects/{projectId}/models/{modelId}/versions/{versionId}/artifacts, one presigned
URL per file), 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.