第12章-实战案例与最佳实践

第十二章:实战案例与最佳实践

12.1 数字孪生平台

12.1.1 项目概述

数字孪生平台是Astral3D最典型的应用场景,通过构建物理世界的数字化副本,实现实时监控、分析预测和智能决策。

项目需求:

  • 建筑/园区的三维可视化展示
  • 设备状态实时监控
  • 数据图表展示
  • 告警信息推送
  • 历史数据回放

12.1.2 系统架构

┌─────────────────────────────────────────────────────────────────────┐
│                        数字孪生平台架构                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                     前端展示层                               │   │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐        │   │
│  │  │3D场景  │  │数据面板│  │告警中心│  │控制台  │        │   │
│  │  └─────────┘  └─────────┘  └─────────┘  └─────────┘        │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                              │                                      │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                     数据服务层                               │   │
│  │  ┌───────────┐  ┌───────────┐  ┌───────────┐               │   │
│  │  │WebSocket │  │ REST API │  │  数据缓存 │               │   │
│  │  │实时数据   │  │ 历史查询 │  │   Redis   │               │   │
│  │  └───────────┘  └───────────┘  └───────────┘               │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                              │                                      │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                     物联网接入层                             │   │
│  │  ┌─────┐  ┌─────┐  ┌─────┐  ┌─────┐  ┌─────┐             │   │
│  │  │传感器│  │ PLC │  │ BMS │  │ EMS │  │ SCADA│             │   │
│  │  └─────┘  └─────┘  └─────┘  └─────┘  └─────┘             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

12.1.3 核心代码实现

// 数字孪生场景管理器
class DigitalTwinManager {
  private viewer: Viewer;
  private devices: Map<string, DeviceModel> = new Map();
  private wsClient: WebSocket;
  private dataCache: Map<string, DeviceData> = new Map();
  
  constructor(viewer: Viewer) {
    this.viewer = viewer;
    this.initWebSocket();
  }
  
  // 初始化WebSocket连接
  private initWebSocket() {
    this.wsClient = new WebSocket('wss://api.example.com/realtime');
    
    this.wsClient.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.handleRealtimeData(data);
    };
  }
  
  // 加载场景
  async loadScene(sceneUrl: string) {
    const model = await this.viewer.loader.load(sceneUrl);
    this.viewer.scene.add(model);
    
    // 提取设备对象
    model.traverse((obj) => {
      if (obj.userData.deviceId) {
        this.registerDevice(obj.userData.deviceId, obj);
      }
    });
  }
  
  // 注册设备
  registerDevice(deviceId: string, object: THREE.Object3D) {
    const device = new DeviceModel(deviceId, object);
    this.devices.set(deviceId, device);
  }
  
  // 处理实时数据
  private handleRealtimeData(data: { deviceId: string; values: any }) {
    const device = this.devices.get(data.deviceId);
    if (device) {
      device.updateData(data.values);
      this.dataCache.set(data.deviceId, data.values);
      
      // 检查告警
      this.checkAlarms(data.deviceId, data.values);
    }
  }
  
  // 检查告警
  private checkAlarms(deviceId: string, values: any) {
    const device = this.devices.get(deviceId);
    if (!device) return;
    
    const thresholds = device.getThresholds();
    
    for (const [key, value] of Object.entries(values)) {
      const threshold = thresholds[key];
      if (threshold) {
        if (value > threshold.max || value < threshold.min) {
          this.triggerAlarm(deviceId, key, value, threshold);
        }
      }
    }
  }
  
  // 触发告警
  private triggerAlarm(
    deviceId: string, 
    parameter: string, 
    value: number, 
    threshold: any
  ) {
    const device = this.devices.get(deviceId);
    if (device) {
      // 高亮设备
      device.setAlarmState(true);
      
      // 发送告警通知
      this.emit('alarm', {
        deviceId,
        parameter,
        value,
        threshold,
        time: new Date()
      });
    }
  }
  
  // 获取设备数据
  getDeviceData(deviceId: string) {
    return this.dataCache.get(deviceId);
  }
  
  // 聚焦到设备
  focusDevice(deviceId: string) {
    const device = this.devices.get(deviceId);
    if (device) {
      this.viewer.cameraController.focusOn(device.object, 2);
    }
  }
}

