npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@taranjeetsinghh/model-lab

v0.8.0-alpha.1

Published

A React Three Fiber 3D model inspector, animation authoring studio, and production sequence runtime.

Readme

@taranjeetsinghh/model-lab

Full guides, live examples, API reference, and architecture documentation are available in apps/docs. Run npm run dev:docs from the workspace root.

A React Three Fiber package for inspecting glTF models, authoring immersive camera/model/lighting sequences, and replaying those sequences in production websites.

Installation

npm install @taranjeetsinghh/model-lab three @react-three/fiber @react-three/drei react react-dom

Import the package stylesheet once in the application that uses the full editor:

import '@taranjeetsinghh/model-lab/styles.css'

1. Full authoring studio

import { ModelLabEditor } from '@taranjeetsinghh/model-lab/editor'
import '@taranjeetsinghh/model-lab/styles.css'

export function StudioPage() {
  return (
    <div style={{ height: '100vh' }}>
      <ModelLabEditor
        dracoDecoderPath="/draco/"
        onConfigChange={(config) => console.log(config)}
        onSequenceChange={(sequence) => console.log(sequence)}
      />
    </div>
  )
}

The editor includes local GLB/GLTF loading, scene/material inspection, transform controls, editable model pivots, texture UV controls, camera editing, model animations, bounding boxes, vertex normals, sequence keyframes, screenshots, and JSON export/import.

Imperative editor API

import { useRef } from 'react'
import {
  ModelLabEditor,
  type ModelLabEditorHandle,
} from '@taranjeetsinghh/model-lab/editor'

export function StudioPage() {
  const editor = useRef<ModelLabEditorHandle>(null)

  return (
    <>
      <button onClick={() => editor.current?.fitCamera()}>Fit</button>
      <button onClick={() => console.log(editor.current?.getConfig())}>Read config</button>
      <ModelLabEditor ref={editor} />
    </>
  )
}

Available methods:

  • getConfig()
  • getPreset()
  • setConfig(partialConfig)
  • loadFiles(files)
  • openFiles()
  • openFolder()
  • fitCamera()
  • reset()
  • captureScreenshot()
  • setPivot(point, { preserveVisualPosition })
  • centerPivot({ preserveVisualPosition })
  • resetPivot({ preserveVisualPosition })

Model pivot / rotation-axis editing

Some assets are exported with an origin far away from their visible geometry. Model Lab now places the imported asset below a pivot transform, so rotation, scale, transform gizmos, embedded glTF animation, and immersive sequences can all use a corrected axis without modifying geometry data.

Inside the editor, open Pivot / rotation axis and choose one of these workflows:

  • Set to model center computes the center of the imported geometry bounds.
  • Pick on model lets you click a visible surface or point-cloud particle.
  • Pivot point accepts any exact XYZ value in the imported model's local space.
  • Use imported origin restores [0, 0, 0].
  • Move pivot to world origin places the currently selected pivot at world position zero.

Keep Keep model visually in place enabled to move the pivot without moving the rendered asset. The same compensation is applied to every existing sequence keyframe.

Programmatic editor control

const editor = useRef<ModelLabEditorHandle>(null)

<button onClick={() => editor.current?.centerPivot()}>
  Center rotation axis
</button>

<button
  onClick={() =>
    editor.current?.setPivot([1.25, 0.4, -2], {
      preserveVisualPosition: true,
    })
  }
>
  Use custom pivot
</button>

Runtime preset

The pivot is included in the exported viewer JSON:

const config = createViewerConfig({
  pivot: {
    point: [1.25, 0.4, -2],
    visible: false,
  },
})

<ModelLabViewer src="/models/machine.glb" config={config} />

For a controlled configuration that already has authored keyframes, use setViewerPivot() so the current model pose and sequence positions are compensated:

import { setViewerPivot } from '@taranjeetsinghh/model-lab/pivot'

const nextConfig = setViewerPivot(currentConfig, boundsCenter, {
  preserveVisualPosition: true,
})

Existing React Three Fiber canvas

import { ModelPivotGroup } from '@taranjeetsinghh/model-lab/pivot'

<ModelPivotGroup
  ref={modelRef}
  pivot={preset.viewer.pivot.point}
  position={preset.viewer.model.position}
  rotation={preset.viewer.model.rotation}
  scale={preset.viewer.model.scale}
