Appearance
Terrain
Inspect and manage the project's site terrain — the singleton created by core.io.import.terrain. At most one terrain exists per project. All edits are undoable. Accessed via snaptrude.core.io.terrain.
Datum = the terrain's vertical position. setDatum(offset) shifts the terrain vertically by a relative offset (cumulative — each call moves it further, not to a fixed level); getDatum() reads the terrain's current vertical position (world Y, internal units — import places the highest point at y = 0, so a never-shifted terrain reads a negative baseline, not 0).
Types
TerrainReport
Cut/fill earthwork report: { cutVolume: number; fillVolume: number; netVolume: number }.
Functions
exists()
Whether the project has a site terrain. → boolean.
get()
Resolve the project's TerrainHandle, or null. Same handle import.terrain returns.
getDatum()
The terrain's current vertical position (world Y, internal units), or null if no terrain. A never-shifted terrain reads its import baseline (negative), not 0.
setDatum(offset)
Shift the whole terrain vertically down by offset (internal units) from its current position; pass a negative value to raise it. Relative and cumulative — setDatum(5) twice shifts down by 10 total, not to a fixed level. Undoable. Throws if writes are disabled, there is no terrain, or the terrain is locked.
ts
await snaptrude.core.io.terrain.setDatum(12); // shift terrain down by 12 (relative)delete()
Delete the site terrain. Undoable. Throws if none.
getReport()
The cut/fill TerrainReport, or null.
ts
const r = await snaptrude.core.io.terrain.getReport();
if (r) console.log(`cut ${r.cutVolume}, fill ${r.fillVolume}, net ${r.netVolume}`);replaceMesh(positions, indices, units?, options?)
Replace the terrain surface with a caller-supplied triangle mesh. Undoable (one step), and the replacement persists with the project (the mesh is stored in bucket storage; reload applies it instead of the map heightmap). The returned promise resolves only after the surface is fully applied and recorded — it is safe to toggle terrain resolution or read the surface back the moment it resolves.
positions is a flat [x, y, z, …] array in world space — the same frame design.query.geometry.getTriangulatedMeshes reads, so a read-modify-write round trip needs no conversion. indices is a flat triangle list into positions. Pass units: "meters" to author in metres instead of internal units.
Survives: datum, geolocation, opacity, lock state, elevation/satellite toggles (the map texture is re-projected best-effort — approximate alignment, reported in warnings). Resets: cut/fill history and the earthwork report. A predominantly down-facing mesh is flipped automatically (warned); zero-area slivers are tolerated and counted.
Options (all optional):
| Field | Meaning |
|---|---|
coordinateSpace: "world" | Declares the input frame (world is the only supported space) |
expectedTerrain | Throws HANDLE_INVALID if the live terrain is not this handle (session-scoped) |
expectedModelRevision | Compare-and-swap against the session terrain-mesh revision (starts at 0, increments per replace; the result returns the new value). Stale → PRECONDITION_FAILED |
baseline: "make-replacement-original" | Declares the (only, default) semantics: the replacement becomes the persisted surface; the app's terrain Reset restores the original heightmap |
preserve.datum: false | Reset the terrain transform — authored coordinates become the local frame |
preserve.satellite: false | Switch the satellite drape off after the replace |
preserve.material: false | Skip texture re-projection — the whole surface takes the ground material |
preserve.geolocation: false | Rejected (VALIDATION) — geolocation is always preserved in this version |
clientMutationId | Session-scoped idempotency: a repeated id returns the original result without re-executing |
Limits: 500,000 vertices / 500,000 triangles; every coordinate finite and within the scene bound.
→ { terrain: TerrainHandle; modelRevision: number; vertexCount: number; triangleCount: number; warnings: string[] }
Throws if writes are disabled, there is no terrain, the terrain is locked, the mesh fails validation, expectedTerrain mismatches, or expectedModelRevision is stale.
ts
// Projects with no terrain yet: import first, then replace.
if (!(await snaptrude.core.io.terrain.exists())) {
await snaptrude.core.io.import.terrain(40.7128, -74.006, 300, 300);
}
const result = await snaptrude.core.io.terrain.replaceMesh(positions, indices, "meters", {
expectedTerrain: await snaptrude.core.io.terrain.get(),
preserve: { datum: true, geolocation: true, satellite: true, material: true },
clientMutationId: "survey-r1"
});
// result.modelRevision → pass back as expectedModelRevision next timeisElevationEnabled() / enableElevation() / disableElevation()
Read/toggle terrain elevation (DEM height — a real 3D topography vs a flat plane). Toggles are undoable. Read → boolean | null.
isSatelliteEnabled() / enableSatellite() / disableSatellite()
Read/toggle draped satellite imagery. Toggles are undoable. Read → boolean | null.
getOpacity() / setOpacity(opacity)
Read/set terrain opacity 0..1. setOpacity is undoable; throws if no terrain or the terrain is locked.
ts
await snaptrude.core.io.terrain.setOpacity(0.5);Errors
Failed calls reject with a typed PluginError — see Error Handling. Conditions specific to this namespace:
| Code | Thrown by | When | details |
|---|---|---|---|
PRECONDITION_FAILED | setDatum, delete, enableElevation, disableElevation, enableSatellite, disableSatellite, setOpacity | The project has no site terrain | — |
Reads never throw for a missing terrain — get, getDatum, getReport, isElevationEnabled, isSatelliteEnabled, and getOpacity return null instead; check exists() (or import one with core.io.import.terrain) before mutating. The "writes are disabled" rejections carry METHOD_NOT_PERMITTED.