// 设备模型类
class DeviceModel {
  id: string;
  object: THREE.Object3D;
  private originalMaterial: THREE.Material;
  private alarmMaterial: THREE.MeshStandardMaterial;
  private data: any = {};
  private inAlarm: boolean = false;
  
  constructor(id: string, object: THREE.Object3D) {
    this.id = id;
    this.object = object;
    
    // 保存原始材质
    if (object instanceof THREE.Mesh) {
      this.originalMaterial = object.material;
    }
    
    // 创建告警材质
    this.alarmMaterial = new THREE.MeshStandardMaterial({
      color: 0xff0000,
      emissive: 0xff0000,
      emissiveIntensity: 0.5
    });
  }
  
  updateData(values: any) {
    this.data = { ...this.data, ...values };
    
    // 更新显示(如温度颜色映射)
    this.updateVisualization();
  }
  
  private updateVisualization() {
    // 根据数据更新可视化
    if (this.data.temperature !== undefined && this.object instanceof THREE.Mesh) {
      const temp = this.data.temperature;
      const color = this.temperatureToColor(temp);
      
      if (!this.inAlarm) {
        (this.object.material as THREE.MeshStandardMaterial).color.setHex(color);
      }
    }
  }
  
  private temperatureToColor(temp: number): number {
    // 温度映射到颜色 (蓝 -> 绿 -> 红)
    const normalized = Math.max(0, Math.min(1, (temp - 20) / 60)); // 20-80度
    
    if (normalized < 0.5) {
      // 蓝到绿
      const t = normalized * 2;
      return new THREE.Color(0, t, 1 - t).getHex();
    } else {
      // 绿到红
      const t = (normalized - 0.5) * 2;
      return new THREE.Color(t, 1 - t, 0).getHex();
    }
  }
  
  setAlarmState(alarm: boolean) {
    this.inAlarm = alarm;
    
    if (this.object instanceof THREE.Mesh) {
      if (alarm) {
        this.object.material = this.alarmMaterial;
        this.startAlarmAnimation();
      } else {
        this.object.material = this.originalMaterial;
        this.stopAlarmAnimation();
      }
    }
  }
  
  private alarmAnimationId: number = 0;
  
  private startAlarmAnimation() {
    const animate = () => {
      const intensity = (Math.sin(Date.now() * 0.01) + 1) * 0.5;
      this.alarmMaterial.emissiveIntensity = intensity;
      this.alarmAnimationId = requestAnimationFrame(animate);
    };
    animate();
  }
  
  private stopAlarmAnimation() {
    cancelAnimationFrame(this.alarmAnimationId);
  }
  
  getThresholds() {
    return this.object.userData.thresholds || {};
  }
}

12.1.4 数据面板组件

<!-- DataPanel.vue -->
<template>
  <div class="data-panel">
    <div class="panel-header">
      <h3>{{ deviceName }}</h3>
      <span :class="['status', statusClass]">{{ statusText }}</span>
    </div>
    
    <div class="metrics">
      <div v-for="metric in metrics" :key="metric.key" class="metric-item">
        <div class="metric-label">{{ metric.label }}</div>
        <div class="metric-value" :style="{ color: metric.color }">
          {{ metric.value }} {{ metric.unit }}
        </div>
        <div class="metric-bar">
          <div 
            class="metric-bar-fill" 
            :style="{ width: metric.percentage + '%', backgroundColor: metric.color }"
          ></div>
        </div>
      </div>
    </div>
    
    <div class="chart-container">
      <div ref="chartRef" class="chart"></div>
    </div>
    
    <div class="actions">
      <button @click="focusDevice">定位设备</button>
      <button @click="showHistory">历史数据</button>
      <button @click="controlDevice">远程控制</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import * as echarts from 'echarts';

const props = defineProps<{
  deviceId: string;
  deviceData: any;
}>();

const emit = defineEmits(['focus', 'showHistory', 'control']);

const chartRef = ref<HTMLElement>();
let chart: echarts.ECharts;

const deviceName = computed(() => props.deviceData?.name || '设备');

const statusClass = computed(() => {
  if (props.deviceData?.alarm) return 'alarm';
  if (props.deviceData?.online) return 'online';
  return 'offline';
});

const statusText = computed(() => {
  if (props.deviceData?.alarm) return '告警';
  if (props.deviceData?.online) return '在线';
  return '离线';
});

const metrics = computed(() => [
  {
    key: 'temperature',
    label: '温度',
    value: props.deviceData?.temperature?.toFixed(1) || '--',
    unit: '°C',
    percentage: Math.min(100, (props.deviceData?.temperature || 0) / 100 * 100),
    color: getTemperatureColor(props.deviceData?.temperature)
  },
  {
    key: 'humidity',
    label: '湿度',
    value: props.deviceData?.humidity?.toFixed(1) || '--',
    unit: '%',
    percentage: props.deviceData?.humidity || 0,
    color: '#3498db'
  },
  {
    key: 'power',
    label: '功率',
    value: props.deviceData?.power?.toFixed(1) || '--',
    unit: 'kW',
    percentage: Math.min(100, (props.deviceData?.power || 0) / 50 * 100),
    color: '#f39c12'
  }
]);

const getTemperatureColor = (temp: number) => {
  if (!temp) return '#ccc';
  if (temp < 30) return '#3498db';
  if (temp < 50) return '#2ecc71';
  if (temp < 70) return '#f39c12';
  return '#e74c3c';
};

onMounted(() => {
  if (chartRef.value) {
    chart = echarts.init(chartRef.value);
    updateChart();
  }
});

watch(() => props.deviceData, () => {
  updateChart();
});

const updateChart = () => {
  if (!chart) return;
  
  chart.setOption({
    tooltip: { trigger: 'axis' },
    xAxis: {
      type: 'time',
      axisLine: { lineStyle: { color: '#444' } }
    },
    yAxis: {
      type: 'value',
      axisLine: { lineStyle: { color: '#444' } }
    },
    series: [{
      name: '温度',
      type: 'line',
      smooth: true,
      data: props.deviceData?.history || []
    }]
  });
};

const focusDevice = () => emit('focus', props.deviceId);
const showHistory = () => emit('showHistory', props.deviceId);
const controlDevice = () => emit('control', props.deviceId);
</script>

12.2 BIM可视化平台

12.2.1 项目需求

  • 加载和展示BIM模型(Revit/IFC)
  • 构件属性查询
  • 楼层过滤和剖切
  • 测量和标注
  • 施工进度模拟

12.2.2 BIM平台实现

// BIM平台管理器
class BIMPlatform {
  private viewer: Viewer;
  private ifcLoader: IFCLoader;
  private model: THREE.Object3D;
  private elements: Map<number, BIMElement> = new Map();
  private floors: Map<string, THREE.Object3D[]> = new Map();
  
  constructor(viewer: Viewer) {
    this.viewer = viewer;
    this.ifcLoader = new IFCLoader();
  }
  
  // 加载IFC模型
  async loadIFC(url: string) {
    this.model = await this.ifcLoader.load(url, {
      lightweight: true,
      extractProperties: true,
      groupByStorey: true
    });
    
    this.viewer.scene.add(this.model);
    
    // 提取元素和楼层
    this.extractElements();
    this.extractFloors();
    
    // 聚焦到模型
    this.viewer.cameraController.focusOn(this.model);
  }
  
  // 提取BIM元素
  private extractElements() {
    this.model.traverse((obj) => {
      if (obj.userData.expressId) {
        const element = new BIMElement(
          obj.userData.expressId,
          obj,
          this.ifcLoader.getProperties(obj.userData.expressId)
        );
        this.elements.set(obj.userData.expressId, element);
      }
    });
  }
  
