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
TypeScript
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>;
}