Hooks
Open a drawing in a component and read buerli's state. Start with useBuerliCadFacade.
Hooks
useBuerliCadFacade()
function useBuerliCadFacade(
name,
build?,
deps?
): {
api: UnwrappedApiType;
drawingId: DrawingID;
facade: BuerliCadFacade;
Geometry: (props) => Element;
graphic: (opts?) => Promise<
| {
containers: ScgGraphicContainer[];
}
| null>;
tree: (opts?) => Promise<ScgTree>;
};
Opens the drawing called name and returns its ClassCAD API, its facade and a component that renders it, and with
build, makes its model.
The hook suspends until the drawing is connected, so it needs a <Suspense> boundary above it (a
react-three-fiber Canvas has one). When the drawing cannot connect, it throws what kept it from connecting (a
token the key service refuses, an engine that cannot be reached) to the nearest error boundary, as
facade.connect() rejects with it. Components that pass the same name share one drawing; another name opens
another drawing. When the last component that uses a name unmounts, its drawing is closed and removed
(facade.destroy()), on the next tick: a component that takes the name up again before that, as React's
StrictMode does when it mounts each component twice in development, keeps the drawing.
build makes the model with the drawing's api: the hook runs it once the drawing is connected, and again
whenever a value in deps changes. A run that a newer one replaces, or whose drawing closes, stops at its next call
of api, so quick changes of deps don't mix two models, and StrictMode's second mount, or content that a
Suspense boundary hides and shows again, doesn't build again. The builds on one drawing take turns, those of several hook calls too: what ran last is
what the drawing holds, so one model is best made by one build. An error build throws goes to the nearest error
boundary.
Call init from @buerli.io/classcad before the first drawing opens: it sets how drawings reach the engine.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
name | string | undefined | The drawing's name, which is also the key components share it by. |
build? | (api) => Promise<unknown> | undefined | Makes the drawing's model with its api. The latest build is the one that runs. |
deps? | DependencyList | [] | The values the model is made from: build runs again when one of them changes. Default []. |
Returns
The drawing's api, facade, drawingId, tree, graphic and Geometry.
| Name | Type | Description |
|---|---|---|
api | UnwrappedApiType | The ClassCAD API of the drawing: each call returns its result and throws when the engine reports an error. |
drawingId | DrawingID | The id of the drawing. |
facade | BuerliCadFacade | The drawing's facade: three.js geometry and scenes of its products, and the drawing's lifecycle. |
Geometry() | (props) => Element | A BuerliGeometry for this drawing, which suspends while the drawing has pending commands. |
graphic() | (opts?) => Promise< | { containers: ScgGraphicContainer[]; } | null> | Reads the drawing's current tessellation: the facade's graphic. 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>. 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) |
tree() | (opts?) => Promise<ScgTree> | Reads the drawing's structure tree, by object id: the facade's tree. 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. 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) |
Examples
A box whose size is a prop: the hook builds it, and builds it again when size changes.
import { useBuerliCadFacade } from '@buerli.io/react'
function Box({ size = 100 }) {
const { Geometry } = useBuerliCadFacade(
'box',
async (api) => {
await api.v1.common.clear()
const part = await api.v1.part.create({ name: 'Box' })
await api.v1.part.box({ id: part, width: size, length: size, height: size })
},
[size],
)
return <Geometry />
}
Without build, the hook opens the drawing, and the component works with its api and facade itself:
import { useBuerliCadFacade } from '@buerli.io/react'
function PartCount() {
const { tree } = useBuerliCadFacade('box')
const count = async () => Object.values(await tree()).filter((node) => node.class === 'CC_Part').length
return <button onClick={async () => alert(await count())}>Count parts</button>
}
useBuerli()
function useBuerli<T>(selector, equalityFn?): T;
Selects a part of buerli's state, and renders again when it changes.
A selector may return a new object or array each time: what decides whether the component renders
again is equalityFn (Object.is if none is given), the same as with zustand 4's hooks.
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type | Description |
|---|---|---|
selector | (state) => T | What to take from the state. |
equalityFn? | (a, b) => boolean | Whether two selections are the same. |
Returns
T
What the selector returned.
Example
Tabs for the open drawings, which switch the active one:
import { useBuerli } from '@buerli.io/react'
function DrawingTabs() {
const drawings = useBuerli((state) => state.drawing.refs)
const active = useBuerli((state) => state.drawing.active)
const setActive = useBuerli((state) => state.api.setActiveDrawing)
return (
<nav>
{Object.values(drawings).map((drawing) => (
<button key={drawing.id} disabled={drawing.id === active} onClick={() => setActive(drawing.id)}>
{drawing.name}
</button>
))}
</nav>
)
}
useDrawing()
function useDrawing<T>(
id,
sel,
equalityFn?
): T | undefined;
Selects a part of a drawing's state, and renders again when it changes.
The drawing's state holds its structure tree, geometry, selection and plugins. While the drawing does not exist,
or when the selector throws (the error is logged), the hook returns undefined. Whether the selection changed is
decided by equalityFn (Object.is if none is given).
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type | Description |
|---|---|---|
id | DrawingID | The drawing. |
sel | (state) => T | undefined | What to take from the drawing's state. |
equalityFn? | (a, b) => boolean | Whether two selections are the same. |
Returns
T | undefined
What the selector returned, or undefined.
Example
A list of the drawing's parts, from its structure tree, which updates as parts are made or renamed:
import { useBuerliCadFacade, useDrawing } from '@buerli.io/react'
function PartList() {
const { drawingId } = useBuerliCadFacade('main')
// Select something that keeps its identity (the tree), and derive from it while rendering
const tree = useDrawing(drawingId, (drawing) => drawing.structure.tree)
const parts = Object.values(tree ?? {}).filter((node) => node.class === 'CC_Part')
return (
<ul>
{parts.map((part) => (
<li key={part.id}>{part.name}</li>
))}
</ul>
)
}