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

@isopodlabs/binary_meshes

v0.1.2

Published

A package for reading and writing various 3D mesh file formats with TypeScript.

Readme

Binary Meshes

npm version GitHub stars License

This package reads and writes 3D mesh formats, using the @isopodlabs/binary, @isopodlabs/binary_archives and @isopodlabs/xml libraries.

☕ Support My Work

If you use this package, consider buying me a cup of tea to support future updates!

Usage

import * as fs from 'fs';
import * as path from 'path';
import { formatOf, flatten, placed, STL, OBJ } from '@isopodlabs/binary_meshes';

// everything in the file: objects and how they are placed, materials, textures, per-vertex/face/corner attributes
const model = await formatOf('.3mf')!.read(new Uint8Array(fs.readFileSync('part.3mf')));
for (const {mesh, transform} of placed(model.build))
    draw(mesh, transform, model.materials);

// an OBJ's .mtl (and texture images) are read through a callback
const obj = OBJ.read(bytes, {file: name => fs.readFileSync(path.join(dir, name))});

// or just the geometry of what the file builds, as one mesh
const mesh = await formatOf('.3mf')!.load(bytes);        // = flatten(await read(bytes))
fs.writeFileSync('part.stl', STL.save(mesh));

read and load return a Promise for AMF, 3MF (either can be zipped) and DWG; the others return directly. Malformed files throw an Error saying what is wrong with them.

Supported File Types

| Format | Extensions | Read | Write | |---|---|---|---| | STL | .stl | binary and ASCII | binary (save), ASCII (saveText) | | OFF | .off | text and binary, with the ST, C, N, 4 and n prefixes | text, with vertex colours | | OBJ | .obj | every statement; .mtl materials through options.file | v and f lines | | PLY | .ply | ASCII, binary little- and big-endian; every element and property | binary little-endian | | AMF | .amf | plain or zipped; objects, volumes, materials, textures, constellations | plain | | 3MF | .3mf | core, materials, production and beam lattice extensions; every part of the package | one object | | DXF | .dxf | 3D faces, polyface and polygon meshes, 3D lines, blocks and inserts (via @isopodlabs/dwg) | | | DWG | .dwg | as DXF, R13 to R2018 | |

Writers take a Mesh's geometry (and OFF its vertex colours). STL, AMF and 3MF are written as triangles cut by ear clipping, so concave faces come out right.

What each format's read gives

  • STL: binary: the header's text as metadata.header (its bytes as extras.header); the facets' normals as face normals; each facet's attribute word as properties.attributes; colours from it by the Materialise convention (COLOR= in the header, its default as extras.color, and MATERIAL= as a material) or the VisCAM/SolidView one. ASCII: an object per solid, with its name and facet normals.
  • OFF: per-vertex normals, colours and texture coordinates as the prefixes say; face colours (or a colour map index, properties.colorMapIndex); 4OFF's w and nOFF's coordinates in properties; the header's edge count.
  • OBJ: vt and vn as per-corner uvs and normals; vertex colours (v x y z r g b); usemtl per face; o, g and s as face sets; l as lines, p as vertices; w components in properties. .mtl statements are kept by keyword in each material's properties (maps with their options), Kd/d as its colour and map_Kd as its texture (see also readMtl). Every other statement (curves, surfaces, vp, ...) is in extras.statements as written.
  • PLY: every element with its properties' types and data (extras.elements), the comments and obj_info lines; normals, colours and uvs by their usual names, texcoord lists as corner uvs, material_index, edge elements as lines, material elements as materials, TextureFile comments as textures; any other vertex or face property in the mesh's properties.
  • AMF: an object per object (its volumes as one mesh, a face set each, their materials per face; colours per corner from triangle, vertex, volume or object; texmap coordinates; vertex normals; curved-edge tangents), and per constellation (its instances as children, placed by their deltas and rotations); materials with metadata and composites; textures decoded. extras.xml is the whole document.
  • 3MF: every object of every model part, with its attributes, metadata group and components (with their transforms and production attributes); base materials, composites (their colour mixed) and texture groups as materials, colour groups as per-corner colours, texture coordinates as corner uvs, multiproperties resolved through their layers, and each triangle's pid/p1..p3 as written; beam lattices as lines and properties.beamlattice. extras: the model and build attributes, every relationship, the thumbnail, every part's bytes, and every model part's XML (less the elements read into meshes) for anything else, such as slices.
  • DXF and DWG: model space as the built object, the active paper space as another, and a block per object; each mesh of its 3DFACEs (invisible edges in properties.invisibleEdges), polyface meshes and polygon meshes (closed, and surface-fitted, as their flags say), with its LINEs and 3D polylines as lines. INSERTs (and each cell of a MINSERT) are instances, placed by base point, scale, rotation and extrusion. Colours per face: true colour, colour index, or the layer's; BYBLOCK is left none. Layers as face sets, each face's entity handle in properties.handle, $INSUNITS as the unit. extras.document is the whole drawing as @isopodlabs/dwg reads it (2D curves, text, dimensions and all), extras.entities counts the entities not made into meshes or lines, and extras.unsupported those not read (for a DWG, modeler geometry kept in its AcDs section).

