Drawings
A drawing is one model in the store: its structure tree, geometry, selections and plugins, which
@buerli.io/classcad keeps up to date. Read it when an app needs to know what the model holds.
Functions
getDrawing()
function getDrawing(id): DrawingState;
Returns a drawing's state: its structure tree, geometry, selection and plugins, and its API.
It is the state as it is now, read once; to follow it as it changes, use subscribeToDrawing, or
useDrawing of @buerli.io/react in a component. Returns undefined for a drawing that does not exist (any more),
although the type does not say so.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | DrawingID | The drawing, as @buerli.io/classcad returns it when it connects. |
Returns
The drawing's state, or undefined.
Example
import { BuerliCadFacade, init, WASMClient } from '@buerli.io/classcad'
import { getDrawing } from '@buerli.io/core'
init(drawingId => new WASMClient(drawingId, { token: 'ccpk_…' }))
const facade = new BuerliCadFacade()
const drawingId = await facade.connect()
const part = drawingId && (await facade.api.v1.part.create({ name: 'Bracket' }))
if (drawingId && part) {
await facade.api.v1.part.box({ id: part, length: 100, width: 50, height: 20 })
const drawing = getDrawing(drawingId)
// The part in the structure tree, and the three.js geometry of its solids
const object = drawing.structure.tree[part]
const solids = (object.solids ?? []).map(id => drawing.geometry.cache[id])
console.log(object.name, solids.length)
}
subscribeToDrawing()
function subscribeToDrawing<T>(
buerliApi,
drawingId,
sel,
callback
): () => void;
Calls back when a part of a drawing changes: what sel takes from the drawing's state, compared by reference.
The callback gets the new selection; it is not called for the state as it is when subscribing. While the drawing
does not exist, the selection is undefined (so removing the drawing calls back with undefined). In React,
useDrawing of @buerli.io/react does this for a component.
Type Parameters
| Type Parameter | Default type |
|---|---|
T | any |
Parameters
| Parameter | Type | Description |
|---|---|---|
buerliApi | StoreApi<BuerliState> | The store, api. |
drawingId | DrawingID | The drawing. |
sel | (d) => T | What to take from the drawing's state. It should return parts of the state, or values: a new object or array each time counts as a change each time. |
callback | (arg) => void | Called with what sel returns, each time it changes. |
Returns
A function that unsubscribes.
() => void
Example
import { api, getDrawing, subscribeToDrawing } from '@buerli.io/core'
// Log the name of the product that is being edited, whenever another one is
const unsubscribe = subscribeToDrawing(api, drawingId, drawing => drawing.structure.currentProduct, productId => {
if (productId) console.log(getDrawing(drawingId).structure.tree[productId]?.name)
})
// … and stop again
unsubscribe()
Types
DrawingState
A drawing: the state of one model and its ClassCAD connection, and its API.
@buerli.io/classcad fills it as ClassCAD changes the model: the structure tree (structure) and the graphic
(graphic), from which the three.js geometry (geometry) is made. The rest is the client's: selections, plugins
and the connection. getDrawing reads it; useDrawing of @buerli.io/react follows a part of it in a
component. The state is immutable: change it through api (DrawingAPI), and compare by reference to
see what changed.
Example
import { DrawingID, DrawingState } from '@buerli.io/core'
import { useDrawing } from '@buerli.io/react'
// The names of what the current product holds; renders again when they change
const childNames = (drawing: DrawingState) => {
const { tree, currentProduct } = drawing.structure
return currentProduct ? tree[currentProduct]?.children?.map(id => tree[id]?.name).join(', ') : undefined
}
function Contents({ drawingId }: { drawingId: DrawingID }) {
const names = useDrawing(drawingId, childNames)
return <p>{names}</p>
}
Properties
| Property | Modifier | Type | Description |
|---|---|---|---|
api | readonly | DrawingAPI | What the drawing can do: its DrawingAPI. |
cad | readonly | { connection: { active: boolean; connected: boolean; }; pending: { args?: any; id: string; name: string; promise?: Promise<any>; stamp: number; }[]; states: { captionMap: Record<string, { caption: string; stateName: string; undoable?: boolean; }>; current: number; stack: string[]; }; } | The ClassCAD connection: whether it is up, the undo states, the calls under way. |
cad.connection | public | { active: boolean; connected: boolean; } | The state of the client's connection: a client that is active but not connected has lost the engine and tries again. |
cad.connection.active | public | boolean | Whether the client is connected or trying to connect. |
cad.connection.connected | public | boolean | Whether the client is connected. |
cad.pending | public | { args?: any; id: string; name: string; promise?: Promise<any>; stamp: number; }[] | The ClassCAD calls under way; while there are some, apps show that they are busy. |
cad.states | public | { captionMap: Record<string, { caption: string; stateName: string; undoable?: boolean; }>; current: number; stack: string[]; } | The undo history of the model. |
cad.states.captionMap | public | Record<string, { caption: string; stateName: string; undoable?: boolean; }> | What each change is called (caption), by transaction id, with the state it made (stateName) and whether it can be undone (undoable). |
cad.states.current | public | number | The state the model is in. |
cad.states.stack | public | string[] | The names of the states there are (numbers, as strings). |
geometry | readonly | { bounds: GeometryBounds; cache: Record<ObjectID, ContainerGeometryT & { dirtyStamp?: number; }>; config: { product: Record<ObjectID, GeometryConfig>; }; stamp: number; } | The three.js geometry made from the graphic, the bounds of the model, and how products are shown. |
geometry.bounds | public | GeometryBounds | The bounds of all products of the model. |
geometry.cache | public | Record<ObjectID, ContainerGeometryT & { dirtyStamp?: number; }> | The geometry by container, the solid's CAD entity id (as in graphic.containers and solids of a part): its faces, edges and points as GeometryElements. |
geometry.config | public | { product: Record<ObjectID, GeometryConfig>; } | How the geometry is shown, per product or solid. |
geometry.config.product | public | Record<ObjectID, GeometryConfig> | The GeometryConfig of products and solids, by id, as setConfig sets it. |
geometry.stamp | public | number | When the geometry or the bounds last changed, in milliseconds since 1970; -1 before. |
graphic | readonly | { containers: Record<number, IGraphicContainer>; } | The graphic (tessellation) as ClassCAD sends it, by container. |
graphic.containers | public | Record<number, IGraphicContainer> | The graphic containers by id, the solid's CAD entity id: the tessellated faces, edges and points of a solid (ScgGraphicContainer of @classcad/api-js), with its material and bounds in properties. |
id | readonly | DrawingID | The drawing's id. |
interaction | readonly | { hovered?: InteractionInfo | null; selected?: InteractionInfo[]; } | What the user has selected and hovers in the model. |
interaction.hovered? | public | InteractionInfo | null | What is under the pointer, or null. |
interaction.selected? | public | InteractionInfo[] | What is selected. |
name | readonly | string | The drawing's name, as given when it connected; Drawing #<n> if none was. |
plugin | readonly | { active: { feature: PluginID | null; global: PluginID[]; }; feature: PluginID[]; global: PluginID[]; refs: Record<PluginID, PluginState>; visible: PluginID[]; } | The drawing's plugins, and which of them are active and visible. |
plugin.active | public | { feature: PluginID | null; global: PluginID[]; } | What is open: the feature (its plugin), and the global plugins. |
plugin.active.feature | public | PluginID | null | The open feature, or null. |
plugin.active.global | public | PluginID[] | The global plugins that are open. |
plugin.feature | public | PluginID[] | The ids of the features' plugins. |
plugin.global | public | PluginID[] | The ids of the global plugins. |
plugin.refs | public | Record<PluginID, PluginState> | The plugins by id. |
plugin.visible | public | PluginID[] | The plugins whose 3D view is shown although they are not open. |
selection | readonly | { active: SelectorID | null; refs: Record<SelectorID, Selector>; } | The selectors, and which one is active. |
selection.active | readonly | SelectorID | null | The active selector, the one the user picks into, or null. |
selection.refs | readonly | Record<SelectorID, Selector> | The selectors by id. |
structure | readonly | ScgStructure & { products: number[]; visibleContainers: number[]; } | The structure tree, as ClassCAD sends it (ScgStructure): tree holds its objects by id, root is the root part or assembly, currentProduct the part or assembly being edited, currentInstance the instance being edited in an assembly (0 for none). products are the parts and instances below the root, and visibleContainers their solids, the graphic containers that are shown. |
DrawingAPI
What a drawing can do: the functions that read and change its state, by part of the state.
It is api of the drawing's state (DrawingState). Most of it is for plugins and UI; a few functions
come in handy for apps: transformations and bounds of products (structure), selections (selection,
interaction), and colors and visibility of geometry (geometry).
Example
import { getDrawing } from '@buerli.io/core'
const { api } = getDrawing(drawingId)
// Put a marker on a product's center, in world coordinates
const { center, radius } = api.structure.calculateProductBounds(productId)
marker.position.copy(center)
marker.scale.setScalar(radius / 10)
// Show the product's solids as wireframes
for (const solidId of api.structure.getProductSolidIds(productId)) {
api.geometry.setConfig(solidId, { meshes: { wireframe: true } })
}
Properties
| Property | Type | Description |
|---|---|---|
geometry | { filter: (cb, options?) => GeometryElement[]; findDecorated: () => GeometryElement[]; setConfig: (objId, config) => void; setContainerColor: (containerId, r, g, b) => void; setGraphicColor: (containerId, graphicId, r, g, b) => void; setOpacity: (containerId, opacity) => void; } | The three.js geometry of the solids: colors, opacity, how they are shown, finding elements. |
geometry.filter | (cb, options?) => GeometryElement[] | Returns the elements of the geometry (faces, edges, arcs and lines) that cb accepts. |
geometry.findDecorated | () => GeometryElement[] | Returns the elements of the geometry that ClassCAD gave a label or a color of their own. |
geometry.setConfig | (objId, config) => void | Sets how the geometry of a product or a solid is shown: merges config into the one it has, or removes it with null. |
geometry.setContainerColor | (containerId, r, g, b) => void | Colors a solid in the 3D view (its faces without a color of their own), until its geometry comes anew from ClassCAD. The model is not changed. |
geometry.setGraphicColor | (containerId, graphicId, r, g, b) => void | Colors a face of a solid in the 3D view, until its geometry comes anew from ClassCAD. The model is not changed. |
geometry.setOpacity | (containerId, opacity) => void | Sets the opacity of a solid in the 3D view (its faces without an opacity of their own), until its geometry comes anew from ClassCAD. The model is not changed. |
graphic | { filter: (cb, options?) => Item[]; } | The graphic as ClassCAD sent it: finding elements in it. |
graphic.filter | (cb, options?) => Item[] | Returns the elements of the graphic (arcs, cones, edges, lines, meshes and vertices) that cb accepts, each with its container (containerId) and solid (solidId). |
interaction | { select: (item, multi?) => void; setHovered: (item) => void; setSelected: (items) => void; } | What the user has selected and hovers in the model (interaction of the state). |
interaction.select | (item, multi?) => void | Selects an item as a click does: it becomes the selection, or with multi is added to it. An item that is selected is unselected instead: with multi, or when it is all that is selected. Items are told apart by their uniqueIdent. |
interaction.setHovered | (item) => void | Sets what is under the pointer. |
interaction.setSelected | (items) => void | Sets what is selected, in place of what was. |
plugin | { createPlugin: (def, defaultId?, drawingId?, objectId?) => (deactivate) => void; setActiveFeature: (id) => void; setActiveGlobal: (id, active) => void; setVisiblePlugin: (id, visible) => void; } | The drawing's plugins: creates them, opens and shows them. |
plugin.createPlugin | (def, defaultId?, drawingId?, objectId?) => (deactivate) => void | Creates a plugin in the drawing, and returns a function that removes it again. PluginManager of @buerli.io/react does it for features as they are opened, and a drawing does it for the global plugins when it is made. A plugin given as a promise has an empty description until it resolves. |
plugin.setActiveFeature | (id) => void | Opens a feature, or closes it with null: makes it the active feature, whose plugin shows its panel. @buerli.io/classcad then opens the feature in ClassCAD, and closes the one before. The active selector is deactivated, and the plugin of the feature before goes back to its initialState unless it is persistent. |
plugin.setActiveGlobal | (id, active) => void | Opens or closes a global plugin. Warns, and does nothing, for an id that is not a global plugin's. |
plugin.setVisiblePlugin | (id, visible) => void | Shows or hides a plugin's 3D view while the plugin is not open. A feature's plugin that is visible is created even while its feature is not open. |
reset | () => void | Sets the drawing back to an empty one, keeping its id and name, and creates the global plugins anew. @buerli.io/classcad calls it when the drawing disconnects. |
selection | { activateSelector(selectorId): void; areItemsSelected: (items, selectorId?) => boolean; createSelector(selectorId, filter, activate, initialItems, maxLen, customSelect?, customUnSelect?): () => void; getGraphicItem: (productId, containerId, graphicId?) => | SelectedItem<ProductElement> | null; isItemSelected: (item, selectorId?) => boolean; select(items, selectorId?, options?): void; selectGraphic: (selectorId, items, options?) => void; setHovered(item, selectorId?): void; setItems(id, items): void; unselect(items, selectorId?, options?): void; unselectAll(selectorId?, options?): void; } | The selectors: creates them, activates them, selects items in them. |
selection.activateSelector | (selectorId) => void | Makes a selector the active one, the one the user picks into; a selector that is active already is deactivated instead, and null deactivates any. Opening a feature (plugin.setActiveFeature) deactivates the active selector. |
selection.areItemsSelected | (items, selectorId?) => boolean | Returns whether all the items are in a selector (by id and scope). |
selection.createSelector | (selectorId, filter, activate, initialItems, maxLen, customSelect?, customUnSelect?) => () => void | Creates a selector, and returns a function that removes it again. |
selection.getGraphicItem | (productId, containerId, graphicId?) => | SelectedItem<ProductElement> | null | Returns the selected item of an element of the geometry, or null when its geometry is not there (yet). Without graphicId, the item stands for the whole solid. |
selection.isItemSelected | (item, selectorId?) => boolean | Returns whether an item is in a selector (by id and scope). |
selection.select | (items, selectorId?, options?) => void | Adds items to a selector, those that are not in it yet, and clears its hovered item. A selector with a maxLen keeps that many: the oldest items go first, and of more new items than fit, the first ones are kept. The selector's filter is not asked: the 3D view asks it before it selects. A selector with customSelect leaves it to that to set the items. |
selection.selectGraphic | (selectorId, items, options?) => void | Selects elements of the geometry by their ids, in a selector: the face, edge or point graphicId of the solid containerId in the product productId, or the whole solid without a graphicId. Elements whose geometry is not there (yet) are left out. |
selection.setHovered | (item, selectorId?) => void | Sets the item under the pointer for a selector. |
selection.setItems | (id, items) => void | Sets a selector's items, in place of the ones it has; customSelect and customUnSelect use it. |
selection.unselect | (items, selectorId?, options?) => void | Removes items from a selector (by id), and clears its hovered item. |
selection.unselectAll | (selectorId?, options?) => void | Removes all items from a selector. |
structure | { calculateGlobalTransformation: (objectId, matrix) => Matrix4; calculateProductBounds: (productId, base?) => { center: Vector3; max: Vector3; min: Vector3; radius: number; }; collectProducts: (root) => ScgObject[]; collectUsedSolidIds: () => number[]; filterTree: (tree, filter) => ScgObject[]; findTree: (tree, filter) => ScgObject | undefined; get: () => ScgStructure & { products: number[]; visibleContainers: number[]; }; getProductSolidIds: (productId) => number[]; } | The structure tree: reads it, finds objects in it, and computes where products are. |
structure.calculateGlobalTransformation | (objectId, matrix) => Matrix4 | Returns where an object is in world coordinates: its coordinate system and those of its parents up to the root, multiplied. Throws for an id that is not in the tree. |
structure.calculateProductBounds | (productId, base?) => { center: Vector3; max: Vector3; min: Vector3; radius: number; } | Returns the bounds of a product with the products below it, in world coordinates: the box (min, max) and the sphere around it (center, radius). Without solids, radius is -1. |
structure.collectProducts | (root) => ScgObject[] | Returns the object and the objects below it that have solids or link to a product (instances). |
structure.collectUsedSolidIds | () => number[] | Returns the solids that the objects of the tree use: the ids in their geometryIdList. |
structure.filterTree | (tree, filter) => ScgObject[] | Returns the objects of a structure tree that filter accepts. |
structure.findTree | (tree, filter) => ScgObject | undefined | Returns the first object of a structure tree that filter accepts, or undefined. |
structure.get | () => ScgStructure & { products: number[]; visibleContainers: number[]; } | Returns the drawing's structure, structure of its state. |
structure.getProductSolidIds | (productId) => number[] | Returns the solids of a product; for an instance, those of the product it links to. Throws for an id that is not in the tree. |
BuerliState
The state of the buerli store (api): the drawings, the options of init, and the latest message.
The options are the ones init of @buerli.io/classcad was given.
Example
import { init, WASMClient } from '@buerli.io/classcad'
import { BuerliState } from '@buerli.io/core'
import { elements, useBuerli } from '@buerli.io/react'
// The options of the store come with init
init(drawingId => new WASMClient(drawingId, { token: 'ccpk_…' }), {
elements,
config: { geometry: { edges: { color: '#333333' }, points: { hidden: true } } },
})
// Components select from the state
const drawingCount = (state: BuerliState) => state.drawing.ids.length
function DrawingCount() {
const count = useBuerli(drawingCount)
return <span>{count} drawings open</span>
}
Properties
| Property | Modifier | Type | Description |
|---|---|---|---|
api | readonly | GeneralApi & DrawingManagementApi | The functions that change the drawings of the store: - removeDrawing(id) removes a drawing; @buerli.io/classcad then disconnects the drawing's client. When it is the active drawing, the one after it (or before it) becomes active first, in a change of its own. - setActiveDrawing(id) makes a drawing the active one, or none with null. Both throw before init of @buerli.io/classcad. Drawings are made by @buerli.io/classcad when they connect. |
drawing | readonly | { active: DrawingID | null; ids: DrawingID[]; refs: Record<DrawingID, DrawingState>; } | The drawings. |
drawing.active | public | DrawingID | null | The active drawing, or null. |
drawing.ids | public | DrawingID[] | The ids of all drawings, in the order they were made. |
drawing.refs | public | Record<DrawingID, DrawingState> | The drawings by id. getDrawing reads one. |
initDone | readonly | boolean | Whether the store has its options: init of @buerli.io/classcad has been called. |
message | readonly | { data: | Message & { id: string; } | null; timestamp: number; } | The latest message, from showMessage or updateMessage. |
message.data | public | | Message & { id: string; } | null | The message with its id, or null when there is none (or it was closed). |
message.timestamp | public | number | When the message last changed, in milliseconds since 1970; -1 before the first one. |
options | readonly | { config: { geometry: GeometryConfig; }; elements: any; globalPlugins: ( | Plugin | Promise<Plugin>)[]; plugins: Record<string, PluginImpl>; theme: { dark: string; highlightedGeom: string; hoveredGeom: string; primary: string; secondary: string; }; } | The options, as init of @buerli.io/classcad gives them (options, its second argument). Each init replaces the options it names and keeps the others: a theme given replaces the whole theme, not only the colors in it. |
options.config | public | { geometry: GeometryConfig; } | How things are shown. |
options.config.geometry | public | GeometryConfig | How the geometry of all solids is shown; a product's or a solid's own config (drawing.api.geometry.setConfig) goes over it. By default points are hidden and edges are black. |
options.elements | public | any | The form controls that plugins are made of (Button, Input, Selection, …): elements of @buerli.io/react or of @buerli.io/react-cad, or an app's own. Plugins that show a panel need them. |
options.globalPlugins | public | ( | Plugin | Promise<Plugin>)[] | The plugins that every drawing gets when it is made (and again when it is reset), like the Measure plugin of @buerli.io/react-cad. Their descriptions have global: true. |
options.plugins | public | Record<string, PluginImpl> | The plugins of features, by the ClassCAD class of the feature (CC_Extrusion, …). PluginManager of @buerli.io/react creates a feature's plugin when the feature is opened. A plugin can be given as it is, as a promise, or as a function that returns either. |
options.theme | public | { dark: string; highlightedGeom: string; hoveredGeom: string; primary: string; secondary: string; } | Colors, as CSS colors. The 3D view of @buerli.io/react shows selected and hovered geometry in highlightedGeom and hoveredGeom; primary, secondary and dark are there for apps and plugins. |
options.theme.dark | public | string | A dark color; #54545d by default. |
options.theme.highlightedGeom | public | string | The color of selected geometry; #e36b7c by default. |
options.theme.hoveredGeom | public | string | The color of the geometry under the pointer; #40a9ff by default. |
options.theme.primary | public | string | The main color; #e36b7c by default. |
options.theme.secondary | public | string | A light second color; #fcc7cb by default. |
DrawingID
type DrawingID = string | number;
A drawing's id, which a drawing gets when it connects (a uuid).
Example
import { useBuerliCadFacade, useDrawing } from '@buerli.io/react'
// A component that opens a drawing, and reads its state by the drawing's id
function PartName() {
const { drawingId } = useBuerliCadFacade('bracket')
const name = useDrawing(drawingId, drawing => drawing.structure.tree[drawing.structure.root]?.name)
return <h2>{name}</h2>
}
Objects
api
const api: StoreApi<BuerliState>;
The buerli store: a zustand store of BuerliState, which holds all drawings, the options and the latest message.
api.getState() reads the state as it is now, api.subscribe(listener) calls back on every change (the listener
gets the new and the previous state) and returns a function that unsubscribes. Change the state with the
functions in it (api.getState().api, a drawing's api), not with setState. In React, useBuerli and
useDrawing of @buerli.io/react select from it.
Example
import { api } from '@buerli.io/core'
const { active, ids } = api.getState().drawing
// Remove all drawings but the active one (their clients disconnect)
ids.filter(id => id !== active).forEach(id => api.getState().api.removeDrawing(id))
// Follow which drawing is active
const unsubscribe = api.subscribe((state, prev) => {
if (state.drawing.active !== prev.drawing.active) console.log('active drawing', state.drawing.active)
})