Speckle Offline loader

Hello!

I’m working on offline model data caching. I implemented offline loader what works with a simple JSON structure and it uses speckle/objectloader2.

I would like to implement JSON export from Speckle scene. Somebody can help me to a give an example to query JSON model data with GrapQL? For example I would like to load this scene from JSON:

I just would like to show this scene (geometry, material etc.) with offline loader from JSON.

This is my JSON offline loader code:

import {
    Viewer,
    DefaultViewerParams,
    CameraController,
    SelectionExtension,
    SpeckleLoader
} from '@speckle/viewer';
import { ObjectLoader2Factory } from '@speckle/objectloader2';

class CustomSpeckleOfflineLoader extends SpeckleLoader {
    constructor(targetTree: any, resourceData: unknown) {
        super(targetTree, 'about:blank', undefined, undefined, resourceData);

        this.loader = ObjectLoader2Factory.createFromObjects(
            Array.isArray(resourceData) ? resourceData : [resourceData]
        );
    }

    protected initObjectLoader(
        _resource: string,
        _authToken?: string,
        _enableCaching?: boolean
    ): any {
        return this.loader;
    }
}

async function main() {
    const container = document.getElementById('renderer') as HTMLCanvasElement;
    if (!container) throw new Error('Canvas element not found');

    const viewer = new Viewer(container, { ...DefaultViewerParams, showStats: false, verbose: true });
    await viewer.init();

    viewer.createExtension(CameraController);
    viewer.createExtension(SelectionExtension);

    const loadButton = document.createElement('button');
    loadButton.textContent = 'Load JSON';
    loadButton.style.position = 'absolute';
    loadButton.style.top = '10px';
    loadButton.style.left = '10px';
    loadButton.style.zIndex = '10';
    document.body.appendChild(loadButton);

    loadButton.addEventListener('click', async () => {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.click();

        fileInput.onchange = async () => {
            const files = fileInput.files;
            if (!files || files.length === 0) {
                alert('Please select a JSON file!');
                return;
            }

            const file = files[0];
            try {
                const text = await file.text();
                const jsonData = JSON.parse(text);

                const loader = new CustomSpeckleOfflineLoader(viewer.getWorldTree(), jsonData);
                await viewer.loadObject(loader, true);

                console.log(`File ${file.name} successfully loaded!`);
            } catch (err) {
                console.error('Error loading JSON:', err);
                alert('An error occurred while loading the JSON. Please check the file!');
            }
        };
    });
}

main();

And it is simple JSON structure what the loader can eat:

{
  "speckle_type": "Objects.Other.Base",
  "id": "offline_base_root",
  "applicationId": "offline",
  "totalChildrenCount": 1,
  "createdAt": "2025-09-29T00:00:00.000Z",
  "createdBy": "offline",
  "units": "meters",
  "elements": [
    {
      "speckle_type": "Objects.Other.Base",
      "id": "cube_mesh_base_1",
      "applicationId": "offline",
      "totalChildrenCount": 1,
      "createdAt": "2025-09-29T00:00:00.000Z",
      "createdBy": "offline",
      "units": "meters",
      "elements": [
        {
          "speckle_type": "Objects.Geometry.Mesh",
          "id": "cube_mesh_1",
          "vertices": [
            0,0,0,1,0,0,1,1,0,0,1,0,
            0,0,1,1,0,1,1,1,1,0,1,1
          ],
          "faces": [
            0,0,1,2,0,2,3,0,
            0,4,5,6,0,6,7,4,
            0,0,1,5,0,5,4,0,
            0,1,2,6,0,6,5,1,
            0,2,3,7,0,7,6,2,
            0,3,0,4,0,4,7,3
          ],
          "colors": [4294967295],
          "units": "meters"
        }
      ]
    }
  ]
}

Hey @Robert_Oze

Interesting approach! Before we get into GraphQL/JSON details, can I ask what problem you’re trying to solve by exporting versions to JSON and re-loading them offline?

Speckle is already built around:

  • caching and streaming data efficiently,
  • letting the viewer load objects on demand,
  • and handling large models without forcing everything into a single JSON blob.

So, if the end goal is “I need a model available on a plane,” “I want to archive a snapshot,” or “I want to bypass the Speckle server,” these are pretty different use cases. The platform may already cover some of them without requiring you to roll your own exporter.

Once we understand the why, we can guide you on whether:

  • GraphQL + REST export really is the right path,
  • or whether there’s a more straightforward way to get the same outcome with Speckle’s existing tooling.

Or not,… or something completely different…

1 Like

Well, yes. Basically, it was a stillborn idea, but with good compression, you can achieve roughly 2–3 times faster loading speed, so that’s how I managed to make it work.