<html>
<head>
<script src="https://api.catenda.com/js/v1/viewer3d"></script>
</head>
<body>
<div id="splash">
<div id="title">
<div id="title-text">
View
</div>
<div id="play">
<button id="play-button" disabled=true onclick="start()">
<i id="play-icon" class="fa fa-play"></i>
</button>
</div>
</div>
<div id="error">
The example is unavailable<br>
Please try again later
</div>
<div id="logo">
<image id="logo-image" src="https://archbee-image-uploads.s3.amazonaws.com/v9iuLCcKE2amnKqiFVllF/qCmgrBQdRA0cjxeSZ0Rvn_logo-api-light.png" />
</div>
</div>
<div id="gui"></div>
<div id="scene">
<div id="viewer-container">
<div id="viewer-3d"></div>
</div>
<div id="console">
<div id="console-header">
<div id="console-header-text">Output</div>
<button id="console-copy" onclick="copyConsole()">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 12H1C0.447 12 0 11.553 0 11V1C0 0.448 0.447 0 1 0H11C11.553 0 12 0.448 12 1V11C12 11.553 11.553 12 11 12Z" fill="currentColor" />
<path d="M15 16H4V14H14V4H16V15C16 15.553 15.553 16 15 16Z" fill="currentColor" />
</svg>
<span id="tooltip">Copy</span>
</button>
</div>
<div id="console-text"></div>
</div>
</div>
<script type="text/javascript">
bimsync.setOnLoadCallback(function() {
document.getElementById("play-button").disabled = false
});
bimsync.load();
</script>
</body>
</html>
html {
height: 100%;
overflow: hidden;
width: 100%;
}
body {
background-color: #ade0bc;
background-image: url("https://archbee-image-uploads.s3.amazonaws.com/oGjtxJ5MUZmGondFfGBOZ-0l0obCRKymJG7Q9KTtAhl-20240826-204445.png");
background-position: left;
background-repeat: no-repeat;
background-size: cover;
color: #004d47;
font-family: Inter;
height: 100%;
margin: 0;
overflow: hidden;
padding: 0;
width: 100%;
}
#console {
display: flex;
flex-direction: column;
height: 100px;
width: 100%;
/* Be above measurements */
z-index: 1;
}
#console-copy {
align-items: center;
background-color: #f3f3f5;
border: none;
color: #6e7574;
display: flex;
justify-content: center;
margin-right: 16px;
padding: 5px;
/* Be above measurements */
z-index: 1;
}
#console-copy:hover {
color: #212322;
cursor: pointer;
}
#console-copy:active {
color: #c5c7c7;
}
#console-copy #tooltip {
border-radius: 6px;
bottom: 102px;
background-color: black;
color: #fff;
font-size: 12px;
padding: 10px;
position: absolute;
right: 2px;
visibility: hidden;
width: 40px;
}
#console-copy:hover #tooltip {
visibility: visible;
}
#console-header {
align-items: center;
background-color: #f3f3f5;
display: flex;
height: 32px;
justify-content: space-between;
}
#console-header-text {
color: #6e7574;
font-size: 14px;
margin-left: 16px;
user-select: none;
}
#console-text {
background-color: white;
color: black;
flex: 1;
font-family: monospace;
font-size: 14px;
overflow: auto;
padding: 10px 16px 10px 16px;
word-break: break-all;
}
#error {
align-self: center;
bottom: 50px;
display: none;
font-size: 20px;
font-weight: 600;
position: absolute;
text-align: center;
}
#gui {
display: none;
left: 20px;
position: absolute;
top: 20px;
/* Be above measurements */
z-index: 1;
}
#logo {
bottom: 15px;
position: absolute;
right: 15px;
}
#logo-image {
width: 200px;
}
#play {
align-items: center;
display: flex;
justify-content: center;
}
#play-button {
align-items: center;
background-color: #004d47;
border-color: transparent;
border-radius: 50%;
color: #ade0bc;
display: flex;
font-size: 26px;
height: 60px;
justify-content: center;
margin-top: 24px;
width: 60px;
}
#play-button:disabled {
cursor: wait;
}
#play-button:hover:enabled {
background-color: #001e1c;
cursor: pointer;
}
#play-icon {
padding-left: 5px;
}
#scene {
display: flex;
flex-direction: column;
height: 100%;
justify-content: flex-end;
width: 100%;
}
#splash {
display: flex;
flex-direction: column;
height: 100%;
min-height: 100%;
}
#splash #title {
display: flex;
flex: 1;
flex-direction: column;
font-size: 36px;
font-weight: 600;
height: 100%;
justify-content: center;
text-align: center;
}
#title-text {
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
}
#viewer-container {
flex: 1;
overflow: hidden;
}
#viewer-3d {
background: linear-gradient(rgb(237, 255, 254) 0px, rgb(255, 255, 255));
height: 100%;
width: 100%;
}
.lil-gui {
--background-color: #30343a;
--focus-color: #00665e;
--hover-color: #005952;
--number-color: #ade0bc;
--string-color: #ade0bc;
--text-color: #ade0bc;
--title-background-color: #24272b;
--title-text-color: #ade0bc;
--widget-color: #004d47;
--width: 210px;
}
// Global variables to ease access
let viewer3d = null;
let gui = null;
const modelId = "my-model-id";
// Enum of Ids for HTML elements referenced in this file
const ElementId = {
CONSOLE_COPY: "console-copy",
CONSOLE_TEXT: "console-text",
ERROR: "error",
GUI: "gui",
LOADING_ICON: "loading-icon",
PLAY_BUTTON: "play-button",
PLAY_ICON: "play-icon",
SPLASH: "splash",
TOOLTIP: "tooltip",
VIEWER_3D: "viewer-3d"
};
// Enum of style values used in this file
const Style = {
FLEX: "flex",
NONE: "none"
};
// Reset tooltip text
const consoleCopy = document.getElementById(ElementId.CONSOLE_COPY);
const onCopyLeave = function () {
document.getElementById(ElementId.TOOLTIP).innerHTML = "Copy";
};
consoleCopy.addEventListener("mouseleave", onCopyLeave);
consoleCopy.addEventListener("touchend", onCopyLeave);
/**
* Catenda 3D viewer View Example.
* The runExample function contains the 3D viewer related calls to fulfill the example shown.
*/
// The code specific to the example
const runExample = function () {
// gui labels
const CAMERA_VIEWPOINT = "Camera Viewpoint";
const GET_VIEWPOINT = "Get Camera Viewpoint";
const LOOK_AT = "Look At";
const LOOK_AT_SELECTED = "Look at Selected Objects";
const LOOK_AT_BOUNDING_BOX = "Look at Predefined Bounding Box";
const NAMED_VIEWPOINT = "Named Camera Viewpoint";
const SET_ORTHOGONAL_VIEWPOINT = "Set Orthogonal Camera Viewpoint";
const SET_PERSPECTIVE_VIEWPOINT = "Set Perspective Camera Viewpoint";
const TRANSITION_DURATION = "Transition Duration";
const TRANSITION_EASING = "Transition Easing";
// Available easing functions. Source: https://sole.github.io/tween.js/examples/03_graphs.html
const easingFunctions = [
"LinearNone",
"QuadraticIn",
"QuadraticOut",
"QuadraticInOut",
"CubicIn",
"CubicOut",
"CubicInOut",
"QuarticIn",
"QuarticOut",
"QuarticInOut",
"QuinticIn",
"QuinticOut",
"QuinticInOut",
"SinusoidalIn",
"SinusoidalOut",
"SinusoidalInOut",
"ExponentialIn",
"ExponentialOut",
"ExponentialInOut",
"CircularIn",
"CircularOut",
"CircularInOut",
"ElasticIn",
"ElasticOut",
"ElasticInOut",
"BackIn",
"BackOut",
"BackInOut",
"BounceIn",
"BounceOut",
"BounceInOut"
];
// Default easing function
const initialEasingFunction = "LinearNone";
// Default named viewpoint
const initialNamedViewpoint = "home";
// List of available named viewpoints
const namedViewpoints = [
"home",
"front",
"back",
"left",
"right",
"top",
"bottom"
];
// Hide spaces in the 3D viewer
hideSpaces();
// Set an initial viewpoint
viewer3d.setViewpoint(initialNamedViewpoint);
// Set an initial transition duration
viewer3d.setTransitionSettings({ duration: 1000 });
/**
* Define button callbacks
*/
const onGetViewpointClick = function () {
const viewpoint = viewer3d.getViewpoint();
setOutput(viewpoint);
};
const onLookAtBoundingBoxClick = function () {
setOutput(viewer3d.getBoundingBox());
// Predefined bounding box
const boundingBox = {
max: {
x: -42,
y: 5,
z: 5
},
min: {
x: -44,
y: 0,
z: -1.2
}
};
viewer3d.lookAtBoundingBox(boundingBox);
};
const onLookAtSelectedClick = function () {
const selectedObjectIds = viewer3d.getSelected();
viewer3d.lookAt(selectedObjectIds);
};
const onNamedViewpointChange = function (namedViewpoint) {
// Store current transition duration
const duration = state[TRANSITION_DURATION];
// Remove transition duration before setting viewpoint
viewer3d.setTransitionSettings({ duration: 0 });
viewer3d.setViewpoint(namedViewpoint);
// Restore transition duration
viewer3d.setTransitionSettings({ duration });
setOutput(viewer3d.getViewpoint());
};
const onSetOrthogonalViewpointClick = function () {
// Predefined viewpoint
const orthogonalViewpoint = {
type: "orthogonal",
direction: { x: -0.5, y: 0.5, z: -0.5 },
location: {
x: -40,
y: 40,
z: 0
},
viewToWorldScale: 150
};
// Store current transition duration
const duration = state[TRANSITION_DURATION];
// Remove transition duration before setting viewpoint
viewer3d.setTransitionSettings({ duration: 0 });
viewer3d.setViewpoint(orthogonalViewpoint);
// Restore transition duration
viewer3d.setTransitionSettings({ duration });
setOutput(viewer3d.getViewpoint());
};
const onSetPerspectiveViewpointClick = function () {
// Predefined viewpoint
const perspectiveViewpoint = {
type: "perspective",
direction: { x: -0.5, y: 0.5, z: -0.5 },
location: {
x: 25,
y: -30,
z: 60
}
};
// Store current transition duration
const duration = state[TRANSITION_DURATION];
// Remove transition duration before setting viewpoint
viewer3d.setTransitionSettings({ duration: 0 });
viewer3d.setViewpoint(perspectiveViewpoint);
// Restore transition duration
viewer3d.setTransitionSettings({ duration });
setOutput(viewer3d.getViewpoint());
};
const onTransitionDurationChange = function (duration) {
viewer3d.setTransitionSettings({ duration });
};
const onTransitionEasingChange = function (easing) {
viewer3d.setTransitionSettings({ easing });
};
// Map of gui items to data and callbacks
const state = {
[GET_VIEWPOINT]: onGetViewpointClick,
[LOOK_AT_BOUNDING_BOX]: onLookAtBoundingBoxClick,
[LOOK_AT_SELECTED]: onLookAtSelectedClick,
[NAMED_VIEWPOINT]: initialNamedViewpoint,
[SET_ORTHOGONAL_VIEWPOINT]: onSetOrthogonalViewpointClick,
[SET_PERSPECTIVE_VIEWPOINT]: onSetPerspectiveViewpointClick,
[TRANSITION_DURATION]: 1000,
[TRANSITION_EASING]: initialEasingFunction
};
/**
* Build gui panel
*/
const cameraViewpointFolder = gui.addFolder(CAMERA_VIEWPOINT);
cameraViewpointFolder
.add(state, NAMED_VIEWPOINT, namedViewpoints)
.onChange(onNamedViewpointChange);
cameraViewpointFolder.add(state, SET_ORTHOGONAL_VIEWPOINT);
cameraViewpointFolder.add(state, SET_PERSPECTIVE_VIEWPOINT);
cameraViewpointFolder.add(state, GET_VIEWPOINT);
const lookAtFolder = gui.addFolder(LOOK_AT);
lookAtFolder.add(state, LOOK_AT_SELECTED).disable();
lookAtFolder.add(state, LOOK_AT_BOUNDING_BOX);
lookAtFolder
.add(state, TRANSITION_DURATION, 0, 10000, 1000)
.onFinishChange(onTransitionDurationChange);
lookAtFolder
.add(state, TRANSITION_EASING, easingFunctions)
.onChange(onTransitionEasingChange);
/**
* Listen to viewer events
*/
const onSelect = function (event) {
const { selected } = event;
if (selected.length > 0) {
enableGuiProperty(LOOK_AT_SELECTED);
} else {
disableGuiProperty(LOOK_AT_SELECTED);
}
};
viewer3d.addEventListener("viewer3d.select", onSelect);
};
/**
* Code required for bootstrapping a 3D viewer instance with which to execute the example
*/
// Bootstrap a 3D viewer instance and load a model
const initialize = async function (onViewerReady) {
// Show the loading indicator until the 3D viewer is ready
showLoadingIndicator();
// Hide any previous error message
hideElement(ElementId.ERROR);
// Create a gui instance for 3D viewer controls
gui = new lil.GUI({
container: document.getElementById("gui")
});
gui.title("Options");
// HTML Div element where the 3D viewer is mounted
const div3d = document.getElementById(ElementId.VIEWER_3D);
// Options used when creating a 3D viewer instance
const viewerOptions = {
enableTouch: true,
textRenderMode: "dom"
};
// Create a 3D viewer instance
viewer3d = new bimsync.viewer3d.Viewer3D(div3d, viewerOptions);
// Options used when loading the model
const loadModelOptions = {
modelId: modelId
};
// Fetch a 3D viewer token for the model to load
const url3d = await getToken();
// Load the model
viewer3d
.loadModelsFromToken(url3d, loadModelOptions)
.then(function () {
// 3D viewer is ready, stop loading
showPlayButton();
// Hide the splash screen
hideElement(ElementId.SPLASH);
// Show the 3D viewer gui
showElement(ElementId.GUI);
// 3D viewer is ready, run the callback
onViewerReady();
})
.catch(function (error) {
showError();
});
};
// Entry point called when user clicks the play button
const start = function () {
// Bootstrap a 3D viewer instance then run the example
initialize(runExample);
};
/**
* Utility functions used in this file
*/
// Set console text in clipboard
const copyConsole = async function () {
await navigator.clipboard.writeText(
document.getElementById(ElementId.CONSOLE_TEXT).innerHTML
);
document.getElementById(ElementId.TOOLTIP).innerHTML = "Copied!";
};
// Disable a property in the gui panel
const disableGuiProperty = function (propertyName) {
gui
.controllersRecursive()
.find(({ property }) => property === propertyName)
.disable();
};
// Enable a property in the gui panel
const enableGuiProperty = function (propertyName) {
gui
.controllersRecursive()
.find(({ property }) => property === propertyName)
.enable();
};
// Retrieve a 3D viewer token
const getToken = async function () {
/**
* Fetch a 3D viewer token from the Catenda API
* Here we are using a CodePen specific endpoint for demo purposes
* Replace this with your own token endpoint in a real application
* Find out more at https://developers.catenda.com/viewer-3d/bim-models
* The model this token provides access to is licensed under the Creative Commons Attribution 4.0 International License.
* (C) original authors
* https://github.com/buildingSMART/Sample-Test-Files
* More info and a link to the full license text is available on http://creativecommons.org/licenses/by/4.0/
*/
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const tryCount = 4;
let response, result;
// Try to fetch the token a few times before giving up
for (let i = 1; i <= tryCount; i++) {
response = await fetch(
"https://documentation-edge.developers.catenda.com/token3d"
);
if (response.ok) {
break;
} else {
// If the response is not ok, we wait a bit before trying again
if (i < tryCount) {
await delay(i * 1000);
}
continue;
}
}
// If the response is ok, we can proceed
if (response && response.ok) {
const data = await response.json();
result = data.url;
} else {
showError();
}
return result;
};
// Hide an HTML element passing its id
const hideElement = function (elementId) {
document.getElementById(elementId).style.display = Style.NONE;
};
// Hide spaces in the 3D viewer
const hideSpaces = function () {
const spaceObjectIds = viewer3d
.getProducts()
.filter((product) => product.ifcType === "IfcSpace")
.map((space) => space.objectId);
viewer3d.hide(spaceObjectIds);
};
// Set the text displayed in the output panel
const setOutput = function (value) {
const text = value instanceof Object ? JSON.stringify(value) : value;
document.getElementById(ElementId.CONSOLE_TEXT).innerHTML = text;
};
// Show an HTML element passing its id
const showElement = function (elementId) {
document.getElementById(elementId).style.display = Style.FLEX;
};
// Reset after an error
const showError = function () {
viewer3d.dispose();
gui.destroy();
showPlayButton();
showElement(ElementId.ERROR);
};
// Display the loading indicator
const showLoadingIndicator = function () {
const playButton = document.getElementById(ElementId.PLAY_BUTTON);
playButton.disabled = true;
const playIconElement = document.getElementById(ElementId.PLAY_ICON);
let loadingIcon = document.createElement("i");
loadingIcon.id = ElementId.LOADING_ICON;
loadingIcon.classList.add("fa", "fa-spinner", "fa-spin");
playIconElement.replaceWith(loadingIcon);
};
// Display the play button
const showPlayButton = function () {
const playButton = document.getElementById(ElementId.PLAY_BUTTON);
playButton.disabled = false;
const loadingIconElement = document.getElementById(ElementId.LOADING_ICON);
let playIcon = document.createElement("i");
playIcon.id = ElementId.PLAY_ICON;
playIcon.classList.add("fa", "fa-play");
loadingIconElement.replaceWith(playIcon);
};
// Update a value used in the gui panel to reflect a change
const updateGuiPropertyValue = function (propertyName, value) {
gui
.controllersRecursive()
.find(({ property }) => property === propertyName)
.setValue(value);
};