Skip to main content
Version: Next

Prioritize nodes for loading

A developer may assign nodes of a model as "prioritized" in relation to geometry loading. By giving nodes a "prioritization weight", a developer can influence Reveal to load, or keep loaded, certain parts of a model when it would otherwise discard them in favor of e.g. geometry that is closer to the camera. Note that assigning prioritization weights to nodes does not guarantee that they will be loaded at all times; the loader will still account for the rendering budget.

Nodes are given load priority through the node styling API. The NodeAppearance type (see Cad Styling) includes the optional field prioritizedForLoadingHint which can be used to provide a load priority to the node collection. Higher values will make the related geometry more likely to be loaded. While any number can be used as the load priority, it is recommended to use values between 1 and 10. 5 is considered a reasonable default value.

Not all node collection types support the priority hint. An overview of current support is provided in the following table.

NodeCollectionTypeSupports prioritizedForLoadingHint?
TreeIndexNodeCollectionMust be manually provided, see below
PropertyFilterNodeCollectionYes
SinglePropertyFilterNodeCollectionYes
AssetNodeCollectionYes
InvertedNodeCollectionNo
UnionNodeCollectionYes
IntersectionNodeCollectionYes
note

Currently, TreeIndexNodeCollection does not take responsibility to compute the location of the geometry it holds, which the loader needs to prioritize the geometry. Alternatively, the developer may supply this information themselves by using the addAreaPoints() and addAreas() methods on the TreeIndexNodeCollection.

A node collection that is the result of an operation (e.g. a union) involving another node collection that does not support prioritizedForLoadingHint, will not support the prioritizedForLoadingHint field either.

Prioritize highlighted nodes for loading

Many use cases in Reveal involve highlighting a few nodes while the rest of the model is hidden or ghosted. These use cases can benefit from the prioritization feature. Assigning a priority to the nodes of interest in addition to the hightlighting, makes them more likely to be loaded even when the camera is far away:

// import { SinglePropertyFilterNodeCollection } from '@cognite/reveal'

const nodeSet = new SinglePropertyFilterNodeCollection(sdk, model, { requestPartitions: 10 });
const names = ['/60-EL-9016-Q200-E01', '/60-EL-9016-Q200-E02', '/60-EL-9016-Q200-E03'];
nodeSet.executeFilter('Item', 'Name', names);

model.setDefaultNodeAppearance(DefaultNodeAppearance.Ghosted);
model.assignStyledNodeCollection(nodeSet, { ...DefaultNodeAppearance.Highlighted, prioritizedForLoadingHint: 5 });
Unassign the previous collection before prioritizing a new one

A node collection assigned with prioritizedForLoadingHint keeps contributing to load prioritization until it is explicitly unassigned. If your application changes which nodes are prioritized over time — for example, a different set of nodes per navigation step, sequence step, or user selection — you must call model.unassignStyledNodeCollection(nodeSet) (or model.removeAllStyledNodeCollections()) for the previous collection before assigning a new one:

// When switching to a new set of prioritized nodes:
model.unassignStyledNodeCollection(previousNodeSet);
model.assignStyledNodeCollection(newNodeSet, { ...DefaultNodeAppearance.Highlighted, prioritizedForLoadingHint: 5 });

Failing to unassign previous collections causes prioritized sets to accumulate indefinitely. Since every accumulated collection is still evaluated during sector load prioritization, this can severely degrade performance over time — even if each individual set was small enough to be a reasonable prioritization hint on its own.


High-detail output for a pre-computed node subset

Prerequisite

This feature requires a gltf-prioritized-nodes-directory output to exist for the model revision. This output is produced by a separate backend job — Reveal does not trigger that job. If the output does not exist, addCadModel will throw an error.

In addition to the hint-based prioritization above, Reveal supports loading a pre-computed high-detail geometry output for a specific subset of nodes. This output (File3dFormat.GltfPrioritizedNodes) contains full-resolution geometry only for the nodes that were included in the backend job, making it suited for inspection-style use cases where selected objects must always be rendered at maximum detail.

Three usage patterns are supported, and they can be combined depending on the use case.

Option 1 — Replace the model

Swap the entire model to the prioritized output. The original model is removed and the prioritized version is loaded in its place. This is the simplest approach when the use case is a dedicated high-detail view.

// import { File3dFormat } from '@cognite/reveal'

// Remove the standard model first
viewer.removeModel(mainModel);

// Load the prioritized output in its place
const prioritizedModel = await viewer.addCadModel({
modelId,
revisionId,
outputFormat: File3dFormat.GltfPrioritizedNodes
});

viewer.loadCameraFromModel(prioritizedModel);

To revert, remove prioritizedModel and reload without outputFormat:

viewer.removeModel(prioritizedModel);
const standardModel = await viewer.addCadModel({ modelId, revisionId });
Guard against overlapping calls

addCadModel is a network request. If the load-prioritized/revert action can be triggered again before a previous call resolves (e.g. double clicks), calls can resolve out of order and leave the wrong model (or two models) loaded. See examples/src/utils/PrioritizedNodesUI.ts for a version of this pattern hardened against overlapping calls.


Option 2 — Overlay specific nodes

Load the prioritized output as a second model on top of the original, then use styling to ghost the background and show only the selected nodes at full detail. This keeps the surrounding model visible as context.

// import { File3dFormat, NodeIdNodeCollection, DefaultNodeAppearance } from '@cognite/reveal'

let overlayModel; // keep a reference so it can be removed later

function removeOverlay() {
if (overlayModel) {
viewer.removeModel(overlayModel);
overlayModel = undefined;
}
mainModel.setDefaultNodeAppearance(DefaultNodeAppearance.Default);
mainModel.removeAllStyledNodeCollections();
}

