MassingViewer

An IFC-native 3D/2D CAD studio that runs in your browser. View and author building models. Generate plans from them. Mark those plans up. The model never leaves your machine unless you tell it to.

Try the demo · Read the docs


Why this exists

Browser BIM has split into two camps that don't talk to each other.

One camp authors 3D — Forma, TestFit, Arcol, Snaptrude. They are good at massing and live metrics, and weak at drawings. Arcol says so itself: "drawing creation capabilities remain underdeveloped."

The other camp marks up 2D — Bluebeam, Revizto, Procore, Fieldwire. They are excellent at markup and have no 3D authoring at all. They consume PDFs that somebody else made in some other tool.

So the loop from designdrawingreviewrevise crosses three products and a PDF boundary, and the markup lands on a flattened raster that has forgotten which wall it is about.

MassingViewer closes that loop:

Status

Pre-alpha, in active extraction. This repo is being built by lifting the Design Room out of ibuilder/massing — an existing production BIM platform — and rebuilding its shell around a ribbon UI and a pluggable geometry kernel.

What works today, with no backend, no account and no network after first paint:

1,227 unit tests, 62 E2E tests each on Chromium, Firefox, WebKit and iPad, and 12 repo gates are green — plus dedicated Playwright projects for the React shell (9), WCAG 2.2 AA via axe-core (4) and viewport visual regression (2). The E2E suite includes a memory-leak gate: twenty model replacements must leave exactly the same number of GPU buffers as one, because three.js does not free them when an object leaves the scene graph and the symptom of getting that wrong is a tab that gets slow and then dies. Drawings are gated at three tiers, the third of which rasterises them through pinned resvg nightly — because line weight and dash pattern are invisible to a semantic digest, and a plan whose linework all became hairline-thin is unreadable while every digest matches.

What is not built. Levels toggles the construction grid rather than drawing storey planes; openings are subtracted only from rectangular profiles — anything else is drawn solid and reported, never silently; a frame-time gate and a long-task gate are absent (see docs/testing.md); and desktop.yml and mobile.yml would package apps that do not exist yet. See the roadmap.

Nothing here is API-stable until 1.0.0. Packages are published at 0.x, where minor bumps may break — see the versioning policy.

Quick start

git clone https://github.com/MassingCloud/MassingViewer.git
cd MassingViewer
npm install
npm run dev

That opens the studio with a sample building already loaded — no backend, no configuration, no network. If npm install complains about your Node version, it will tell you exactly how to fix it.

Architecture in one screen

Two ideas carry the whole design.

