React Three Fiber integration
Mount the generated factory in R3F and drive it from the runtime contract.
Create once, mount with primitive
The factory builds a plain THREE.Group. In React Three Fiber you want that Group created exactly once, not on every render, so wrap the call in useMemo with an empty dependency array and hand the result to a <primitive> element.
Because the factory returns real Three.js objects rather than JSX, <primitive object={...} /> is the correct mount point. R3F adds it to the scene graph as-is and keeps its named children, materials and userData intact.
import { useMemo } from 'react';
import { createObjectModel } from './models/create-object-model';
function Model() {
const model = useMemo(() => createObjectModel(), []);
return <primitive object={model} />;
}Read the runtime in useFrame
The runtime contract lives on model.userData.sculptRuntime and gives you stable handles to every part by name. Capture it once, then mutate transforms inside useFrame — R3F does not re-run the factory, it simply renders the mutated scene graph each frame.
Resolve names through runtime.nodes rather than walking model.children. Child order is an implementation detail of the spec's node list and can shift between reconstructions; the names in the contract are the stable surface.
const runtime = model.userData.sculptRuntime;
useFrame((_, delta) => {
runtime.nodes.turret.rotation.y += delta * 0.6;
});Dispose only what you own
R3F disposes geometries and materials for objects it created through JSX, but a factory Group is created outside of R3F, so you are responsible for its lifecycle if the component unmounts and will not be reused.
The common case is replacing a generated MeshStandardMaterial with your own. If you do, dispose the material you swapped out — the factory's original materials are shared by materialIndex, so only dispose one once, after you have detached every mesh that referenced it.
useEffect(() => () => {
model.traverse((o) => {
if (o.geometry) o.geometry.dispose();
});
}, [model]);