【问题标题】:How to create Room name Text inside the room element?如何在房间元素内创建房间名称文本?
【发布时间】:2021-08-29 02:14:18
【问题描述】:

有没有办法在 forge viewer 的房间元素中创建房间名称文本?

我在伪造查看器中有房间元素,如下图所示。 因此,我可以从元素属性中读取房间名称。然后,我想在伪造查看器中创建房间名称文本。请问我有解决办法吗?

提前致谢,

【问题讨论】:

    标签: autodesk-forge


    【解决方案1】:

    2021 年 6 月 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: 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);
    

    ==============================

    类似于 Gird 解决方案:https://stackoverflow.com/a/68129012/7745569

    不完美,但它有效。您可能需要根据您的模型调整标签放置点(位置)。目前,标签放置在 Room 边界框底面的中心。

    /////////////////////////////////////////////////////////////////////
    // 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);
    

    这里是 dmeo 快照:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-10-22
      • 2012-11-14
      • 2013-10-09
      • 1970-01-01
      • 2013-10-18
      • 2020-01-10
      • 2013-10-10
      • 2020-06-28
      相关资源
      最近更新 更多