>
  <primitive object={gltf.scene} />
</ModelPivotGroup>

ModelPivotGroup.rotation uses degrees to match exported Model Lab configuration.

2. Production viewer

Export the complete preset from the editor and pass it directly to the lightweight runtime entry point. The preset contains camera, scene, model transforms, material values, texture UV settings, and the immersive sequence.

import {
  ModelLabViewer,
  parseViewerPreset,
} from '@taranjeetsinghh/model-lab/viewer'
import galaxyPresetJson from './galaxy-viewer-config.json'

const galaxyPreset = parseViewerPreset(galaxyPresetJson)

export function GalaxyHero() {
  return (
    <div style={{ height: '100vh' }}>
      <ModelLabViewer
        src="/models/galaxy.glb"
        preset={galaxyPreset}
        playSequence
        sequenceLoop
        controlsWhilePlaying={false}
        dracoDecoderPath="/draco/"
      />
    </div>
  )
}

config and sequence remain available as overrides. A sequence passed through the sequence prop takes precedence over the sequence stored in preset.viewer.sequence.

Replacement texture URLs

Textures already inside the GLB/GLTF are reused automatically. A texture uploaded through the editor exists only as a local browser file, so map its exported filename to a deployable URL:

<ModelLabViewer
  src="/models/galaxy.glb"
  preset={galaxyPreset}
  textureUrls={{
    'galaxy-color.webp': '/models/textures/galaxy-color.webp',
    'galaxy-normal.webp': '/models/textures/galaxy-normal.webp',
  }}
  onTextureError={(name, error) => {
    console.error(`Could not restore ${name}`, error)
  }}
/>

The runtime restores repeat, offset, center, rotation, wrapping, flipY, and color-space values after the texture loads.

Separate sequence export

ModelLabViewer accepts either a SequenceConfig or the complete 3d-model-lab-sequence/v1 payload:

import sequenceExport from './galaxy-warp.json'

<ModelLabViewer
  src="/models/galaxy.glb"
  preset={galaxyPreset}
  sequence={sequenceExport}
  playSequence
/>

Scroll-driven sequence

<ModelLabViewer
  src="/models/galaxy.glb"
  preset={galaxyPreset}
  playSequence={false}
  sequenceProgress={scrollProgress}
/>

sequenceProgress is normalized from 0 to 1. The package maps it onto the authored sequence duration.

Runtime viewer ref

const viewer = useRef<ModelLabViewerHandle>(null)

<ModelLabViewer ref={viewer} src="/models/galaxy.glb" />

viewer.current?.fitCamera()
const png = viewer.current?.captureScreenshot()

Available methods:

  • getModel()
  • getRenderer()
  • getCamera()
  • getPivot()
  • getPivotWorldPosition()
  • getBoundsCenter()
  • fitCamera()
  • captureScreenshot()
  • setPivot(point, { preserveVisualPosition })
  • centerPivot({ preserveVisualPosition })
  • resetPivot({ preserveVisualPosition })

3. Headless sequence player

Use this when the website already has its own React Three Fiber canvas and model loader.

import { OrbitControls, useGLTF } from '@react-three/drei'
import { Canvas } from '@react-three/fiber'
import { useRef } from 'react'
import * as THREE from 'three'
import {
  ImmersiveSequencePlayer,
  ModelPivotGroup,
  type OrbitControlsApi,
} from '@taranjeetsinghh/model-lab/sequence'
import sequencePayload from './galaxy-warp.json'

function Scene() {
  const gltf = useGLTF('/models/galaxy.glb')
  const modelRef = useRef<THREE.Group>(null)
  const controlsRef = useRef<OrbitControlsApi>(null)

  return (
    <>
      <ModelPivotGroup ref={modelRef} pivot={[0, 0, 0]}>
        <primitive object={gltf.scene} />
      </ModelPivotGroup>
      <OrbitControls ref={controlsRef as never} />
      <ImmersiveSequencePlayer
        sequence={sequencePayload}
        modelRef={modelRef}
        controlsRef={controlsRef}
        play
        loop
      />
    </>
  )
}

export function ExistingCanvasExample() {
  return <Canvas><Scene /></Canvas>
}

Applying a preset in a custom scene

