Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

Thatopen Impl Viewer

  • 5 installs
  • 17 repo stars
  • Updated July 8, 2026
  • openaec-foundation/thatopen-claude-skill-package

Helps with ai & agent building tasks.

About

thatopen-impl-viewer is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.

  • thatopen-impl-viewer
  • AI & Agent Building
  • AI-coding skill

Thatopen Impl Viewer by the numbers

  • 5 all-time installs (skills.sh)
  • Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Jul 21, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/thatopen-claude-skill-package --skill thatopen-impl-viewer

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs5
repo stars17
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/thatopen-claude-skill-package

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

ThatOpen Viewer Setup

Overview

This skill covers the complete setup of a ThatOpen BIM viewer: creating a World (scene + camera + renderer), configuring the container element, starting the render loop, and proper cleanup. It includes both the basic SimpleRenderer path and the advanced PostproductionRenderer path with ambient occlusion and edge detection.

Version: @thatopen/components 3.3.x, @thatopen/components-front 3.3.x Prerequisite: thatopen-core-architecture (component system, lifecycle)

Setup Order

ALWAYS follow this exact order when creating a viewer:

1. new Components()
2. components.get(Worlds)
3. worlds.create<Scene, Camera, Renderer>()
4. world.scene = new SimpleScene(components)
5. world.scene.setup()
6. world.renderer = new SimpleRenderer(components, container)
      — OR new PostproductionRenderer(components, container)
7. world.camera = new OrthoPerspectiveCamera(components)
8. components.get(Grids).create(world)          [optional]
9. components.init()                            [REQUIRED]

NEVER reorder these steps. The scene MUST be assigned before the renderer. The camera MUST be assigned last. components.init() MUST be the final call after all world setup is complete.

Container Element

The HTML container element is the DOM element where the WebGL canvas is rendered. It MUST satisfy these requirements:

  • MUST be a block-level element (typically <div>).
  • MUST have explicit width and height via CSS. Zero-size containers

produce a black or invisible viewport.

  • MUST exist in the DOM before passing it to the renderer constructor.
  • MUST NOT be display: none at construction time.
<div id="viewer" style="width: 100%; height: 100vh;"></div>
const container = document.getElementById("viewer") as HTMLDivElement;
// ALWAYS verify the container exists before creating the renderer
if (!container) throw new Error("Viewer container not found");

The renderer creates a <canvas> element inside the container and manages its lifecycle. NEVER manually create or manipulate this canvas.

World Creation

A World connects a Scene, Camera, and Renderer into a single viewport. Use the Worlds component to create worlds.

import * as OBC from "@thatopen/components";

const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);

// Type parameters specify the concrete implementations
const world = worlds.create<
  OBC.SimpleScene,
  OBC.OrthoPerspectiveCamera,
  OBC.SimpleRenderer
>();
  • ALWAYS use components.get(OBC.Worlds) to access the Worlds manager.

NEVER instantiate Worlds directly.

  • The type parameters are for TypeScript type narrowing only. You MUST

still assign the concrete instances manually (see next sections).

  • Multiple worlds are supported. Each world gets its own scene, camera,

and renderer. All worlds update in the same components.init() loop.

Scene Setup

SimpleScene wraps a THREE.Scene and provides default lighting and background setup via the Configurable interface.

world.scene = new OBC.SimpleScene(components);
world.scene.setup(); // Creates default directional + ambient lights
  • SimpleScene implements Configurable. ALWAYS call setup() after

assigning it to get default lights and background.

  • Access the underlying Three.js scene: world.scene.three.
  • To customize lighting, modify the Three.js scene after setup():
world.scene.setup();
const scene = world.scene.three;
scene.background = new THREE.Color(0x202932);
  • To add custom Three.js objects:
const mesh = new THREE.Mesh(geometry, material);
world.scene.three.add(mesh);
world.meshes.add(mesh); // Track for raycasting

Renderer: SimpleRenderer

SimpleRenderer wraps THREE.WebGLRenderer. It handles canvas creation, resizing, and per-frame rendering.

const container = document.getElementById("viewer") as HTMLDivElement;
world.renderer = new OBC.SimpleRenderer(components, container);
  • The renderer creates a WebGL canvas inside the container element.
  • It implements Resizeable and auto-resizes via ResizeObserver.
  • Access the underlying Three.js renderer: world.renderer.three.
  • To customize WebGL parameters:
world.renderer = new OBC.SimpleRenderer(components, container, {
  antialias: true,
  alpha: true,
});

Renderer: PostproductionRenderer

PostproductionRenderer extends the renderer with post-processing effects: ambient occlusion (AO), edge detection, outline rendering, and SMAA anti-aliasing. It is in @thatopen/components-front (browser-only).

import * as OBCF from "@thatopen/components-front";

const world = worlds.create<
  OBC.SimpleScene,
  OBC.OrthoPerspectiveCamera,
  OBCF.PostproductionRenderer
>();

world.renderer = new OBCF.PostproductionRenderer(components, container);

Enabling Post-Processing

Post-processing is disabled by default. ALWAYS enable it explicitly:

world.renderer.postproduction.enabled = true;

Post-Processing Features

Access features via world.renderer.postproduction:

FeaturePropertyDefaultEffect
Ambient OcclusionaoenabledDarkens crevices and edges
Edge DetectioncustomEdgesenabledDraws outlines on geometry
SMAAPart of pipelineenabledAnti-aliasing
Gloss PassglossEnabledfalseReflective surface pass
const pp = world.renderer.postproduction;
pp.enabled = true;
pp.ao = true;           // ambient occlusion
pp.customEdges = true;  // edge detection outlines

PostproductionRenderer + Grid Interaction

When using PostproductionRenderer, the grid MUST be excluded from post-processing to avoid visual artifacts:

const grids = components.get(OBC.Grids);
const grid = grids.create(world);
world.renderer.postproduction.exclude.add(grid.three);

ALWAYS exclude the grid mesh from post-processing. Failing to do so causes the grid to render with incorrect edges and AO artifacts.

Camera: OrthoPerspectiveCamera

OrthoPerspectiveCamera provides dual perspective/orthographic cameras with orbit, first-person, and plan navigation modes.

world.camera = new OBC.OrthoPerspectiveCamera(components);
  • ALWAYS assign the camera after the scene and renderer.
  • It wraps camera-controls for orbit/pan/zoom interaction.
  • Access the underlying cameras:
  • world.camera.threePerspTHREE.PerspectiveCamera
  • world.camera.threeOrthoTHREE.OrthographicCamera
  • world.camera.three — whichever is currently active

Navigation Modes

const camera = world.camera as OBC.OrthoPerspectiveCamera;
camera.set("Orbit");       // Default: orbit around target
camera.set("FirstPerson"); // WASD-style movement
camera.set("Plan");        // Orthographic top-down view

Framing Objects

// Frame all meshes in the world
world.camera.fit(world.meshes);

// Frame with offset (1.5 = 50% padding)
world.camera.fit(world.meshes, 1.5);

Grid

Grids creates an infinite ground grid for spatial reference.

const grids = components.get(OBC.Grids);
const grid = grids.create(world);
  • ALWAYS create the grid BEFORE calling components.init().
  • The grid auto-fades based on camera distance.
  • Access the underlying Three.js mesh: grid.three.

Starting the Render Loop

components.init();
  • ALWAYS call components.init() AFTER all world setup is complete.
  • This starts the requestAnimationFrame loop.
  • All Updateable components (renderer, camera, etc.) receive

update(delta) calls each frame.

  • NEVER call init() before assigning scene, renderer, and camera.

The update loop will attempt to render an incomplete world.

  • Calling init() multiple times is safe but unnecessary.

Disposal

components.dispose();
  • ALWAYS call components.dispose() when the viewer is no longer needed.
  • This disposes ALL components, worlds, renderers, scenes, and cameras.
  • FragmentsManager is disposed last to prevent dangling references.
  • After calling dispose(), NEVER use any component or world references.
  • In frameworks (React, Vue, Angular), call dispose in the unmount/destroy

lifecycle hook.

Framework Integration Example (React)

useEffect(() => {
  const components = new OBC.Components();
  // ... setup world ...
  components.init();

  return () => {
    components.dispose();
  };
}, []);

Bundler Configuration (Vite)

