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

printo-engine

v0.1.1

Published

Framework-agnostic Three.js engine for 3D product personalization and printable-surface customization

Readme

printo-engine

Framework-agnostic TypeScript engine for 3D product personalization: it composes a 2D design (images + text) into a texture and applies it to a printable surface of a Three.js GLB product.

This package has no runtime dependencies and no UI framework. Three.js is a peer dependency.

Install

npm install printo-engine three

Requirements: browser support for WebGL, ES modules, Canvas 2D, and ResizeObserver. Types are shipped for both the main entry and the printo-engine/types subpath.

Quick Start

import { createPrinto, type ProductConfig } from 'printo-engine';

const mug: ProductConfig = {
  id: 'ceramic-mug',
  name: 'Ceramic Coffee Mug',
  model: '/models/mug.glb',
  defaultBaseColor: '#FFFFFF',
  availableColors: [
    { name: 'Ceramic White', hex: '#FFFFFF' },
    { name: 'Matte Charcoal', hex: '#26292E' },
  ],
  material: { roughness: 0.15, metalness: 0.04, clearcoat: 0.4 },
  materialOverrides: [
    { materialName: 'Coffee', color: '#3D1E0C', roughness: 0.2, clearcoat: 0.6 },
  ],
  printableSurfaces: [
    {
      id: 'mug-outer-wrap',
      name: 'Full Body Cylindrical Wrap',
      strategy: 'uv',
      meshName: 'Coffee-Mug',
      materialName: 'Logo',
      uvBounds: { uMin: 0.09, uMax: 0.91, vMin: 0.14, vMax: 0.77 },
      printableWidthMm: 198,
      printableHeightMm: 80,
      textureResolution: { width: 2048, height: 1024 },
    },
  ],
  camera: {
    initialPosition: [0, 2, 17],
    target: [0, 0.5, 0],
    minDistance: 7,
    maxDistance: 32,
    fov: 42,
  },
};

const configurator = createPrinto({
  container: document.getElementById('viewer')!,
  products: [mug],
  initialProductId: 'ceramic-mug',
  onLoadingCompleted: () => console.log('ready'),
});

await configurator.init();

// Compose artwork into the design document and push it to the product texture.
await configurator.addImage(
  'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><rect width="200" height="200" fill="#111827"/></svg>'),
  { x: 0.5, y: 0.45, scale: 0.6, rotation: 0 },
);
configurator.addText('YOUR BRAND', {
  fontFamily: 'Arial, sans-serif',
  fontSize: 56,
  fontWeight: '700',
  color: '#FFFFFF',
  x: 0.5,
  y: 0.72,
});
configurator.setProductColor('#26292E');
configurator.downloadSnapshot('my-product.png');

Use from a CDN (no build step)

printo-engine is ESM-first and can run directly in the browser from a CDN such as jsDelivr using ES module import maps. Three.js and its addon subpaths are remapped to CDN URLs, then the engine is imported as a plain module:

<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
      "three/examples/jsm/controls/OrbitControls.js": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js",
      "three/examples/jsm/loaders/GLTFLoader.js": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/GLTFLoader.js"
    }
  }
</script>
<script type="module">
  import { createPrinto } from 'https://cdn.jsdelivr.net/npm/printo-engine/dist/index.js';
  // createPrinto(...) and init() as in the Quick Start above.
</script>