When an application owns its own loader and canvas, apply only the exported material/texture portion:

import { useEffect } from 'react'
import { useGLTF } from '@react-three/drei'
import { applyMaterialOverrides } from '@taranjeetsinghh/model-lab/viewer'
import preset from './galaxy-viewer-config.json'

function Galaxy() {
  const gltf = useGLTF('/models/galaxy.glb')

  useEffect(() => {
    void applyMaterialOverrides(gltf.scene, preset.materials, {
      textureUrls: {
        'galaxy-color.webp': '/models/textures/galaxy-color.webp',
      },
    })
  }, [gltf.scene])

  return <primitive object={gltf.scene} />
}

Clone the loaded scene first when the loader cache is shared by several component instances.

Configuration helpers

import {
  createViewerConfig,
  createFovWarpSequence,
  sampleSequence,
} from '@taranjeetsinghh/model-lab'

const config = createViewerConfig({
  camera: { fov: 48, position: [3, 2, 7] },
  scene: { background: '#050711', grid: false },
})

const warp = createFovWarpSequence(config)
const frameAtOneSecond = sampleSequence(warp, 1)

createViewerConfig() safely merges partial settings with defaults and creates independent arrays/keyframes.

Draco-compressed GLB files

The package exposes dracoDecoderPath. Copy Three.js Draco decoder files into the consuming app's public directory:

node_modules/three/examples/jsm/libs/draco/gltf/*
    → public/draco/*

Then use:

<ModelLabViewer dracoDecoderPath="/draco/" ... />

The same prop is available on ModelLabEditor.

Main exports

ModelLabEditor
ModelLabViewer
ImmersiveSequencePlayer
createViewerConfig
createFovWarpSequence
captureSnapshot
applySnapshot
sampleSequence
resolveSequence
createExportPayload
createSequencePayload
createModelBundle
DEFAULT_CONFIG

All configuration, sequence, callback, ref, and model-info TypeScript types are exported from the package root.

Package design

React, React DOM, Three.js, React Three Fiber, and Drei are peer dependencies. This keeps the host application in control of those versions and avoids shipping a second copy of Three.js inside the library bundle.

The build exposes focused entry points:

  • @taranjeetsinghh/model-lab — complete public API.
  • @taranjeetsinghh/model-lab/editor — full authoring studio.
  • @taranjeetsinghh/model-lab/viewer — production viewer and material-preset runtime.
  • @taranjeetsinghh/model-lab/sequence — headless sequence player and sequence utilities.
  • @taranjeetsinghh/model-lab/effects — headless cinematic material-effect driver.

SSR frameworks

WebGL needs a browser. In Next.js or another SSR framework, render the editor/viewer as a client component or load it dynamically with SSR disabled.

Publishing checklist

  1. Replace the placeholder package scope/name.
  2. Update author, repository, homepage, and bugs fields in package.json.
  3. Add screenshots and a hosted demo.
  4. Run npm run typecheck and npm run build.
  5. Run npm pack --dry-run and inspect the included files.
  6. Publish with public access when using a public npm scope.

Cinematic material effects

ModelLabViewer can animate material properties and texture UV transforms alongside a camera sequence:

<ModelLabViewer
  src="/models/galaxy.glb"
  sequence={sequence}
  playSequence
  effect={{
    type: 'cosmic-pulse',
    intensity: 1.2,
    speed: 0.2,
    color: '#67e8f9',
  }}
/>

Supported effect names are cosmic-pulse, texture-vortex, hologram-scan, chrome-breathe, and none. The driver discovers mesh, line, and point-cloud materials; point assets receive animated size, opacity, and color treatments while compatible mesh materials receive roughness, metalness, emissive, wireframe, and UV treatments.

For deterministic scrolling, pass the same normalized progress used by the sequence:

<ModelLabViewer
  src="/models/galaxy.glb"
  sequence={sequence}
  playSequence={false}
  sequenceProgress={scrollProgress}
  effect={{ type: 'texture-vortex', progress: scrollProgress }}
/>

Advanced canvases can import the headless driver from @taranjeetsinghh/model-lab/effects. Inside ModelLabViewer, effects automatically receive render-loop sequence progress when effect.progress is omitted; an explicit progress value still overrides that synchronization for scrolling or scrubbing.