ThatOpen requires specific Vite configuration for WASM files, web workers, and cross-origin isolation headers (needed for SharedArrayBuffer).

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    {
      name: "coop-coep",
      configureServer(server) {
        server.middlewares.use((_req, res, next) => {
          res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
          res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
          next();
        });
      },
    },
  ],
  optimizeDeps: {
    exclude: ["web-ifc"],
  },
  worker: {
    format: "es",
  },
});

COOP/COEP Headers

  • Cross-Origin-Opener-Policy: same-origin and

Cross-Origin-Embedder-Policy: require-corp are REQUIRED for SharedArrayBuffer, which web-ifc uses internally.

  • Without these headers, WASM operations may fail silently or throw.
  • For production, configure these headers on your web server or CDN.

WASM Files

  • web-ifc WASM files (web-ifc.wasm, web-ifc-mt.wasm) MUST be

accessible at runtime.

  • IfcLoader.setup() with default settings auto-resolves WASM paths.
  • For custom paths, copy WASM files to your public/ directory and

configure IfcLoader accordingly.

Worker Files

  • Fragment processing uses web workers for off-main-thread performance.
  • ALWAYS initialize FragmentsManager with a worker URL:

fragments.init(workerURL).

  • Vite handles worker bundling via worker: { format: "es" }.

Minimal Viewer (Copy-Paste Ready)

import * as OBC from "@thatopen/components";

// Container: must have width and height
const container = document.getElementById("viewer") as HTMLDivElement;

// 1. Components container
const components = new OBC.Components();

// 2. Create world
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
  OBC.SimpleScene,
  OBC.OrthoPerspectiveCamera,
  OBC.SimpleRenderer
>();

// 3. Scene with default lights
world.scene = new OBC.SimpleScene(components);
world.scene.setup();

// 4. Renderer bound to container
world.renderer = new OBC.SimpleRenderer(components, container);

// 5. Camera with orbit controls
world.camera = new OBC.OrthoPerspectiveCamera(components);

// 6. Grid (optional)
components.get(OBC.Grids).create(world);

// 7. Start render loop
components.init();

// 8. Cleanup when done
window.addEventListener("beforeunload", () => {
  components.dispose();
});

PostproductionRenderer Viewer (Copy-Paste Ready)

import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";

const container = document.getElementById("viewer") as HTMLDivElement;

const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
  OBC.SimpleScene,
  OBC.OrthoPerspectiveCamera,
  OBCF.PostproductionRenderer
>();

world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.renderer = new OBCF.PostproductionRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);

// Enable post-processing
world.renderer.postproduction.enabled = true;

// Grid — exclude from post-processing
const grids = components.get(OBC.Grids);
const grid = grids.create(world);
world.renderer.postproduction.exclude.add(grid.three);

components.init();

window.addEventListener("beforeunload", () => {
  components.dispose();
});

Critical Rules

1. ALWAYS call components.init() after world setup. Without it, nothing renders. 2. ALWAYS call components.dispose() on cleanup. BIM models consume hundreds of MB of GPU memory. 3. ALWAYS call world.scene.setup() after assigning the scene. Without it, there are no lights. 4. ALWAYS exclude the grid from PostproductionRenderer. 5. ALWAYS ensure the container has non-zero dimensions before creating the renderer. 6. NEVER reorder the setup sequence: scene first, then renderer, then camera. 7. NEVER use component references after calling components.dispose(). 8. NEVER import @thatopen/components-front in Node.js environments. 9. NEVER create the renderer with a container that has display: none. 10. NEVER call components.init() before assigning all three world components (scene, renderer, camera).

Reference Files

  • references/methods.md — World setup methods,

renderer options, camera configuration

  • references/examples.md — Minimal viewer,

PostproductionRenderer, full application setup

  • references/anti-patterns.md — Missing

init, wrong dispose order, WebGL context issues

Source Verification

All API signatures verified against:

  • GitHub: ThatOpen/engine_components main branch
  • npm: @thatopen/components@3.3.3, @thatopen/components-front@3.3.3
  • Research: docs/research/vooronderzoek-thatopen.md
  • Skill: thatopen-core-architecture (SKILL.md, references/)

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.