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

@aectooling/slopifc-wasm

v0.2.0

Published

WebAssembly bindings for slopifc: IFC parsing, metadata and render data for browsers and Node

Readme

@aectooling/slopifc-wasm

WebAssembly bindings for slopifc: IFC parsing, typed metadata and Three.js-shaped render data, for browsers and Node.

The package is the only supported browser-facing interface to slopifc. Applications never instantiate the module, manage linear memory, free strings or call raw exports.

Status: runtime, conversion, metadata, semantic indexes, render data and Three.js adapter. createSlopIfc(), isolated model instances, typed lazy entities, projected metadata, file conversion, retained zero-copy semantic and render tables, Three.js wrappers and deterministic disposal are included in the package.

The current generated runtime accepts IFC2X3 (IFC2X3, IFC2X3TC1), IFC4 (IFC4, IFC4ADD2, IFC4ADD2TC1), and IFC 4.3 ADD2 (IFC4X3_ADD2). Other schema releases are rejected at open time until their generated metadata tables are added.

Contract

The normative contract — integer widths, ownership, prepare() defaults, the model state machine, the close/detachment sequence, status codes and every byte-exact descriptor layout — is docs/spec-wasm-package-abi.md.

Current usage

Convert an IFC source into JavaScript-owned Blobs suitable for download. OBJ returns its referenced MTL file explicitly as sidecar. format: 'glb' produces a single model/gltf-binary Blob with metre coordinates, +Y up, shared meshes, RGBA materials and IFC identity extras; see the GLB contract.

const converted = await runtime.convert(file, {
  format: 'obj',
  geometry: { scopes: ['building'], subtractOpenings: true },
})

function save(output: { name: string; blob: Blob }) {
  const url = URL.createObjectURL(output.blob)
  const link = document.createElement('a')
  link.href = url
  link.download = output.name
  link.click()
  setTimeout(() => URL.revokeObjectURL(url), 0)
}

save(converted.output)
if (converted.sidecar) save(converted.sidecar)

An open model can be converted repeatedly before preparation:

import { createSlopIfc } from '@aectooling/slopifc-wasm'
const runtime = await createSlopIfc()
const model = await runtime.open(file)

const json = await model.convert({ format: 'json' })

// Selection affects emitted geometry; dependencies remain available.
const selected = await model.convert({
  format: 'glb',
  geometry: { expressIDs: [100n, 4294967396n], entityTypes: ['IfcElement'] },
})

const prepared = await model.prepare({
  render: {
    coordinateToOrigin: true,
    subtractOpenings: true,
    // Use false only when the renderer derives flat face normals.
    includeNormals: true,
  },
  metadata: {
    entityTypes: ['IFCPRODUCT'],
    includeSubtypes: true,
    attributes: ['GlobalId', 'Name', 'OwnerHistory'],
  },
  semanticIndex: true,
})

const names = prepared.getMetadata()?.column('Name')
const render = prepared.renderData
const relations = prepared.getSemanticIndex()
const wall = model.getEntities().byGuid('2Ab$4Ad$L9QuMOSyGoOAgE')
console.log(names?.kind === 'utf8' ? names.get(0) : undefined)
console.log(render?.geometries.count, render?.instances.count)
console.log(relations?.edgesForEntityIndex(wall?.getEntityIndex() ?? -1))
console.log(wall?.getExpressID(), wall?.getTypeName())

prepared.dispose()

The semantic relation index is disabled by default. When requested, its sparse columns and edge ranges borrow sealed WASM memory just like render and metadata tables. They detach when the prepared model is disposed.

Geometry conversion uses the same ID/type/scope intersection as preparation. Type names form a union and include subtypes by default; set includeSubtypes: false for exact types. Omitted filters select all scoped products; explicit empty filters select none and never trigger standalone-solid fallback. STEP/GLB report the existing no-geometry error when selection emits nothing. Selection adds no lookup table when omitted. Selected results retain complete-file opening, material and placement dependencies, and conversion leaves the model open.

Lazy binary attributes are returned as { kind: 'binary', value: Uint8Array, bitLength: number }. The package copies the bytes out of the transient WASM decode buffer, so the value remains owned by JavaScript.

Viewer-style loads can return JavaScript-owned buffers from a parser worker and report real phase boundaries along the way:

const snapshot = await runtime.prepareSnapshot(file, {
  render: { includeNormals: false },
  includeProductStructure: true,
  includeElementData: true,
  metadata: {
    entityTypes: ['IFCPRODUCT'],
    includeSubtypes: true,
    attributes: ['GlobalId', 'Name'],
  },
  onProgress({ phase, elapsedMs, entityCount }) {
    console.log(phase, elapsedMs, entityCount)
  },
})

For an interactive viewer, keep a parser session alive and leave element data out of the initial snapshot. getElementData() decodes only the selected element, follows its assigned type, and caches the completed result plus shared property definitions and materials:

const session = await runtime.openSession(file)
const snapshot = await session.prepareSnapshot({
  render: { includeNormals: false },
  includeProductStructure: true,
  includeElementData: false,
  metadata: {
    entityTypes: ['IFCPRODUCT'],
    includeSubtypes: true,
    attributes: ['GlobalId', 'Name'],
  },
})

showModel(snapshot.renderData)

onElementSelected(async (expressID) => {
  const element = await session.getElementData(expressID)
  showProperties(element)
})

onModelClosed(() => {
  snapshot.dispose()
  session.dispose()
})

await session.getMemoryStats() reports source ownership, record indexes, semantic-index bytes, render allocations, shared native caches, and WASM page capacity from the model's owning thread. It works before or after preparation and rejects after disposal. WASM pages include native allocations and allocator slack; do not add the individual allocation counters to the page total. The returned IfcModelSessionMemoryStats does not include JavaScript objects, the caller's source buffer, an owned snapshot, or worker isolate overhead. session.getCacheMetrics() separately reports element, definition, and material cache entries and hits. By default these caches grow with selections until disposal. Use await session.clearCache() after preparation to release them while keeping the session open. Earlier queries finish before clearing; later queries use fresh caches, and already returned snapshots remain valid. Hit/miss counters remain cumulative.

runtime.openSession(source, { maxCacheEntries: 256 }) optionally caps each element, definition and material cache separately, evicting the oldest inserted entry. Zero disables those three caches; project units remain cached until clearing/disposal. These are entry limits, not byte limits, and exclude in-flight requests and caller-owned snapshots. Low caps may increase repeated decoding.

The session performs no property prefetch. Concurrent and repeated requests for one Express ID share the same promise and result object while cached. Failed requests leave the cache so the caller can retry. getCacheMetrics() reports element, definition, and material hits for development checks and benchmarks.

includeProductStructure owns product-to-storey and related-type assignments. includeElementData owns occurrence and type property sets, authored quantities, and semantic material assignments. Both are plain worker-safe records that remain available after the parser closes.

The phases cover worker initialization, record indexing, model structure, geometry preparation, buffer copying, and transfer. They are honest phase boundaries, not estimated per-entity completion percentages.

Compile-checked usage examples live in examples/.

Layout

| Path | Contents | | --- | --- | | src/index.ts | public API barrel | | src/index-internal.ts | internal barrel for tests; not in exports | | src/abi/ | private ABI descriptors, status codes and checked memory views | | src/core/ | runtime/model/entity API, option validation and value decoding | | src/tables/ | render, metadata and semantic-index descriptor adapters | | src/snapshot/ | owned snapshots, retained sessions and lazy element data | | src/parser.worker.ts | worker entry point for snapshots and sessions | | src/three/ | optional adapter, published as @aectooling/slopifc-wasm/three | | tests/fixtures/ | hand-built descriptor fixtures (no WASM required) |

The core never imports three; it is an optional peer dependency of the /three subpath only.

Curve sampling

Pass render: { curveQuality: { chordErrorMetres: 0.001 } } to prepare(), a snapshot or a session for opt-in physical-unit curve sampling. Optional maxAngleRadians and maxSegments default to 0.1 and 4096. The cap is per arc/sweep; exceeding it fails rather than relaxing the requested sampling. Omit curveQuality to preserve existing output. Use geometry diagnostics to observe unsupported or malformed input. Existing cleanup and float32 limits still apply, so this is not a certified final-mesh error bound. These options do not configure browser convert().

The CLI supports --curve-chord-error-metres, --curve-max-angle-radians and --curve-max-segments for OBJ, GLB, STEP and DXF. The last two require an explicit chord error; the default CLI output is unchanged.

Development

await model.querySemantics([42, 18446744073709551615n]) returns owned authored properties, quantities, materials and units with provenance. Call it before prepare() seals the model. The result survives sealing and closure; IDs and integer values are decimal strings. See the shared semantic contract.

bun install
bun run typecheck
bun run test
bun run build       # clean dist, build/copy WASM, emit JS and declarations
bun run check:pack  # pack a throwaway tarball and verify its contents
bun run check       # all of the above

zig build wasm stays a Zig-only step writing to zig-out/wasm/slopifc.wasm; copying it into dist/ is the package build's job. Set SLOPIFC_SKIP_ZIG_BUILD=1 to reuse an artifact a previous step already built. Package builds omit DWARF debug sections from the copied WASM download, keeping the compiler artifact, executable sections and function names unchanged. Set SLOPIFC_WASM_KEEP_DEBUG=1 to retain DWARF in the package for source debugging. Builds regenerate dist/ so deleted or renamed source modules cannot survive in the package. The pack check rejects emitted modules without matching source files, as well as missing public entry points or unexpected assets.

await model.getGeoreferencing() returns owned map metadata and explicit f64 matrices. Call it before prepare(); it does not change rendering. Check each operation's status and use its representation context to choose the transform. See georeferencing and unit composition.