UNPKG

forma-embedded-view-sdk

Version:

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

438 lines (437 loc) 15.6 kB
import type { FormaElement, Properties, Transform, Urn } from "forma-elements"; import type { IframeMessenger } from "./iframe-messenger.js"; export type ProposalElement = FormaElement & { properties?: Properties & { category?: "proposal"; name?: string; flags: Record<string, { scenario?: boolean; }>; }; }; /** * Access proposal metadata and add new elements to it. * * @remarks * Available via {@link auto.Forma | Forma}.{@link index.EmbeddedViewSdk.proposal | proposal}. */ export declare class ProposalApi { #private; /** @hidden */ constructor(iframeMessenger: IframeMessenger); /** * Fetch the top level URN for the proposal. * * @returns Root URN * * @example * const rootUrn = await Forma.proposal.getRootUrn() */ getRootUrn(): Promise<Urn>; /** * Fetch the unique identifier of the proposal. * * @returns Proposal ID * * @example * const proposalId = await Forma.proposal.getId() */ getId(): Promise<string>; /** * Add a new element to the proposal element tree. * * Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info. * * @returns { path: string } object with the path of the new element * * @example * const urn = mockRegisterElementInSystem() // See e.g. integrate-elements module * const { path } = await Forma.proposal.addElement({ urn }) */ addElement(request: { /** * URN of the element to add. */ urn: string; /** * Path to element whose children the new element should be added to. * * Currently the only supported parents are * - top-level of proposal (`"root"`) * - a base layer (`"root/<base key>"`) * * Defaults to "root". */ parentPath?: string; /** * Flattened list of a standard 4x4 affine transform matrix in column-major order, to position the element relative to its parent. * * When the parent is proposal root, the project reference point is used as origin. * * Defaults to equivalent of 4x4 identity matrix. */ transform?: number[]; /** * Name of the element. */ name?: string; }): Promise<{ path: string; }>; /** * Replace an element in the proposal. * * Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info. * * @example * const urn = mockRegisterElementInSystem() // See e.g. integrate-elements module * const { path } = await Forma.proposal.addElement({ urn }) * const newUrn = mockRegisterElementInSystem() // See e.g. integrate-elements module * await Forma.proposal.replaceElement({ path, newUrn }) */ replaceElement(request: { /** * Path of the element you want to replace. * * The path is relative to the proposal element and starts with "root/". * * Currently, the only supported parents are * - top-level of proposal (`"root"`) * - a base layer (`"root/<base key>"`) * * This will be the same value as the path returned from {@link ProposalApi.addElement | addElement}. */ path: string; /** * URN of the new element. */ urn: string; }): Promise<void>; /** * Remove an element in the proposal. * * Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info. * * @example * Remove the first of the selected elements * ```ts * const selection = await Forma.selection.getSelection() * if (selection.length > 0) { * await Forma.proposal.removeElement({ path: selection[0] }) * } * ``` */ removeElement(request: { /** * Path of the element you want to remove. * * The path is relative to the proposal element and starts with "root/". * * Currently, the only supported parents are * - top-level of proposal (`"root"`) * - a base layer (`"root/<base key>"`) * * This will be the same value as the path returned from {@link ProposalApi.addElement | addElement}. */ path: string; }): Promise<void>; /** * Replace existing terrain on the proposal. * * Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info. * * @example * const glb = createGlbSomehow() * await Forma.proposal.replaceTerrain(glb) */ replaceTerrain(request: { /** GLB file. */ glb: ArrayBuffer; }): Promise<void>; /** * Executes a batch of element operations (`add`, `replace`, `remove`) within a single proposal. * For more info see {@link ProposalApi.addElement | addElement}, {@link ProposalApi.replaceElement | replaceElement} and {@link ProposalApi.removeElement | removeElement}. * * This method allows for efficient bulk modifications of elements by combining multiple add, replace, and/or remove operations into a single call. * Operations are performed in the sequence they are provided. This method is useful for applying complex changes to a scene with minimal requests. * * Note: This operation requires edit access. Use {@link EmbeddedViewSdk.getCanEdit | getCanEdit} to verify edit permissions. * * @param request - An array of objects detailing the operations to be performed. * Each object should specify the `operation` type (`"add"`, `"replace"`, `"remove"`) and the required parameters. * * @returns A Promise that resolves to an array of results, one for each operation. For added elements, the result includes the new element path. * * @example * // Example: Adding three new elements to the scene * const urn1 = mockRegisterElementInSystem(); // Register a new element and obtain its urn * const urn2 = mockRegisterElementInSystem(); // Register another new element * const urn3 = mockRegisterElementInSystem(); // Register another new element * * const addElementOperations = [ * { type: "add", urn: urn1 }, * { type: "add", urn: urn2 }, * { type: "add", urn: urn3 }, * ]; * * // Example: Modifying three existing elements in the scene i.e. replacing the element at the first path * // with the second element and removing the elements at the second and third paths * const results = await Forma.proposal.updateElements({ operations: addElementOperations }) * const paths = results.map((result) => result?.path); * console.log(`Added elements at paths: ${paths.join(", ")}`); * * const modifyElementOperations = [ * { type: "replace", urn: urn2, path: paths[0] }, * { type: "remove", path: paths[1] }, * { type: "remove", path: paths[2] }, * ]; * * await Forma.proposal.updateElements({ operations: modifyElementOperations }) * * @throws {Error} If the user does not have edit access, provides invalid paths or attempts to modify terrain element. */ updateElements(request: { operations: ({ type: "add"; /** * Path to element whose children the new element should be added to. * * Currently, the only supported parents are * - top-level of proposal (`"root"`) * - a base layer (`"root/<base key>"`) */ parentPath?: string; /** * URN of the element to add. */ urn: string; /** * Name of the element. */ name?: string; /** * Flattened list of a standard 4x4 affine transform matrix in column-major order, to position the element relative to its parent. */ transform?: number[]; } | { type: "replace"; /** * Path of the element to replace. * * Currently, the only supported paths are * - top-level of proposal (`"root/<key>"`) * - a base layer (`"root/<base key>/<key>"`) */ path: string; /** * URN of the new element. */ urn: string; } | { type: "remove"; /** * Path of the element to remove. * * Currently, the only supported paths are * - top-level of proposal (`"root/<key>"`) * - a base layer (`"root/<base key>/<key>"`) */ path: string; })[]; }): Promise<({ path: string; } | null)[]>; /** * Subscribe to changes in the proposal. * * By default this will be called for every change in the proposal, including changes that might not be persisted as a separate change. * * This can be changed by setting the `debouncedPersistedOnly` option to true, in which case the callback will only be called after the proposal is persisted as well. * If multiple changes are made in quick succession, the callback will be called only once for all changes. * * @example * Forma.proposal.subscribe( * async ({ rootUrn }) => { * console.log(`Proposal ${rootUrn} has been created and persisted`) * }, * { * debouncedPersistedOnly: true, * }, * ) * @param callback event handler for each proposal change * @param options.debouncedPersistedOnly - if true, the callback will be called only after the proposal is persisted. * @returns { unsubscribe } - Object with an `unsubscribe` function to stop listening to events. */ subscribe(callback: (payload: { rootUrn: string; }) => void, options?: { debouncedPersistedOnly?: boolean; }): Promise<{ unsubscribe: () => void; }>; /** * This function is resolved when the currently loaded proposal is properly persisted in the system. * The underlying host application can operate optimistically and return values before the proposal is persisted. * While other APIs require the proposal to be persisted before they can be used. Examples of such APIs are the elements API. * * If you subscribe to proposal updates, you should likely instead look at the `debouncedPersistedOnly` option in the `subscribe` function. */ awaitProposalPersisted(): Promise<void>; /** * This will get all proposals belongs to the current project. * * @returns ProposalElement[] * * @example * const proposals = await Forma.proposal.getAll() */ getAll(): Promise<ProposalElement[]>; /** * This will get the proposal with the given ID and/or revision. * * @returns ProposalElement * * @example * const proposalId = await Forma.proposal.getId() * const proposal = await Forma.proposal.get({ proposalId: proposalId }) */ get(request: { proposalId: string; revision?: string; }): Promise<ProposalElement>; /** * This will create a new proposal with the given name, terrain, base, and children. * * @returns { urn: Urn } object with the URN of the new proposal * * @example * const newProposal = await Forma.proposal.create({ * name: "New Proposal", * terrain: { urn: "urn:adsk-forma-elements:terrain:projectId:elementId:revision", key: "<key>" }, * base: { urn: "urn:adsk-forma-elements:base:projectId:elementId:revision", key: "<key>" }, * children: [], * }) */ create(request: { name: string; terrain: { urn: Urn; key: string; transform?: Transform; name?: string; }; base: { urn: Urn; key: string; transform?: Transform; name?: string; }; children: { urn: Urn; key: string; transform?: Transform; name?: string; }[]; }): Promise<{ urn: Urn; }>; /** * This will update the proposal with the given ID and revision. * * @returns { urn: Urn } object with the URN of the updated proposal * * @example * const proposalId = await Forma.proposal.getId() * const updatedProposal = await Forma.proposal.update({ * proposalId: proposalId, * revision: "revisionId", * proposal: { * name: "Updated Proposal", * terrain: { urn: "urn:adsk-forma-elements:terrain:projectId:elementId:revision", key: "<key>" }, * base: { urn: "urn:adsk-forma-elements:base:projectId:elementId:revision", key: "<key>" }, * children: [], * } * }) */ update(request: { proposalId: string; revision: string; proposal: { name?: string; terrain: { urn: Urn; key: string; transform?: Transform; name?: string; }; base: { urn: Urn; key: string; transform?: Transform; name?: string; }; children: { urn: Urn; key: string; transform?: Transform; name?: string; }[]; }; }): Promise<{ urn: Urn; }>; /** * This will delete the proposal with the given ID. And load the next available proposal in the canvas. * * @example * const proposalId = await Forma.proposal.getId() * await Forma.proposal.delete({ * proposalId: proposalId, * }) * * @remarks * This is a soft delete, meaning the proposal can be restored. * * @experimental */ delete(request: { proposalId: string; }): Promise<void>; /** * This will create a new proposal with the same elements as the original. * * @experimental * * @returns { urn: Urn } object with the URN of the duplicated proposal * * @example * const proposalId = await Forma.proposal.getId() * const duplicatedProposal = await Forma.proposal.duplicate({ * proposalId: proposalId, * }) */ duplicate(request: { proposalId: string; revision?: string; }): Promise<ProposalElement>; /** * Switch to a different proposal. * * @example * const proposals = await Forma.proposal.getAll() * await Forma.proposal.switch({ * proposalId: proposals[0].urn.split(":")[4], * revision: proposals[0].urn.split(":")[5], * }) * * @remarks * This will switch to the proposal with the given ID and/or revision. * If your extension loads data from the proposal or operates on the current proposal in any way, * you should listen to the `proposal change` event using the {@link subscribe} method to know when the proposal has been switched. * Otherwise, you might be operating on the wrong proposal or the proposal contextual data might be incorrect. */ switch(request: { proposalId: string; revision?: string; }): Promise<void>; }