1. Every action is one serializable command. Clicking a ribbon button, typing WALL 0,0 @5<0 on the command line, running a macro, and a plugin calling the API all produce the same CommandInvocation. Interactive multi-step commands (AutoCAD's "specify next point" loop) are a pure reducer that accumulates arguments and emits one invocation when complete. From that single property you get undo/redo, the command palette, the command line, keyboard remapping, macro recording, the plugin API, the audit log, and — later — multi-user sync. One abstraction, seven features.

2. The geometry engine is pluggable. Authoring goes through a KernelProvider:

LocalKernel (default) RemoteKernel
Runs Entirely in your browser, in a Worker Against a massing server
Writes IFC with Its own STEP entity table, patched in place ifcopenshell
Operations 15 of 96, conformance-verified All 96, discovered at runtime
Undo Snapshot — always works Inverse replay — only where the server has one
Offline Yes No

Operations the active kernel can't perform are dimmed with the reason, never hidden. You see the whole tool surface and learn what connecting a server would unlock. Both kernels are held to the same published conformance suite (@massing/kernel-conformance), so a third-party kernel is a supported thing to write rather than a reverse-engineering exercise.

Read docs/architecture.md for the package graph and the layering rules CI enforces.

Packages

Core packages are framework-agnostic vanilla TypeScript — no React, no DOM — so they are usable from any host, including massing's own vanilla-DOM app. React appears only in the shell.

Package What it is
@massing/core Document model, GUID identity, units
@massing/kernel-api The KernelProvider contract
@massing/kernel-conformance The contract suite every kernel must pass
@massing/kernel-memory A reference kernel that passes it — the template for writing your own
@massing/kernel-local LocalKernel — writes IFC in a Worker, offline
@massing/kernel-remote RemoteKernel — the same contract over massing's authoring service
@massing/ifc STEP entity table with byte-preserving emit
@massing/tessellate IFC extruded solids to typed arrays — placement, rotation, opening subtraction
@massing/geometry-math Object snapping, polar tracking, dynamic input, CAD command grammar
@massing/ui-model Ribbon, gallery and canvas-mode models — pure data, no DOM
@massing/catalog The parametric element palette
@massing/assets Family libraries as data — parse, index, search
@massing/viewport three.js rendering, WebGPU-first with a WebGL2 fallback (the only package that imports three)
@massing/drawings2d Plan/section generation, sheet furniture, SVG + DXF + PDF output
@massing/markup BCF-native issues and pins, anchored to GlobalIds
@massing/authoring The interactive prompt-loop session
@massing/commands Command bus, keybindings, palette index
@massing/plugin-host Contribution manifests and lazy activation
@massing/fileio Drag-and-drop and file-picker opening
@massing/observability Crash sinks, telemetry, audit — all no-op by default
@massing/pwa The unified service worker: COOP/COEP plus precaching
@massing/i18n Message catalogues and the units grammar
@massing/ribbon The vanilla-DOM ribbon renderer
@massing/ui-react Ribbon, command palette, command line, docking
@massing/embed createMassingViewer() — the facade massing imports

@massing/geometry-math is deliberately useful on its own:

npm i @massing/geometry-math
import { resolveSnap, polarConstrain, segmentSnaps } from "@massing/geometry-math";

const candidates = segmentSnaps([{ x: 0, z: 0 }, { x: 5, z: 0 }], /* closed */ false);
resolveSnap({ x: 4.98, z: 0.01 }, candidates, 0.1);
// → { x: 5, z: 0, kind: "endpoint", dist: 0.0224 }

Roadmap

Milestone Ships State
M0 Repo, CI gates, 2D engine bake-off, sample fixtures ✅ done
M1 Walking skeleton — load and orbit a model, zero backend ✅ done
M2 geometry-math + commands published ✅ done
M3 kernel-api + conformance suite + RemoteKernel 🔨 RemoteKernel passes the suite against a behavioural stub. The recipe ledger's remote column stays at 0, because filling it needs a run against a real massing server — see docs/kernels/authoring.md
M4 LocalKernelauthor offline ✅ done, 15 of 96 operations, ratcheted
M5 2D drawings + markup — the full loop done. SVG, DXF, PDF and BCF export, plus all three golden tiers: semantic digests and structural assertions per PR, and nightly rasterisation against pinned resvg. Door and window openings are cut into plans; non-rectangular profiles are drawn solid and reported
M6 Authoring tools, gizmos, inspector done. Wall/slab/column with snapping, a construction grid, a dynamic-input HUD and typed imperial dimensions — M6's own acceptance test passes in E2E — plus a move/rotate/push-pull gizmo and a property inspector. Storey planes and a guide underlay remain, and are M10
M7 React ribbon shell, accessibility, i18n 🔨 ribbon, docking, palette, axe-core at serious+ and the i18n framework done. German is complete but not native-speaker reviewed, which the gate prints on every run, and no tool carries aria-pressed — see docs/accessibility.md and docs/i18n.md
M8 Enterprise hardening → 1.0.0 🔨 sinks, telemetry, audit, migrations, flags, CSP and the unified service worker done. The memory-leak gate is absent and is the expensive omission
M9 massing consumes @massing/* 🔨 packages/embed is the facade; massing has not adopted it

Design principles

These are load-bearing, not aspirational. Each is enforced by a test or a CI gate.

  1. Elements are referenced by IFC GlobalId, never by transient viewer IDs. Viewer IDs change between loads; a markup anchored to one is a markup that silently detaches.
  2. A control that cannot do what it says is worse than an absent one — so dim it and say why. A greyed button reading "needs Editor" is onboarding. A missing button is a support ticket.
  3. Nothing blocks the main thread. CSG, section cutting, IFC parsing and drawing generation all run in Workers with a cancellable job queue. Retrofitting this is a rewrite, so it is structural: the kernel API is async and has no synchronous path to accidentally take.
  4. A drawing that is missing an element must say so. Every generated drawing carries the list of elements that failed to section, and its GUID coverage. A plan silently missing a wall renders perfectly and lies.
  5. Permissive licenses only. MIT/BSD/Apache/MPL/BSL. CI refuses GPL, LGPL, AGPL and source-available dependencies on the transitive tree.
  6. Offline is the default, not a mode. Every WASM binary, font and tile is served from origin. CI fails if a build references a third-party host.

Contributing

See CONTRIBUTING.md. In short: Node 24, npm install, npm run verify, sign your commits off with -s.

Security issues: please use private vulnerability reporting rather than a public issue. See SECURITY.md.

ibuilder/massing The full construction platform this was extracted from. MassingViewer is its Design Room, standing alone.
MassingCloud/massingifc The kernel contracts and plugin SDK this builds on.
MassingCloud/massing-pdf The PDF engine behind sheet markup.
MassingCloud/massingviser Unrelated, despite the adjacent name — a separate Python project.

License

MIT. See LICENSE, and NOTICE for provenance.