  // 提取楼层
  private extractFloors() {
    this.model.traverse((obj) => {
      if (obj.userData.storey) {
        const floorName = obj.userData.storey;
        if (!this.floors.has(floorName)) {
          this.floors.set(floorName, []);
        }
        this.floors.get(floorName)!.push(obj);
      }
    });
  }
  
  // 按类型过滤
  filterByType(type: string, visible: boolean) {
    this.elements.forEach(element => {
      if (element.type === type) {
        element.setVisible(visible);
      }
    });
  }
  
  // 按楼层过滤
  filterByFloor(floorName: string, visible: boolean) {
    const floorElements = this.floors.get(floorName);
    if (floorElements) {
      floorElements.forEach(obj => {
        obj.visible = visible;
      });
    }
  }
  
  // 显示单层
  showSingleFloor(floorName: string) {
    this.floors.forEach((elements, name) => {
      const visible = name === floorName;
      elements.forEach(obj => {
        obj.visible = visible;
      });
    });
  }
  
  // 剖切
  private clippingPlane: THREE.Plane;
  
  setClippingPlane(height: number) {
    if (!this.clippingPlane) {
      this.clippingPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), height);
      this.viewer.renderer.clippingPlanes = [this.clippingPlane];
      this.viewer.renderer.localClippingEnabled = true;
    } else {
      this.clippingPlane.constant = height;
    }
  }
  
  clearClippingPlane() {
    this.viewer.renderer.clippingPlanes = [];
  }
  
  // 获取元素属性
  getElementProperties(expressId: number) {
    const element = this.elements.get(expressId);
    return element?.properties;
  }
  
  // 高亮元素
  highlightElement(expressId: number) {
    const element = this.elements.get(expressId);
    if (element) {
      element.highlight();
    }
  }
  
  // 清除高亮
  clearHighlight() {
    this.elements.forEach(element => element.unhighlight());
  }
  
  // 施工模拟
  async simulateConstruction(schedule: ConstructionSchedule[]) {
    // 隐藏所有元素
    this.elements.forEach(element => element.setVisible(false));
    
    // 按时间顺序显示
    for (const item of schedule) {
      await this.delay(item.duration);
      
      item.elementIds.forEach(id => {
        const element = this.elements.get(id);
        if (element) {
          element.setVisible(true);
          element.animateIn();
        }
      });
    }
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// BIM元素类
class BIMElement {
  expressId: number;
  object: THREE.Object3D;
  properties: any;
  type: string;
  
  private originalMaterial: THREE.Material;
  private highlightMaterial: THREE.MeshStandardMaterial;
  
  constructor(expressId: number, object: THREE.Object3D, properties: any) {
    this.expressId = expressId;
    this.object = object;
    this.properties = properties;
    this.type = properties?.type || 'Unknown';
    
    if (object instanceof THREE.Mesh) {
      this.originalMaterial = object.material;
    }
    
    this.highlightMaterial = new THREE.MeshStandardMaterial({
      color: 0x00ff00,
      transparent: true,
      opacity: 0.8
    });
  }
  
  setVisible(visible: boolean) {
    this.object.visible = visible;
  }
  
  highlight() {
    if (this.object instanceof THREE.Mesh) {
      this.object.material = this.highlightMaterial;
    }
  }
  
  unhighlight() {
    if (this.object instanceof THREE.Mesh) {
      this.object.material = this.originalMaterial;
    }
  }
  
  animateIn() {
    // 淡入动画
    this.object.scale.set(0.01, 0.01, 0.01);
    
    const animate = () => {
      if (this.object.scale.x < 1) {
        this.object.scale.multiplyScalar(1.1);
        requestAnimationFrame(animate);
      } else {
        this.object.scale.set(1, 1, 1);
      }
    };
    animate();
  }
}

interface ConstructionSchedule {
  phase: string;
  elementIds: number[];
  duration: number;
}

12.3 工业监控系统

12.3.1 项目需求

  • 工厂车间3D可视化
  • 设备运行状态监控
  • 生产数据实时展示
  • 告警和异常处理
  • 远程控制功能

12.3.2 工业监控实现

// 工业监控场景
class IndustrialMonitorScene {
  private viewer: Viewer;
  private machines: Map<string, MachineController> = new Map();
  private productionLine: ProductionLine;
  private dataSource: IndustrialDataSource;
  
  constructor(viewer: Viewer) {
    this.viewer = viewer;
    this.dataSource = new IndustrialDataSource();
  }
  
  async initialize(sceneUrl: string) {
    // 加载场景
    const model = await this.viewer.loader.load(sceneUrl);
    this.viewer.scene.add(model);
    
    // 初始化机器控制器
    model.traverse((obj) => {
      if (obj.userData.machineId) {
        const machine = new MachineController(obj);
        this.machines.set(obj.userData.machineId, machine);
      }
    });
    
    // 初始化生产线
    this.productionLine = new ProductionLine(this.viewer.scene);
    
    // 连接数据源
    this.connectDataSource();
  }
  
  private connectDataSource() {
    this.dataSource.connect();
    
    this.dataSource.on('machineStatus', (data) => {
      const machine = this.machines.get(data.machineId);
      if (machine) {
        machine.updateStatus(data);
      }
    });
    
    this.dataSource.on('productionData', (data) => {
      this.productionLine.updateProduction(data);
    });
    
    this.dataSource.on('alarm', (data) => {
      this.handleAlarm(data);
    });
  }
  
  private handleAlarm(data: AlarmData) {
    const machine = this.machines.get(data.machineId);
    if (machine) {
      machine.setAlarmState(true, data.level);
      
      // 显示告警弹窗
      this.showAlarmPopup(data);
    }
  }
  
  private showAlarmPopup(data: AlarmData) {
    // 在3D场景中显示告警标记
    const machine = this.machines.get(data.machineId);
    if (machine) {
      const marker = this.createAlarmMarker(data);
      marker.position.copy(machine.object.position);
      marker.position.y += 2;
      this.viewer.scene.add(marker);
    }
  }
  
  private createAlarmMarker(data: AlarmData): THREE.Object3D {
    const group = new THREE.Group();
    
    // 告警图标
    const geometry = new THREE.SphereGeometry(0.3);
    const material = new THREE.MeshBasicMaterial({
      color: data.level === 'critical' ? 0xff0000 : 0xffff00
    });
    const sphere = new THREE.Mesh(geometry, material);
    group.add(sphere);
    
    // 告警文字
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d')!;
    canvas.width = 256;
    canvas.height = 64;
    ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
    ctx.fillRect(0, 0, 256, 64);
    ctx.fillStyle = 'white';
    ctx.font = '24px Arial';
    ctx.fillText(data.message, 10, 40);
    
    const texture = new THREE.CanvasTexture(canvas);
    const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
    const sprite = new THREE.Sprite(spriteMaterial);
    sprite.scale.set(4, 1, 1);
    sprite.position.y = 0.8;
    group.add(sprite);
    
    return group;
  }
}

// 机器控制器
class MachineController {
  object: THREE.Object3D;
  private status: MachineStatus = 'idle';
  private animationMixer: THREE.AnimationMixer;
  private runningAction: THREE.AnimationAction;
  
  constructor(object: THREE.Object3D) {
    this.object = object;
    
    // 如果有动画,初始化混合器
    if (object.animations && object.animations.length > 0) {
      this.animationMixer = new THREE.AnimationMixer(object);
      this.runningAction = this.animationMixer.clipAction(object.animations[0]);
    }
  }
  
  updateStatus(data: MachineStatusData) {
    const newStatus = data.status as MachineStatus;
    
    if (newStatus !== this.status) {
      this.status = newStatus;
      this.updateVisualization();
    }
    
    // 更新运行参数显示
    this.updateParameters(data.parameters);
  }
  
  private updateVisualization() {
    switch (this.status) {
      case 'running':
        this.setColor(0x00ff00);
        this.runningAction?.play();
        break;
      case 'idle':
        this.setColor(0x888888);
        this.runningAction?.stop();
        break;
      case 'warning':
        this.setColor(0xffff00);
        break;
      case 'error':
        this.setColor(0xff0000);
        this.runningAction?.stop();
        break;
    }
  }
  
  private setColor(color: number) {
    this.object.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        (child.material as THREE.MeshStandardMaterial).color.setHex(color);
      }
    });
  }
  
  private updateParameters(params: any) {
    // 更新参数显示
  }
  
  setAlarmState(alarm: boolean, level: string) {
    if (alarm) {
      this.startAlarmAnimation(level);
    } else {
      this.stopAlarmAnimation();
    }
  }
  
  private alarmInterval: number;
  
  private startAlarmAnimation(level: string) {
    const color = level === 'critical' ? 0xff0000 : 0xffff00;
    let on = true;
    
    this.alarmInterval = setInterval(() => {
      this.setColor(on ? color : 0x000000);
      on = !on;
    }, 500) as unknown as number;
  }
  
  private stopAlarmAnimation() {
    clearInterval(this.alarmInterval);
    this.updateVisualization();
  }
  
  update(delta: number) {
    this.animationMixer?.update(delta);
  }
}

type MachineStatus = 'idle' | 'running' | 'warning' | 'error';

interface MachineStatusData {
  machineId: string;
  status: string;
  parameters: any;
}

interface AlarmData {
  machineId: string;
  level: 'warning' | 'critical';
  message: string;
  time: Date;
}

12.4 最佳实践

12.4.1 代码组织

src/
├── core/                    # 核心模块
│   ├── viewer/              # Viewer相关
│   ├── scene/               # 场景管理
│   └── utils/               # 工具函数
├── features/                # 功能模块
│   ├── selection/           # 选择功能
│   ├── measurement/         # 测量功能
│   ├── annotation/          # 标注功能
│   └── export/              # 导出功能
├── components/              # UI组件
│   ├── panels/              # 面板组件
│   ├── toolbar/             # 工具栏组件
│   └── dialogs/             # 对话框组件
├── stores/                  # 状态管理
│   ├── scene.ts             # 场景状态
│   ├── selection.ts         # 选择状态
│   └── settings.ts          # 设置状态
├── services/                # 服务层
│   ├── api.ts               # API服务
│   ├── storage.ts           # 存储服务
│   └── realtime.ts          # 实时数据服务
└── types/                   # 类型定义
    └── index.ts

12.4.2 性能优化清单

// 性能优化检查清单
const performanceChecklist = {
  // 渲染优化
  rendering: [
    '启用视锥裁剪 (frustumCulled = true)',
    '使用LOD (Level of Detail)',
    '合并静态几何体',
    '使用实例化渲染',
    '控制Draw Call数量',
    '优化阴影贴图尺寸'
  ],
  
  // 加载优化
  loading: [
    '模型压缩 (Draco/KTX2)',
    '分包加载',
    '按需加载',
    '使用Web Worker加载',
    '实现加载进度显示'
  ],
  
  // 内存优化
  memory: [
    '及时释放不用的资源',
    '使用纹理压缩',
    '限制纹理尺寸',
    '使用纹理缓存',
    '避免内存泄漏'
  ],
  
  // 代码优化
  code: [
    '避免每帧创建对象',
    '使用对象池',
    '减少DOM操作',
    '使用requestAnimationFrame',
    '防抖和节流'
  ]
};

