Learn BimCT Viewer through hands-on examples. Each example includes complete source code, live demos, and detailed explanations.
๐ก New to BimCT? Start with Example 1 and work your way through sequentially.
Edit and run code examples in real-time with our interactive playground
Open Playground โ| Example | Topic | Resources |
|---|---|---|
| Example 1 | Minimal Initialization | View ยท Download ยท Try |
| Example 2 | Initialization Options | View ยท Download ยท Try |
| Example 3 | Event Handling | View ยท Download ยท Try |
| Example 4 | Custom Context Menus | View ยท Download ยท Try |
| Example 5 | Element Control | View ยท Download ยท Try |
| Example 6 | Scene Views & State | View ยท Download ยท Try |
| Example 7 | Working with Pins | View ยท Download ยท Try |
| Example 8 | IoT Sensors | View ยท Download ยท Try |
| Example 9 | Dynamic Elements | View ยท Download ยท Try |
What you'll learn: Initialize the BIM Engine and renderer, then load a 3D model with minimal code.
window.onload = async function () {
let element = document.getElementById("myViewer");
// Step 1: Initialize the BIM Engine Core
await bimctViewer.initializeBimEngineCore();
// Step 2: Initialize the Renderer
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, {
key: "YOUR_LICENSE_KEY",
assetsPath: "https://bimct-converter.nomitech.com/converter/resources/...",
});
await renderer.init();
// Step 3: Load a Model
await renderer.loadModelFromUrl("House.bimct");
// Step 4: Start the Rendering Loop
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.loadModelFromUrl
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Configure the viewer with comprehensive initialization options including UI components, rendering quality, and behavior settings.
let renderOptions = {
key: "YOUR_LICENSE_KEY",
ecoRenderingOn: true, // Auto-pause after inactivity
skymapId: "grass", // Use skymap background
sunLight: true,
shadowMaps: true,
// Navigation cube configuration
cube: {
show: true,
compassShow: false,
showRotationArrows: true,
},
// Enable BimCT built-in UIs
gui: {
useContextMenu: true,
useToolbar: true,
useLeftToolbar: true,
useHomeToolbar: true,
useSettingsWindow: true,
useDesignTreeWindow: true,
usePropertiesWindow: true,
useFilterElementsWindow: true,
useWebXRWindow: true,
},
snappingEnabled: true,
measurementSystem: bimctViewer.MEASUREMENT_SYSTEM.METRIC,
assetsPath: "https://bimct-converter.nomitech.com/converter/resources/...",
};
// Initialize with options
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
// Listen to model loaded event
renderer.modelLoadedEvent.subscribe(async (renderer, modelIds) => {
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
});
await renderer.loadModelFromUrl("House.bimct");
Key APIs: RendererInitializationOptions, BimCTWebGL2Renderer.modelLoadedEvent
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Register and respond to viewer events including selection, visibility, measurements, and state changes.
async function registerViewerEvents(renderer) {
// Selection Change Event
renderer.selectionChangeEvent.subscribe((renderer, elements) => {
console.log("SELECTION CHANGED, ELEMENTS ARRAY: ", elements);
});
// Visibility Change Event
renderer.visibilityChangeEvent.subscribe(async (renderer) => {
console.log(
"VISIBILITY CHANGED, NEW VISIBILITY: ",
await renderer.getVisibilityState()
);
});
// Context Menu Event
renderer.contextMenuEvent.subscribe((renderer, event) => {
console.log("CUSTOM CONTEXT MENU", event);
});
// Engine State Changed Event
renderer.stateChangedEvent.subscribe((renderer) => {
console.log("ENGINE STATE CHANGED");
});
// Measurement Events
renderer.measurementsPointPushBackEvent.subscribe(
(renderer, measurements) => {
console.log(
"MEASUREMENT POINT PUSHED BACK, MEASUREMENTS ARRAY: ",
measurements
);
}
);
renderer.measurementsUpdatedEvent.subscribe((renderer, measurements) => {
console.log("MEASUREMENTS UPDATED, MEASUREMENTS ARRAY: ", measurements);
});
renderer.measurementsSelectedEvent.subscribe((renderer, measurements) => {
console.log("MEASUREMENTS SELECTED, MEASUREMENTS ARRAY: ", measurements);
});
renderer.pickLinesUpdatedEvent.subscribe((renderer, measurements) => {
console.log("PICK LINES UPDATED, PICKED LINES ARRAY: ", measurements);
});
}
// Initialize and register events
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
await registerViewerEvents(renderer);
await renderer.loadModelFromUrl("House.bimct");
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.selectionChangeEvent, BimCTWebGL2Renderer.visibilityChangeEvent, BimCTWebGL2Renderer.stateChangedEvent, BimCTWebGL2Renderer.contextMenuEvent, BimCTWebGL2Renderer.measurementsUpdatedEvent
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Create custom right-click context menu items to access element metadata, take snapshots, and save/load scene views.
// Helper function to download files
function downloadURI(uri, name) {
const link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
link.remove();
}
// Register custom context menu items
function registerCustomMenuItems(renderer) {
renderer.contextMenu.setCustomMenuText("My Custom Menu");
// Get metadata of selected elements
renderer.contextMenu.addCustomMenuItem({
text: "Get Metadata of Selected Elements",
click: async (d) => {
const selectedElements = renderer.selectedElements;
if (selectedElements.length > 0) {
renderer.messageBox("Hit F12 to check metadata in console...");
// Get element name and type
const elemNameType = renderer.metadataAccessor.getElementDescription(
selectedElements[0].modelId,
selectedElements[0].elementId
);
console.log("First Element Name and Type:", elemNameType);
// Get element with all properties
const elementWithProperties = renderer.metadataAccessor.getElementData(
selectedElements[0].modelId,
selectedElements[0].elementId,
true // Include properties
);
console.log("First Element with Properties:", elementWithProperties);
} else {
renderer.messageBox("Nothing is selected...");
}
},
});
// Download snapshot
renderer.contextMenu.addCustomMenuItem({
text: "Download Snapshot PNG",
click: async (d) => {
const uri = await renderer.takeSnapshot();
downloadURI(uri, "BimCT_Snapshot.png");
},
});
// Save scene view
renderer.contextMenu.addCustomMenuItem({
text: "Save Scene View",
click: async (d) => {
const currentView = await renderer.getSceneView();
localStorage.setItem("currentView", JSON.stringify(currentView));
renderer.messageBox("Scene saved to local storage...");
},
});
// Load scene view
renderer.contextMenu.addCustomMenuItem({
text: "Load Scene View",
click: async (d) => {
const currentView = localStorage.getItem("currentView");
if (currentView) {
const data = JSON.parse(currentView);
await renderer.setSceneView(data, true);
renderer.messageBox("Scene loaded from local storage...");
}
},
});
}
// Initialize
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
registerCustomMenuItems(renderer);
await renderer.loadModelFromUrl("House.bimct");
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.contextMenuEvent, BimCTMetadataAccessor.getElementData, BimCTWebGL2Renderer.takeSnapshot, BimCTWebGL2Renderer.getSceneView
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Control element visibility, selection, highlighting, and colorization with complete state management.
// Register custom menu items for element control
function registerCustomMenuItems(renderer) {
renderer.contextMenu.setCustomMenuText("Example 5");
let _highlightedState = null;
let _colorizationState = null;
// Select elements by class
renderer.contextMenu.addCustomMenuItem({
text: "Highlight all Roofs",
click: async (d) => {
renderer.setPickMode(bimctViewer.RENDERER_PICK_MODE.ELEMENT);
renderer.selectByClass("Roof");
},
});
// Save/Load highlighted state
renderer.contextMenu.addCustomMenuItem({
text: "Save Highlighted State",
click: async (d) => {
_highlightedState = await renderer.getHighlightedState();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Highlighted State",
click: async (d) => {
if (_highlightedState != null) {
await renderer.setHighlightedState(_highlightedState);
}
},
});
// Selection controls
renderer.contextMenu.addCustomMenuItem({
text: "Clear Selection",
click: async (d) => {
await renderer.clearSelection();
},
});
// Visibility controls
renderer.contextMenu.addCustomMenuItem({
text: "Isolate Selected Element(s)",
click: async (d) => {
renderer.isolateSelectedElements();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Hide Selected Element(s)",
click: async (d) => {
renderer.hideSelectedElements();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Show all Elements",
click: async (d) => {
renderer.showAllElements(true, true);
},
});
// Colorization controls
renderer.contextMenu.addCustomMenuItem({
text: "Colorize Selected Elements(s)",
click: async (d) => {
renderer.colorizeSelectedElements(0.8, 0.2, 0.2); // Red color
// Also works with faces:
// renderer.colorizeInternalIds(await renderer.getHighlightedInternalIds(), 0.8, 0.2, 0.2)
},
});
renderer.contextMenu.addCustomMenuItem({
text: "De-Colorize Selected Elements(s)",
click: async (d) => {
renderer.decolorizeSelectedElements();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "De-Colorize all Elements",
click: async (d) => {
renderer.decolorizeAllElements();
},
});
// Save/Load colorization state
renderer.contextMenu.addCustomMenuItem({
text: "Save Colorization State",
click: async (d) => {
_colorizationState = await renderer.getColorizationState();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Colorization State",
click: async (d) => {
if (_colorizationState != null) {
await renderer.setColorizationState(_colorizationState);
}
},
});
}
// Auto-select columns on model load
function registerModelEvents(renderer) {
renderer.modelLoadedEvent.subscribe((renderer, modelIds) => {
renderer.selectByClass("Column");
});
}
// Initialize
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
registerCustomMenuItems(renderer);
registerModelEvents(renderer);
await renderer.loadModelFromUrl("House.bimct");
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.clearSelection, BimCTWebGL2Renderer.isolateSelectedElements, BimCTWebGL2Renderer.hideSelectedElements, BimCTWebGL2Renderer.showAllElements, BimCTWebGL2Renderer.colorizeSelectedElements, BimCTWebGL2Renderer.decolorizeAllElements, BimCTWebGL2Renderer.getColorizationState, BimCTWebGL2Renderer.setColorizationState
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Save and restore complete viewer states including camera position, visibility, colors, measurements, and settings. Demonstrates comprehensive state management and model loading/unloading.
// Register state management menu items
function registerCustomMenuItems(renderer) {
renderer.contextMenu.setCustomMenuText("Example 6");
// Save/Load full scene view
renderer.contextMenu.addCustomMenuItem({
text: "Save Full Scene View",
click: async (d) => {
const currentView = await renderer.getSceneView(true, true);
localStorage.setItem("currentView", JSON.stringify(currentView));
console.log("Scene view saved:", currentView);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Full Scene View",
click: async (d) => {
const currentView = localStorage.getItem("currentView");
if (currentView) {
const data = JSON.parse(currentView);
await renderer.setSceneView(data, true);
}
},
});
// Camera state management
let cameraState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Current Camera",
click: async (d) => {
cameraState = await renderer.getCameraState();
console.log("Camera state:", cameraState);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Current Camera",
click: async (d) => {
await renderer.setCameraState(cameraState, false); // No transition
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Current Camera Transition",
click: async (d) => {
await renderer.setCameraState(cameraState, true); // With transition
},
});
// Visibility state management
let visibilityState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Visibility State",
click: async (d) => {
visibilityState = await renderer.getVisibilityState();
console.log("Visibility state:", visibilityState);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Visibility State",
click: async (d) => {
await renderer.setVisibilityState(visibilityState);
},
});
// Colorization state management
let colorizationState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Colorization State",
click: async (d) => {
colorizationState = await renderer.getColorizationState();
console.log("Colorization state:", colorizationState);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Colorization State",
click: async (d) => {
await renderer.setColorizationState(colorizationState);
},
});
// Measurements state management
let measurementsState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Measurement State",
click: async (d) => {
measurementsState = await renderer.getMeasurementsState();
console.log("Measurements state:", measurementsState);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Measurement State",
click: async (d) => {
await renderer.setMeasurementsState(measurementsState);
},
});
// Settings state management
let settingsState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Settings State",
click: async (d) => {
settingsState = await renderer.getViewerSettingsState();
console.log("Settings state:", settingsState);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Settings State",
click: async (d) => {
await renderer.setViewerSettingsState(settingsState);
},
});
// Model management
renderer.contextMenu.addCustomMenuItem({
text: "Unload all Models",
click: async (d) => {
await renderer.unloadAll();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Another Model",
click: async (d) => {
await renderer.loadModelFromUrl("RacBasicV7.bimct");
},
});
}
// Auto-select and position camera on model load
function registerModelEvents(renderer, bimctViewer) {
renderer.modelLoadedEvent.subscribe((renderer, modelIds) => {
renderer.selectByClass("Window");
setTimeout(() => {
renderer.resetCamera3D(
bimctViewer.BIM_CAMERA_POSITION.BCP_FRONT_TOP_RIGHT,
true
);
}, 2000);
});
}
// Initialize
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
registerCustomMenuItems(renderer);
registerModelEvents(renderer, bimctViewer);
await renderer.loadModelFromUrl("House.bimct");
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.getSceneView, BimCTWebGL2Renderer.setSceneView, BimCTWebGL2Renderer.unloadModel
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Add visual markers (pins) to your models with custom colors or images, manage pin state, and implement interactive features like dragging and camera states.
// Register custom context menu items for pin insertion
function registerCustomMenuItems(renderer) {
renderer.contextMenu.setCustomMenuText("Example 7");
// Add colored pins
renderer.contextMenu.addCustomMenuItem({
text: "Insert a RED Pin",
click: async (d) => {
renderer.startPinAdditionByColor(
"red", // Color
{ name: "Red Pin" } // User Object
);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Insert a GREEN Pin",
click: async (d) => {
renderer.startPinAdditionByColor("green", { name: "Green Pin" });
},
});
// Add custom image as pin
renderer.contextMenu.addCustomMenuItem({
text: "Insert an Labor Image as Pin",
click: async (d) => {
const pinText = await renderer.inputTextMessageBox(
"Type a name for this Labor",
"Insert a Labor",
"Tony Worker",
bimctViewer.MessageIcon.INFORMATION
);
renderer.startPinAdditionByImage(
"https://developers.nomitech.com/examples/labor32.png",
32, // width
32, // height
{ name: pinText } // User Object
);
},
});
// Move pin with animation
renderer.contextMenu.addCustomMenuItem({
text: "Move last Pin to here (animated)",
enabled: () => renderer.pins.pins.length > 0,
click: async (d) => {
const pos = await renderer.screenToWorldPosition(d.x, d.y);
if (pos) {
const lastPin = renderer.pins.pins[renderer.pins.pins.length - 1];
renderer.pins.animationInterpolations = 40;
renderer.pins.animationTime = 300; // milliseconds
renderer.pins.movePin3D(lastPin, pos.x, pos.y, pos.z, true, true);
}
},
});
// Save/Load pin state
let _pinState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Pin(s) State",
enabled: () => renderer.pins.pins.length > 0,
click: async (d) => {
_pinState = await renderer.getPinState();
renderer.messageBox("Pin(s) state saved!", "Pin State Saved");
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Pin(s) State",
click: async (d) => {
if (_pinState) {
await renderer.setPinState(_pinState);
renderer.messageBox("Pin(s) state loaded!", "Pin State Loaded");
}
},
});
}
// Register pin events
function registerPinEvents(renderer) {
// Pin insertion event - store camera state
renderer.pinInsertEvent.subscribe(async (renderer, pinWidget) => {
const res = await renderer.messageBox(
`Do you want to make ${pinWidget.userObject.name} draggable?`,
"Pin added",
bimctViewer.MessageOption.YESNO,
bimctViewer.MessageIcon.QUESTION
);
if (res == bimctViewer.MessageResult.YES) {
pinWidget.draggingAllowed = true;
}
// Store camera state in user object
pinWidget.userObject.cameraState = await renderer.getCameraState();
});
// Pin clicked - restore camera state
renderer.pinClickedEvent.subscribe(async (renderer, pinWidgetEvent) => {
await renderer.messageBox(
`You clicked on ${pinWidgetEvent.pinWidget.userObject.name}! Close to load camera.`,
"Pin Clicked!"
);
const cameraState = pinWidgetEvent.pinWidget.userObject.cameraState;
renderer.setCameraState(cameraState, true);
});
// Pin moved - update camera state
renderer.pinMovedEvent.subscribe(async (renderer, pinWidgetEvent) => {
pinWidgetEvent.pinWidget.userObject.cameraState =
await renderer.getCameraState();
await renderer.messageBox(
`You moved ${pinWidgetEvent.pinWidget.userObject.name}! Camera state updated.`,
"Pin Moved!"
);
});
// Show label on hover
renderer.pinHighlightedEvent.subscribe(async (renderer, pinWidget) => {
pinWidget.label = pinWidget.userObject.name;
});
renderer.pinDehighlightedEvent.subscribe(async (renderer, pinWidget) => {
pinWidget.label = undefined;
});
}
// Initialize viewer
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
registerCustomMenuItems(renderer);
registerPinEvents(renderer);
await renderer.loadModelFromUrl("House.bimct");
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.startPinAdditionByColor, BimCTWebGL2Renderer.pinInsertEvent, BimCTWebGL2Renderer.pinClickedEvent, BimCTWebGL2Renderer.clearPins, BimCTWebGL2Renderer.getPinState, BimCTWebGL2Renderer.setPinState
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Create an IoT sensor simulation using pins to represent temperature sensors that colorize elements based on readings. Uses the BimCT Sensors API for advanced temperature visualization.
const minTemperature = -10;
const maxTemperature = 60;
const factor = 0.4;
// Generate unique sensor ID
function generateSensorGlobalId() {
const unique =
Date.now().toString(36) + Math.random().toString(36).substring(2);
return `sensor${unique}`;
}
// Create sensor channel and group for selected elements
async function createSessionGroup(renderer) {
await renderer.sensors.cleanup();
await renderer.pins.removeAll();
const channel = await renderer.sensors.addChannel(
"MyChannel",
minTemperature,
maxTemperature,
factor
);
const group = await channel.addGroup("MyGroup");
for (let element of renderer.sceneState.selectedElements) {
await group.addElementByInternalId(element.internalId);
}
renderer.sensors.setActive(channel);
return group;
}
// Register custom context menu items
function registerCustomMenuItems(renderer) {
renderer.contextMenu.setCustomMenuText("Example 8");
// Create channel for selected elements
renderer.contextMenu.addCustomMenuItem({
text: "Create Channel for Selected Elements",
click: async (d) => {
await createSessionGroup(renderer);
await renderer.messageBox(
`Sensor channel created for ${renderer.sceneState.selectedElements.length} elements`
);
renderer.clearSelection();
},
});
// Insert temperature sensors
renderer.contextMenu.addCustomMenuItem({
text: "Insert Cold Temperature Sensor",
click: async (d) => {
if (!renderer.sensors.findChannelByName("MyChannel")) {
renderer.messageBox("Please create a Sensor Channel first.");
return;
}
renderer.startPinAdditionByColor("DarkBlue", {
temperature: -10,
sensorName: generateSensorGlobalId(),
});
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Insert Hot Temperature Sensor",
click: async (d) => {
if (!renderer.sensors.findChannelByName("MyChannel")) {
renderer.messageBox("Please create a Sensor Channel first.");
return;
}
renderer.startPinAdditionByColor("DarkRed", {
temperature: 35,
sensorName: generateSensorGlobalId(),
});
},
});
// Save/Load sensor state
let _sensorsState = null;
let _pinsState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save Sensors State",
click: async (d) => {
_sensorsState = await renderer.getSensorsState();
_pinsState = await renderer.getPinState();
renderer.messageBox("Sensors state saved!", "State Saved");
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load Sensors State",
click: async (d) => {
if (_pinsState) {
await renderer.setPinState(_pinsState);
await renderer.setSensorsState(_sensorsState);
renderer.messageBox("Sensors state loaded!", "State Loaded");
}
},
});
}
// Register pin events for sensor interaction
function registerPinEvents(renderer) {
// Add sensor when pin is inserted
renderer.pinInsertEvent.subscribe(async (renderer, pinWidget) => {
const group = renderer.sensors.findGroupByName("MyChannel", "MyGroup");
if (group) {
await group.addSensor(
pinWidget.userObject.sensorName,
pinWidget.position3D,
pinWidget.userObject.temperature
);
pinWidget.draggingAllowed = true;
}
});
// Update sensor when pin is moved
renderer.pinMovedEvent.subscribe(async (renderer, event) => {
const sensor = renderer.sensors.findSensorByName(
"MyChannel",
"MyGroup",
event.pinWidget.userObject.sensorName
);
if (sensor) {
sensor.update(
event.pinWidget.position3D,
event.pinWidget.userObject.temperature
);
}
});
// Edit sensor value on click
renderer.pinClickedEvent.subscribe(async (renderer, event) => {
const sensor = renderer.sensors.findSensorByName(
"MyChannel",
"MyGroup",
event.pinWidget.userObject.sensorName
);
if (sensor) {
const channel = renderer.sensors.findChannelByName("MyChannel");
const result = await renderer.inputNumberMessageBox(
`Set Temperature (${channel.low} to ${channel.high}):`,
"Set Sensor Value",
sensor.value
);
if (result !== null) {
event.pinWidget.loadByColor("black");
await sensor.update(sensor.point, result);
event.pinWidget.userObject.temperature = sensor.value;
}
}
});
}
// Initialize viewer
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
registerCustomMenuItems(renderer);
registerPinEvents(renderer);
await renderer.loadModelFromUrl("House.bimct");
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.startPinAdditionByColor, BimCTWebGL2Renderer.pinInsertEvent, BimCTWebGL2Renderer.pinMovedEvent, BimCTWebGL2Renderer.pinClickedEvent, BimCTWebGL2Renderer.getSensorsState, BimCTWebGL2Renderer.setSensorsState
๐ View Live Demo | ๐ฅ Download Source
What you'll learn: Work with dynamic 3D objects that can be positioned, rotated, cloned, and transformed at runtime. This example demonstrates a CCTV camera placement system with full transformation controls.
// Add new dynamic element at position with surface alignment
async function addNewDynamicElement(renderer, position, normalToPlace) {
let loadedModel = null;
const sourceDynamicElementModel = findSourceDynamicElement(renderer);
const state = await renderer.getCameraState();
// Clone or load CCTV model
if (sourceDynamicElementModel == null) {
loadedModel = await renderer.loadModelFromUrl(
"CCTV_Camera_Cylindrical.bimct",
undefined,
false
);
} else {
loadedModel = await renderer.cloneModel(
sourceDynamicElementModel.id64,
undefined,
true
);
}
await renderer.setCameraState(state, false);
const elements = renderer.sceneState.getElementsByModelId(loadedModel.id64);
const internalIds = elements.map((e) => e.internalId);
let bBox = await renderer.getElementsBoundingBox(internalIds);
// Align to surface normal
if (normalToPlace != null) {
renderer.dynamicElements.lookAt(
internalIds,
bBox.center,
normalToPlace.rotateFrontToRight()
);
bBox = await renderer.getElementsBoundingBox(internalIds);
const translationVector = bBox.translationToReachFace(
position,
normalToPlace
);
await renderer.dynamicElements.translateTo(internalIds, translationVector);
} else {
await renderer.dynamicElements.moveTo(internalIds, position, bBox.center);
}
await renderer.selectByInternalIds(internalIds);
await renderer.zoomToSelectedElements();
}
// Transform selected dynamic elements
async function translateSelectedElements(renderer, tx, ty, tz) {
const internalIds = renderer.selectedElements.map((e) => e.internalId);
await renderer.dynamicElements.translateTo(
internalIds,
new bimctViewer.Vector3d(tx, ty, tz)
);
}
async function rotateSelectedElements(renderer, rx, ry, rz) {
const internalIds = renderer.selectedElements.map((e) => e.internalId);
const bBox = await renderer.getElementsBoundingBox(internalIds);
await renderer.dynamicElements.rotateAroundXYZ(
internalIds,
bBox.center,
new bimctViewer.Vector3d(rx, ry, rz)
);
}
// Context menu for CCTV placement and control
async function installApplicationMenu(renderer) {
renderer.contextMenu.setCustomMenuText("CCTV Demo");
// Add CCTV camera
renderer.contextMenu.addCustomMenuItem({
text: "Add a CCTV Camera (3D)",
click: async (d) => {
renderer.startPinAdditionByImage("CCTV_Camera_Square.png", 32, 32, {
cameraType: "CCTV 3D",
});
},
});
// Remove selected cameras
renderer.contextMenu.addCustomMenuItem({
text: "Remove Selected CCTV Camera(s)",
enabled: () => hasSelectedDynamicElements(renderer),
click: async (d) => {
await deleteSelectedDynamicElements(renderer);
},
});
// Save/Load state
let dynamicElementsState = null;
let cameraModelsState = null;
renderer.contextMenu.addCustomMenuItem({
text: "Save CCTV Cameras State",
click: async (d) => {
const cctvCamerasModelIds = renderer.sceneState.models
.filter((m) => m.cacheInfo?.url === "CCTV_Camera_Cylindrical.bimct")
.map((m) => m.id64);
cameraModelsState = await renderer.getModelsWithURLState(
cctvCamerasModelIds
);
dynamicElementsState = await renderer.getDynamicElementsState();
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Load CCTV Cameras State",
enabled: () => dynamicElementsState != null && cameraModelsState != null,
click: async (d) => {
const state = await renderer.getCameraState();
await deleteAllDynamicElements(renderer);
await renderer.setModelsWithURLState(cameraModelsState);
await renderer.setDynamicElementsState(dynamicElementsState);
await renderer.setCameraState(state, false);
},
});
// Movement controls
renderer.contextMenu.addCustomMenuItem({
text: "Move to Right",
enabled: () => hasSelectedDynamicElements(renderer),
click: async (d) => {
await translateSelectedElements(
renderer,
await askUserForMillimeters(renderer),
0,
0
);
},
});
renderer.contextMenu.addCustomMenuItem({
text: "Rotate on Y",
enabled: () => hasSelectedDynamicElements(renderer),
click: async (d) => {
await rotateSelectedElements(
renderer,
0,
await askUserForDegrees(renderer),
0
);
},
});
}
// Register pin insert event for CCTV placement
renderer.pinInsertEvent.subscribe(async (renderer, pinWidget) => {
if (pinWidget.userObject.cameraType == "CCTV 3D") {
renderer.pins.removePin(pinWidget, true);
const position = await renderer.worldToScreenSpacePosition(
pinWidget.position3D
);
const normalBehind = await renderer.pickNormal(
position.point.x,
position.point.y
);
await addNewDynamicElement(renderer, pinWidget.position3D, normalBehind);
}
});
// Initialize
window.onload = async function () {
let element = document.getElementById("myViewer");
await bimctViewer.initializeBimEngineCore();
let renderer = new bimctViewer.BimCTWebGL2Renderer(element, renderOptions);
await renderer.init();
await registerViewerEvents(renderer);
await installApplicationMenu(renderer);
await renderer.loadModelFromUrl("RacAdvanced.bimct");
renderer.overlay.clearLoading();
renderer.beginDrawLoop();
};
Key APIs: BimCTWebGL2Renderer.loadModelFromUrl, BimCTWebGL2Renderer.cloneModel, BimCTWebGL2Renderer.startPinAdditionByImage, BimCTWebGL2Renderer.pinInsertEvent, BimCTWebGL2Renderer.getDynamicElementsState, BimCTWebGL2Renderer.setDynamicElementsState, BimCTWebGL2Renderer.getModelsWithURLState, BimCTWebGL2Renderer.setModelsWithURLState
Note: To create
.bimctmodels with dynamic elements, you must specify which elements should be dynamic during conversion using the Converter API.
๐ View Live Demo | ๐ฅ Download Source
Recommended order for beginners: