Render Document-related (Pushpin) Issues and RFIs in Your App
This tutorial demonstrates how to use the Viewer SDK to render pushpin issues and RFIs in your app. Pushpins are visual markers that denote the location of a BIM 360 issue or RFI in a document. The Viewer SDK includes an extension that creates and renders pushpin issues and RFIs. This tutorial demonstrates how to use the Viewer SDK to load BIM 360 documents and render pushpins. The steps include finding the version ID of the document the pushpins were added to, initializing the Viewer, loading the document, retrieving the pushpin issue and RFI IDs associated with the document, and rendering the pushpins in the Viewer.
To see a fully working sample, see this Pushpin app.
Before You Begin
- Register an app
- Acquire a 3-legged OAuth token with
viewables:read
data:create
data:read
anddata:write
scopes. - Upload the file to BIM 360 Document Management.
- Add pushpins issues or RFIs to a document in BIM 360, either by creating them in the UI or by using the Create Pushpin Issues in Your App tutorial.
- Find the project ID of the document that the issues or RFIs were added to. See the initial steps of the Attachments tutorial for more details.
- Verify the ID of the project’s container. For more details see the Retrieve a Container ID tutorial.
- Verify that you have access to the relevant BIM 360 account and BIM 360 project.
- Read through the Basic Viewer documentation.
Step 1: Find the Item ID and Version ID
Find the item ID and version ID of the document the pushpins were added to by calling GET projects/:project_id/folders/:folder_id/contents with a 3-legged token, using the project ID (b.cGVyc29uYWw6d2l
) and the folder ID of the folder that contains the document that the pushpins were added to (urn:adsk.wipprod:fs.folder:co.O7Zwai
).
The version ID is used to render the document in the Viewer (step 2).
The item ID is used to filter the pushpins (step 3).
Request
curl -X GET -H "Authorization: Bearer nFRJxzCD8OOUr7hzBwbr06D76zAT"
"https://developer.api.autodesk.com/project/v1/projects/b.cGVyc29uYWw6d2l/folders/urn:adsk.wipprod:fs.folder:co.BJU3PTc4Sd2CmXM492XUiA/contents"
Response
{
"jsonapi": {
"version": "1.0"
},
},
"data": [
{
"type": "folders",
"id": "urn:adsk.wipprod:fs.folder:co.BJU3PTc4Sd2CmXM492XUiA",
"attributes": {
"name": "Project Files",
"displayName": "Project Files",
"createTime": "2017-07-17T13:06:56.0000000Z",
"createUserId": "",
"createUserName": "",
"lastModifiedTime": "2017-09-24T07:46:08.0000000Z",
"lastModifiedUserId": "X9WYLGPNCHSL",
"lastModifiedUserName": "John Smith",
"objectCount": 4,
"hidden": false,
"extension": {
"type": "folders:autodesk.bim360:Folder",
"version": "1.0",
"schema": {
"href": "https://developer.api.autodesk.com/schema/v1/versions/folders:autodesk.bim360:Folder-1.0"
}
},
{
"type": "items",
"id": "urn:adsk.wip:dm.lineage:7_5NcbL1Q1GSjRLJe9ffvw",
"attributes": {
"displayName": "My First Document",
"createTime": "2017-10-02T11:25:57.0000000Z",
"createUserId": "X9WYLGPNCHVK",
"createUserName": "John Smith",
"lastModifiedTime": "2017-10-02T11:27:22.0000000Z",
"lastModifiedUserId": "X9WYLGPNCHVK",
"lastModifiedUserName": "John Smith",
"hidden": false,
"extension": {
"type": "items:autodesk.bim360:File",
"version": "1.0",
"schema": {
"href": "https://developer.api.autodesk.com/schema/v1/versions/items:autodesk.bim360:File-1.0"
},
"data": {}
}
]
}
"included": [
{
"type": "versions",
"id": "urn:adsk.wipprod:fs.file:vf.7_5NcbL1Q1GSjRLJe9ffvw?version=1",
"attributes": {
"name": "My First Document",
"displayName": "My First Document",
"createTime": "2017-10-02T11:25:57.0000000Z",
"createUserId": "X9WYLGPNCHVK",
"createUserName": "John Smith",
"lastModifiedTime": "2017-10-02T11:27:22.0000000Z",
"lastModifiedUserId": "X9WYLGPNCHVK",
"lastModifiedUserName": "John Smith",
"hidden": false,
"versionNumber": 1,
"fileType": "ifc",
"extension": {
"type": "versions:autodesk.bim360:File",
"version": "1.0",
"schema": {
"href": "https://developer.api.autodesk.com/schema/v1/versions/versions:autodesk.bim360:File-1.0"
},
"data": {
"processState": "PROCESSING_COMPLETE",
"extractionState": "SUCCESS",
"splittingState": "NOT_SPLIT",
"reviewState": "NOT_IN_REVIEW",
"revisionDisplayLabel": "1",
}
}
Find the name of the item (data.attributes.displayName), and note the following:
The version ID (included[i].id) - urn:adsk.wipprod:fs.file:7_5NcbL1Q1GSjRLJe9ffvw?version=1
, which is used to render the document in the Viewer (step 2). Note that you do not need to encode the version ID to Base64 format since it is already encoded when the document was uploaded to BIM 360 Document Management.
The item ID (data.id) - urn:adsk.wipprod:dm.lineage:7_5NcbL1Q1GSjRLJe9ffvw
, which is used to filter the pushpins (step 3).
Step 2: Initialize the Viewer
Initialize the Viewer and load the document and the Pushpin extension using the Viewer SDK.
// Set up the following handles:
var viewerApp;
var PushPinExtensionHandle;
// Initialize the Viewer and load the document (model)
function launchViewer(urn) {
var options = {
env: 'AutodeskProduction',
// Replace <YOUR_ACCESS_TOKEN> with a 3-legged access token.
getAccessToken: <YOUR_ACCESS_TOKEN>
};
// Replace <YOUR_VERSION_ID> with a the version ID of the document.
var documentId = 'urn:' + <YOUR_VERSION_ID>;
var config3d = {
extensions: []
};
Autodesk.Viewing.Initializer(options, function onInitialized() {
viewerApp = new Autodesk.Viewing.ViewingApplication('forgeViewer'); // setting viewerApp handle
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config3d);
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure); // loading document
});
}
// Select a sheet of the model to display in the Viewer.
function onDocumentLoadSuccess(doc) {
// The `bubble` object contains metadata about the viewer and its displayed content.
// Use `bubble.search` to filter for data within the object.
// The "geometry" type retrieves all of the document's 2D and 3D sheets.
var viewables = viewerApp.bubble.search({ 'type': 'geometry' });
if (!viewables.length) {
return console.error('Document contains no viewables.');
}
// In this example, we are using the `viewerApp` handle to select and load the first sheet (view).
// Alternatively load a different sheet.
viewerApp.selectItem(viewables[0], onItemLoadSuccess, onItemLoadFail);
}
// Load the Pushpin extension
function onItemLoadSuccess(viewer, item) {
...
// Select pushpin buttons for the Viewer toolbar.
// By default, the buttons will not appear in the toolbar.
var extensionOptions = {
hideIssuesButton: false,
hideRfisButton: true,
hideFieldIssuesButton: true,
};
// Use the `viewer` reference to call `loadExtension` with the extension name and the extension options:
viewer.loadExtension('Autodesk.BIM360.Extension.PushPin', extensionOptions)
.then(function (extension) {
PushPinExtensionHandle = extension;
// After the extension loads, call `getIssues`. This is a simple AJAX function that should call
// your server. The actual `get issues` call should be implemented there using your 3-legged token.
//
// Note that in this example we called `getIssues` after the model is rendered in case the Viewer, document or extension fails to load. However, you can call `getIssues` before loading the extension to spare the time it takes to render the model.
getIssues() // Implement the call; see step 3.
.then(function(issues) {
registerOnSelectEvent(issues); // See step 4
renderIssues(issues); // See step 5
})
});
}
Step 3: Retrieve the Issues or RFIs
This tutorial will retrieve pushpin issues.
Within our server, we will implement the GET issues call.
We recommend using the target_urn
filter when calling these endpoints so that you only retrieve pushpins associated with the specific document and version. You need to specify the document (the item ID you retrieved in step 1), and the version number (the number at the end of the version ID string you retrieved in step 1), in the followng format: item_id@version_number
. For example, urn:adsk.wipprod:dm.lineage:7_5NcbL1Q1GSjRLJe9ffvw@1
.
Step 4: Set an Event Listener (optional)
The Pushpin extension offers a number of events. In this example, we are going to listen for the pushpin.selected
event, which is fired when the user selects a pushpin. You can set up your app to display pushpin-related information when the pushpin is selected.
registerOnSelectEvent(issues) {
PushPinExtensionHandle.pushPinManager.addEventListener('pushpin.selected', function (e) {
if (e.value) {
// Use the pushpin data (that you retrieved in step 3) to search for the selected pushpin metadata that you can display in the UI.
var issue = findIssueByLabel(issues, e.value.itemData.label);
// Implement the function ``findIssueByLabel``, which searches for the pushpin by comparing the pushin data to the event data: `issue.attributes.identifier` === `e.value.itemData.label`.
// You can choose which data to display and how to display it.
}
};
Step 5: Render Pushpin Data
In order to render the pushpins data you need to traverse all the retrieved issues or RFIs and apply the createItem
Pushpins extension function to them.
To rerender all the pushpins on the sheet you first need to remove the current pushpins by using PushPinExtensionHandle.removeAllItems();
, and then use the following function:
renderIssues(issues) {
issues.forEach(function (issue) {
var issueAttributes = issue.attributes;
var pushpinAttributes = issue.attributes.pushpin_attributes;
// Notice the last rendering condition, which will enforce rendering the pushpin on the current sheet.
// We simply compare the issue sheet metadata against the current sheet.
if (pushpinAttributes && issueAttributes.sheet_metadata &&
issueAttributes.sheet_metadata.sheetGuid === viewerApp.selectedItem.guid()) {
PushPinExtensionHandle.createItem({
id: issue.id, // The issue ID.
label: issueAttributes.identifier, // The value displayed when you select the pushpin.
// The shape and color of the pushpin, in the following format: ``type-status`` (e.g., ``issues-open``).
status: issue.type && issueAttributes.status.indexOf(issue.type) === -1 ?
`${issue.type}-${issueAttributes.status}` : issueAttributes.status,
position: pushpinAttributes.location, // The x, y, z coordinates of the pushpin.
type: issue.type, // The issue type.
objectId: pushpinAttributes.object_id, // (Only for 3D models) The object the pushpin is situated on.
viewerState: pushpinAttributes.viewer_state // The current viewer state. For example, angle, camera, zoom.
});
} // if
} // forEach
};
Step 6: Extra Functionality
Issues and RFIs are associated with a specific sheet, which is why we filtered the pushpins for a specific sheet in the last step. You can allow your users to switch between sheets or display a pushpin on a specific sheet, by using a combination of the above steps. We will, however, go over a few minor details in order make sure everything is perfectly clear.
Note that these extra optional steps are only applicable to Project Files folder files.
Use an External View of Pushpin Data to Display a Pushpin in the Viewer
In order to display a list of pushpin data you can simply use the data from the retrieved issues and RFIs. If you click on the pushpin name in the list of pushpin data, you can configure your app to load the sheet by its GUID and load the pushpin in the sheet:
// If the pushpin is not in the current sheet, switch to the correct sheet to view it.
// Note that this code is similar to step 2, where we described how to load the initial sheet (`onDocumentLoadSuccess`).
if (issue.sheetGuid !== viewerApp.selectedItem.guid()) {
var viewable = viewerApp.bubble.search({ 'guid': issue.sheetGuid }); // get sheet by guid
if (!viewable.length) {
return console.error('Sheet could not be found.');
}
// Select sheet to display (callbacks are the same as in `onDocumentLoadSuccess`)
viewerApp.selectItem(viewable[0], onItemLoadSuccess, onItemLoadFail);
// To highlight this pushpin in the sheet, use this function `PushPinExtensionHandle.selectOne(issue_id);` within the `onItemLoadSuccess` function.
} else {
// If the pushpin is in the current sheet, select the pushpin
PushPinExtensionHandle.selectOne(issue.id);
}
Use an External View of Document Sheets to Select Different Sheets
Displaying and interacting with the list of sheets is very similar to displaying the pushpin as described above:
var viewables = viewerApp.bubble.search({ 'type': 'geometry' }); // get all sheets / views
if (!viewables.length) {
return console.error('Document contains no viewables.');
}
// Select the sheet you want to load.