@isopodlabs/binary_meshes
v0.1.2
Published
A package for reading and writing various 3D mesh file formats with TypeScript.
Maintainers
Readme
Binary Meshes
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 asextras.header); the facets' normals as face normals; each facet's attribute word asproperties.attributes; colours from it by the Materialise convention (COLOR=in the header, its default asextras.color, andMATERIAL=as a material) or the VisCAM/SolidView one. ASCII: an object persolid, 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'swand nOFF's coordinates inproperties; the header's edge count. - OBJ:
vtandvnas per-corner uvs and normals; vertex colours (v x y z r g b);usemtlper face;o,gandsas face sets;las lines,pas vertices;wcomponents inproperties..mtlstatements are kept by keyword in each material'sproperties(maps with their options),Kd/das its colour andmap_Kdas its texture (see alsoreadMtl). Every other statement (curves, surfaces,vp, ...) is inextras.statementsas written. - PLY: every element with its properties' types and data (
extras.elements), the comments andobj_infolines; normals, colours and uvs by their usual names,texcoordlists as corner uvs,material_index,edgeelements as lines,materialelements as materials,TextureFilecomments as textures; any other vertex or face property in the mesh'sproperties. - 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;texmapcoordinates; vertex normals; curved-edge tangents), and perconstellation(its instances as children, placed by their deltas and rotations); materials with metadata and composites; textures decoded.extras.xmlis 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..p3as written; beam lattices as lines andproperties.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 inproperties.invisibleEdges), polyface meshes and polygon meshes (closed, and surface-fitted, as their flags say), with itsLINEs and 3D polylines as lines.INSERTs (and each cell of aMINSERT) are instances, placed by base point, scale, rotation and extrusion. Colours per face: true colour, colour index, or the layer's;BYBLOCKis left none. Layers as face sets, each face's entity handle inproperties.handle,$INSUNITSas the unit.extras.documentis the whole drawing as @isopodlabs/dwg reads it (2D curves, text, dimensions and all),extras.entitiescounts the entities not made into meshes or lines, andextras.unsupportedthose 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 oneLicense
This project is licensed under the MIT License.
