Modeling
BuerliCadFacade connects a drawing, runs the ClassCAD API on it (api.v1.*) and turns the model into three.js
geometry.
Classes
BuerliCadFacade
A drawing connected to ClassCAD: its API, and helpers that turn the model into three.js objects.
connect() creates a drawing in the buerli store and connects it with a client from the factory given to
init. Then api is the drawing's ClassCAD API, unwrapped: await api.v1.part.create(…) resolves to the
new part's id and rejects when the engine reports an error; warnings are logged. createBufferGeometry and
createScene make three.js geometry of what was built, tree and graphic read the drawing as the store holds
it. The static utils do the facade's work for any drawing, by its id.
Example
import * as THREE from 'three'
import { init, BuerliCadFacade, WASMClient } from '@buerli.io/classcad'
init(drawingId => new WASMClient(drawingId, { token: 'ccpk_…' }))
// A parametric flange: one facade per drawing
export async function flange(diameter: number, thickness: number) {
const facade = new BuerliCadFacade()
await facade.connect('Flange')
const { part } = facade.api.v1
const flange = (await part.create({ name: 'Flange' }))!
await part.cylinder({ id: flange, diameter, height: thickness })
const geometries = (await facade.createBufferGeometry(flange)) ?? []
const meshes = geometries.map(geometry => new THREE.Mesh(geometry, new THREE.MeshStandardMaterial()))
// Done with it: the drawing leaves the store
await facade.destroy()
return meshes
}
Accessors
api
Get Signature
get api(): UnwrappedApiType;
The drawing's ClassCAD API (ClassCADApi): calls resolve to their result and reject on an error.
Throws before connect() and after disconnect().
Returns
UnwrappedApiType
drawingId
Get Signature
get drawingId(): DrawingID;
The id of the drawing the facade connected. Throws before connect().
Returns
DrawingID
Constructors
Constructor
new BuerliCadFacade(): BuerliCadFacade;
Returns
Methods
connect()
connect(drawingName?): Promise<DrawingID | null>;
Creates a drawing, connects it to ClassCAD with a client from the factory given to init, and makes its
api available.
Rejects when the facade is connected already, and when init has not been called. When connecting fails, the
error is logged and shown as a message, and it resolves to null.
Parameters
| Parameter | Type | Description |
|---|---|---|
drawingName? | string | The drawing's name in the store; by default Drawing #<n>. |
Returns
Promise<DrawingID | null>
The id of the new drawing, or null when it could not connect.
createBufferGeometry()
createBufferGeometry(objectId?): Promise<
| BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>[]
| undefined>;
Creates the buffer geometry of any product (part or assembly).
Each part, and each instance of a part in an assembly, becomes one geometry: its bodies merged, placed where it is in the model. Bodies the store has no graphic for yet are requested from ClassCAD first. Rejects when an id is not in the drawing's tree.
Parameters
| Parameter | Type | Description |
|---|---|---|
objectId? | number | number[] | The product, its instance or a solid; or several of them. |
Returns
Promise<
| BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>[]
| undefined>
The geometries, or undefined when there is no id or nothing to show.
Example
import * as THREE from 'three'
// A cylinder with diameter 20 and height 30, as a mesh of one's own
const part = (await facade.api.v1.part.create({ name: 'Pin' }))!
await facade.api.v1.part.cylinder({ id: part, diameter: 20, height: 30 })
const [geometry] = (await facade.createBufferGeometry(part)) ?? []
if (geometry) scene.add(new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: 'orange' })))
createPolyline()
createPolyline(
owner,
fPts,
close?
): Promise<void>;
Adds a polyline through the given points to a shape curve of the model, its corners rounded where a radius is
given (v1.curve.polyline2d).
A corner with a radius above 0 becomes an arc of that radius. A corner whose arc does not fit between its neighbours is left out.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
owner | number | undefined | The shape curve, from v1.curve.shape. |
fPts | { point: Vector3; radius: number; }[] | undefined | The corners, each with the radius to round it with (0 for a sharp corner). |
close? | boolean | true | Closes the polyline. Default true. |
Returns
Promise<void>
Example
import * as THREE from 'three'
// A plate with two rounded corners, extruded from a polyline
const part = (await facade.api.v1.part.create({ name: 'Plate' }))!
const ei = (await facade.api.v1.part.entityInjection({ id: part }))!
const curve = (await facade.api.v1.curve.shape({ id: ei }))!
await facade.createPolyline(curve, [
{ point: new THREE.Vector3(0, 0, 0), radius: 0 },
{ point: new THREE.Vector3(100, 0, 0), radius: 10 },
{ point: new THREE.Vector3(100, 50, 0), radius: 10 },
{ point: new THREE.Vector3(0, 50, 0), radius: 0 },
])
await facade.api.v1.solid.extrusion({ id: ei, curves: [curve], direction: [0, 0, 8] })
createScene()
createScene(objectId?, options?): Promise<{
materials: {
[key: string]: Material<MaterialEventMap>;
};
nodes: {
[key: string]: Object3D<Object3DEventMap>;
};
scene: Scene;
}>;
Creates the threeJS scene of any product (part or assembly).
Assemblies and instances become groups placed as in the model, parts become groups of their bodies. A body is
one mesh per color (meshPerGeometry: false, default) or one mesh per face. Bodies the store has no graphic
for yet are requested from ClassCAD first. Rejects when an id is not in the drawing's tree.
Parameters
| Parameter | Type | Description |
|---|---|---|
objectId? | number | number[] | The product, its instance or a solid, or several of them; by default the drawing's root. |
options? | { meshPerGeometry?: boolean; structureOnly?: boolean; } | Options. |
options.meshPerGeometry? | boolean | Makes a mesh for each face (its userData.id is the face's graphic id) instead of merging a body's faces of one color into one mesh. |
options.structureOnly? | boolean | Makes the nodes without geometry and materials. |
Returns
Promise<{
materials: {
[key: string]: Material<MaterialEventMap>;
};
nodes: {
[key: string]: Object3D<Object3DEventMap>;
};
scene: Scene;
}>
The scene, its nodes and their materials.
Example
import * as THREE from 'three'
// The whole assembly in a three.js scene, a node per instance
const { scene: model, nodes } = await facade.createScene(assembly)
scene.add(model)
// The nodes by the id of the object they show, and by name
nodes[`${wheel}`].rotation.z = Math.PI / 4
createThreeShape()
createThreeShape(owner, shape): Promise<void>;
Adds the curves of a three.js Shape to a shape curve of the model, in one batch of API calls.
Lines, arcs and ellipses, quadratic and cubic Bézier curves and splines (as interpolation curves) are added, in the XY plane; other curves and the shape's holes are not.
Parameters
| Parameter | Type | Description |
|---|---|---|
owner | number | The shape curve, from v1.curve.shape. |
shape | Shape | The outline. |
Returns
Promise<void>
Example
import * as THREE from 'three'
// A disc, extruded from a three.js outline
const part = (await facade.api.v1.part.create({ name: 'Disc' }))!
const ei = (await facade.api.v1.part.entityInjection({ id: part }))!
const outline = new THREE.Shape()
outline.absarc(0, 0, 40, 0, Math.PI * 2, false)
const curve = (await facade.api.v1.curve.shape({ id: ei }))!
await facade.createThreeShape(curve, outline)
await facade.api.v1.solid.extrusion({ id: ei, curves: [curve], direction: [0, 0, 5] })
destroy()
destroy(): Promise<void>;
Disconnects from ClassCAD and removes the drawing from the buerli store. The facade can connect() again.
A failing disconnect does not keep the drawing from being removed.
Returns
Promise<void>
disconnect()
disconnect(): Promise<void>;
Disconnects from ClassCAD. The drawing stays in the buerli store, reset (without its model), and keeps its
client; api is not available until BuerliCadFacade.reconnect.
Returns
Promise<void>
graphic()
graphic(opts?): Promise<
| {
containers: ScgGraphicContainer[];
}
| null>;
The current graphic (tessellation) of the drawing as { containers }, as
held by the buerli store. Pass recalc: true to recalculate first.
Note that graphic element ids are payload-local - they rotate on every
re-tessellation. The stable join between graphic and structure tree is
container.owner === <CC_Solid id>.
Parameters
| Parameter | Type | Description |
|---|---|---|
opts? | { recalc?: boolean; } | Options. |
opts.recalc? | boolean | Recalculates the model before reading the graphic. |
Returns
Promise<
| {
containers: ScgGraphicContainer[];
}
| null>
The graphic containers, or null if the drawing is not available.
Example
import * as THREE from 'three'
// Frames the camera on the model: the bounds of every body
const graphic = await facade.graphic()
const bounds = new THREE.Box3()
for (const container of graphic?.containers ?? []) {
if (!container.meshes?.length) continue
bounds.expandByPoint(new THREE.Vector3().fromArray(container.properties.min))
bounds.expandByPoint(new THREE.Vector3().fromArray(container.properties.max))
}
const center = bounds.getCenter(new THREE.Vector3())
const size = bounds.getSize(new THREE.Vector3()).length()
camera.position.copy(center).add(new THREE.Vector3(0, -size, size))
camera.lookAt(center)
reconnect()
reconnect(): Promise<void>;
Disconnects, connects the drawing's client again and recalculates the model (v1.common.recalc); then api is
available again.
Returns
Promise<void>
selectGeometry()
selectGeometry(types, countOrResolver?): Promise<InteractionInfo[]>;
Lets the user pick geometry of the given types in the 3D view, and resolves with what was picked.
The drawing's geometry must be shown for selection, for example by BuerliGeometry of @buerli.io/react with
selection. With a count it resolves once that many items are picked; with a function, that function is
called at the first pick with one that ends the selection. Each item carries the picked element's ids, and in
userData its geometry in world coordinates and its positions (matchingPoints).
Parameters
| Parameter | Type | Description |
|---|---|---|
types | ( | POINT | LINE | ARC | CIRCLE | PLANE | CYLINDER | CONE | SPHERE | NURBSCURVE | NURBSSURFACE)[] | The kinds of geometry that can be picked. |
countOrResolver? | number | ((resolve) => void) | How many items to pick (default 1), or a function that gets the function that ends the selection. |
Returns
Promise<InteractionInfo[]>
The picked items.
Examples
import { ScgGraphicType } from '@buerli.io/classcad'
// A box whose edges the user picks to round them; the drawing is shown with <BuerliGeometry selection />
const part = (await facade.api.v1.part.create({ name: 'Block' }))!
await facade.api.v1.part.box({ id: part, length: 60, width: 40, height: 20 })
const edges = await facade.selectGeometry([ScgGraphicType.LINE, ScgGraphicType.ARC], 2)
await facade.api.v1.part.fillet({ id: part, references: edges.map(edge => edge.graphicId!), radius: 4 })
import { ScgGraphicType } from '@buerli.io/classcad'
// As many faces as the user likes, until "Done"
const faces = await facade.selectGeometry([ScgGraphicType.PLANE, ScgGraphicType.CYLINDER], finish => {
doneButton.onclick = () => finish()
})
tree()
tree(opts?): Promise<ScgTree>;
The structure tree (id -> node) of the drawing, as held by the buerli store.
The tree is kept up to date by the regular structure broadcast; pass
refresh: true to explicitly request it from ClassCAD first.
Parameters
| Parameter | Type | Description |
|---|---|---|
opts? | { refresh?: boolean; } | Options. |
opts.refresh? | boolean | Loads the tree from ClassCAD before reading it. |
Returns
Promise<ScgTree>
The structure tree, keyed by object id.
Example
import { ccUtils, ScgClassType } from '@buerli.io/classcad'
// The parts of a loaded model, by name
const tree = await facade.tree({ refresh: true })
const parts = Object.values(tree).filter(node => ccUtils.base.isA(node.class, ScgClassType.CCPart))
const wheel = parts.find(node => node.name === 'Wheel')
console.log(parts.map(node => node.name), wheel?.id)
Properties
| Property | Modifier | Type | Description |
|---|---|---|---|
utils | public static | { callMiddleware: (drawingId, middlewareCommand, data) => Promise<ServerResponse | undefined>; connect: (drawingName?) => Promise<DrawingID | null>; disconnect: (drawingId) => Promise<void>; fetchTree: (drawingId) => Promise<void>; getEmissionConfig: (drawingId) => Promise<EmissionConfig>; reconnect: (drawingId) => Promise<void>; redo: (drawingId, state?) => Promise<void>; setEmissionConfig: (drawingId, partial) => Promise<EmissionConfig>; undo: (drawingId, state?) => Promise<void>; withEmissionConfig: <T>(drawingId, partial, fn) => Promise<T>; } | The facade's work for any drawing, by its id: connect, disconnect, undo, emission config, middleware commands. For drawings an app connects without a facade, and for the drawings of react-cad and Buerligons. |
utils.callMiddleware | public | (drawingId, middlewareCommand, data) => Promise<ServerResponse | undefined> | Sends a command of the server's own (AwvNodeClient.executeMiddleware) and resolves to the reply. Messages of the reply are logged as warnings; on an error it is logged and the call resolves to undefined. |
utils.connect | public | (drawingName?) => Promise<DrawingID | null> | Creates a drawing and connects it: makes its client with the factory given to init, calls the onClient callbacks, connects the client. Rejects when init has not been called. When connecting fails, the error is logged and shown as a message, and it resolves to null. |
utils.disconnect | public | (drawingId) => Promise<void> | Disconnects the drawing's client and resets the drawing: it stays in the store, without its model. Rejects when the drawing has no client. |
utils.fetchTree | public | (drawingId) => Promise<void> | Loads the drawing's structure tree from the engine into the store. |
utils.getEmissionConfig | public | (drawingId) => Promise<EmissionConfig> | Effective per-connection emission config of the drawing's client (see IComClient.getEmissionConfig). |
utils.reconnect | public | (drawingId) => Promise<void> | Disconnects the drawing, connects its client again and recalculates the model (v1.common.recalc). |
utils.redo | public | (drawingId, state?) => Promise<void> | Steps forward in the drawing's undo history, to state when given (AwvNodeClient.redo). Messages of the engine are logged as warnings; an error is logged, not thrown. |
utils.setEmissionConfig | public | (drawingId, partial) => Promise<EmissionConfig> | Merges partial into the per-connection emission config and returns the effective flags. |
utils.undo | public | (drawingId, state?) => Promise<void> | Steps back in the drawing's undo history, to state when given (AwvNodeClient.undo). Messages of the engine are logged as warnings; an error is logged, not thrown. |
utils.withEmissionConfig | public | <T>(drawingId, partial, fn) => Promise<T> | Runs fn with a temporary emission config and restores the previous one afterwards (also when fn throws). This is the "own config for a script" pattern: e.g. switch every graphic category off while a script issues hundreds of commands, then restore the app's config and fetch tree + graphic once. On engines without GetEmissionConfig/SetEmissionConfig (EMISSION_CONFIG_UNSUPPORTED) fn simply runs with the fixed config. Only the flags in partial are restored. Resolves to what fn resolves to. EmissionConfig has an example. |
Types
ClassCADApi
type ClassCADApi = UnwrappedApiType;
The ClassCAD API of a BuerliCadFacade, unwrapped: calls resolve to their result and reject on an error.
api.v1 holds the namespaces (part, sketch, assembly, solid, curve, common, drawing2d), which are
also on the object itself. See createUnwrappedApi.
Example
import { BuerliCadFacade } from '@buerli.io/classcad'
import type { ClassCADApi } from '@buerli.io/classcad'
// A model function that works with any facade's API
async function plateWithHole(api: ClassCADApi, size: number) {
const part = (await api.v1.part.create({ name: 'Plate' }))!
const plate = (await api.v1.part.box({ id: part, length: size, width: size, height: 10 }))!
const hole = (await api.v1.part.cylinder({ id: part, diameter: size / 2, height: 10 }))!
await api.v1.part.boolean({ id: part, type: 'SUBTRACTION', target: plate, tools: [hole] })
return part
}
const facade = new BuerliCadFacade()
await facade.connect()
const part = await plateWithHole(facade.api, 100)
Advanced
Most apps do not need these directly: types they meet through the exports above, and what is there for special cases or for building on buerli itself.
ccUtils
const ccUtils: {
assembly: __module;
base: __module;
part: __module;
};
Helpers that look things up in a drawing's structure tree: classes, ancestors, work geometry, sketches, constraints.
They read the tree in the buerli store; none of them calls the engine.
Type Declaration
Example
import { ccUtils, ScgClassType } from '@buerli.io/classcad'
// The part a picked solid belongs to, and that part's sketches
const partId = ccUtils.base.getAncestorIdByClass(facade.drawingId, solidId, ScgClassType.CCPart)
if (partId !== null) {
const sketches = ccUtils.part.getSketch(facade.drawingId, partId)
const tree = await facade.tree()
console.log(tree[partId].name, sketches.map(id => tree[id].name))
}
createApi()
function createApi(drawingId, cached?): ApiType;
The ClassCAD API of a drawing; every call resolves to the engine's reply, { result, messages, maxLevel }.
v1 holds the namespaces (part, sketch, assembly, solid, curve, common, drawing2d); they are also
on the object itself, so createApi(id).part is createApi(id).v1.part. A call rejects when the reply carries an
error, with that reply, and when the drawing has no client. Warnings and errors are logged and shown as messages
of the drawing. v0, the old API, is deprecated. createUnwrappedApi gives the results without the reply
around them.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
drawingId | DrawingID | undefined | A drawing that is connected, for example by BuerliCadFacade.connect. |
cached? | boolean | true | true (default) returns the same object for the drawing on every call, false makes a new one. |
Returns
ApiType
Example
import { BuerliCadFacade, createApi, MessageLevel } from '@buerli.io/classcad'
const drawingId = await BuerliCadFacade.utils.connect('Bracket')
if (drawingId) {
const api = createApi(drawingId)
const part = (await api.v1.part.create({ name: 'Bracket' })).result!
// The reply carries the engine's messages; a warning does not reject
const { messages } = await api.v1.part.box({ id: part, length: 80, width: 40, height: 5 })
const warnings = messages?.filter(m => m.level === MessageLevel.warning) ?? []
if (warnings.length) console.warn(warnings.map(m => m.message))
}
createUnwrappedApi()
function createUnwrappedApi(drawingId, cached?): UnwrappedApiType;
The ClassCAD API of a drawing, unwrapped: calls resolve to their result and reject on an engine error.
The same namespaces as createApi (v1.part, v1.sketch, …, also on the object itself); this is what
BuerliCadFacade.api is. A rejection carries the engine's reply ({ result, messages, maxLevel }).
Warnings are logged and shown as messages of the drawing.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
drawingId | DrawingID | undefined | A drawing that is connected, for example by BuerliCadFacade.connect. |
cached? | boolean | true | true (default) returns the same object for the drawing on every call, false makes a new one. |
Returns
UnwrappedApiType
Example
import { BuerliCadFacade, createUnwrappedApi } from '@buerli.io/classcad'
const drawingId = await BuerliCadFacade.utils.connect('Plate')
if (drawingId) {
const api = createUnwrappedApi(drawingId)
try {
const part = (await api.v1.part.create({ name: 'Plate' }))!
await api.v1.part.box({ id: part, length: 200, width: 100, height: 8 })
} catch (reply) {
// The engine's reply, with the error in its messages
console.error(reply)
}
}
getApiFacade()
function getApiFacade(drawingId): IFacade & {
callUnsafeApi: (command) => Promise<FacadeReturn>;
};
The low-level facade a drawing's API calls go through: one ClassCAD function, called by its name.
callSafeApiV(version, namespace, func, args, options) runs <version>.<namespace>.<func> and resolves to the
engine's reply, { result, messages, maxLevel }; callSafeApi takes the namespace with its version
('v1.part'). fetchTree loads the structure tree anew, callUnsafeApi sends a command as it is. Replies are
handled as in createApi. Use it for a function the typed API does not have, or one picked at runtime.
Parameters
| Parameter | Type | Description |
|---|---|---|
drawingId | DrawingID | The drawing whose client the calls go to. |
Returns
IFacade & {
callUnsafeApi: (command) => Promise<FacadeReturn>;
}
Example
import { getApiFacade } from '@buerli.io/classcad'
// A call named at runtime, e.g. by a script or a tool an AI agent calls
async function call(name: string, args: Record<string, unknown>) {
const [namespace, func] = name.split('.') // 'part.box'
const { result, messages } = await getApiFacade(drawingId).callSafeApiV('v1', namespace, func, args)
return { result, messages }
}
const { result: part } = await call('part.create', { name: 'Part' })
await call('part.box', { id: part, length: 50 })