如何在房间元素内创建房间名称文本?
How to create Room name Text inside the room element?
有没有什么方法可以在 forge 查看器的房间元素内创建房间名称文本?
我在 Forge 查看器中有房间元素,如下图所示。
所以,我可以从元素属性中读取房间名称。然后,我想在伪造查看器中创建房间名称文本。可以给我解决方案吗?
提前致谢,
更新2021-06-29
添加了一些条件以避免输入无效数据。
/////////////////////////////////////////////////////////////////////
// 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:
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);
===============================
类似于 Gird 解决方案:
不完美,但它有效。您可能需要根据您的模型调整标签放置点(位置)。目前,标签放置在房间边界框底面的中心。
/////////////////////////////////////////////////////////////////////
// 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:
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);
这里是 dmeo 快照:
有没有什么方法可以在 forge 查看器的房间元素内创建房间名称文本?
我在 Forge 查看器中有房间元素,如下图所示。 所以,我可以从元素属性中读取房间名称。然后,我想在伪造查看器中创建房间名称文本。可以给我解决方案吗?
提前致谢,
更新2021-06-29
添加了一些条件以避免输入无效数据。
/////////////////////////////////////////////////////////////////////
// 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:
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);
===============================
类似于 Gird 解决方案:
不完美,但它有效。您可能需要根据您的模型调整标签放置点(位置)。目前,标签放置在房间边界框底面的中心。
/////////////////////////////////////////////////////////////////////
// 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:
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);
这里是 dmeo 快照: