How do I render directly to the Cesium viewer from a Plugin?


Not every graphic is an Entity. A coverage overlay, a site boundary, a route line, a heads-up label, a debug grid: these are decoration, they are not records, and they should not be selectable, styled, isolated or hidden by Navigator's own controls.

For those, draw straight onto the Cesium viewer. A Plugin is handed the live viewer and the whole Cesiumnamespace, so anything the CesiumJS documentation describes is available. What follows is the part that is specific to Navigator.

Three rules

Request a render. Navigator runs Cesium on demand rather than at a fixed frame rate, so the scene is only redrawn when something asks for it. Call: viewer.scene.requestRender()to refresh the Cesium view.

Dispose everything. A Plugin's dispose function is the only cleanup that runs. Anything added to viewer.entities, viewer.scene.primitives or viewer.dataSources outlives the Plugin otherwise, and the user has no way to remove it short of reloading.

Stay out of the register. Do not add decoration through the Menu Item manager or the visual register. Those exist to map graphics back to Entity records, and a graphic registered there becomes selectable.

Choosing where to add it

Cesium offers three surfaces and the choice matters more for a Plugin than for an app, because a Plugin shares the scene with everything Navigator has already drawn.

SurfaceWhen to use it
viewer.entitiesA handful of graphics, described declaratively. Points, labels, billboards, polylines, polygons, models. The easiest surface, and the one to reach for first. These are Cesium entities and have nothing to do with Nextspace Entities.
viewer.dataSourcesA named group you want to show, hide or remove as one, or content loaded from GeoJSON, KML or CZML. Add a CustomDataSource and put your entities in it, then removing the data source removes the lot.
viewer.scene.primitivesVolume, or a custom appearance or shader. Thousands of markers through a billboard or label collection, a ground overlay, geometry you have batched yourself. More work per graphic and much cheaper per graphic.

A data source group
Many markers, cheaply
Redrawing on camera move
A value that changes
/**
 * Background Plugin that draws a boundary and a set of labels as one
 * removable group.
 */
async function Run(params) {
    const { viewer, Cesium } = params.pluginParams;

    const source = new Cesium.CustomDataSource("MyPluginOverlay");
    await viewer.dataSources.add(source);

    const ring = params.plugin.Settings?.boundary;

    source.entities.add({
        polygon: {
            hierarchy: new Cesium.PolygonHierarchy(
                Cesium.Cartesian3.fromDegreesArray(ring)
            ),
            material: Cesium.Color.fromCssColorString("#33B1FF").withAlpha(0.25),
            // Drapes over terrain and tilesets rather than cutting through them.
            classificationType: Cesium.ClassificationType.BOTH
        },
        polyline: {
            positions: Cesium.Cartesian3.fromDegreesArray(ring),
            width: 3,
            material: Cesium.Color.fromCssColorString("#33B1FF"),
            clampToGround: true
        }
    });

    viewer.scene.requestRender();

    return () => {
        viewer.dataSources.remove(source, true);
        viewer.scene.requestRender();
    };
}

Things that will bite

Do not destroy the viewer, or replace its terrain provider, imagery layers, camera controller or clock. Navigator owns those, and a Plugin changing one changes the whole scene for every other surface. Where a Plugin genuinely needs a global change, restore the previous value in its dispose function.

Check the viewer is alive before touching it. A Plugin can be disposed while an asynchronous read is in flight, and the Project View can be torn down under it. Guard the resume path.

// Both checks, because a destroyed viewer throws on nearly every property.
if (!viewer || viewer.isDestroyed()) {
    return;
}

Prefer clamping to guessing a height. clampToGround for lines and classificationType for areas let Cesium drape the graphic over whatever is actually there. Computing a height yourself puts the graphic underground as soon as the scene has terrain or a tileset the Plugin did not expect.

Give collections a stable identity. Where a Plugin can be loaded more than once, in two Project Views or as both a cursor tool and a background script, name its data source and check for an existing one before adding a second.