import {BCFViewpointsPlugin} from '@xeokit/xeokit-sdk/src/plugins/BCFViewpointsPlugin/BCFViewpointsPlugin.js'
BCFViewpointsPlugin
Extends:
Viewer plugin that saves and loads BCF viewpoints as JSON objects.
- [Example 1: Saving viewer state to a BCF viewpoint]
- [Example 2: Loading viewer state from a BCF viewpoint]
Overview
BCF is an open standard that enables workflow communications between BIM software tools. An XML schema, called Building Collaboration Format (BCF), encodes messages that inform one BIM tool of issues found by another.
A BCF viewpoint captures a viewpoint of a model that highlights an issue. The viewpoint can then be loaded by another viewer to examine the issue.
Using this plugin, a xeokit Viewer can exchange BCF-encoded viewpoints with other BIM software, allowing us to use the Viewer to report and view issues in BIM models.
This plugin's viewpoints conform to the BCF Version 2.1 specification.
Supported BCF Elements
BCFViewpointsPlugin saves and loads the following state in BCF viewpoints:
- Camera position, orientation and projection
- Entity visibilities and selection states
- SectionPlanes to slice the model
- LineSets to show 3D lines
- Bitmaps to show images
Saving a BCF Viewpoint
In the example below we'll create a Viewer, load an .XKT
model into it using an XKTLoaderPlugin,
slice the model in half using a SectionPlanesPlugin, create a grid ground plane using a LineSet and a 2D
plan view using a Bitmap, then use a BCFViewpointsPlugin#getViewpoint
to save a viewpoint to JSON, which we'll log to the JavaScript developer console.
import {Viewer, XKTLoaderPlugin, SectionPlanesPlugin,
LineSet, Bitmap, buildGridGeometry, BCFViewpointsPlugin} from "xeokit-sdk.es.js";
// Create a Viewer
const viewer = new Viewer({
canvasId: "myCanvas",
transparent: true
});
// Set camera position and orientation
viewer.scene.camera.eye = [-48.93, 54.54, 50.41];
viewer.scene.camera.look = [0.55, -0.61, -0.55];
viewer.scene.camera.up = [0, -1, 0];
viewer.scene.camera.perspective.fov = 60;
// Add a XKTLoaderPlugin
const xktLoader = new XKTLoaderPlugin(viewer);
// Add a SectionPlanesPlugin
const sectionPlanes = new SectionPlanesPlugin(viewer);
// Add a BCFViewpointsPlugin
const bcfViewpoints = new BCFViewpointsPlugin(viewer);
// Load an .XKT model
const modelNode = xktLoader.load({
id: "myModel",
src: "./models/xkt/Schependomlaan.xkt",
edges: true // Emphasise edges
});
// Slice it in half
sectionPlanes.createSectionPlane({
id: "myClip",
pos: [0, 0, 0],
dir: [0.5, 0.0, 0.5]
});
// Create a bitmap
const bitmap = new Bitmap(viewer.scene, {
src: "../assets/images/schependomlaanPlanView.png",
visible: true,
height: 24.0,
pos: [-15, 0, -10],
normal: [0, -1, 0],
up: [0, 0, 1],
collidable: false,
opacity: 1.0,
clippable: false,
pickable: true
});
// Create a grid ground plane
const geometryArrays = buildGridGeometry({
size: 60,
divisions: 10
});
new LineSet(viewer.scene, {
positions: geometryArrays.positions,
indices: geometryArrays.indices,
position: [10,0,10],
clippable: false
});
// When model is loaded, select some objects and capture a BCF viewpoint to the console
modelNode.on("loaded", () => {
const scene = viewer.scene;
scene.setObjectsSelected([
"3b2U496P5Ebhz5FROhTwFH",
"2MGtJUm9nD$Re1_MDIv0g2",
"3IbuwYOm5EV9Q6cXmwVWqd",
"3lhisrBxL8xgLCRdxNG$2v",
"1uDn0xT8LBkP15zQc9MVDW"
], true);
const viewpoint = bcfViewpoints.getViewpoint();
const viewpointStr = JSON.stringify(viewpoint, null, 4);
console.log(viewpointStr);
});
The saved BCF viewpoint would look something like below. Note that some elements are truncated for brevity.
{
"perspective_camera": {
"camera_view_point": { "x": -48.93, "y": 54.54, "z": 50.41 },
"camera_direction": { "x": 0.55, "y": -0.61, "z": -0.55},
"camera_up_vector": { "x": 0.37, "y": -0.41, "z": 0.83 },
"field_of_view": 60.0
},
"lines": [{
"start_point": { "x": 1.0, "y": 1.0, "z": 1.0 },
"end_point": { "x": 0.0, "y": 0.0, "z": 0.0 },
//...(truncated)
}],
"bitmaps": [{
"bitmap_type": "png",
"bitmap_data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB9AAAAdp...", //...(truncated)
"location": { "x": -15, "y": 10, "z": 0 },
"normal": { "x": 0, "y": 0, "z": -1 },
"up": { "x": 0, "y": -1, "z": 0 },
"height": 24
}],
"clipping_planes": [{
"location": { "x": 0.0, "y": 0.0, "z": 0.0 },
"direction": { "x": 0.5, "y": 0.0, "z": 0.5 }
}],
"snapshot": {
"snapshot_type": "png",
"snapshot_data": "data:image/png;base64,......"
},
"components": {
"visibility": {
"default_visibility": false,
"exceptions": [{
"ifc_guid": "4$cshxZO9AJBebsni$z9Yk",
"originating_system": "xeokit.io",
"authoring_tool_id": "xeokit/v3.2"
},
//...
]
},
"selection": [{
"ifc_guid": "4$cshxZO9AJBebsni$z9Yk",
},
//...
]
}
}
Saving View Setup Hints
BCFViewpointsPlugin can optionally save hints in the viewpoint, which indicate how to set up the view when loading it again.
Here's the BCFViewpointsPlugin#getViewpoint call again, this time saving some hints:
const viewpoint = bcfViewpoints.getViewpoint({ // Options
spacesVisible: true, // Force IfcSpace types visible in the viewpoint (default is false)
spaceBoundariesVisible: false, // Show IfcSpace boundaries in the viewpoint (default is false)
openingsVisible: true // Force IfcOpening types visible in the viewpoint (default is false)
});
Loading a BCF Viewpoint
Assuming that we have our BCF viewpoint in a JSON object, let's now restore it with BCFViewpointsPlugin#setViewpoint:
bcfViewpoints.setViewpoint(viewpoint);
Handling BCF Incompatibility with xeokit's Camera
xeokit's Camera#look is the current 3D point-of-interest (POI).
A BCF viewpoint, however, has a direction vector instead of a POI, and so BCFViewpointsPlugin#getViewpoint saves xeokit's POI as a normalized vector from Camera#eye to Camera#look, which unfortunately loses that positional information. Loading the viewpoint with BCFViewpointsPlugin#setViewpoint will restore Camera#look to the viewpoint's camera position, offset by the normalized vector.
As shown below, providing a rayCast
option to setViewpoint
will set Camera#look to the closest
surface intersection on the direction vector. Internally, setViewpoint
supports this option by firing a ray
along the vector, and if that hits an Entity, sets Camera#look to ray's intersection point with the
Entity's surface.
bcfViewpoints.setViewpoint(viewpoint, {
rayCast: true // <<--------------- Attempt to set Camera#look to surface intersection point (default)
});
Dealing With Loaded Models that are not in the Viewpoint
If, for example, we load model "duplex", hide some objects, then save a BCF viewpoint with
BCFViewpointsPlugin#getViewpoint
, then load another model, "schependomlaan", then load the viewpoint again
with BCFViewpointsPlugin#setViewpoint
, then sometimes all of the objects in model "schependomlaan" become
visible, along with the visible objects in the viewpoint, which belong to model "duplex".
The reason is that, when saving a BCF viewpoint, BCF logic works like the following pseudo code:
If numVisibleObjects < numInvisibleObjects
save IDs of visible objects in BCF
exceptions = "visible objects"
else
save IDS of invisible objects in BCF
exceptions = "invisible objects"
When loading the viewpoint again:
If exceptions = "visible objects"
hide all objects
show visible objects in BCF
else
show all objects
hide invisible objects in BCF
When the exception is "visible objects", loading the viewpoint shows all the objects in the first, which includes objects in "schependomlaan", which can be confusing, because those were not even loaded when we first saved the viewpoint..
To solve this, we can supply a defaultInvisible
option to BCFViewpointsPlugin#getViewpoint, which
will force the plugin to save the IDs of all visible objects while making invisible objects the exception.
That way, when we load the viewpoint again, after loading model "schependomlaan", the plugin will hide all objects in the scene first (which will include objects belonging to model "schependomlaan"), then make the objects in the viewpoint visible (which will only be those of object "duplex").
const viewpoint = bcfViewpoints.getViewpoint({ // Options
//..
defaultInvisible: true
});
Behaviour with XKTLoaderPlugin globalizeObjectIds
Whenever we use XKTLoaderPlugin to load duplicate copies of the same model, after configuring
XKTLoaderPlugin#globalizeObjectIds true
to avoid Entity
ID clashes, this has consequences
for BCF viewpoints created by BCFViewpointsPlugin#getViewpoint.
When no duplicate copies of a model are loaded like this, viewpoints created by BCFViewpointsPlugin#getViewpoint will
continue to load as usual in other BIM viewers. Conversely, a viewpoint created for a single model in other BIM viewers
will continue to load as usual with BCFViewpointsPlugin
.
When duplicate copies of a model are loaded, however, viewpoints created by BCFViewpointsPlugin#getViewpoint
will contain certain changes that will affect the viewpoint's portability, however. Such viewpoints will
use authoring_tool_id
fields to save the globalized Entity#id
values, which enables the viewpoints to
capture the states of the individual Entitys
that represent the duplicate IFC elements. Take a look at the
following two examples to learn more.
- Example: Saving a BCF viewpoint containing duplicate models
- Example: Loading a BCF viewpoint containing duplicate models
Caveat: when loading a BCF viewpoint, we always assume that we have loaded in our target BIM viewer the same models that were
loaded in the viewpoint's original authoring application when the viewpoint was created. In the case of multi-model
viewpoints, the target BIM viewer, whether it be xeokit or another BIM viewer, will need to first have those exact
models loaded, with their objects having globalized IDs, following the same prefixing scheme we're using in
xeokit. Then, the viewpoint's authoring_tool_id
fields will be able to resolve to their objects within the
target viewer.
Constructor Summary
Public Constructor | ||
public |
constructor(viewer: Viewer, cfg: Object) |
Member Summary
Public Members | ||
public |
Identifies the authoring tool to include in BCF viewpoints saved by this plugin. |
|
public |
Identifies the originating system to include in BCF viewpoints saved by this plugin. |
Method Summary
Public Methods | ||
public |
destroy() Destroys this BCFViewpointsPlugin. |
|
public |
getViewpoint(options: *): * Saves viewer state to a BCF viewpoint. |
|
public |
setViewpoint(bcfViewpoint: *, options: *) Sets viewer state to the given BCF viewpoint. |
Inherited Summary
From class Plugin | ||
public |
ID for this Plugin, unique within its Viewer. |
|
public |
The Viewer that contains this Plugin. |
|
public |
destroy() Destroys this Plugin and removes it from its Viewer. |
|
public |
Logs an error message to the JavaScript developer console, prefixed with the ID of this Plugin. |
|
public |
Fires an event on this Plugin. |
|
public |
Returns true if there are any subscribers to the given event on this Plugin. |
|
public |
Logs a message to the JavaScript developer console, prefixed with the ID of this Plugin. |
|
public |
Cancels an event subscription that was previously made with Plugin#on or Plugin#once. |
|
public |
Subscribes to an event on this Plugin. |
|
public |
Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is subIdd. |
|
public |
scheduleTask(task: *) Schedule a task to perform on the next browser interval |
|
public |
Logs a warning message to the JavaScript developer console, prefixed with the ID of this Plugin. |
Public Constructors
public constructor(viewer: Viewer, cfg: Object) source
Creates this Plugin and installs it into the given Viewer.
Override:
Plugin#constructorParams:
Name | Type | Attribute | Description |
viewer | Viewer | The Viewer. |
|
cfg | Object | Plugin configuration. |
|
cfg.id | String |
|
Optional ID for this plugin, so that we can find it within Viewer#plugins. |
cfg.originatingSystem | String |
|
Identifies the originating system for BCF records. |
cfg.authoringTool | String |
|
Identifies the authoring tool for BCF records. |
Public Members
Public Methods
public getViewpoint(options: *): * source
Saves viewer state to a BCF viewpoint.
See BCFViewpointsPlugin
class comments for more info.
Params:
Name | Type | Attribute | Description |
options | * |
|
Options for getting the viewpoint. |
options.spacesVisible | Boolean |
|
Indicates whether |
options.openingsVisible | Boolean |
|
Indicates whether |
options.spaceBoundariesVisible | Boolean |
|
Indicates whether the boundaries of |
options.spacesTranslucent | Boolean |
|
Indicates whether |
options.spaceBoundariesTranslucent | Boolean |
|
Indicates whether the boundaries of |
options.openingsTranslucent | Boolean |
|
Indicates whether |
options.snapshot | Boolean |
|
Indicates whether the snapshot should be included in the viewpoint. |
options.defaultInvisible | Boolean |
|
When |
options.reverseClippingPlanes | Boolean |
|
When |
Return:
* | BCF JSON viewpoint object |
public setViewpoint(bcfViewpoint: *, options: *) source
Sets viewer state to the given BCF viewpoint.
Note that xeokit's Camera#look is the point-of-interest, whereas the BCF camera_direction
is a
direction vector. Therefore, when loading a BCF viewpoint, we set Camera#look to the absolute position
obtained by offsetting the BCF camera_view_point
along camera_direction
.
When loading a viewpoint, we also have the option to find Camera#look as the closest point of intersection
(on the surface of any visible and pickable Entity) with a 3D ray fired from camera_view_point
in
the direction of camera_direction
.
Params:
Name | Type | Attribute | Description |
bcfViewpoint | * | BCF JSON viewpoint object, shows default visible entities and restores camera to initial default position. |
|
options | * |
|
Options for setting the viewpoint. |
options.rayCast | Boolean |
|
When |
options.immediate | Boolean |
|
When |
options.duration | Boolean |
|
Flight duration in seconds. Overrides CameraFlightAnimation#duration. Only applies when |
options.reset | Boolean |
|
When |
options.reverseClippingPlanes | Boolean |
|
When |
options.updateCompositeObjects | Boolean |
|
When |