API

Model

interface Model {
    unit?:      string;                     // what a coordinate is measured in, when the file says; nothing is scaled by it
    metadata:   Record<string, string>;
    materials:  Material[];
    textures:   Texture[];
    objects:    Object3D[];                 // every object the file defines, placed or not
    build:      Instance[];                 // what the file is a model of
    extras:     Record<string, unknown>;    // everything format-specific (see above)
}
interface Object3D {
    name?:      string;
    mesh?:      Mesh;
    children:   Instance[];                 // other objects placed within this one
    metadata:   Record<string, string>;
    properties: Record<string, unknown>;
}
interface Instance {                        // an object placed by a transform; an object may be placed any number of times
    object:     Object3D;
    transform?: float3x4;
    properties?: Record<string, unknown>;
}
interface Material {
    name?:      string;
    color?:     float4;                     // r, g, b, a, 0..1
    texture?:   number;                     // index into Model.textures
    properties: Record<string, unknown>;
}
interface Texture {
    path?:      string;
    data?:      Uint8Array;
    contentType?: string;
    properties: Record<string, unknown>;
}

Mesh

interface Mesh {
    points:         float3[];               // float2/3/4 and float3x4 from @isopodlabs/maths/vector
    faces:          number[][];             // each a loop of indices into points, anticlockwise seen from outside
    lines?:         number[][];
    vertices?:      number[];
    normals?:       Attribute<float3>;
    colors?:        Attribute<float4>;
    uvs?:           Attribute<float2>;
    faceMaterials?: number[];               // per face, index into Model.materials, -1 for none
    faceSets?:      Record<string, Record<string, number[]>>;   // e.g. {group: {wheels: [0, 1]}}
    properties?:    Record<string, Attribute<unknown>>;          // anything else, by the file's name for it
}

// per vertex or face: values parallel to points or faces, or indices into values (-1 for none);
// per corner: an index loop per face, parallel to its corners
type Attribute<T> =
    | {per: 'vertex' | 'face', values: T[], indices?: number[]}
    | {per: 'corner', values: T[], indices: number[][]};

Format

interface Format {
    extensions: string[];
    check(data: Uint8Array): boolean;
    read(data: Uint8Array, options?: ReadOptions): Model | Promise<Model>;
    load(data: Uint8Array, options?: ReadOptions): Mesh | Promise<Mesh>;
    save?(mesh: Mesh): Uint8Array | Promise<Uint8Array>;
}
interface ReadOptions {
    file?(name: string): Uint8Array | undefined;    // another file the one being read names: an .mtl, a texture image
}

Also STL.saveText(mesh, name?), AMF.readText(data) (an uncompressed AMF, synchronously), and readMtl(data, textures, options?).

Functions

const formats: Format[];
function formatOf(ext: string): Format | undefined;        // '.stl', any case
function placed(instances: Instance[]): {mesh: Mesh, transform: float3x4}[];  // every mesh placed, transforms composed
function flatten(model: Model): Mesh;                       // the geometry of the build, as one mesh
function modelOf(mesh: Mesh, name?: string): Model;
function triangles(mesh: Mesh): number[][];                 // every face cut into triangles
function faceNormal(mesh: Mesh, face: number[]): float3;    // unit, concave faces too; zero for a face of no area
function bounds(mesh: Mesh): {min: float3, max: float3} | undefined;
function transform(mesh: Mesh, m: float3x4): Mesh;          // the geometry moved
function merge(meshes: Mesh[]): Mesh;                       // the geometry of all, as one

License

This project is licensed under the MIT License.