12.4.3 错误处理

// 全局错误处理
class ErrorHandler {
  private static instance: ErrorHandler;
  
  static getInstance(): ErrorHandler {
    if (!this.instance) {
      this.instance = new ErrorHandler();
    }
    return this.instance;
  }
  
  handle(error: Error, context?: string) {
    console.error(`[${context || 'Unknown'}]`, error);
    
    // 发送错误报告
    this.reportError(error, context);
    
    // 显示用户友好的错误信息
    this.showErrorMessage(error, context);
  }
  
  private reportError(error: Error, context?: string) {
    // 发送到错误追踪服务
    // fetch('/api/errors', { ... })
  }
  
  private showErrorMessage(error: Error, context?: string) {
    // 显示错误提示
    notification.error({
      title: '发生错误',
      content: this.getReadableMessage(error, context)
    });
  }
  
  private getReadableMessage(error: Error, context?: string): string {
    const messages: Record<string, string> = {
      'NetworkError': '网络连接失败,请检查网络设置',
      'LoadError': '模型加载失败,请检查文件格式',
      'WebGLError': '3D渲染失败,请更新显卡驱动',
      'default': '操作失败,请重试'
    };
    
    const type = error.name || 'default';
    return messages[type] || messages['default'];
  }
}

// 使用
try {
  await loadModel(url);
} catch (error) {
  ErrorHandler.getInstance().handle(error, 'ModelLoading');
}

12.4.4 测试策略

// 单元测试示例
import { describe, it, expect } from 'vitest';

describe('Vector3Utils', () => {
  it('should calculate distance correctly', () => {
    const p1 = { x: 0, y: 0, z: 0 };
    const p2 = { x: 3, y: 4, z: 0 };
    
    const distance = Vector3Utils.distance(p1, p2);
    
    expect(distance).toBe(5);
  });
});

// 集成测试示例
describe('Viewer Integration', () => {
  let viewer: Viewer;
  
  beforeEach(() => {
    const container = document.createElement('div');
    document.body.appendChild(container);
    viewer = new Viewer({ container });
  });
  
  afterEach(() => {
    viewer.dispose();
  });
  
  it('should load model successfully', async () => {
    const model = await viewer.loader.load('test-model.glb');
    
    expect(model).toBeDefined();
    expect(viewer.scene.children).toContain(model);
  });
});

// E2E测试示例
describe('Editor E2E', () => {
  it('should select object on click', async () => {
    await page.goto('http://localhost:3000');
    await page.waitForSelector('#viewer');
    
    // 点击场景中的对象
    await page.click('#viewer', { position: { x: 400, y: 300 } });
    
    // 验证选择状态
    const selectedCount = await page.evaluate(() => {
      return window.viewer.selection.getSelected().length;
    });
    
    expect(selectedCount).toBeGreaterThan(0);
  });
});

12.5 本章小结

本章通过三个实战案例介绍了Astral3D的实际应用:

  1. 数字孪生平台:实时数据监控、告警处理、数据可视化
  2. BIM可视化平台:模型加载、属性查询、楼层过滤、施工模拟
  3. 工业监控系统:设备监控、状态可视化、告警处理

同时介绍了开发最佳实践:

  • 代码组织结构
  • 性能优化清单
  • 错误处理机制
  • 测试策略

通过本章的学习,读者应该能够将所学知识应用到实际项目中,构建专业的3D可视化应用。


教程总结

本教程全面介绍了Astral3D的学习、使用和二次开发:

学习教程(第1-3章)

  • Astral3D概述和特性
  • 环境搭建和快速开始
  • 核心架构和技术栈

使用教程(第4-7章)

  • 场景编辑与模型管理
  • BIM轻量化与CAD解析
  • 粒子系统与天气系统
  • 动画编辑器

二次开发教程(第8-12章)

  • 插件系统开发
  • 脚本运行时开发
  • 二次开发入门
  • 二次开发进阶
  • 实战案例与最佳实践

希望本教程能够帮助读者快速掌握Astral3D,并在数字孪生、建筑可视化、工业监控等领域构建出色的3D应用。

相关资源


posted @ 2026-01-10 13:17  我才是银古  阅读(17)  评论(0)    收藏  举报