A complete standalone single-file example — product switcher, base colors, auto-rotate, reset design, snapshot download — is in the repository at example/index.html. It loads its product GLB files from the hosted Printo demo, so it needs no local assets. Serve the page over HTTP (ES modules do not load from file://).

Notes:

  • The versionless …/npm/printo-engine/dist/index.js URL resolves to the latest dist-tag. Pin a version (e.g. [email protected]/dist/index.js) for reproducible deployments.
  • Three.js above is 0.185.1; the engine's peer range is >=0.160.0 <1.0.0.
  • Types (printo-engine/types) are TypeScript-only and are consumed via npm install, not from the CDN page.

How it works

Design document            Surface texture              Rendered product
┌────────────────────┐     ┌────────────────────┐     ┌────────────────────┐
│ image elements     │ ──▶ │ composited canvas  │ ──▶ │ UV material texture │
│ text elements      │     │ (textureResolution)│     │ applied via GLB     │
│ productColor       │     └────────────────────┘     │ materialName/mesh  │
└────────────────────┘                                └────────────────────┘
  • The design document is a plain data structure ({ productColor, elements }) with no Three.js objects. It is the source of truth for the design.
  • The compositor draws the design onto an offscreen canvas matching each surface's textureResolution.
  • The viewer maps that canvas onto the product. The artwork follows the model's real geometry because it is driven by the product's UVs — not by anything overlaid on the rendered frame.

Mapping

Only UV mapping is implemented and shipped.

  • A printable surface with strategy: 'uv' is exactly one texture mapped onto the surface's material.
  • Optional uvBounds clips the artwork to a sub-rectangle of the texture (used for e.g. a wrapped band on a mug).
  • Without uvBounds, the surface uses the full texture plane (used for a garment like a T-shirt).
  • The material is located by materialName first (preferred), falling back to meshName.

No projection (DecalGeometry) and no "hybrid" mapping engine are included. The API does not pretend otherwise: the strategy union is 'uv' only.

Design coordinates

Element x/y are normalized texture-plane coordinates (0–1) anchored at the center, not screen pixels.

Note: where a centered x: 0.5 lands on the model depends on how the model was UV-unwrapped (whether the seam sits at the front or the back of the printable material). Verify your art placement on the actual model, or rotate the camera/model so the intended printable face is visible. The engine applies the product camera exactly as configured; it does not auto-frame to the viewport aspect ratio.

Product Configuration

| Field | Description | |---|---| | id | Unique identifier | | name | Display name | | model | Path to GLB/glTF model (browser-served, CORS-permissive) | | defaultBaseColor | Hex color of the non-printable, non-overridden materials | | availableColors | Array of { name, hex } selectable colors | | material | PBR base: roughness, metalness, optional clearcoat | | materialOverrides | Per-material { materialName, color?, roughness?, metalness?, clearcoat? } | | printableSurfaces | Array of UV printable surface definitions | | camera | initialPosition, target, minDistance, maxDistance, fov |

Printable surface (UVPrintableSurface)

| Field | Description | |---|---| | id | Unique surface id | | name | Display name | | strategy | 'uv' (always) | | meshName | Mesh whose material carries the texture | | materialName | Material name to texture (preferred over meshName) | | uvBounds? | Optional { uMin, uMax, vMin, vMax } clip region | | printableWidthMm / printableHeightMm | Physical design area (informational / future print export) | | textureResolution | { width, height } of the composited texture |

Rendering & materials

  • Every mesh material is replaced by a THREE.MeshPhysicalMaterial built from the product's material properties while preserving the source normalMap, aoMap, and normalScale.
  • The printable surface's material receives the composited canvas texture.
  • materialOverrides fix specific materials to a given color/PBR profile.
  • Recoloring via setProductColor recolors only the non-printable body materials. The printable texture's background is re-composed in the product color, so embedded artwork is never double-tinted.

API

createPrinto(options)

Creates a PrintoConfigurator. Options: container, products, initialProductId, and optional event callbacks (onProductChanged, onDesignChanged, onLoadingStarted, onLoadingCompleted, onError).

Configurator methods

| Method | Description | |---|---| | init() | Load and render the initial product. Await it before adding design content. | | getCurrentProduct() / getAvailableProducts() / getCurrentDesign() | Read current state. getCurrentDesign returns a deep copy of elements. | | setProduct(id) | Switch products (discards stale in-flight loads). | | setProductColor(hex) | Recolor non-printable body materials and re-compose the texture background. | | addImage(dataUrl, options?) | Add/replace the image element (unshifts it so it renders behind text). | | updateImage(updates) | Update the image (optionally swap src, which re-preloads before drawing). | | removeImage() | Remove the image element. | | addText(text, options?) / updateText(id, options) / removeText(id) | Manage text elements. | | updateElement(id, updates) / removeElement(id) | Manage any element generically. | | setArtworkTransform(id, transform) | Apply x/y/scale/rotation. | | resetDesign() | Clear elements and reset to the product default color. | | resetCamera() / setAutoRotate(bool) / resize() | Camera helpers. (Auto-resize happens via ResizeObserver.) | | takeSnapshot() / downloadSnapshot(filename?) | Capture the rendered scene as PNG. | | dispose() | Dispose resources and listeners. |

Events

configurator.on('productChanged', (e) => e.product);
configurator.on('designChanged',  (e) => e.design); // also fires on mutations now
configurator.on('loadingStarted', (e) => e.product);
configurator.on('loadingCompleted', (e) => e.product);
configurator.on('error', (e) => { e.message; e.originalError; });

Asset licensing

Models and textures are not bundled in this package. Your GLB assets are yours; select models whose licensing permits your use, and keep an asset manifest with source URLs and license terms.

Live demo

The Printo demo (live at https://fenomen-alex.github.io/printo/) is a static Vite + TypeScript application that uses this engine for mug and T-shirt customization.