Use a generated Three.js model in React Three Fiber
Mount a generated THREE.Group in React Three Fiber, animate named parts with useFrame, and dispose procedural geometry safely.
PhotoTo3D exports a normal THREE.Group, so it works in React Three Fiber without a custom loader or proprietary runtime. Create the group once, mount it with <primitive>, then use the exported runtime contract to address individual parts.
Mount a THREE.Group 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 { useEffect, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import { createObjectModel } from './models/create-object-model';
function Model() {
const model = useMemo(() => createObjectModel(), []);
useFrame((_, delta) => {
model.userData.sculptRuntime.nodes.turret.rotation.y += delta * 0.6;
});
useEffect(() => () => {
model.traverse((object) => {
if (object instanceof THREE.Mesh) object.geometry.dispose();
});
}, [model]);
return <primitive object={model} />;
}
Animate named parts with 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 generated geometry safely
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 MeshPhysicalMaterial with your own. If you do, dispose the material you swapped out. The factory’s original materials are shared by materialIndex, so only dispose each material once, after every mesh that referenced it has been detached.
useEffect(() => () => {
model.traverse((object) => {
if (object instanceof THREE.Mesh) object.geometry.dispose();
});
}, [model]);
What stays available in R3F
Mounting through <primitive> preserves the same contract as plain Three.js:
model.userData.sculptRuntime.nodescontains stable handles to named parts.- Geometry and materials remain standard Three.js objects.
- Exported pivots and parent-child relationships remain intact.
- No PhotoTo3D network request or subscription check runs when the model loads.
Use the named runtime handles for animation and interaction instead of relying on child array order. That keeps application code stable when a later reconstruction adds or rearranges parts.