async function loadOverlayView(nodeIds) {
// Always clean up any previous overlay first, otherwise overlay models and
// styled collections accumulate every time this is called with a new set of nodes.
removeOverlay();

// Load prioritized output as overlay
overlayModel = await viewer.addCadModel({
modelId,
revisionId,
outputFormat: File3dFormat.GltfPrioritizedNodes
});

// Ghost the original model and hide the selected nodes within it
// (they will be shown at full detail by the overlay instead)
mainModel.setDefaultNodeAppearance(DefaultNodeAppearance.Ghosted);
const mainNodeSet = new NodeIdNodeCollection(client, mainModel);
await mainNodeSet.executeFilter(nodeIds);
mainModel.assignStyledNodeCollection(mainNodeSet, { visible: false });

// In the overlay, hide everything except the selected nodes
overlayModel.setDefaultNodeAppearance({ visible: false });
const overlayNodeSet = new NodeIdNodeCollection(client, overlayModel);
await overlayNodeSet.executeFilter(nodeIds);
overlayModel.assignStyledNodeCollection(overlayNodeSet, DefaultNodeAppearance.Default);
}

Call removeOverlay() on its own to remove the overlay and restore the original appearance without loading a new one.

Must remove the previous overlay before loading a new one — and guard against overlapping calls

If your application changes the overlaid node set over time (e.g. a different selection per navigation step), you must remove the existing overlay model and its styled collections before loading the next one, as shown above. Skipping this causes overlay models and styled node collections to accumulate on every switch, which severely degrades performance.

The sample above is simplified for readability and is not safe against overlapping calls: addCadModel and executeFilter are network requests and can resolve out of the order they were started. If loadOverlayView is called again before a previous call finishes (rapid selection changes, double clicks), a stale, superseded call can still resolve afterwards and apply its (now outdated) result, overwrite a newer call's state, orphan a model, or surface a stale error — since none of that is guarded here.

Do not hand-roll this from the snippet above for production use. Reuse the LatestCallGuard pattern from the fully worked, race-condition-hardened reference implementation instead: examples/src/utils/PrioritizedNodesUI.ts in this repository, which applies the same guard consistently to all three options on this page (Replace, Overlay, Lock).

note

If any of the supplied nodeIds do not exist in the model/revision, NodeIdNodeCollection.executeFilter will silently return an empty set for those IDs. Check that the resolved IndexSet is non-empty before proceeding.


Option 3 — Lock sectors on the main model

Prevent specific nodes' sectors from being evicted when the CAD render budget is reduced. Locked sectors remain loaded regardless of camera position or budget pressure.

Node locking uses tree indices, not node IDs. Use NodeIdNodeCollection to resolve node IDs to tree indices first:

// import { NodeIdNodeCollection, DefaultNodeAppearance } from '@cognite/reveal'

async function lockNodes(nodeIds) {
const nodeCollection = new NodeIdNodeCollection(client, mainModel);
await nodeCollection.executeFilter(nodeIds);

const treeIndices = nodeCollection.getIndexSet().toIndexArray();

if (treeIndices.length === 0) {
// None of the node IDs exist in this model/revision
return;
}

// Release any previously locked set right before applying the new one, otherwise locked
// tree indices and styled collections accumulate every time this is called with a new set.
mainModel.unlockAllTreeIndices();
mainModel.removeAllStyledNodeCollections();

// Style and lock
mainModel.assignStyledNodeCollection(nodeCollection, DefaultNodeAppearance.Default);
mainModel.lockTreeIndices(treeIndices);
}
Must release the previous lock before locking a new set — and guard against overlapping calls

Locking a new set of nodes does not automatically release a previously locked set, as shown above — otherwise locked tree indices and styled collections accumulate across every switch, and locked sectors are never evicted (see Limitations), so the render budget consumed by locking keeps growing.

The sample above is simplified for readability and is not safe against overlapping calls: executeFilter is a network request and can resolve out of the order it was started. If lockNodes can be called again before a previous call finishes (rapid selection changes), a stale, superseded call can still resolve afterwards and apply its now-outdated lock, overwrite a newer call's state, or surface a stale error — none of that is guarded here.

Do not hand-roll this from the snippet above for production use. Reuse the LatestCallGuard pattern from the fully worked, race-condition-hardened reference implementation instead: examples/src/utils/PrioritizedNodesUI.ts in this repository, which applies the same guard consistently to all three options on this page (Replace, Overlay, Lock).

Locking is reactive: if a locked tree index's geometry is discovered in a new sector during loading, that sector is automatically locked as well.

To unlock individual tree indices:

mainModel.unlockTreeIndices(treeIndices);

To remove all locks at once:

mainModel.unlockAllTreeIndices();
mainModel.removeAllStyledNodeCollections();

Limitations

LimitationDetail
Backend prerequisiteA gltf-prioritized-nodes-directory output must already exist for the revision. Reveal does not trigger backend jobs.
Locked sectors count toward render budgetLocked sectors are never evicted, but they still contribute to the total render cost. Locking many sectors may degrade performance on lower-end devices.
lockTreeIndices requires tree indicesNode IDs must be resolved to tree indices first (e.g. via NodeIdNodeCollection).
Lock mode does not increase geometry detailLocking sectors on the standard model prevents eviction but does not load higher-detail geometry. For higher detail, use the prioritized output (Option 1 or 2).
GltfPrioritizedNodes contains only a subsetThe prioritized output only contains geometry for the nodes included in the backend job. It cannot be used as a full model replacement if coverage of all nodes is required.