Install
$ agentstack add skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-impl-bim-web-viewer ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
crosstech-impl-bim-web-viewer
Technology Boundary
| Aspect | Side A: BIM/IFC Data | Side B: Web Browser | |--------|---------------------|---------------------| | Format | IFC STEP file (.ifc) — IFC4 / IFC4X3 | WebGL rendering via Three.js | | Coordinates | Z-up (IFC standard) | Y-up (Three.js / WebGL) | | Data model | EXPRESS schema, entity graph | JavaScript objects, typed arrays | | Units | Millimeters or meters (per IfcProject) | Three.js scene units (unitless) | | Size limit | No limit (files reach 500MB–1GB) | ~2GB WASM memory, ~4GB tab total |
The Bridge: @thatopen/components (v3.x) — converts IFC to Fragments format via web-ifc WASM, then renders through Three.js. Handles coordinate transformation, geometry tessellation, and entity-to-mesh mapping automatically.
Directionality: IFC → Web browser (one-way rendering + interactive inspection). Writing IFC from the browser is NOT covered by this skill.
Decision Tree: Which Approach?
Need a BIM web viewer?
├── Using standard BIM features (navigate, select, clip, inspect)?
│ └── YES → Use @thatopen/components (Option A)
├── Integrating into existing Three.js app with custom rendering?
│ └── YES → Use custom web-ifc + Three.js pipeline (Option B)
├── Need server-side IFC processing (validation, authoring)?
│ └── YES → Use IfcOpenShell on backend + web-ifc on frontend (Hybrid)
└── File size > 200MB?
└── YES → ALWAYS pre-convert to Fragments on server (see Large Model Handling)
Option A: @thatopen/components Viewer (Recommended)
Installation
npm install @thatopen/components @thatopen/components-front @thatopen/fragments three web-ifc
CRITICAL: The Three.js version MUST match the version pinned by @thatopen/components. Run npm ls three and verify only ONE version is installed. Mismatched versions cause silent rendering failures.
Complete Minimal Viewer
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
import * as THREE from "three";
// 1. Initialize component system
const components = new OBC.Components();
// 2. Create world (scene + camera + renderer)
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
// 3. Attach to DOM
const container = document.getElementById("viewer")!;
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.SimpleCamera(components);
world.scene = new OBC.SimpleScene(components);
// 4. Configure IFC loader with WASM path
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup({
autoSetWasm: false,
wasm: { path: "https://unpkg.com/web-ifc@0.0.77/", absolute: true },
});
// 5. Setup FragmentsManager — handles model lifecycle
const fragments = components.get(OBC.FragmentsManager);
fragments.list.onItemSet.add(({ value: model }) => {
model.useCamera(world.camera.three);
world.scene.three.add(model.object);
fragments.core.update(true);
});
// 6. Add interaction components
const highlighter = components.get(OBCF.Highlighter);
highlighter.multiple = "ctrlKey";
highlighter.zoomToSelection = true;
const hider = components.get(OBC.Hider);
// 7. Load IFC file
const response = await fetch("/models/building.ifc");
const data = new Uint8Array(await response.arrayBuffer());
await ifcLoader.load(data, false, "my-model");
Key Components Reference
| Component | Package | Purpose | |-----------|---------|---------| | OBC.IfcLoader | components | IFC → Fragments conversion and loading | | OBC.FragmentsManager | components | Model lifecycle, Fragment caching, disposal | | OBC.Hider | components | Hide, show, isolate elements by ID or category | | OBCF.Highlighter | components-front | Selection highlighting via raycasting | | OBCF.Clipper | components-front | Section planes through models |
Selection and Property Inspection
// Highlight on click — returns selected element IDs
const highlighter = components.get(OBCF.Highlighter);
await highlighter.highlight("select");
// Highlight by EXPRESS ID programmatically
const idMap: OBC.ModelIdMap = {};
idMap[model.modelId] = new Set([101, 102, 103]);
await highlighter.highlightByID("select", idMap);
// Clear selection
await highlighter.clear("select");
Hide / Isolate Elements
const hider = components.get(OBC.Hider);
// Isolate walls only
const wallIds = model.getItemsOfCategories(["IFCWALL"]);
const wallMap: OBC.ModelIdMap = {};
wallMap[model.modelId] = wallIds;
await hider.isolate(wallMap);
// Show everything again
await hider.set(true);
Z-Fighting Prevention
ALWAYS apply polygon offset to Fragment materials to prevent z-fighting on coplanar BIM faces:
fragments.core.models.materials.list.onItemSet.add(({ value: material }) => {
material.polygonOffset = true;
material.polygonOffsetUnits = 1;
material.polygonOffsetFactor = Math.random();
});
Option B: Custom web-ifc + Three.js Pipeline
Use this ONLY when integrating BIM data into an existing Three.js application where @thatopen/components cannot be adopted.
Core Pipeline
import * as THREE from "three";
import { IfcAPI } from "web-ifc";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// Initialize web-ifc
const ifcApi = new IfcAPI();
ifcApi.SetWasmPath("/static/wasm/"); // MUST be called BEFORE Init()
await ifcApi.Init();
// Load IFC file
const ifcData = new Uint8Array(await fetch("model.ifc").then(r => r.arrayBuffer()));
const modelID = ifcApi.OpenModel(ifcData, { COORDINATE_TO_ORIGIN: true });
// Store expressID → mesh mapping for selection
const expressIdToMesh = new Map();
// Stream geometry and convert to Three.js meshes
ifcApi.StreamAllMeshes(modelID, (flatMesh) => {
const geometries = flatMesh.geometries;
for (let i = 0; i {
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(mouse, camera);
const hits = raycaster.intersectObjects(scene.children);
if (hits.length > 0) {
const expressID = hits[0].object.userData.expressID;
const entity = ifcApi.GetLine(modelID, expressID, true); // flatten=true
displayProperties(entity);
}
});
Section Planes (Custom)
const clipPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 5);
renderer.clippingPlanes = [clipPlane];
renderer.localClippingEnabled = true;
// Update plane position interactively
function setClipHeight(y: number) {
clipPlane.constant = y;
}
Coordinate System Rules
| Source | Coordinate System | Action Required | |--------|-------------------|-----------------| | web-ifc geometry output | Y-up (transformed internally) | NONE — render directly | | IfcOpenShell geometry | Z-up (IFC native) | ALWAYS rotate: matrix.makeRotationX(-Math.PI / 2) | | COORDINATE_TO_ORIGIN: true | Centered at origin | Real-world coordinates are LOST — store separately if needed | | COORDINATE_TO_ORIGIN: false | Real-world offset | Model may appear far from origin — camera setup required |
Large Model Handling (>100MB IFC)
Memory Constraints
| Constraint | Limit | |-----------|-------| | WASM linear memory | ~2GB practical maximum | | Browser tab total | 2–4GB before crash | | 8GB RAM laptop | ~1.7GB practical tab limit |
Performance Strategy
- ALWAYS pre-convert to Fragments on server for files >200MB. First-load IFC parsing is the bottleneck; cached Fragments load near-instantly.
- ALWAYS use
StreamAllMeshesinstead ofLoadAllGeometry. Streaming processes geometry incrementally without peak memory spike. - ALWAYS call
ifcApi.CloseModel(modelID)when done to free WASM memory. - Configure conservative memory limits:
const modelID = ifcApi.OpenModel(data, {
MEMORY_LIMIT: 1073741824, // 1GB — safe for most browsers
TAPE_SIZE: 33554432, // 32MB read buffer
});
- Use geometry instancing for repeated elements (identical windows, doors). web-ifc provides shared
geometryExpressIDvalues that indicate reusable geometry. - Export Fragments for caching:
const fragsBuffer = await model.getBuffer(false);
// Store on server or in IndexedDB for instant reload
The Fragments Format
IFC files are NEVER rendered directly. The pipeline is:
IFC file → web-ifc WASM parsing → Fragment conversion → Three.js rendering
- First load: slow (parsing + tessellation + conversion)
- Subsequent loads from cached
.fragfiles: near-instant - Fragments use Google Flatbuffers — 5-10x smaller than source IFC
- ALWAYS cache Fragments server-side for production applications
Property Access Patterns
| Operation | @thatopen/components | Raw web-ifc | |-----------|---------------------|-------------| | Get by type | model.getItemsOfCategories(["IFCWALL"]) | ifcApi.GetLineIDsWithType(modelID, IFCWALL) | | Get entity | Via FragmentsManager | ifcApi.GetLine(modelID, expressID, true) | | Property sets | ifcApi.properties.getPropertySets(modelID, id, true) | Same | | Spatial structure | ifcApi.properties.getSpatialStructure(modelID) | Same | | Access name | entity.Name.value | entity.Name.value |
CRITICAL: web-ifc wraps string/numeric values in { value: ... } containers. ALWAYS access .value for the actual data. References are Handle objects unless flatten=true is passed to GetLine.
Critical Warnings
NEVER set the WASM path after calling ifcApi.Init() — the WASM module loads during Init() and path changes after that point have no effect.
NEVER use LoadAllGeometry for models with more than 10,000 elements — use StreamAllMeshes to avoid memory spikes.
NEVER mix Three.js versions — @thatopen/components pins a specific Three.js version. A second version in node_modules causes silent rendering failures.
NEVER manually rotate web-ifc output by -90 degrees — web-ifc ALREADY transforms IFC Z-up coordinates to Three.js Y-up internally.
ALWAYS call CloseModel(modelID) when disposing a model to free WASM memory.
ALWAYS verify the WASM path resolves correctly in your bundler (Webpack/Vite) — .wasm files MUST be copied to the output directory.
Reference Links
- [references/methods.md](references/methods.md) — Complete API signatures for @thatopen/components and web-ifc viewer methods
- [references/examples.md](references/examples.md) — Working code examples for common viewer features
- [references/anti-patterns.md](references/anti-patterns.md) — What NOT to do when building BIM web viewers
Official Sources
- https://github.com/ThatOpen/engine_web-ifc (web-ifc v0.0.77)
- https://github.com/ThatOpen/engine_components (@thatopen/components v3.3.2)
- https://docs.thatopen.com/Tutorials/Components/Core/IfcLoader
- https://docs.thatopen.com/api/@thatopen/components-front/classes/Highlighter
- https://threejs.org/docs/
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Impertio-Studio
- Source: Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.