Skip to main content

React

API#

The React headless API consts of one global function init and two hooks, useSolid and useHistory. Init connects to ClassCAD. Solid and History open sessions and have different APIs. Solid is for creating geometry, History is for creating history-based geometry, and manipulating it later on with constraints and paremeters

type HeadlessConfig = Partial<BuerliState['options']> & {
/** Suspense entry invalidation in ms, default: 0 (keep-alive forever) */
lifespan?: number
/** Suspense cache equality function, default: (a, b) => a === b (reference equality) */
equal?: (a: any, b: any) => boolean
}
init(source: string, options?: Partial<BuerliState["options"]>)
useSolid(name: string, config?: HeadlessConfig = {})
useHistory(name: string, config?: HeadlessConfig = {})

Both hooks, useSolid and useHistory, return the same API:

type HeadlessReturn<Impl, Store> = {
/** The headless instance, for example: new Solid() */
instance: InstanceType<Impl>
/** The drawingId of the instance */
drawingId: DrawingID
/** The headless instance API, for example: new solid().init(api => ...) */
api: ApiType<Impl>
/** Executes ClassCAD commands */
run: (callback: Fn) => Promise<Await<ReturnType<Fn>>>
/** Executes ClassCAD commands and returns a memoized result (using React suspense) */
cache: (callback: Fn, dependencies: Keys) => Await<ReturnType<Fn>>
/** Looks up a previously memoized result (that was executed by the cache function above) */
peek: (dependencies: Keys) => unknown
/** Executes ClassCAD commands and preloads the memoized result */
preload: (callback: Fn, dependencies: Keys) => unknown
/** Renderable component that ties a @buerli.io/react BuerliGeometry to the headless instance */
Geometry: (props: BuerliGeometryProps) => JSX.Element
}

Usage#

You must call init before you can use the headless API, this connects your browser tab to ClassCAD. If you provide a "ws://..." URL, the connection will be made via WebSocket. If you provide a "http://..." URL, the connection will be via WASM (the endpoint must provide a valid classcad.wasm).

You should do this in the root of your application.

import { useEffect } from 'react'
import { Canvas } from '@react-three/fiber'
import { init, useSolid, useHistory } from '@buerli.io/react'
init('ws://localhost/9091')

Then you can use the headless API in your components. useSolid or useHistory will provide the necessary methods and data, as well as a Geometry component that is primed to the session, you can use it to render out the objects that you have created.

By default, the headless instance is named default, if you don't provide a name every call to useSolid or useHistory will return the same instance, no matter in which component you call it.

function Box() {
const { run, Geometry } = useSolid()
useEffect(() => {
// This will create a box
run(async (api) => {
const id = await api.createBox(100, 100, 100)
})
}, [])
// This will render the output
return <Geometry />
}
function App() {
return (
<Canvas>
<Box />
</Canvas>
)
}

You can give the headless instance a custom name, which allows you to refer to it in multiple components:

function Foo() {
const { run } = useSolid('main')
...
function Bar() {
// This will access the same instance as Foo
const { run } = useSolid('main')
...
function Render() {
const { Geometry } = useSolid('main')
// This will render the outcome of the "main" session
return <Geometry />

Or, create multiple headless instances, but whose state you can still access between components:

function SessionA() {
const { run } = useSolid('A')
...
function SessionB() {
// This is a different session than SessionA
const { run } = useSolid('B')
...

Run#

run Executes ClassCAD commands. You can call it in events, effects, or anywhere you like.

function Scene({ width = 100 }) {
const { run, Geometry } = useHistory()
useEffect(() => {
// This will create two cylinders, and then merge them
run(async (api) => {
const part = await api.createPart('Part')
const wcsy = await api.createWorkCoordSystem(part, 8, [], [], [0, width / 3, 0], [Math.PI / 3, 0, 0])
const wcsx = await api.createWorkCoordSystem(part, 8, [], [], [0, -width / 5, -width / 8], [0, 0, 0])
const a = await api.cylinder(part, [wcsx], 10, width)
const b = await api.cylinder(part, [wcsy], 10, width)
await api.boolean(part, 0, [a, b])
})
}, [])
return <Geometry />
}

Cache#

cache suspends and returns the result of the function as a cached and memoized value. The results can be displayed in whichever way you like, for instance returning a geometry and adding it to a mesh right away.

The dependecies are cache keys. Similar to a useMemo, the inner function is called when the cache keys change. The dependencies are also passed to the inner function, following api so you could hoist it.

function Scene({ width = 100 }) {
const { cache } = useHistory()
const geo = cache(
async (api, ...args) => {
const part = await api.createPart('Part')
const wcsy = await api.createWorkCoordSystem(part, 8, [], [], [0, width / 3, 0], [Math.PI / 3, 0, 0])
const wcsx = await api.createWorkCoordSystem(part, 8, [], [], [0, -width / 5, -width / 8], [0, 0, 0])
const a = await api.cylinder(part, [wcsx], 10, width)
const b = await api.cylinder(part, [wcsy], 10, width)
const solid = await api.boolean(part, 0, [a, b])
return await api.createBufferGeometry(solid)
},
[width],
)
return (
<mesh geometry={geo}>
<meshStandardMaterial />
</mesh>
)
}