Skip to content

Instantly share code, notes, and snippets.

@yiskang
Created March 21, 2023 08:33
Show Gist options
  • Save yiskang/e96f544252a989daef022cd89e799f46 to your computer and use it in GitHub Desktop.
Save yiskang/e96f544252a989daef022cd89e799f46 to your computer and use it in GitHub Desktop.
A APS Viewer extension for getting Revit Project Base Point position by AEC Model Data
<html>
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no" />
<meta charset="utf-8">
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css"
type="text/css">
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>
<script src="RevitProjectBasePointExtension.js"></script>
<style>
body {
margin: 0;
}
#forgeViewer {
width: 100%;
height: 100%;
margin: 0;
background-color: #F0F8FF;
}
</style>
</head>
<body>
<div id="forgeViewer"></div>
<script>
function fetchForgeToken(callback) {
fetch('http://127.0.0.1:8090/api/forge/oauth/token', {
method: 'get',
headers: new Headers({ 'Content-Type': 'application/json' })
})
.then((response) => {
if (response.status === 200) {
return response.json();
} else {
return Promise.reject(
new Error(`Failed to fetch token from server (status: ${response.status}, message: ${response.statusText})`)
);
}
})
.then((data) => {
if (!data) return Promise.reject(new Error('Empty token response'));
callback(data.access_token, data.expires_in);
})
.catch((error) => console.error(error));
}
let viewer;
let options = {
env: 'AutodeskProduction2',
api: 'streamingV2',
getAccessToken: fetchForgeToken,
};
Autodesk.Viewing.Initializer(options, function () {
let htmlDiv = document.getElementById('forgeViewer');
let config3d = {
extensions: [
'RevitProjectBasePointExtension'
]
};
viewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv, config3d);
let startedCode = viewer.start();
if (startedCode > 0) {
console.error('Failed to create a Viewer: WebGL not supported.');
return;
}
let documentId = 'urn:...';
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
function onDocumentLoadSuccess(doc) {
let viewables = doc.getRoot().getDefaultGeometry(true);
viewer.loadDocumentNode(doc, viewables, { skipHiddenFragments: false }).then(async (model) => {
await doc.downloadAecModelData();
let projectBasePointPosition = await viewer.getExtension('RevitProjectBasePointExtension').reportProjectBasePoint(model/*, true*/); //!<<< Uncomment to map the point from Revit sacpe to Viewer space.
console.log(projectBasePointPosition);
});
}
function onDocumentLoadFailure() {
console.error('Failed fetching Forge manifest');
}
});
</script>
</body>
</html>
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
(function () {
(function (scope) {
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2007-2013 James Coglan and other contributors
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the 'Software'), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// https://github.com/metabolize/matrix-inverse
///////////////////////////////////////////////////////////////////////////////////////
let Sylvester = {}
Sylvester.Matrix = function () { }
Sylvester.Matrix.create = function (elements) {
var M = new Sylvester.Matrix()
return M.setElements(elements)
}
Sylvester.Matrix.I = function (n) {
var els = [],
i = n,
j
while (i--) {
j = n
els[i] = []
while (j--) {
els[i][j] = i === j ? 1 : 0
}
}
return Sylvester.Matrix.create(els)
}
Sylvester.Matrix.prototype = {
dup: function () {
return Sylvester.Matrix.create(this.elements)
},
isSquare: function () {
var cols = this.elements.length === 0 ? 0 : this.elements[0].length
return this.elements.length === cols
},
toRightTriangular: function () {
if (this.elements.length === 0) return Sylvester.Matrix.create([])
var M = this.dup(),
els
var n = this.elements.length,
i,
j,
np = this.elements[0].length,
p
for (i = 0; i < n; i++) {
if (M.elements[i][i] === 0) {
for (j = i + 1; j < n; j++) {
if (M.elements[j][i] !== 0) {
els = []
for (p = 0; p < np; p++) {
els.push(M.elements[i][p] + M.elements[j][p])
}
M.elements[i] = els
break
}
}
}
if (M.elements[i][i] !== 0) {
for (j = i + 1; j < n; j++) {
var multiplier = M.elements[j][i] / M.elements[i][i]
els = []
for (p = 0; p < np; p++) {
// Elements with column numbers up to an including the number of the
// row that we're subtracting can safely be set straight to zero,
// since that's the point of this routine and it avoids having to
// loop over and correct rounding errors later
els.push(
p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier
)
}
M.elements[j] = els
}
}
}
return M
},
determinant: function () {
if (this.elements.length === 0) {
return 1
}
if (!this.isSquare()) {
return null
}
var M = this.toRightTriangular()
var det = M.elements[0][0],
n = M.elements.length
for (var i = 1; i < n; i++) {
det = det * M.elements[i][i]
}
return det
},
isSingular: function () {
return this.isSquare() && this.determinant() === 0
},
augment: function (matrix) {
if (this.elements.length === 0) {
return this.dup()
}
var M = matrix.elements || matrix
if (typeof M[0][0] === 'undefined') {
M = Sylvester.Matrix.create(M).elements
}
var T = this.dup(),
cols = T.elements[0].length
var i = T.elements.length,
nj = M[0].length,
j
if (i !== M.length) {
return null
}
while (i--) {
j = nj
while (j--) {
T.elements[i][cols + j] = M[i][j]
}
}
return T
},
inverse: function () {
if (this.elements.length === 0) {
return null
}
if (!this.isSquare() || this.isSingular()) {
return null
}
var n = this.elements.length,
i = n,
j
var M = this.augment(Sylvester.Matrix.I(n)).toRightTriangular()
var np = M.elements[0].length,
p,
els,
divisor
var inverse_elements = [],
new_element
// Sylvester.Matrix is non-singular so there will be no zeros on the
// diagonal. Cycle through rows from last to first.
while (i--) {
// First, normalise diagonal elements to 1
els = []
inverse_elements[i] = []
divisor = M.elements[i][i]
for (p = 0; p < np; p++) {
new_element = M.elements[i][p] / divisor
els.push(new_element)
// Shuffle off the current row of the right hand side into the results
// array as it will not be modified by later runs through this loop
if (p >= n) {
inverse_elements[i].push(new_element)
}
}
M.elements[i] = els
// Then, subtract this row from those above it to give the identity matrix
// on the left hand side
j = i
while (j--) {
els = []
for (p = 0; p < np; p++) {
els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i])
}
M.elements[j] = els
}
}
return Sylvester.Matrix.create(inverse_elements)
},
setElements: function (els) {
var i,
j,
elements = els.elements || els
if (elements[0] && typeof elements[0][0] !== 'undefined') {
i = elements.length
this.elements = []
while (i--) {
j = elements[i].length
this.elements[i] = []
while (j--) {
this.elements[i][j] = elements[i][j]
}
}
return this
}
var n = elements.length
this.elements = []
for (i = 0; i < n; i++) {
this.elements.push([elements[i]])
}
return this
},
toThreeMatrix4: function () {
return new Autodesk.Viewing.Private.LmvMatrix4(true).fromArray(this.elements.flat());
}
}
class SylvesterMatrix {
constructor(elements) {
this.elements = elements;
const data = [
[this.elements[0], this.elements[1], this.elements[2], this.elements[3]],
[this.elements[4], this.elements[5], this.elements[6], this.elements[7]],
[this.elements[8], this.elements[9], this.elements[10], this.elements[11]],
[this.elements[12], this.elements[13], this.elements[14], this.elements[15]]
];
this.m = Sylvester.Matrix.create(data);
}
determinant() {
return this.m.determinant();
}
inverse() {
return this.m.inverse();
}
toThreeMatrix4() {
return this.m.toThreeMatrix4();
}
}
scope.SylvesterMatrix = SylvesterMatrix;
})(this);
/**
* A APS Viewer extension for getting Revit Project Base Point position by AEC Model Data
* @class
*/
class RevitProjectBasePointExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
}
async searchAsync(model, text, attributeNames, options) {
return new Promise((resolve, reject) => {
model.search(text, resolve, reject, attributeNames, options);
});
}
async getBulkProperties2Async(dbIds, options, model) {
return new Promise((resolve, reject) => {
model.getBulkProperties2(
dbIds,
options,
(result) => resolve(result),
(error) => reject(error)
);
});
}
readMatrixFromArray12(params) {
return new Autodesk.Viewing.Private.LmvMatrix4(true).fromArray([
params[0], params[1], params[2], 0.0,
params[3], params[4], params[5], 0.0,
params[6], params[7], params[8], 0.0,
params[9], params[10], params[11], 1.0 // Note that the 1 is essential - otherwise multiplying with translations has no effect!
]);
}
async getProjectLocationToModelTransformation(model) {
const aecModelData = await Autodesk.Viewing.Document.getAecModelData(model.getDocumentNode());
const refPointTransformation = this.readMatrixFromArray12(aecModelData.refPointTransformation);
const projectLocationToModelTransformation = new SylvesterMatrix(refPointTransformation.elements).inverse().toThreeMatrix4();
return projectLocationToModelTransformation;
}
async getBasePointData(model, category = 'Revit Base Point') {
return new Promise(async (resolve, reject) => {
const found = await this.searchAsync(model, category, ['Category'], { searchHidden: true });
if (!found || found.length <= 0) return reject('Base point not found');
const result = await this.getBulkProperties2Async(found, { propFilter: ['N/S', 'E/W', 'Elev', 'Angle to True North'] }, model);
if (!result) return reject('Base point not found');
const data = result[0];
return resolve(data);
});
}
async reportProjectBasePoint(model, useViewerSpace = false) {
const projectLocationToModelTransformation = await this.getProjectLocationToModelTransformation(model);
const basePointData = await this.getBasePointData(model);
const eastWestProp = basePointData.properties.find(p => p.attributeName == 'E/W');
const northSouthProp = basePointData.properties.find(p => p.attributeName == 'N/S');
const elevProp = basePointData.properties.find(p => p.attributeName == 'Elev');
const angletonProp = basePointData.properties.find(p => p.attributeName == 'Angle to True North');
const eastWestVal = Autodesk.Viewing.Private.convertToDisplayUnits(eastWestProp.displayValue, eastWestProp.type, eastWestProp.units, model.getUnitString());
const northSouthVal = Autodesk.Viewing.Private.convertToDisplayUnits(northSouthProp.displayValue, northSouthProp.type, northSouthProp.units, model.getUnitString());
const elevVal = Autodesk.Viewing.Private.convertToDisplayUnits(elevProp.displayValue, elevProp.type, elevProp.units, model.getUnitString());
const basePoint = new THREE.Vector3(eastWestVal.displayValue, northSouthVal.displayValue, elevVal.displayValue);
const basePointInRvt = basePoint.clone().applyMatrix4(projectLocationToModelTransformation);
if (useViewerSpace)
return basePointInRvt.clone().applyMatrix4(model.getModelToViewerTransform());
return basePointInRvt;
}
load() {
return true;
}
unload() {
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('RevitProjectBasePointExtension', RevitProjectBasePointExtension);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment