Skip to main content

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​

ParameterTypeDescription
idDrawingIDThe drawing, as @buerli.io/classcad returns it when it connects.

Returns​

DrawingState

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 ParameterDefault type
Tany

Parameters​

ParameterTypeDescription
buerliApiStoreApi<BuerliState>The store, api.
drawingIdDrawingIDThe drawing.
sel(d) => TWhat 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) => voidCalled 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​

PropertyModifierTypeDescription
apireadonlyDrawingAPIWhat the drawing can do: its DrawingAPI.
cadreadonly{ 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.connectionpublic{ 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.activepublicbooleanWhether the client is connected or trying to connect.
cad.connection.connectedpublicbooleanWhether the client is connected.
cad.pendingpublic{ 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.statespublic{ captionMap: Record<string, { caption: string; stateName: string; undoable?: boolean; }>; current: number; stack: string[]; }The undo history of the model.
cad.states.captionMappublicRecord<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.currentpublicnumberThe state the model is in.
cad.states.stackpublicstring[]The names of the states there are (numbers, as strings).
geometryreadonly{ 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.boundspublicGeometryBoundsThe bounds of all products of the model.
geometry.cachepublicRecord<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.configpublic{ product: Record<ObjectID, GeometryConfig>; }How the geometry is shown, per product or solid.
geometry.config.productpublicRecord<ObjectID, GeometryConfig>The GeometryConfig of products and solids, by id, as setConfig sets it.
geometry.stamppublicnumberWhen the geometry or the bounds last changed, in milliseconds since 1970; -1 before.
graphicreadonly{ containers: Record<number, IGraphicContainer>; }The graphic (tessellation) as ClassCAD sends it, by container.
graphic.containerspublicRecord<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.
idreadonlyDrawingIDThe drawing's id.
interactionreadonly{ hovered?: InteractionInfo | null; selected?: InteractionInfo[]; }What the user has selected and hovers in the model.
interaction.hovered?publicInteractionInfo | nullWhat is under the pointer, or null.
interaction.selected?publicInteractionInfo[]What is selected.
namereadonlystringThe drawing's name, as given when it connected; Drawing #<n> if none was.
pluginreadonly{ 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.activepublic{ feature: PluginID | null; global: PluginID[]; }What is open: the feature (its plugin), and the global plugins.
plugin.active.featurepublicPluginID | nullThe open feature, or null.
plugin.active.globalpublicPluginID[]The global plugins that are open.
plugin.featurepublicPluginID[]The ids of the features' plugins.
plugin.globalpublicPluginID[]The ids of the global plugins.
plugin.refspublicRecord<PluginID, PluginState>The plugins by id.
plugin.visiblepublicPluginID[]The plugins whose 3D view is shown although they are not open.
selectionreadonly{ active: SelectorID | null; refs: Record<SelectorID, Selector>; }The selectors, and which one is active.
selection.activereadonlySelectorID | nullThe active selector, the one the user picks into, or null.
selection.refsreadonlyRecord<SelectorID, Selector>The selectors by id.
structurereadonlyScgStructure & { 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​

PropertyTypeDescription
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) => voidSets 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) => voidColors 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) => voidColors 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) => voidSets 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?) => voidSelects 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) => voidSets what is under the pointer.
interaction.setSelected(items) => voidSets 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) => voidCreates 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) => voidOpens 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) => voidOpens or closes a global plugin. Warns, and does nothing, for an id that is not a global plugin's.
plugin.setVisiblePlugin(id, visible) => voidShows 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() => voidSets 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) => voidMakes 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?) => booleanReturns whether all the items are in a selector (by id and scope).
selection.createSelector(selectorId, filter, activate, initialItems, maxLen, customSelect?, customUnSelect?) => () => voidCreates a selector, and returns a function that removes it again.
selection.getGraphicItem(productId, containerId, graphicId?) => | SelectedItem<ProductElement> | nullReturns 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?) => booleanReturns whether an item is in a selector (by id and scope).
selection.select(items, selectorId?, options?) => voidAdds 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?) => voidSelects 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?) => voidSets the item under the pointer for a selector.
selection.setItems(id, items) => voidSets a selector's items, in place of the ones it has; customSelect and customUnSelect use it.
selection.unselect(items, selectorId?, options?) => voidRemoves items from a selector (by id), and clears its hovered item.
selection.unselectAll(selectorId?, options?) => voidRemoves 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) => Matrix4Returns 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 | undefinedReturns 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​

PropertyModifierTypeDescription
apireadonlyGeneralApi & DrawingManagementApiThe 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.
drawingreadonly{ active: DrawingID | null; ids: DrawingID[]; refs: Record<DrawingID, DrawingState>; }The drawings.
drawing.activepublicDrawingID | nullThe active drawing, or null.
drawing.idspublicDrawingID[]The ids of all drawings, in the order they were made.
drawing.refspublicRecord<DrawingID, DrawingState>The drawings by id. getDrawing reads one.
initDonereadonlybooleanWhether the store has its options: init of @buerli.io/classcad has been called.
messagereadonly{ data: | Message & { id: string; } | null; timestamp: number; }The latest message, from showMessage or updateMessage.
message.datapublic| Message & { id: string; } | nullThe message with its id, or null when there is none (or it was closed).
message.timestamppublicnumberWhen the message last changed, in milliseconds since 1970; -1 before the first one.
optionsreadonly{ 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.configpublic{ geometry: GeometryConfig; }How things are shown.
options.config.geometrypublicGeometryConfigHow 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.elementspublicanyThe 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.globalPluginspublic( | 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.pluginspublicRecord<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.themepublic{ 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.darkpublicstringA dark color; #54545d by default.
options.theme.highlightedGeompublicstringThe color of selected geometry; #e36b7c by default.
options.theme.hoveredGeompublicstringThe color of the geometry under the pointer; #40a9ff by default.
options.theme.primarypublicstringThe main color; #e36b7c by default.
options.theme.secondarypublicstringA 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)
})