You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular集成web-ifc-viewer:IFC构件显隐功能异常问题

问题:Angular封装web-ifc-viewer实现IFC构件显隐功能失效

在Angular项目中封装web-ifc-viewer开发通用BIM浏览器,无法预先定义构件类别,执行toggleLayer方法后无任何效果,subset对象日志始终无变化,请求排查解决。

组件代码

import {AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {IfcViewerAPI} from "web-ifc-viewer";
import {Subject, takeUntil} from "rxjs";
import {AppThemes, BimViewerService} from "./bim-viewer.service";

@Component({
  selector: 'almaviva-bim-viewer',
  templateUrl: './bim-viewer.template.html',
  styleUrls: ['./bim-viewer.component.scss']
})
export class BimViewerComponent implements AfterViewInit {
  @ViewChild('viewerContainer')
  container!: ElementRef;
  viewer?: IfcViewerAPI;
  model: any;
  ifcElement: any;
  loadingValue: number = 0;


  constructor() {
  }

  ngAfterViewInit(): void {
    if (this.container) {
      this.viewer = new IfcViewerAPI({container: this.container.nativeElement});
      this.loadIfc('/assets/sample.ifc');
    }

  }

  private async loadIfc(url: string) {
    try {
      if (this.viewer) {
        await this.viewer.IFC.loader.ifcManager.useWebWorkers(true, '/assets/IFCWorker.js');
        await this.viewer.IFC.setWasmPath("wasm/");
        this.viewer.axes.setAxes(1000);
        this.viewer.grid.setGrid(1000);
        await this.viewer.IFC.loader.ifcManager.applyWebIfcConfig({
          USE_FAST_BOOLS: true,
          COORDINATE_TO_ORIGIN: true
        });
        this.viewer.IFC.loader.ifcManager.setOnProgress(
          (event) => {
            this.loadingValue = Math.floor((event.loaded * 100) / event.total);
          }
        )
        this.model = await this.viewer.IFC.loadIfcUrl(url);
        const project = await this.viewer.IFC.getSpatialStructure(this.model.modelID, true);
        this.ifcElement = project.children[0];
        await this.viewer.shadowDropper.renderShadow(this.model.modelID);
      }
    } catch (e) {
      console.log(e);
    }
  }

  async toggleLayer(event: Event, layer: any) {
      const subset = this.viewer?.IFC.loader.ifcManager.createSubset({
      modelID: this.model.modelID,
      ids: [layer.expressID],
      removePrevious: true,
      customID: `${layer.expressID}-custom-id`
    });
    if (subset) {
      this.viewer?.context.getScene().remove(subset);
    }
  }
}

HTML模板

<div>
  <mat-sidenav-container>
    <mat-sidenav mode="side" opened>
      <mat-toolbar>
      <span>
        BIM
      </span>
      </mat-toolbar>
      <mat-progress-bar mode="determinate" [value]="loadingValue"></mat-progress-bar>
      <mat-list role="list" *ngIf="!ifcElement">
          <mat-list-item role="listitem">
            Caricamento IFC in corso...
          </mat-list-item>
      </mat-list>
      <mat-accordion *ngIf="ifcElement">
        <mat-expansion-panel *ngFor="let arch of ifcElement?.children || []">
          <mat-expansion-panel-header>
            <mat-panel-title>
              {{arch.Name?.value || arch.LongName?.value || 'Architettura'}}
            </mat-panel-title>
          </mat-expansion-panel-header>
          <mat-list role="list">
            <mat-list-item role="listitem" *ngFor="let layer of arch.children">
              <mat-checkbox (click)="toggleLayer($event, layer)">
                {{layer.Name?.value || layer.LongName?.value || 'N/A'}}
              </mat-checkbox>
            </mat-list-item>
          </mat-list>
        </mat-expansion-panel>
      </mat-accordion>
    </mat-sidenav>
    <mat-sidenav-content>
      <div id="viewer-container" #viewerContainer></div>
      <div class="loading-spinner-wrapper" *ngIf="loadingValue!==100">
        <mat-spinner mode="indeterminate" diameter="35"></mat-spinner>
      </div>
    </mat-sidenav-content>
  </mat-sidenav-container>
</div>

问题排查与修复

核心问题分析

  1. 逻辑倒置:当前代码每次调用createSubset后立即将子集从场景移除,等于完全没有创建有效可控制的子集。
  2. 目标错误:传入的layer是IFC空间结构元素(如楼层、区域),这类元素本身无几何图形,仅传它的expressID创建子集不会生成任何可见内容。
  3. 无状态保存:未保留已创建子集的引用,无法后续切换可见性。

修复方案

1. 添加子集状态存储

在组件类中新增Map保存已创建的子集引用:

private subsets = new Map<string, THREE.Group>();

2. 重写toggleLayer方法

async toggleLayer(event: Event, layer: any) {
  // 阻止事件冒泡,避免触发父级折叠面板的展开/折叠
  event.stopPropagation();
  const subsetId = `${layer.expressID}-custom-id`;
  let subset = this.subsets.get(subsetId);

  if (subset) {
    // 切换现有子集的可见性
    subset.visible = !subset.visible;
    return;
  }

  // 递归获取当前空间节点下所有带几何的构件ID
  const getAllElementIds = (node: any): number[] => {
    let ids: number[] = [];
    // 排除纯空间结构元素,只收集实际构件
    const spatialTypes = ['IFCPROJECT', 'IFCSITE', 'IFCBUILDING', 'IFCBUILDINGSTOREY'];
    if (!spatialTypes.includes(node.type)) {
      ids.push(node.expressID);
    }
    if (node.children) {
      node.children.forEach((child: any) => {
        ids = ids.concat(getAllElementIds(child));
      });
    }
    return ids;
  };

  const allIds = getAllElementIds(layer);
  if (!allIds || allIds.length === 0) return;

  // 创建子集并添加到场景
  subset = this.viewer?.IFC.loader.ifcManager.createSubset({
    modelID: this.model.modelID,
    ids: allIds,
    removePrevious: false,
    customID: subsetId
  });

  if (subset) {
    this.subsets.set(subsetId, subset);
    // 初始可见状态可根据需求调整
    // subset.visible = false;
  }
}

3. 补充说明

  • 必须递归遍历空间结构节点,收集所有子构件的expressID,才能创建包含可见几何的子集。
  • 阻止事件冒泡是为了避免点击复选框时触发父级mat-expansion-panel的展开/折叠行为,导致交互冲突。
  • 用Map存储子集引用,确保每次切换时直接操作已存在的子集,无需重复创建,提升性能。

内容的提问来源于stack exchange,提问作者Ludovico Dellavecchia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 16:54:22