Skip to main content

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​

BuerliCadFacade

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​
ParameterTypeDescription
drawingName?stringThe 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​
ParameterTypeDescription
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​
ParameterTypeDefault valueDescription
ownernumberundefinedThe shape curve, from v1.curve.shape.
fPts{ point: Vector3; radius: number; }[]undefinedThe corners, each with the radius to round it with (0 for a sharp corner).
close?booleantrueCloses 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​
ParameterTypeDescription
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?booleanMakes 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?booleanMakes 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​
ParameterTypeDescription
ownernumberThe shape curve, from v1.curve.shape.
shapeShapeThe 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​
ParameterTypeDescription
opts?{ recalc?: boolean; }Options.
opts.recalc?booleanRecalculates 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​
ParameterTypeDescription
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​
ParameterTypeDescription
opts?{ refresh?: boolean; }Options.
opts.refresh?booleanLoads 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​

PropertyModifierTypeDescription
utilspublic 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.callMiddlewarepublic(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.connectpublic(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.disconnectpublic(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.fetchTreepublic(drawingId) => Promise<void>Loads the drawing's structure tree from the engine into the store.
utils.getEmissionConfigpublic(drawingId) => Promise<EmissionConfig>Effective per-connection emission config of the drawing's client (see IComClient.getEmissionConfig).
utils.reconnectpublic(drawingId) => Promise<void>Disconnects the drawing, connects its client again and recalculates the model (v1.common.recalc).
utils.redopublic(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.setEmissionConfigpublic(drawingId, partial) => Promise<EmissionConfig>Merges partial into the per-connection emission config and returns the effective flags.
utils.undopublic(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.withEmissionConfigpublic<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​

NameTypeDescription
assembly__moduleAssemblies: findProduct (of an instance), getInstance, getPartTemplate, getAssemblyTemplate, getTransformation, getMatePath, getWorkGeometry, and the constraints (getConstraint, getFastenedConstraint, getRevoluteConstraint, …).
base__moduleClass checks and tree walks: isA (a class or one derived from it), getChildren, getAncestors, getAncestorIdByClass, getDescendants, findObject (by identifier or name).
part__moduleParts: getWorkGeometry, getSketch, getSketchRegion, getSketchlines.

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​

ParameterTypeDefault valueDescription
drawingIdDrawingIDundefinedA drawing that is connected, for example by BuerliCadFacade.connect.
cached?booleantruetrue (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​

ParameterTypeDefault valueDescription
drawingIdDrawingIDundefinedA drawing that is connected, for example by BuerliCadFacade.connect.
cached?booleantruetrue (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​

ParameterTypeDescription
drawingIdDrawingIDThe 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 })