tutar-3d
v1.0.1
Published
Offline 3D GLB model viewer SDK for React Native
Downloads
119
Maintainers
Readme
tutar-3d
Offline 3D GLB model viewer SDK for React Native (Android-only). Wraps react-native-filament behind a small component-first API. The SDK itself makes no network calls — model bytes are read from local storage and rendered entirely on-device.
Android-only. iOS is not currently supported — the SDK's native module is implemented for Android only, and the iOS equivalent has not been written yet.
Installation
npm install tutar-3d \
react-native-filament \
react-native-worklets-core \
react-native-fs \
react-native-svgAll four are peer dependencies — the SDK does not bundle them:
react-native-filament— the underlying 3D renderer.react-native-worklets-core— required by Filament's render callbacks.react-native-fs— used for local file access when loading models.react-native-svg(>=13) — toolbar icons in<DraggableModelViewer>.
On React Native 0.73, pin
react-native-svgto15.1.0(15.2+ requires RN ≥ 0.74).
In your metro.config.js, add glb (and gltf, bin, hdr, ktx if you use them) to resolver.assetExts. In your babel.config.js, add the react-native-worklets-core/plugin plugin.
Android needs JDK 17. Rebuild the Android app after installing (the SDK's native module autolinks at build time, not at JS-reload time).
Usage
import { TUTAR, ModelViewer } from 'tutar-3d';
TUTAR.initialize({ debug: __DEV__ });
export default function App() {
return (
<ModelViewer
source={{ uri: 'file:///data/data/your.app/files/models/robot.glb' }}
cameraPosition={[0, 0, 5]}
cameraTarget={[0, 0, 0]}
/>
);
}source must be { uri: 'file:///...' } pointing at a model on local storage. Bundled require() assets and http(s):// URIs are rejected — stage the model bytes to local storage in your host app first.
API
TUTAR.initialize(opts?: { debug?: boolean })— call once before rendering any viewer. Idempotent.TUTAR.isInitialized(): boolean<ModelViewer source style cameraPosition cameraTarget castShadow receiveShadow paused animationIndex interactive transformToUnitCube loadingIndicator labelsVisible onLabelsExtracted recenterNonce audioEnabled muted onAudioAvailable /><DraggableModelViewer />— a draggable/pinch-resizable floating viewer with a toolbar (orbit toggle, play/pause, labels toggle if present, mute toggle if the model has audio, recenter, close). Border + toolbar auto-hide after 4s idle in drag mode.
animationIndex defaults to 'all' (every clip plays and loops on its own duration); pass a number to play a single clip. While any viewer is being dragged/resized, all mounted viewers pause to keep the gesture smooth.
recenterNonce (default 0) is a recenter signal for <ModelViewer>: whenever its value changes, the camera resets to its home position/target (any orbit/pan/zoom is undone) without reloading the model. Bump it from your own "recenter" control, e.g. setN((n) => n + 1). (<DraggableModelViewer> drives this internally from its toolbar's recenter button, so you don't pass it there.)
Labels
The SDK auto-detects per-node text labels embedded in the GLB and renders them as a side-pinned overlay with connector lines back to each anchor in the 3D scene. To label a node in your model authoring tool, set extras.prop on it (the same field Three.js surfaces as userData.prop):
{
"nodes": [
{ "name": "Piston_A", "extras": { "prop": "Piston A" }, "translation": [...] }
]
}<DraggableModelViewer> parses the GLB on load; if any nodes carry a prop string, the toolbar grows a labels toggle (price-tag glyph). Labels are hidden by default — the user taps to reveal. Models with no labeled nodes never see the toggle button.
For <ModelViewer> directly, pass labelsVisible to control visibility and (optionally) onLabelsExtracted={count => ...} if your own UI needs to know how many labels were found.
Audio
The SDK auto-plays a per-model narration track in sync with the animation. Drop an .mp3 next to the model on the device — same folder, same base name (robot.glb → robot.mp3). If it's there, the SDK loops it while the animation plays and pauses it the moment the animation pauses (and whenever the app is backgrounded). No mp3 means the model just renders silently.
/data/data/your.app/files/models/robot.glb ← model
/data/data/your.app/files/models/robot.mp3 ← narration (optional)Playback runs through the SDK's own native module (Android MediaPlayer), so there's no extra peer dependency, and it's fully offline. Rebuild the Android app after upgrading so the native audio module autolinks.
<DraggableModelViewer>grows a mute toggle in its toolbar once it detects a track (the play/pause button already starts/stops the audio along with the animation).- For
<ModelViewer>directly:audioEnabled(defaulttrue) turns the feature off,muted(defaultfalse) silences without stopping, andonAudioAvailable={available => ...}tells your UI whether a track was found.
Customizing <DraggableModelViewer>
Beyond the inherited <ModelViewer> props (source, audio, etc.), the floating viewer accepts optional appearance/layout props: width, height, minSize, maxSize, initialPosition, resizeSensitivity, borderColor, borderThickness, borderRadius, dashLength, dashGap, resizeHandleColor, buttonStyle, buttonActiveStyle, iconStyle, containerStyle, and an onClose callback fired when the user taps the close button.
Development
See CLAUDE.md for the dev environment, monorepo layout, and example app workflow.
License
MIT
