Forge Viewer:dbid に沿ったマークアップの 2D 上の表示

Forge Viewer:dbid に沿ったマークアップの表示 でご紹介した内容は、あいにく、3D 環境にのみに対応するものです。要素に対するフラグメントの関連付けは 2D 環境と 3D 環境とで異なっているため、そのまま getFragmentList() だけでは  2D シート/図面上の要素から適切なフラグメント情報を得ることが出来ません。

3D 環境の場合、メッシュが大きすぎたり、異なるマテリアルを使用したりすると、1 つの要素(1 つの dbid)を複数のフラグメントに分割して形状を表現しています。一方、2D 環境の場合、1 つの要素が 1 つのフラグメントで表現されることがほとんどなためです。

2D 環境で dbid から要素に対するフラグメントの Bounding Box (境界ボックス)を取得し、その中心座標を得るには VertexBufferReader() を利用する次のコードを利用することが出来ます。

                                 :
_viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, onSelected);
                                 :
function onSelected(event) {
    var dbIdArray = event.dbIdArray;
    if (dbIdArray.length > 0) {
        console.log("dbId = " + dbIdArray[0]);
        console.log(get2DBounds(dbIdArray[0], _viewer.model).center());
    }
}
                                 :
function find2DBounds(fragList, fragId, dbId, bc) {
    const mesh = fragList.getVizmesh(fragId);
    const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry);
    vbr.enumGeomsForObject(dbId, bc);
}
function get2DBounds(dbId, model, useInstancing) {
    const it = model.getData().instanceTree;
    const fragList = model.getFragmentList();
    let bounds = new THREE.Box3();
    let bc = new Autodesk.Viewing.Private.BoundsCallback(bounds);
    const dbId2fragId = model.getData().fragments.dbId2fragId;
    const remappedId = model.reverseMapDbId(dbId);
    const fragIds = dbId2fragId[remappedId];
    if (Array.isArray(fragIds)) {
        for (let i = 0; i < fragIds.length; i++) {
            find2DBounds(fragList, fragIds[i], remappedId, bc);
        }
    } else if (typeof fragIds === 'number') {
        find2DBounds(fragList, fragIds, remappedId, bc);
    }
    return bc.bounds;
}

先のブログの IconMarkupExtension エクステンションをそのまま流用する場合には、IconMarkupExtension.js 内に次のようなかたちで実装を追加、修正する必要があります。

class IconMarkupExtension extends Autodesk.Viewing.Extension {
    constructor(viewer, options) {
        super(viewer, options);
        this._group = null;
        this._button = null;
        this._icons = options.icons || [];
    }
    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.container.children[0].classList.add('fas', this.options.button.icon);
        this._group.addControl(this._button);
    }
    showIcons(show) {
        const $viewer = $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer');
        // remove previous...
        $('#' + 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) => {
            if (this.options.onClick)
                this.options.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, function (fragId) {
                _this._frags['dbId' + icon.dbId].push(fragId);
                _this.updateIcons(); // re-position of each fragId found
            });
        }
    }
    getModifiedWorldBoundingBox(dbId) {
        var nodebBox = get2DBounds(dbId, this.viewer.model);
        return nodebBox;
    }
    updateIcons() {
        for (const label of $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer.update')) {
            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');
        }
    }
    find2DBounds(fragList, fragId, dbId, bc) {
        const mesh = fragList.getVizmesh(fragId);
        const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry);
        vbr.enumGeomsForObject(dbId, bc);
    }
    get2DBounds(dbId, model, useInstancing) {
        const it = model.getData().instanceTree;
        const fragList = model.getFragmentList();
        let bounds = new THREE.Box3();
        let bc = new Autodesk.Viewing.Private.BoundsCallback(bounds);
        const dbId2fragId = model.getData().fragments.dbId2fragId;
        const remappedId = model.reverseMapDbId(dbId);
        const fragIds = dbId2fragId[remappedId];
        if (Array.isArray(fragIds)) {
            for (let i = 0; i < fragIds.length; i++) {
                find2DBounds(fragList, fragIds[i], remappedId, bc);
            }
        } else if (typeof fragIds === 'number') {
            find2DBounds(fragList, fragIds, remappedId, bc);
        }
        return bc.bounds;
    }

}
Autodesk.Viewing.theExtensionManager.registerExtension('IconMarkupExtension', IconMarkupExtension);

エクステンション呼び出し側の定義は、同じ手法を用いることが出来ます。

_viewer.loadExtension('IconMarkupExtension', {
    button: {
        icon: 'fa-thermometer-half',
        tooltip: 'Show Temperature'
    },
    icons: [
        { dbId: 83066, label: 'ささら', css: 'fa-2x temperatureOk fas temperatureBorder' },
        { dbId: 6650, label: '屋上', css: 'fa-2x temperatureOk fas temperatureBorder' },
        { dbId: 2246, label: 'フェンス', css: 'fa-thermometer-half temperatureHigh fas temperatureBorder' },
        { dbId: 1069, label: 'レベル線', css: 'fas temperatureHigh fa-2x faa-flash animated' },
    ],
    onClick: (id) => {
        _viewer.select(id);
        _viewer.utilities.fitToView();
    }
});

By Toshiaki Isezaki

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading