UNPKG

forma-embedded-view-sdk

Version:

The Forma Embedded View SDK is a JavaScript library for creating custom extensions in Autodesk Forma Site Design (previously Spacemaker).

310 lines (309 loc) 10.9 kB
/** * Interact with the terrain in the 3D scene. * * @remarks * Available via {@link auto.Forma | Forma}.{@link index.EmbeddedViewSdk.terrain | terrain}. */ export class TerrainApi { #iframeMessenger; groundTexture; /** @hidden */ constructor(iframeMessenger) { this.#iframeMessenger = iframeMessenger; this.groundTexture = new GroundTextureApi(iframeMessenger); } /** * Fetch the bounding box for the terrain. * * @returns Axis-aligned bounding box for the terrain. * More specifically, the minimum and maximum (x,y,z) values, in the local coordinate system. */ async getBbox() { return await this.#iframeMessenger.sendRequest("scene/terrain/bbox/get"); } /** * Retrieves the elevation of the terrain (in meters above sea level) at a specific point within the scene. * * If the coordinates are outside the terrain mesh, it returns the minimum elevation in the terrain. */ async getElevationAt(request) { return await this.#iframeMessenger.sendRequest("scene/terrain/get-elevation-at", request); } /** * Returns whether the terrain is internal. * * @remarks * Internal terrain is managed fully within Forma Site Design. * Non-internal terrain can potentially be coming from other cloud applications, * including Revit Cloud Model and other Forma Connected Clients. * * Certain operations such as {@link TerrainApi.addPads | addPads}, * {@link TerrainApi.applyPads | applyPads}, and * {@link ProposalApi.replaceTerrain | replaceTerrain} are only supported * for internal terrains. */ async isInternal() { return await this.#iframeMessenger.sendRequest("scene/terrain/is-internal"); } /** * Retrieves all terrain pads defined in the current terrain. * * @remarks * Terrain pads are flat areas applied to the terrain surface. * * Each pad defines a polygon boundary at a specific elevation. The terrain mesh * within the polygon is modified to match the pad's elevation. An optional slope * angle defines how the terrain transitions from surrounding areas to the pad. * * @returns An array of {@link TerrainPad} objects representing all terrain pads. * Returns an empty array if no pads are defined on the terrain. * * @example * ```typescript * // Retrieve all terrain pads * const pads = await Forma.terrain.getPads(); * * // Log information about each pad * for (const pad of pads) { * console.log(`Pad ID: ${pad.id}`); * console.log(` Elevation: ${pad.elevation}m`); * console.log(` Vertices: ${pad.coordinates.length}`); * if (pad.slopeAngle !== undefined) { * console.log(` Slope angle: ${pad.slopeAngle}°`); * } * } * ``` */ async getPads() { return await this.#iframeMessenger.sendRequest("scene/terrain/get-pads"); } /** * Adds new terrain pads to the existing pads on the terrain. * * @remarks * This method appends the provided pads to the current set of terrain pads * without removing or modifying existing ones. Use this when you want to * incrementally add new flat areas to the terrain while preserving existing pads. * * This method is only supported when the terrain is internal. * Use {@link TerrainApi.isInternal | isInternal} to check whether the terrain is internal. * * Each pad must specify its slope using either `slopeAngle` (in degrees) or * `slopePercentage`, but not both. * * @param pads - Array of terrain pads to add. * * @example * ```typescript * // Add multiple pads with different slope specifications * await Forma.terrain.addPads([ * { * id: "7ab85ec80b3e2", * coordinates: [ * { x: 0, y: 0 }, * { x: 100, y: 0 }, * { x: 100, y: 100 }, * { x: 0, y: 100 } * ], * elevation: 50, * applySlope: true, * slopeAngle: 30 // Using degrees * }, * { * id: "70234fcf791b2", * coordinates: [ * { x: 200, y: 0 }, * { x: 300, y: 0 }, * { x: 300, y: 100 }, * { x: 200, y: 100 } * ], * elevation: 45, * applySlope: true, * slopePercentage: 50 // Using percentage * }, * { * id: "70234fcf791b3", * coordinates: [ * { x: 200, y: 0 }, * { x: 300, y: 0 }, * { x: 300, y: 100 }, * { x: 200, y: 100 } * ], * elevation: 45, * applySlope: false // No slope applied; abrupt pad * } * ]); * ``` * * @see {@link TerrainApi.applyPads} to replace all existing pads. * @see {@link TerrainApi.getPads} to retrieve current pads. */ async addPads(pads) { this.#validatePadsInput(pads); await this.#iframeMessenger.sendRequest("scene/terrain/add-pads", pads); } /** * Replaces all existing terrain pads with the provided array of pads. * * @remarks * This method unconditionally sets the terrain pads to the provided array, * removing any existing pads that are not included in the new array. * Use this when you want complete control over the terrain pads, or when * you need to remove pads. * * This method is only supported when the terrain is internal. * Use {@link TerrainApi.isInternal | isInternal} to check whether the terrain is internal. * * To remove all pads, call this method with an empty array. * * @param pads - Array of terrain pads to set. Pass an empty array to remove all pads. * * @example * ```typescript * // Replace all pads with a new set * await Forma.terrain.applyPads([ * { * id: "7ab85ec80b3e2", * coordinates: [ * { x: 0, y: 0 }, * { x: 100, y: 0 }, * { x: 100, y: 100 }, * { x: 0, y: 100 } * ], * elevation: 50, * applySlope: true, * slopeAngle: 30 * } * ]); * ``` * * @example * ```typescript * // Remove all terrain pads * await Forma.terrain.applyPads([]); * ``` * * @see {@link TerrainApi.addPads} to add pads without removing existing ones. * @see {@link TerrainApi.getPads} to retrieve current pads. */ async applyPads(pads) { this.#validatePadsInput(pads); await this.#iframeMessenger.sendRequest("scene/terrain/apply-pads", pads); } #validatePadsInput(pads) { pads.forEach((value, index) => { const error = this.#validatePadInput(value); if (error) { throw new Error(`Error at index ${index}: ${error}`); } }); } #validatePadInput(pad) { if (pad.applySlope && pad.slopeAngle === undefined && pad.slopePercentage === undefined) { return "Either slopeAngle or slopePercentage must be specified"; } if (pad.slopeAngle !== undefined && (pad.slopeAngle <= 0 || pad.slopeAngle >= 90)) { return "slopeAngle must be greater than 0 and strictly lower than 90 degrees"; } if (pad.slopePercentage !== undefined && pad.slopePercentage <= 0) { return "slopePercentage must be larger than 0"; } return undefined; } } /** * Manage ground textures applied to the terrain object in the 3D scene. * * @remarks * Available via {@link auto.Forma | Forma}.{@link index.EmbeddedViewSdk.terrain | terrain}.{@link terrain.TerrainApi.groundTexture | groundTexture}. */ export class GroundTextureApi { #iframeMessenger; /** @hidden */ constructor(iframeMessenger) { this.#iframeMessenger = iframeMessenger; } /** * Add a ground texture to the terrain. * * @example * // Create canvas * const canvas = document.createElement("canvas"); * canvas.width = 100; * canvas.height = 100; * * // Fill canvas with blue color * const ctx = canvas.getContext("2d"); * if (ctx) { * ctx.fillStyle = "blue"; * ctx.fillRect(0, 0, 100, 100); * * // Add canvas as ground texture to position (0, 0) in the local coordinate system. * // The texture will cover a 100x100 meter square area on the terrain, * // with lower left corner in (x: -50, y: -50) and upper right corner in (x: 50, y: 50). * await Forma.terrain.groundTexture.add({ * name: "myGroundTexture", * canvas, * position: { x: 0, y: 0, z: 1 }, * scale: { x: 1, y: 1 }, * }); * } */ async add(request) { const canvasUrl = request.canvas.toDataURL(); await this.#iframeMessenger.sendRequest("scene/terrain/ground-texture/add", { name: request.name, canvasUrl, position: request.position, scale: request.scale, }); } /** * Update the texture data for an existing ground texture object. * * @example * // Create a new canvas filled with red and update the ground texture with this new texture * const newCanvas = document.createElement("newCanvas"); * newCanvas.width = 100; * newCanvas.height = 100; * const ctx = newCanvas.getContext("2d"); * if (ctx) { * ctx.fillStyle = "red"; * ctx.fillRect(0, 0, 100, 100); * await Forma.terrain.groundTexture.updateTextureData({ * name: "myGroundTexture", * canvas: newCanvas, * }); * } */ async updateTextureData(request) { const canvasUrl = request.canvas.toDataURL(); await this.#iframeMessenger.sendRequest("scene/terrain/ground-texture/update-texture-data", { name: request.name, canvasUrl }); } /** * Update the placement of an existing ground texture object. * * @example * // Move "myGroundTexture" to (100, 100) in the local coordinate system. * await Forma.terrain.groundTexture.updatePosition({ * name: "myGroundTexture", * position: { x: 100, y: 100, z: 1 } * }) */ async updatePosition(request) { await this.#iframeMessenger.sendRequest("scene/terrain/ground-texture/update-position", request); } /** * Remove an existing ground texture object. * * @example * // Remove "myGroundTexture". * await Forma.terrain.groundTexture.remove({ name: "myGroundTexture" }) */ async remove(request) { await this.#iframeMessenger.sendRequest("scene/terrain/ground-texture/remove", request); } }