Kaynağa Gözat

plt云图渲染更换three.js设计结构;完成文件初始渲染;完成域相关功能开发;云图部分功能开发

lichunyang 2 ay önce
ebeveyn
işleme
643d89f203
41 değiştirilmiş dosya ile 3264 ekleme ve 451 silme
  1. 117 0
      src/components/ThreeScene/ThreeScene.vue
  2. 195 63
      src/components/cloudChart/dialog/CloudMapDialog.vue
  3. 86 24
      src/components/cloudChart/dialog/ContourDialog.vue
  4. 230 171
      src/components/cloudChart/dialog/DomainDialog.vue
  5. 306 103
      src/components/cloudChart/index.vue
  6. 332 0
      src/composables/useThree.js
  7. 0 0
      src/composables/useThreeData.js
  8. 98 0
      src/store/modules/pltData.js
  9. 0 32
      src/store/modules/pltData.ts
  10. 73 0
      src/utils/three/controls/orbitControls.js
  11. 29 0
      src/utils/three/core/animationLoop.js
  12. 129 0
      src/utils/three/core/cameraManager.js
  13. 18 0
      src/utils/three/core/rendererManager.js
  14. 59 0
      src/utils/three/core/sceneManager.js
  15. 72 0
      src/utils/three/dataHandlers/BaseDataHandler.js
  16. 0 0
      src/utils/three/dataHandlers/EdgeMeshHandler.js
  17. 419 0
      src/utils/three/dataHandlers/PltHandler.js
  18. 0 0
      src/utils/three/dataHandlers/PointCloudHandler.js
  19. 20 0
      src/utils/three/dataHandlers/TopologyHandler.js
  20. 178 0
      src/utils/three/gui/guiManager.js
  21. 146 0
      src/utils/three/helpers/axesHelper.js
  22. 14 0
      src/utils/three/helpers/gridHelper.js
  23. 50 0
      src/utils/three/helpers/index.js
  24. 19 0
      src/utils/three/helpers/statsHelper.js
  25. 0 0
      src/utils/three/interactivity/EventDispatcher.js
  26. 17 0
      src/utils/three/interactivity/SelectionManager.js
  27. 17 0
      src/utils/three/lights/ambientLight.js
  28. 30 0
      src/utils/three/lights/directionalLight.js
  29. 48 0
      src/utils/three/lights/lightManager.js
  30. 17 0
      src/utils/three/lights/pointLight.js
  31. 24 0
      src/utils/three/lights/spotLight.js
  32. 51 0
      src/utils/three/materials/ContourMaterial.js
  33. 99 0
      src/utils/three/materials/materialManager.js
  34. 0 0
      src/utils/three/objects/fileHandlers/BdfHandler.js
  35. 0 0
      src/utils/three/objects/fileHandlers/CgnsHandler.js
  36. 0 0
      src/utils/three/objects/fileHandlers/PltHandler.js
  37. 186 0
      src/utils/three/objects/fileHandlers/XyzHandler.js
  38. 58 0
      src/utils/three/objects/objectManager.js
  39. 1 1
      src/views/threejsView/utils/renderers/CgnsJSONRenderer.js
  40. 125 56
      src/views/threejsView/utils/renderers/PltDataRenderer.js
  41. 1 1
      tsconfig.json

+ 117 - 0
src/components/ThreeScene/ThreeScene.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="three-container">
+    <canvas ref="canvasRef"></canvas>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from "vue"
+import { useThree } from "@/composables/useThree"
+import { defineExpose } from "vue"
+import * as THREE from "three"
+
+const props = defineProps({
+  sceneConfig: {
+    type: Object,
+    default: () => ({
+      backgroundColor: 0xffffff, // 白色背景
+      fog: false,
+      fogOptions: {}
+    })
+  },
+  cameraConfig: {
+    type: Object,
+    default: () => ({
+      type: "perspective",
+      position: { x: 0, y: 0, z: 1 },
+      lookAt: { x: 0, y: 0, z: 0 },
+      fov: 45,
+      near: 0.01,
+      far: 1000
+    })
+  },
+  controlsConfig: {
+    type: Object,
+    default: () => ({
+      enableDamping: true,
+      dampingFactor: 0.25
+    })
+  },
+  showHelpers: {
+    type: Boolean,
+    default: false
+  },
+  showGUI: {
+    type: Boolean,
+    default: true
+  },
+  apiData: {
+    type: Object,
+    default: () => null
+  }
+})
+
+const canvasRef = ref(null)
+const {
+  init,
+  dispose,
+  renderer,
+  camera,
+  handleResize: threeResize,
+  updateData,
+  loadFile,
+  showZone,
+  hideZone,
+  toggleZone,
+  showAll,
+  hideAll,
+  getZoneVisibility,
+  setZoneVisibility,
+  updateColorMapping
+} = useThree(canvasRef, props)
+
+onMounted(() => {
+  nextTick(() => { // 等待DOM更新
+    init();
+    // 首次手动触发resize
+    handleResize(); 
+    window.addEventListener('resize', handleResize);
+  });
+});
+
+const handleResize = () => {
+  threeResize() // 调用useThree提供的统一方法
+}
+
+onUnmounted(() => {
+  dispose()
+  window.removeEventListener("resize", handleResize)
+})
+
+defineExpose({ 
+  loadFile, 
+  updateData,
+  showZone,
+  hideZone,
+  toggleZone,
+  showAll,
+  hideAll,
+  getZoneVisibility,
+  setZoneVisibility,
+  updateColorMapping
+   })
+</script>
+
+<style>
+.three-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden; /* 防止滚动条影响尺寸 */
+}
+canvas {
+  display: block;
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 195 - 63
src/components/cloudChart/dialog/CloudMapDialog.vue

@@ -17,17 +17,20 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="名称:" :label-width="formLabelWidth1">
-              <el-input v-model="ytvalue.name"></el-input>
+              <el-input v-model="cloudConfig.name"></el-input>
             </el-form-item>
             <el-form-item label="类型:" :label-width="formLabelWidth1">
-              <el-input v-model="ytvalue.type"></el-input>
+              <el-input v-model="cloudConfig.type" disabled></el-input>
             </el-form-item>
             <el-form-item label="标量名:" :label-width="formLabelWidth1">
-              <el-select v-model="ytvalue.scalarname">
-                <el-option 
-                  v-for="item in scalarnameoptions" 
-                  :key="item.value" 
-                  :label="item.label" 
+              <el-select
+                v-model="cloudConfig.scalarname"
+                @change="handleScalarChange"
+              >
+                <el-option
+                  v-for="item in availableScalars"
+                  :key="item.value"
+                  :label="item.label"
                   :value="item.value"
                 />
               </el-select>
@@ -35,10 +38,16 @@
             <el-form-item label="" :label-width="formLabelWidth1">
               <el-row>
                 <el-col :span="12">
-                  <el-checkbox label="极值" v-model="ytvalue.jzcheck"></el-checkbox>
+                  <el-checkbox
+                    label="极值"
+                    v-model="cloudConfig.jzcheck"
+                  ></el-checkbox>
                 </el-col>
                 <el-col :span="12">
-                  <el-checkbox label="单元值离散到点" v-model="ytvalue.dycheck"></el-checkbox>
+                  <el-checkbox
+                    label="单元值离散到点"
+                    v-model="cloudConfig.dycheck"
+                  ></el-checkbox>
                 </el-col>
               </el-row>
             </el-form-item>
@@ -51,12 +60,15 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="名称:" :label-width="formLabelWidth1">
-              <el-input v-model="ytvalue.name2"></el-input>
+              <el-input v-model="cloudConfig.name2"></el-input>
             </el-form-item>
             <el-form-item label=" " :label-width="formLabelWidth1">
               <el-row>
                 <el-col :span="24">
-                  <el-checkbox label="平滑云图" v-model="ytvalue.check3"></el-checkbox>
+                  <el-checkbox
+                    label="平滑云图"
+                    v-model="cloudConfig.check3"
+                  ></el-checkbox>
                 </el-col>
               </el-row>
             </el-form-item>
@@ -69,20 +81,20 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="数据范围类型:" :label-width="formLabelWidth1">
-              <el-select v-model="ytvalue.dataAreaType">
-                <el-option 
-                  v-for="item in dataAreaTypeoptions" 
-                  :key="item.value" 
-                  :label="item.label" 
+              <el-select v-model="cloudConfig.dataAreaType">
+                <el-option
+                  v-for="item in dataAreaTypeoptions"
+                  :key="item.value"
+                  :label="item.label"
                   :value="item.value"
                 />
               </el-select>
             </el-form-item>
             <el-form-item label="最大值:" :label-width="formLabelWidth1">
-              <el-input v-model="ytvalue.max"></el-input>
+              <el-input v-model="cloudConfig.max"></el-input>
             </el-form-item>
             <el-form-item label="最小值:" :label-width="formLabelWidth1">
-              <el-input v-model="ytvalue.min"></el-input>
+              <el-input v-model="cloudConfig.min"></el-input>
             </el-form-item>
           </el-form>
         </el-collapse-item>
@@ -93,15 +105,23 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="最大值:" :label-width="formLabelWidth1">
-              <el-row style="width: 100%;">
-                <el-col :span="22"><el-input v-model="ytvalue.maxcv"></el-input></el-col>
-                <el-col :span="2"><el-color-picker v-model="color1" @change="updateMaxValue"/></el-col>
+              <el-row style="width: 100%">
+                <el-col :span="22"
+                  ><el-input v-model="cloudConfig.maxcv"></el-input
+                ></el-col>
+                <el-col :span="2"
+                  ><el-color-picker v-model="color1" @change="updateMaxValue"
+                /></el-col>
               </el-row>
             </el-form-item>
             <el-form-item label="最小值:" :label-width="formLabelWidth1">
-              <el-row style="width: 100%;">
-                <el-col :span="22"><el-input v-model="ytvalue.mincv"></el-input></el-col>
-                <el-col :span="2"><el-color-picker v-model="color2" @change="updateMinValue"/></el-col>
+              <el-row style="width: 100%">
+                <el-col :span="22"
+                  ><el-input v-model="cloudConfig.mincv"></el-input
+                ></el-col>
+                <el-col :span="2"
+                  ><el-color-picker v-model="color2" @change="updateMinValue"
+                /></el-col>
               </el-row>
             </el-form-item>
           </el-form>
@@ -112,91 +132,203 @@
 </template>
 
 <script setup>
-import { ref, defineProps, defineEmits } from 'vue'
-import SubDialog from './SubDialog.vue'
+import { ref, computed, watch, onMounted } from "vue"
+import SubDialog from "./SubDialog.vue"
+import * as THREE from "three"
 
 const props = defineProps({
   modelValue: Boolean,
   initialData: {
     type: Object,
     default: () => ({})
-  }
+  },
+  threeSceneRef: Object,
+  pltData: Object
 })
 
-const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
+const emit = defineEmits(["update:modelValue", "confirm", "cancel"])
 
 // 表单标签宽度
-const formLabelWidth1 = '120px'
+const formLabelWidth1 = "120px"
 
 // 折叠面板当前激活项
-const activeNames = ref(['1', '2', '3', '4'])
-
-// 表单数据
-let ytvalue = ref({
-  name:'00',
-  type:'00',
-  scalarname:'00',
-  jzcheck:'1',
-  dycheck:'1',
-  name2:'00',
-  check3:'1',
-  dataAreaType:'当前时间步',
-  max:'0.00',
-  min:'0.00',
-  maxcv:'34, 103, 177, 1',
-  mincv:'232, 0, 0, 1'
-})
+const activeNames = ref(["1", "2", "3", "4"])
 
 // 颜色选择器
-let color1 = ref('#2267B1')
-let color2 = ref('#E80000')
+const color1 = ref("#2267B1")
+const color2 = ref("#E80000")
 
-// 下拉选项
-let scalarnameoptions = ref([
-  {label:'00',value:'00'}
+// 可用标量选项
+const availableScalars = ref([
+  { label: "压力系数", value: "CoefPressure" },
+  { label: "马赫数", value: "Mach" },
+  { label: "X方向速度", value: "VelocityX" },
+  { label: "Y方向速度", value: "VelocityY" },
+  { label: "Z方向速度", value: "VelocityZ" }
 ])
 
-let dataAreaTypeoptions = ref([
-  { label:'当前时间步', value: '当前时间步'},
-  { label:'所有时间步', value: '所有时间步'},
-  { label:'固定值', value: '固定值'}
+// 数据范围类型选项
+const dataAreaTypeoptions = ref([
+  { label: "当前时间步", value: "当前时间步" },
+  { label: "所有时间步", value: "所有时间步" },
+  { label: "固定值", value: "固定值" }
 ])
 
+// 表单数据
+const cloudConfig = ref({
+  name: "",
+  type: "point scalar",
+  scalarname: "CoefPressure",
+  jzcheck: false,
+  dycheck: false,
+  name2: "10",
+  check3: true,
+  dataAreaType: "当前时间步",
+  max: "0.00",
+  min: "0.00",
+  maxcv: "34, 103, 177, 1",
+  mincv: "232, 0, 0, 1"
+})
+
+// 计算当前可用的标量字段
+const availableScalarFields = computed(() => {
+  if (!props.pltData?.zones?.length) return []
+
+  // 从第一个zone中获取所有可用的变量
+  const firstZone = props.pltData.zones[0]
+  return Object.keys(firstZone.variables || {})
+})
+
+// 过滤可用的标量选项
+const filteredScalarOptions = computed(() => {
+  return availableScalars.value.filter((item) =>
+    availableScalarFields.value.includes(item.value)
+  )
+})
+
+// 初始化时设置默认标量
+onMounted(() => {
+  if (filteredScalarOptions.value.length > 0) {
+    cloudConfig.value.scalarname = filteredScalarOptions.value[0].value
+    updateDataRange()
+  }
+})
+
+// 处理标量变化
+const handleScalarChange = (selectedScalar) => {
+  updateDataRange()
+  updateCloudMap()
+}
+
+// 更新数据范围
+const updateDataRange = () => {
+  if (!props.pltData?.zones || !cloudConfig.value.scalarname) return
+
+  let min = Infinity
+  let max = -Infinity
+
+  // 遍历所有zone找到最大最小值
+  props.pltData.zones.forEach((zone) => {
+    const scalarData = zone.variables?.[cloudConfig.value.scalarname]
+    if (!scalarData) return
+
+    const zoneMin = Math.min(...scalarData)
+    const zoneMax = Math.max(...scalarData)
+
+    min = Math.min(min, zoneMin)
+    max = Math.max(max, zoneMax)
+  })
+
+  // 更新表单中的范围值
+  cloudConfig.value.min = min.toFixed(4)
+  cloudConfig.value.max = max.toFixed(4)
+}
+
+// 更新云图显示
+const updateCloudMap = () => {
+  if (!props.threeSceneRef || !cloudConfig.value.scalarname) return
+  
+  // 获取当前选择的标量名和配置
+  const scalarName = cloudConfig.value.scalarname
+  const min = parseFloat(cloudConfig.value.min)
+  const max = parseFloat(cloudConfig.value.max)
+  const colorMap = "rainbow"
+  const showExtremes = cloudConfig.value.jzcheck
+
+  // 调用PltHandler的更新方法
+  props.threeSceneRef.updateColorMapping(scalarName, {
+    colorMap,
+    min,
+    max,
+    showExtremes
+  })
+}
+
 // 更新最大值颜色
 const updateMaxValue = () => {
-  ytvalue.value.maxcv = hexToRgba(color1.value)
+  cloudConfig.value.maxcv = hexToRgba(color1.value)
+  updateCloudMap()
 }
+
+// 更新最小值颜色
 const updateMinValue = () => {
-  ytvalue.value.mincv = hexToRgba(color2.value)
+  cloudConfig.value.mincv = hexToRgba(color2.value)
+  updateCloudMap()
+}
+
+// 16进制颜色转RGBA字符串
+const hexToRgba = (hex) => {
+  const r = parseInt(hex.slice(1, 3), 16)
+  const g = parseInt(hex.slice(3, 5), 16)
+  const b = parseInt(hex.slice(5, 7), 16)
+  return `${r}, ${g}, ${b}, 1`
 }
 
 // 确认操作
 const handleConfirm = () => {
-  emit('confirm', ytvalue.value)
-  emit('update:modelValue', false)
+  emit("confirm", cloudConfig.value)
+  emit("update:modelValue", false)
 }
 
 // 取消操作
 const handleCancel = () => {
-  emit('cancel')
-  emit('update:modelValue', false)
+  emit("cancel")
+  emit("update:modelValue", false)
 }
+
+// 监听pltData变化
+watch(
+  () => props.pltData,
+  (newData) => {
+    if (newData) {
+      updateDataRange()
+      updateCloudMap()
+    }
+  },
+  { deep: true }
+)
 </script>
 
 <style scoped>
+/* 原有样式保持不变 */
 .collapse-title {
   font-weight: bold;
   font-size: 14px;
 }
 
-/* 调整折叠面板内容间距 */
 .el-collapse-item__content {
   padding-bottom: 10px;
 }
 
-/* 调整表单项间距 */
 .el-form-item {
   margin-bottom: 16px;
 }
+</style>
 
+<style>
+.extreme-label {
+    transform: translate(-50%, 0);
+    white-space: nowrap;
+    pointer-events: none;
+}
 </style>

+ 86 - 24
src/components/cloudChart/dialog/ContourDialog.vue

@@ -17,15 +17,15 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="名称:" :label-width="formLabelWidth1">
-              <el-input v-model="dzxvalue.name"></el-input>
+              <el-input v-model="contourValue.name"></el-input>
             </el-form-item>
             <el-form-item label="类型:" :label-width="formLabelWidth1">
-              <el-input v-model="dzxvalue.type"></el-input>
+              <el-input v-model="contourValue.type"></el-input>
             </el-form-item>
             <el-form-item label="标量名:" :label-width="formLabelWidth1">
-              <el-select v-model="dzxvalue.scalarname2" style="width: 100%">
+              <el-select v-model="contourValue.scalarname2" style="width: 100%">
                 <el-option
-                  v-for="item in scalarname2options" 
+                  v-for="item in scalarname2" 
                   :key="item.value" 
                   :label="item.label" 
                   :value="item.value"
@@ -41,7 +41,7 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="层级:" :label-width="formLabelWidth1">
-              <el-input v-model="dzxvalue.cengji"></el-input>
+              <el-input v-model="contourValue.cengji"></el-input>
             </el-form-item>
           </el-form>
         </el-collapse-item>
@@ -52,10 +52,10 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="最大值:" :label-width="formLabelWidth1">
-              <el-input v-model="dzxvalue.max"></el-input>
+              <el-input v-model="contourValue.max"></el-input>
             </el-form-item>
             <el-form-item label="最小值:" :label-width="formLabelWidth1">
-              <el-input v-model="dzxvalue.min"></el-input>
+              <el-input v-model="contourValue.min"></el-input>
             </el-form-item>
           </el-form>
         </el-collapse-item>
@@ -70,11 +70,16 @@ import SubDialog from './SubDialog.vue'
 
 const props = defineProps({
   modelValue: Boolean,
+  activeZone: String,
+  variableOptions: {
+    type: Array,
+    default: () => []
+  },
   initialData: {
     type: Object,
-    default: () => ({})
+    default: () => ({ ranges: {} })
   }
-})
+});
 
 const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
 
@@ -85,26 +90,83 @@ let formLabelWidth1 = ref(140)
 const activeNames2 = ref(['1', '2', '3'])
 
 // 表单数据
-let dzxvalue = ref({
-  name:'00',
-  type:'00',
-  scalarname2:'00',
-  cengji:'00',
-  max:'0.00',
-  min:'0.00'
+const contourValue = ref({
+  name: 'contour_1',
+  type: 'uniform',
+  scalarname2: props.variableOptions[0]?.value || '',
+  cengji: 10,
+  max: props.initialData?.ranges[props.variableOptions[0]?.value]?.max.toFixed(2) || '1.00',
+  min: props.initialData?.ranges[props.variableOptions[0]?.value]?.min.toFixed(2) || '0.00',
+  width: 0.02,
+  color: '#000000',
+  autoRange: true
+});
+
+const scalarname2 = ref([])
+
+const availableVariables = computed(() => {
+  return props.variableOptions.map(item => ({
+    label: item,
+    value: item
+  }))
 })
 
-// 下拉选项
-let scalarname2options = ref([
-  {label:'00',value:'00'}
-])
+const contourSpacing = computed(() => {
+  const levelCount = parseInt(contourValue.value.cengji) || 10
+  return (parseFloat(contourValue.value.max) - parseFloat(contourValue.value.min)) / levelCount
+})
 
-// 确认操作
-const handleConfirm = () => {
-  emit('confirm', dzxvalue.value)
-  emit('update:modelValue', false)
+// 初始化变量选项
+// 自动更新范围
+watch(() => contourValue.value.scalarname2, (newVar) => {
+  if (contourValue.value.autoRange && props.initialData.ranges?.[newVar]) {
+    contourValue.value.max = props.initialData.ranges[newVar].max.toFixed(2)
+    contourValue.value.min = props.initialData.ranges[newVar].min.toFixed(2)
+  }
+})
+
+// 自动计算范围
+const updateAutoRange = () => {
+  if (!contourValue.value.autoRange || !props.initialData?.ranges) return
+  const variable = contourValue.value.scalarname2
+  if (variable && props.initialData.ranges[variable]) {
+    contourValue.value.max = props.initialData.ranges[variable].max.toFixed(2)
+    contourValue.value.min = props.initialData.ranges[variable].min.toFixed(2)
+  }
 }
 
+// 计算等值线间距
+const spacing = computed(() => {
+  return (parseFloat(contourValue.value.max) - parseFloat(contourValue.value.min)) / contourValue.value.cengji;
+});
+
+// 自动更新范围
+watch(() => contourValue.value.scalarname2, (newVar) => {
+  if (contourValue.value.autoRange && props.initialData?.ranges[newVar]) {
+    contourValue.value.max = props.initialData.ranges[newVar].max.toFixed(2);
+    contourValue.value.min = props.initialData.ranges[newVar].min.toFixed(2);
+  }
+});
+
+// 确认按钮处理
+const handleConfirm = () => {
+  const params = {
+    zoneName: props.activeZone,
+    variableName: contourValue.value.scalarname2,
+    options: {
+      spacing: spacing.value,
+      width: contourValue.value.width,
+      color: parseInt(contourValue.value.color.replace('#', '0x')),
+      minValue: parseFloat(contourValue.value.min),
+      maxValue: parseFloat(contourValue.value.max),
+      opacity: 1.0
+    }
+  };
+  
+  emit('confirm', params);
+  emit('update:modelValue', false);
+};
+
 // 取消操作
 const handleCancel = () => {
   emit('cancel')

+ 230 - 171
src/components/cloudChart/dialog/DomainDialog.vue

@@ -9,210 +9,261 @@
     @cancel="handleCancel"
   >
     <div>
-      <el-row style="margin-bottom: 10px;" :gutter="20">
-        <el-col :key="showAll" :span="8">
-          <el-button @click="showAll" style="width: 100%;">显示全部</el-button>
+      <el-row style="margin-bottom: 10px" :gutter="20">
+        <el-col :span="8">
+          <el-button @click="showAll" style="width: 100%">显示全部</el-button>
         </el-col>
-        <el-col :key="hideAll" :span="8">
-          <el-button @click="hideAll" style="width: 100%;">隐藏全部</el-button>
+        <el-col :span="8">
+          <el-button @click="hideAll" style="width: 100%">隐藏全部</el-button>
         </el-col>
-        <el-col :key="reverseAll" :span="8">
-          <el-button @click="reverseAll" style="width: 100%;">倒转互换</el-button>
+        <el-col :span="8">
+          <el-button @click="reverseAll" style="width: 100%"
+            >倒转互换</el-button
+          >
         </el-col>
       </el-row>
-      <el-row style="margin-bottom: 10px;" :gutter="20">
+      <el-row style="margin-bottom: 10px" :gutter="20">
         <el-col :span="8">
-          <el-button @click="showSelected" style="width: 100%;">显示</el-button>
+          <el-button @click="showSelected" style="width: 100%"
+            >显示选中</el-button
+          >
         </el-col>
         <el-col :span="8">
-          <el-button @click="hideSelected" style="width: 100%;">隐藏</el-button>
+          <el-button @click="hideSelected" style="width: 100%"
+            >隐藏选中</el-button
+          >
         </el-col>
         <el-col :span="8">
-          <el-button @click="surfaceRendering" style="width: 100%;">表面绘制</el-button>
+          <el-button
+            @click="surfaceRendering"
+            style="width: 100%"
+            :disabled="selectedDomains.length === 0"
+          >
+            表面绘制
+          </el-button>
         </el-col>
       </el-row>
     </div>
     <div class="classtable tabledomain">
       <el-table
-        :data="tableData" 
-        style="width: 100%; height: 230px" 
-        border="true" 
-        :header-cell-class-name="headerCellClassName"
+        ref="domainTable"
+        :data="domainTableData"
+        style="width: 100%; height: 230px"
+        border
         @selection-change="handleSelectionChange"
+        @row-click="handleRowClick"
+        :header-cell-class-name="headerCellClassName"
       >
         <el-table-column type="selection" width="55" />
-        <el-table-column prop="domain" label="域名称" />
-        <el-table-column prop="status" label="状态" /> <!-- 直接显示原始值 -->
+        <el-table-column prop="name" label="域名" />
+        <el-table-column label="状态">
+          <template #default="{ row }">
+            <el-tag :type="row.visible ? 'success' : 'danger'">
+              {{ row.visible ? "显示" : "隐藏" }}
+            </el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="drawType" label="绘制类型" />
         <el-table-column prop="planeRange" label="平面范围" />
       </el-table>
     </div>
   </SubDialog>
+
   <!-- 表面绘制弹窗 -->
   <SurfaceDialog
     v-model="showSurfaceDialog"
     :initial-data="surfaceFormData"
     @surfaceConfirm="handleSurfaceConfirm"
-/>
+  />
 </template>
 
-<script setup>
-import { ref, defineProps, defineEmits } from 'vue'
-import SubDialog from './SubDialog.vue'
-import SurfaceDialog from './SurfaceDialog.vue'
-import { usePltDataStore } from '@/store/modules/pltData'
-
-const pltStore = usePltDataStore()
-const tableData = computed(() => {
-  return pltStore.getDomainNames().map(name => ({
-    domain: name,
-    status: 'show',
-    drawType: 'Exposed',
-    planeRange: '(1,1,1)'
-  }))
-})
-
-const props = defineProps({
-  modelValue: Boolean,
-  initialData: {
-    type: Array,
-    default: () => []
-  }
-})
-
-const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
-
-const selectedRows = ref([]); // 存储选中的行
-
-// 表面绘制弹窗状态
-const showSurfaceDialog = ref(false)
-const surfaceFormData = ref({
-  slotType: '0',
-  start: '1',
-  end: '1',
-  skip: '1'
-})
-
-const selectedDomains = computed(() => {
-  return selectedRows.value.map(row => row.domain)
-})
-
-// 监听选中行变化
-const handleSelectionChange = (rows) => {
-  selectedRows.value = rows;
-};
-
-// 显示全部
-const showAll = () => {
-  tableData.value.forEach(item => {
-    item.status = 'show'
-  })
-}
+<script>
+import { ref, computed, nextTick } from "vue"
+import SubDialog from "./SubDialog.vue"
+import SurfaceDialog from "./SurfaceDialog.vue"
+import { usePltDataStore } from "@/store/modules/pltData"
+import * as THREE from "three"
 
-// 隐藏全部
-const hideAll = () => {
-  tableData.value.forEach(item => {
-    item.status = 'hide'
-  })
-}
+export default {
+  components: {
+    SubDialog,
+    SurfaceDialog
+  },
+  props: {
+    modelValue: Boolean,
+    threeSceneRef: Object,
+    renderer: Object,
+    pltData: Object,
+    scene: Object
+  },
+  emits: ["update:modelValue", "confirm", "cancel"],
+  setup(props, { emit }) {
+    const pltStore = usePltDataStore()
 
-// 显示选中行
-const showSelected = () => {
-  selectedRows.value.forEach(row => {
-    row.status = 'show'
-  })
-}
+    // 表格数据
+    const domainTableData = computed(() => {
+      return pltStore.domainNames.map((name) => {
+        const config = pltStore.domainConfig(name)
+        return {
+          name,
+          visible: config.visible,
+          drawType: config.drawType,
+          planeRange: config.clippingPlane ? "(已设置)" : "(1,1,1)"
+        }
+      })
+    })
 
-// 隐藏选中行
-const hideSelected = () => {
-  selectedRows.value.forEach(row => {
-    row.status = 'hide'
-  })
-}
+    // 选中行处理
+    const selectedRows = ref([])
+    const selectedDomains = computed(() => {
+      return selectedRows.value.map((row) => row.name)
+    })
+    const handleSelectionChange = (rows) => {
+      selectedRows.value = rows
+    }
+    const setupListeners = () => {
+      if (!props.threeSceneRef?.pltHandler) return
 
-// 倒转互换所有行状态
-const reverseAll = () => {
-  tableData.value.forEach(item => {
-    item.status = item.status === 'show' ? 'hide' : 'show'
-  })
-}
+      const removeListener = props.threeSceneRef.pltHandler.on(
+        "visibilityChange",
+        (event) => {
+          // 更新表格数据或其他UI状态
+        }
+      )
 
-// 按钮数据
-let domainbtnbox1 = ref(['显示全部','隐藏全部','倒转互换'])
-let domainbtnbox2 = ref(['显示','隐藏','表面绘制'])
-
-// 表格列配置
-let tabledomainColumns = ref([
-  {label:"状态", prop:'state'},
-  {label:"绘制类型", prop:'type'},
-  {label:"平面范围", prop:'area'},
-])
-
-const slotTypeOptions = ref([
-  { value: "0", label: "Exposed" },
-  { value: "1", label: "I-plans" },
-  { value: "2", label: "J-plans" },
-  { value: "3", label: "K-plans" }
-])
-
-// 表头样式
-const headerCellClassName = ({ column }) => {
-  // console.log('列:',column.property)
-  if (column.property === 'state') {
-    console.log('yanse',column.property)
-    return 'header-blue';
-  } else if (column.property === 'type') {
-    return 'header-green';
-  } else if (column.property === 'area') {
-    return 'header-yellow';
-  }
-  return '';
-};
-
-// 表面绘制按钮点击
-const surfaceRendering = () => {
-  if (selectedRows.value.length === 0) {
-    return
-  } 
-  
-  // 初始化表面绘制表单数据
-  surfaceFormData.value = {
-    slotType: '0',
-    start: '1',
-    end: '1',
-    skip: '1',
-    domains: selectedDomains.value // 传递选中的域
-  }
-  
-  showSurfaceDialog.value = true
-}
+      return removeListener
+    }
+    let removeListener
+    onMounted(() => {
+      removeListener = setupListeners()
+    })
 
-// 表面绘制确认
-const handleSurfaceConfirm = (surfaceData) => {
-  console.log('表面绘制数据:', surfaceData)
-  // 找到对应的label
-  const selectedOption = slotTypeOptions.value.find(option => option.value === surfaceData.slotType);
-  const drawTypeLabel = selectedOption ? selectedOption.label : surfaceData.slotType;
-  
-  // 更新选中行的绘制类型和平面范围
-  selectedRows.value.forEach(row => {
-    row.drawType = drawTypeLabel;  // 使用label而不是value
-    row.planeRange = `(${surfaceData.start},${surfaceData.end},${surfaceData.skip})`
-  })
-  
-  showSurfaceDialog.value = false
-}
+    onUnmounted(() => {
+      removeListener?.()
+    })
 
-// 确认操作
-const handleConfirm = () => {
-  emit('confirm', tableData.value)
-  emit('update:modelValue', false)
-}
+    const handleRowClick = (row) => {
+      // toggleZone(row.name);
+    }
+
+    // 域全部显示
+    const showAll = () => {
+      pltStore.setAllDomainsVisibility(true)
+      props.threeSceneRef?.showAll()
+    }
+    // 域全部隐藏
+    const hideAll = () => {
+      pltStore.setAllDomainsVisibility(false)
+      props.threeSceneRef?.hideAll()
+    }
+    // 所有域域反转显示
+    const reverseAll = () => {
+      if (!props.threeSceneRef) {
+        console.error("threeSceneRef not available")
+        return
+      }
+      pltStore.domainNames.forEach((name) => {
+        const current = pltStore.domainConfig(name).visible
+        // 更新Three.js场景
+        props.threeSceneRef?.setZoneVisibility(name, !current)
+        // 更新store
+        pltStore.setDomainVisibility(name, !current)
+      })
+    }
+    const showSelected = () => {
+      selectedDomains.value.forEach((name) => {
+        props.threeSceneRef?.showZone(name)
+        pltStore.setDomainVisibility(name, true)
+      })
+    }
+    const hideSelected = () => {
+      selectedDomains.value.forEach((name) => {
+        props.threeSceneRef?.hideZone(name)
+        pltStore.setDomainVisibility(name, false)
+      })
+    }
+
+    // 表面绘制控制
+    const showSurfaceDialog = ref(false)
+    const surfaceFormData = ref({
+      slotType: "0",
+      start: "1",
+      end: "1",
+      skip: "1"
+    })
+
+    const slotTypeOptions = [
+      { value: "0", label: "Exposed" },
+      { value: "1", label: "I-plans" },
+      { value: "2", label: "J-plans" },
+      { value: "3", label: "K-plans" }
+    ]
+
+    const surfaceRendering = () => {
+      if (selectedDomains.value.length === 0) return
+      showSurfaceDialog.value = true
+    }
+
+    const handleSurfaceConfirm = (data) => {
+      const drawType =
+        slotTypeOptions.find((opt) => opt.value === data.slotType)?.label ||
+        data.slotType
+      selectedDomains.value.forEach((name) => {
+        pltStore.setDomainDrawType(name, drawType)
+        const plane = new THREE.Plane(
+          new THREE.Vector3(
+            Number(data.start),
+            Number(data.end),
+            Number(data.skip)
+          ),
+          0
+        )
+        pltStore.applyClippingPlane(name, plane)
+      })
+      showSurfaceDialog.value = false
+    }
+
+    // 对话框控制
+    const handleConfirm = () => {
+      emit("confirm", domainTableData.value)
+      emit("update:modelValue", false)
+    }
+
+    const handleCancel = () => {
+      emit("cancel")
+      emit("update:modelValue", false)
+    }
 
-// 取消操作
-const handleCancel = () => {
-  emit('cancel')
-  emit('update:modelValue', false)
+    // 表头样式
+    const headerCellClassName = ({ column }) => {
+      const styleMap = {
+        name: "header-blue",
+        drawType: "header-green",
+        planeRange: "header-yellow"
+      }
+      return styleMap[column.property] || ""
+    }
+
+    return {
+      domainTableData,
+      selectedRows,
+      selectedDomains,
+      handleSelectionChange,
+      showAll,
+      hideAll,
+      reverseAll,
+      showSelected,
+      hideSelected,
+      showSurfaceDialog,
+      surfaceFormData,
+      surfaceRendering,
+      handleSurfaceConfirm,
+      handleConfirm,
+      handleCancel,
+      headerCellClassName,
+      slotTypeOptions
+    }
+  }
 }
 </script>
 
@@ -220,9 +271,17 @@ const handleCancel = () => {
 .classtable.tabledomain {
   margin-top: 20px;
 }
-/* 表头样式 */
-:deep(.header-cell) {
-  background-color: #f5f7fa;
+
+:deep(.header-blue) {
+  background-color: #e6f7ff;
+  font-weight: bold;
+}
+:deep(.header-green) {
+  background-color: #f6ffed;
+  font-weight: bold;
+}
+:deep(.header-yellow) {
+  background-color: #fffbe6;
   font-weight: bold;
 }
-</style>
+</style>

+ 306 - 103
src/components/cloudChart/index.vue

@@ -25,7 +25,7 @@
         <el-row gutter="20">
           <el-col :span="2"></el-col>
           <el-col v-for="(item, index) in cloudbtnbox" :key="index" :span="4">
-            <el-button style="width: 100%" @click="openSubDialog(item.btnname)">
+            <el-button style="width: 100%" @click="handleButtonClick(item.btnname)">
               <el-image
                 :src="getImgPath(item.url)"
                 alt="img"
@@ -37,37 +37,184 @@
           </el-col>
         </el-row>
       </div>
-      <!-- 动态加载子弹窗 -->
-      <component
-        v-for="(dialog, index) in activeSubDialogs"
-        :key="index"
-        :is="dialog.component"
-        v-model="dialog.visible"
-        v-bind="dialog.props"
-        @confirm="handleSubDialogConfirm(dialog.type, $event)"
-        @cancel="closeSubDialog(dialog.type)"
+      
+      <!-- 文件选择对话框 -->
+      <FileSelectDialog
+        v-model="fileSelectDialogVisible"
+        :renderer="renderer"
+        :three-scene-ref="threeSceneRef"
+        :scene="scene"
+        :plt-data="pltData"
+        @confirm="handleFileSelectConfirm"
+        @cancel="fileSelectDialogVisible = false"
+      />
+      
+      <!-- 域对话框 -->
+      <DomainDialog
+        v-model="domainDialogVisible"
+        :renderer="renderer"
+        :three-scene-ref="threeSceneRef"
+        :scene="scene"
+        :plt-data="pltData"
+        :active-zone="pltStore.currentZone"
+        :variable-options="pltData?.metadata?.variables || []"
+        :initial-data="{ ranges: calculateVariableRanges() }"
+        @confirm="handleDomainConfirm"
+        @cancel="domainDialogVisible = false"
+      />
+      
+      <!-- 云图对话框 -->
+      <CloudMapDialog
+        v-model="cloudMapDialogVisible"
+        :renderer="renderer"
+        :three-scene-ref="threeSceneRef"
+        :scene="scene"
+        :plt-data="pltData"
+        :active-zone="pltStore.currentZone"
+        :variable-options="pltData?.metadata?.variables || []"
+        :initial-data="{ ranges: calculateVariableRanges() }"
+        @confirm="handleCloudMapConfirm"
+        @cancel="cloudMapDialogVisible = false"
       />
+      
+      <!-- 色卡对话框 -->
+      <ColorCardDialog
+        v-model="colorCardDialogVisible"
+        :renderer="renderer"
+        :three-scene-ref="threeSceneRef"
+        :scene="scene"
+        :plt-data="pltData"
+        @confirm="handleColorCardConfirm"
+        @cancel="colorCardDialogVisible = false"
+      />
+      
+      <!-- 等值线对话框 -->
+      <ContourDialog
+        v-model="contourDialogVisible"
+        :renderer="renderer"
+        :three-scene-ref="threeSceneRef"
+        :scene="scene"
+        :plt-data="pltData"
+        :active-zone="pltStore.currentZone"
+        :variable-options="pltData?.metadata?.variables || []"
+        :initial-data="{ ranges: calculateVariableRanges() }"
+        @confirm="handleContourConfirm"
+        @cancel="contourDialogVisible = false"
+      />
+      
       <div
         style="overflow: auto"
         v-loading="isLoading"
         element-loading-text="拼命加载中..."
       >
-        <cloudChart height="400px" :data="pltData"/>
+        <div style="height: 400px; position: relative">
+          <ThreeScene
+            ref="threeSceneRef"
+            :api-data="pltData"
+            :scene-config="sceneConfig"
+            :camera-config="cameraConfig"
+            :controls-config="controlsConfig"
+            :show-helpers="true"
+            :helpers-config="helpersConfig"
+            :light-config="lightConfig"
+            style="width: 100%; height: 100%"
+          />
+        </div>
+        <!-- 色卡 -->
+        <div
+          class="color-bar-container"
+          v-if="colorBarConfig && colorBarConfig.visible"
+        >
+          <div class="color-bar" :style="colorBarStyle"></div>
+          <div class="color-bar-labels">
+            <span v-for="(label, index) in colorBarLabels" :key="index">{{
+              label
+            }}</span>
+          </div>
+          <div class="color-bar-title" v-if="colorBarConfig.showTitle">
+            {{ colorBarConfig.title || "Color Bar" }}
+          </div>
+        </div>
       </div>
     </div>
   </el-dialog>
 </template>
 
 <script setup>
-import { defineProps, defineEmits } from "vue"
+import { ref, markRaw, nextTick } from "vue"
 import FileSelectDialog from "./dialog/FileSelectDialog.vue"
 import DomainDialog from "./dialog/DomainDialog.vue"
 import CloudMapDialog from "./dialog/CloudMapDialog.vue"
 import ColorCardDialog from "./dialog/ColorCardDialog.vue"
 import ContourDialog from "./dialog/ContourDialog.vue"
-import cloudChart from "@/views/threejsView/index.vue" // 云图
-import h5wasm from 'h5wasm'
-import { usePltDataStore } from '@/store/modules/pltData'
+import cloudChart from "@/views/threejsView/index.vue"
+import h5wasm from "h5wasm"
+import { usePltDataStore } from "@/store/modules/pltData"
+import { PltDataRenderer } from "@/views/threejsView/utils/renderers/PltDataRenderer"
+import ThreeScene from "@/components/ThreeScene/ThreeScene.vue"
+import * as THREE from "three"
+
+const scene = new THREE.Scene()
+const camera = new THREE.PerspectiveCamera(
+  75,
+  window.innerWidth / window.innerHeight,
+  0.1,
+  1000
+)
+
+const threeSceneRef = ref(null)
+
+// 对话框可见性状态
+const fileSelectDialogVisible = ref(false)
+const domainDialogVisible = ref(false)
+const cloudMapDialogVisible = ref(false)
+const colorCardDialogVisible = ref(false)
+const contourDialogVisible = ref(false)
+
+const sceneConfig = {
+  backgroundColor: 0xffffff,
+  backgroundColor: 0xc7c7c7c7,
+  fog: true,
+  fogOptions: {
+    color: 0xc7c7c7c7,
+    near: 5,
+    far: 1000
+  }
+}
+
+const cameraConfig = {
+  type: "perspective",
+  position: { x: 0, y: 2, z: 10 },
+  lookAt: { x: 0, y: 0, z: 0 },
+  fov: 60
+}
+
+const controlsConfig = {
+  enableDamping: true,
+  dampingFactor: 0.05,
+  maxPolarAngle: Math.PI * 0.9,
+  minDistance: 2,
+  maxDistance: 20
+}
+
+const helpersConfig = {
+  axesHelperSize: 5,
+  gridHelperSize: 20,
+  statsHelper: true
+}
+
+const lightConfig = {
+  ambient: {
+    color: 0x404040,
+    intensity: 0.8
+  },
+  directional: {
+    color: 0xffffff,
+    intensity: 1.5,
+    position: { x: 3, y: 4, z: 5 }
+  }
+}
+
 const props = defineProps({
   modelValue: {
     type: Boolean,
@@ -83,9 +230,7 @@ const props = defineProps({
 
 const emit = defineEmits(["update:modelValue", "close"])
 
-// 添加色卡配置状态
 const colorCardConfig = ref(null)
-
 let cloudbtnbox = ref([
   { url: "meshFile.png", btnname: "文件选择" },
   { url: "yu.png", btnname: "域" },
@@ -93,62 +238,84 @@ let cloudbtnbox = ref([
   { url: "seka.png", btnname: "色卡" },
   { url: "dengzx.png", btnname: "等值线" }
 ])
-const subDialogComponents = {
-  文件选择: FileSelectDialog,
-  域: DomainDialog,
-  云图: CloudMapDialog,
-  色卡: ColorCardDialog,
-  等值线: ContourDialog
-}
+
 let isLoading = ref(false)
-// 当前活动的子弹窗
-const activeSubDialogs = ref([])
 let pltData = ref()
-// 打开对应的子弹窗
-const openSubDialog = (btnName) => {
-  // 先关闭同类型的对话框(如果存在)
-  closeSubDialog(btnName)
-
-  // 打开新对话框
-  activeSubDialogs.value.push({
-    type: btnName,
-    component: subDialogComponents[btnName],
-    visible: true,
-    props: {}
-  })
-}
-
-// 关闭子弹窗
-const closeSubDialog = (dialogType) => {
-  activeSubDialogs.value = activeSubDialogs.value.filter(
-    (d) => d.type !== dialogType
-  )
-}
+const renderer = ref()
+const pltStore = usePltDataStore()
 
-// 处理子弹窗确认
-const handleSubDialogConfirm = (dialogType, data) => {
-  switch (dialogType) {
+// 处理按钮点击
+const handleButtonClick = (btnName) => {
+  switch (btnName) {
     case "文件选择":
-      // 获取结果文件
-      getResultFile(data.fid)
+      fileSelectDialogVisible.value = true
       break
     case "域":
-      console.log("dddddddddd域", data)
+      domainDialogVisible.value = true
       break
     case "云图":
-      console.log("dddddddddd云图", data)
+      cloudMapDialogVisible.value = true
       break
     case "色卡":
-      colorCardConfig.value = data
-      applyColorCardConfig() // 应用色卡配置
+      colorCardDialogVisible.value = true
       break
     case "等值线":
-      console.log("dddddddddd等值线", data)
+      contourDialogVisible.value = true
       break
     default:
       break
   }
-  closeSubDialog(dialogType)
+}
+
+// 计算所有变量的范围
+const calculateVariableRanges = () => {
+  const ranges = {}
+  pltData.value?.zones?.forEach(zone => {
+    Object.entries(zone.variables || {}).forEach(([name, data]) => {
+      if (!ranges[name]) {
+        ranges[name] = { min: Infinity, max: -Infinity }
+      }
+      ranges[name].min = Math.min(ranges[name].min, ...data)
+      ranges[name].max = Math.max(ranges[name].max, ...data)
+    })
+  })
+  return ranges
+}
+
+// 处理各对话框的确认事件
+const handleFileSelectConfirm = (data) => {
+  getResultFile(data.fid)
+  fileSelectDialogVisible.value = false
+}
+
+const handleDomainConfirm = (data) => {
+  console.log("域设置确认", data)
+  domainDialogVisible.value = false
+}
+
+const handleCloudMapConfirm = (data) => {
+  console.log("云图设置确认", data)
+  cloudMapDialogVisible.value = false
+}
+
+const handleColorCardConfirm = (data) => {
+  colorCardConfig.value = data
+  applyColorCardConfig()
+  colorCardDialogVisible.value = false
+}
+
+const handleContourConfirm = (params) => {
+  threeSceneRef.value?.enableContour(
+    params.zoneName,
+    params.variableName,
+    params.options
+  )
+  
+  threeSceneRef.value?.updateColorMapping(params.variableName, {
+    min: params.options.minValue,
+    max: params.options.maxValue
+  })
+  contourDialogVisible.value = false
 }
 
 const getImgPath = (url) => {
@@ -166,12 +333,11 @@ const getResultFile = (fid) => {
   getPltData(fid)
 }
 
+// 获取PLT数据
 const getPltData = async (fpid) => {
   let url = import.meta.env.VITE_BASE_URL + getUrl()
   try {
-    // 1. 先初始化h5wasm
-    const { ready, File, FS } = await h5wasm.ready;
-     // 必须等待WASM加载完成
+    const { ready, File, FS } = await h5wasm.ready
     const response = await fetch(url, {
       method: "POST",
       headers: {
@@ -185,21 +351,21 @@ const getPltData = async (fpid) => {
         fid: fpid
       })
     })
-    // 获取二进制数据
     const arrayBuffer = await response.arrayBuffer()
-    // 将文件写入虚拟文件系统
     const filename = `data_${Date.now()}.h5`
     FS.writeFile(filename, new Uint8Array(arrayBuffer))
-    // 打开HDF5文件
-    const file = new h5wasm.File(filename, 'r')  // 'r' 表示只读模式
-    // 3. 调试:打印完整结构
-  console.log('HDF5结构:', {
-    keys: Array.from(file.keys()),
-    attrs: Object.entries(file.attrs).map(([k, v]) => ({ [k]: v.value }))
-  })
-    // 解析为Three.js可用格式
-    const parsedData = await parseHDF5ToThreeJS(file);
+    const file = new h5wasm.File(filename, "r")
+    const parsedData = await parseHDF5ToThreeJS(file)
     pltData.value = parsedData
+    console.log("解析后的数据:", parsedData)
+
+    pltStore.initialize(parsedData.zones)
+    await nextTick()
+    ;["far", "symm"].forEach((name) => {
+      if (pltStore.domainNames.includes(name)) {
+        threeSceneRef.value?.setZoneVisibility(name, false)
+      }
+    })
     isLoading.value = false
   } catch (error) {
     isLoading.value = false
@@ -207,30 +373,36 @@ const getPltData = async (fpid) => {
   }
 }
 
+watch(
+  () => pltData.value,
+  (newData) => {
+    if (newData) {
+      threeSceneRef.value?.updateData(newData)
+    }
+  }
+)
+
 // HDF5转Three.js格式的解析器
 const parseHDF5ToThreeJS = (h5file) => {
-  const pltStore = usePltDataStore()
   const result = {
-    "data": { "datasetType": "plt" },
+    data: { datasetType: "plt" },
     metadata: {
-      title: h5file.attrs.title?.value || '',
+      title: h5file.attrs.title?.value || "",
       variables: JSON.parse(h5file.attrs.variables?.value || "[]"),
-      version: h5file.attrs.version?.value || ''
+      version: h5file.attrs.version?.value || ""
     },
     zones: []
   }
   const zones = []
 
-  // 增强型数据提取方法
   const extractDataset = (group, name) => {
     try {
-      // 尝试多种访问方式
-      const dataset = group.get?.(name) ||  // 方法1: get()
-                     group[name]?.value ||  // 方法2: 直接访问
-                     Object.entries(group).find(([k]) => k === name)?.[1]?.value // 方法3: 遍历查找
-      
-      // 特殊处理数组类型数据
-      if (dataset && typeof dataset === 'object' && 'value' in dataset) {
+      const dataset =
+        group.get?.(name) ||
+        group[name]?.value ||
+        Object.entries(group).find(([k]) => k === name)?.[1]?.value
+
+      if (dataset && typeof dataset === "object" && "value" in dataset) {
         return dataset.value
       }
       return dataset
@@ -240,24 +412,21 @@ const parseHDF5ToThreeJS = (h5file) => {
     }
   }
 
-  // 处理每个区域
   for (const zoneKey of h5file.keys()) {
     try {
-      // 正确获取组对象
       const zone = h5file.get(zoneKey)
-      if (!zone || typeof zone !== 'object') continue
+      if (!zone || typeof zone !== "object") continue
 
       console.log(`处理 ${zoneKey}:`, zone)
 
       const zoneData = {
-        name: zoneKey.replace(/^zone_\d+_/, ''), // 去除可能的zone_前缀
-        vertices: extractDataset(zone, 'vertices'),
-        indices: extractDataset(zone, 'indices'),
+        name: zoneKey.replace(/^zone_\d+_/, ""),
+        vertices: extractDataset(zone, "vertices"),
+        indices: extractDataset(zone, "indices"),
         variables: {}
       }
 
-      // 提取变量数据
-      result.metadata.variables.forEach(varName => {
+      result.metadata.variables.forEach((varName) => {
         const data = extractDataset(zone, `var_${varName}`)
         if (data) zoneData.variables[varName] = data
       })
@@ -268,9 +437,8 @@ const parseHDF5ToThreeJS = (h5file) => {
     }
   }
 
-  pltStore.setZones(zones)
+  pltStore.initialize(zones)
   result.zones = zones
-  console.log('最终解析结果:', result)
   return result
 }
 
@@ -284,25 +452,60 @@ function getUrl(channelNo = "service") {
   return url
 }
 
-// 添加应用色卡配置的方法
+// 应用色卡配置
 const applyColorCardConfig = () => {
   if (!pltData.value || !colorCardConfig.value) return
-  
-  // 确保pltData有colorCard配置对象
-  if (!pltData.value.config) {
-    pltData.value.config = {}
-  }
-  
+
+  pltData.value.config = pltData.value.config || {}
   pltData.value.config.colorCard = {
     ...colorCardConfig.value,
     visible: colorCardConfig.value.check1,
     showTitle: colorCardConfig.value.check2
   }
-  
-  // 强制更新云图组件
-  pltData.value = {...pltData.value}
+
+  if (threeSceneRef.value) {
+    threeSceneRef.value.updateColorMapping(null, {
+      colorCard: pltData.value.config.colorCard
+    })
+  }
 }
 </script>
 
 <style scoped>
+/* 原有样式保持不变 */
+.extreme-label {
+  transform: translate(-50%, 0);
+  white-space: nowrap;
+  pointer-events: none;
+}
+.color-bar-container {
+  position: absolute;
+  right: 20px;
+  bottom: 20px;
+  z-index: 1000;
+  background: rgba(255, 255, 255, 0.8);
+  padding: 10px;
+  border-radius: 4px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+}
+
+.color-bar {
+  height: 20px;
+  width: 100%;
+  margin-bottom: 5px;
+  border-radius: 3px;
+  overflow: hidden;
+}
+
+.color-bar-labels {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+}
+
+.color-bar-title {
+  text-align: center;
+  font-weight: bold;
+  margin-top: 5px;
+}
 </style>

+ 332 - 0
src/composables/useThree.js

@@ -0,0 +1,332 @@
+import { ref, watch } from 'vue';
+import * as THREE from 'three';
+import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+import {
+  createScene,
+  disposeScene,
+  updateScene
+} from '@/utils/three/core/sceneManager';
+import {
+  createCamera,
+  disposeCamera,
+  updateCamera,
+  fitCameraToScene
+} from '@/utils/three/core/cameraManager';
+import {
+  createRenderer,
+  disposeRenderer
+} from '@/utils/three/core/rendererManager';
+import { setupLights } from '@/utils/three/lights/lightManager';
+import { setupControls } from '@/utils/three/controls/orbitControls';
+import { setupGUI } from '@/utils/three/gui/guiManager';
+import { ScreenFixedArrow } from '@/utils/three/helpers/axesHelper';
+import { startAnimationLoop } from '@/utils/three/core/animationLoop';
+import { createMaterial } from '@/utils/three/materials/materialManager';
+import { ObjectManager } from '@/utils/three/objects/objectManager';
+import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+import { XyzHandler } from '@/utils/three/objects/fileHandlers/XyzHandler.js';
+import { PltHandler } from '@/utils/three/dataHandlers/PltHandler.js';
+
+let cube, gui, animationId, helpers, lights;
+export function useThree(canvasRef, props) {
+  const scene = ref(null);
+  const camera = ref(null);
+  const renderer = ref(null);
+  const controls = ref(null);
+  const objectManager = ref(null);
+  const cubeRef = ref(null);
+  const screenArrow = ref(null);
+  const dataHandlers = ref({});
+
+  const init = () => {
+
+    // 核心三件套初始化
+    scene.value = markRaw(createScene(props.sceneConfig));
+    camera.value = markRaw(createCamera(props.cameraConfig));
+    renderer.value = markRaw(createRenderer(canvasRef.value));
+    objectManager.value = markRaw(new ObjectManager(scene.value));
+    dataHandlers.value = {
+      plt: markRaw(new PltHandler(scene.value))
+    };
+
+    lights = setupLights(scene.value, {
+      ambient: {
+        color: 0xffffff, // 白色环境光
+        intensity: 0.5    // 适度亮度
+      },
+      directional: {
+        color: 0xffffff,
+        intensity: 1.5,   // 更强的主光源
+        position: { x: 5, y: 10, z: 5 }, // 从斜上方照射
+        castShadow: true  // 按需开启阴影
+      }
+    });
+
+    // // 控制器
+    if (props.controlsConfig) {
+      controls.value = setupControls(camera.value, renderer.value.domElement);
+    }
+
+    // // 辅助工具
+    // if (props.showHelpers) {
+    //   helpers = setupHelpers(scene.value, {
+    //     axesHelper: true,
+    //     gridHelper: true,
+    //     statsHelper: props.showStats || false
+    //   });
+    // }
+
+    // // GUI
+    // if (props.showGUI) {
+    //   gui = setupGUI(scene.value, camera.value);
+    // }
+
+    // 创建屏幕固定箭头(绑定到立方体)
+    if (props.showScreenArrow) {
+      screenArrow.value = markRaw(
+        new ScreenFixedArrow(
+          scene.value,
+          camera.value,
+          cubeRef.value?.mesh || new THREE.Object3D(),
+          props.arrowConfig
+        )
+      );
+    }
+
+    const cleanup = setupEventListeners();
+    onUnmounted(cleanup);
+    animate();
+    handleResize();
+
+  };
+
+  const fileHandlers = { // 非响应式对象
+    xyz: new XyzHandler(scene.value),
+  };
+
+  const loadFile = async (file) => {
+    const extension = file.name.split('.').pop().toLowerCase();
+    console.log('File extension:', extension);
+    console.log('Available handlers:', fileHandlers); // 查看已注册的处理器
+
+    const handler = fileHandlers?.[extension];
+    console.log('ffffffhandler', handler);
+
+    if (!handler) {
+      console.error(`Unsupported file type: ${extension}`);
+      return;
+    }
+
+    try {
+      const object = await handler.load(file);
+      objectManager.value.add(object);
+      return object;
+    } catch (error) {
+      console.error(`Error loading ${file.name}:`, error);
+    }
+  };
+
+  const updateData = (newData) => {
+    if (!newData) return;
+
+    // 清除旧数据
+    if (objectManager.value) {
+      objectManager.value.clearByType('PltZone');
+    }
+
+    // 使用对应处理器解析数据
+    const handler = dataHandlers.value[newData.data.datasetType];
+    if (handler) {
+      handler.parse(newData, {
+        // 传递初始颜色映射配置
+        colorMap: 'rainbow',
+        scalarVariable: 'CoefPressure' // 默认使用压力系数
+      });
+      fitCameraToScene(camera.value, scene.value, {
+        padding: 1.8,       // 比默认1.5稍宽松
+        forceTopView: false, // 自动判断
+        minNear: 0.01,      // 更小的近裁剪面
+        maxFar: 5000        // 更大的远裁剪面
+      }); // 自动调整视角
+    } else {
+      console.error('没有找到匹配的数据处理器');
+    }
+  };
+
+
+  const updateCube = (params) => {
+    if (cubeRef.value) {
+      cubeRef.value.updateParams(params);
+    }
+  };
+
+  const setupEventListeners = () => {
+    const pltHandler = dataHandlers.value.plt;
+    if (!pltHandler) return;
+
+    // 监听域可见性变化
+    const removeVisibilityListener = pltHandler.on('visibilityChange', (event) => {
+      console.log('Zone visibility changed:', event.detail);
+      // 可以在这里触发Vue的响应式更新
+    });
+
+    // 返回清理函数
+    return () => {
+      removeVisibilityListener();
+    };
+  };
+
+
+  const handleResize = () => {
+    const width = canvasRef.value.clientWidth;
+    const height = canvasRef.value.clientHeight;
+    renderer.value.setSize(width, height, false);
+    camera.value.aspect = width / height;
+    camera.value.updateProjectionMatrix();
+
+  }
+
+  /**
+  * 暴露给组件的域控制方法
+  */
+  const zoneControls = {
+    showZone: (name) => {
+      if (!dataHandlers.value.plt) {
+        console.error('PltHandler not initialized');
+        return false;
+      }
+      return dataHandlers.value.plt.setZoneVisibility(name, true);
+    },
+    hideZone: (name) => {
+      if (!dataHandlers.value.plt) {
+        console.error('PltHandler not initialized');
+        return false;
+      }
+      return dataHandlers.value.plt.setZoneVisibility(name, false);
+    },
+    toggleZone: (name) => {
+      if (!dataHandlers.value.plt) {
+        console.error('PltHandler not initialized');
+        return false;
+      }
+      const current = dataHandlers.value.plt.getZoneVisibility(name);
+      return dataHandlers.value.plt.setZoneVisibility(name, !current);
+    },
+    setZoneVisibility: (name, visible) => {
+      if (!dataHandlers.value.plt) {
+        console.error('PltHandler not initialized');
+        return false;
+      }
+      return dataHandlers.value.plt.setZoneVisibility(name, visible);
+    },
+    getZoneVisibility: (name) => {
+      if (!dataHandlers.value.plt) {
+        console.error('PltHandler not initialized');
+        return false;
+      }
+      return dataHandlers.value.plt.getZoneVisibility(name);
+    },
+    showAll: () => {
+      if (dataHandlers.value.plt && typeof dataHandlers.value.plt.setAllZonesVisibility === 'function') {
+        return dataHandlers.value.plt.setAllZonesVisibility(true);
+      } else {
+        console.error('plt handler or setAllZonesVisibility method not available');
+        return false;
+      }
+    },
+    hideAll: () => {
+      if (dataHandlers.value.plt && typeof dataHandlers.value.plt.setAllZonesVisibility === 'function') {
+        return dataHandlers.value.plt.setAllZonesVisibility(false);
+      } else {
+        console.error('plt handler or setAllZonesVisibility method not available');
+        return false;
+      }
+    },
+    createGroup: (groupName, zones) =>
+      dataHandlers.value.plt?.createZoneGroup(groupName, zones),
+    setGroupVisibility: (groupName, visible) =>
+      dataHandlers.value.plt?.setGroupVisibility(groupName, visible),
+    getZoneList: () =>
+      dataHandlers.value.plt
+        ? Array.from(dataHandlers.value.plt.objectMap.keys())
+        : []
+  };
+
+  // 添加颜色映射控制方法
+  const updateColorMapping = (variableName, options = {}) => {
+    if (dataHandlers.value.plt) {
+      return dataHandlers.value.plt.updateColorMapping(variableName, options);
+    }
+    return false;
+  };
+
+
+  const animate = () => {
+    requestAnimationFrame(animate);
+    controls.value.update();
+    screenArrow.value?.update(); // 每帧更新箭头位置和方向
+    renderer.value.render(scene.value, camera.value);
+  };
+
+  const dispose = () => {
+    if (animationId) {
+      cancelAnimationFrame(animationId);
+    }
+
+    screenArrow.value?.dispose();
+    screenArrow.value = null;
+
+    if (objectManager.value) {
+      objectManager.value.dispose();
+      objectManager.value = null;
+    }
+
+    window.removeEventListener('resize', () =>
+      handleResize(canvasRef, camera, renderer));
+
+    if (controls) {
+      controls.value.dispose();
+    }
+
+    if (gui) {
+      gui.destroy();
+    }
+
+    disposeScene(scene.value);
+    disposeCamera(camera.value);
+    disposeRenderer(renderer.value);
+  };
+
+  // 响应式更新配置
+  watch(
+    () => props.sceneConfig,
+    (newConfig) => {
+      updateScene(scene, newConfig);
+    },
+    { deep: true }
+  );
+
+  watch(
+    () => props.cameraConfig,
+    (newConfig) => {
+      updateCamera(camera, newConfig);
+    },
+    { deep: true }
+  );
+
+  return {
+    init,
+    dispose,
+    scene,
+    camera,
+    renderer,
+    controls,
+    helpers,
+    handleResize,
+    updateCube,
+    loadFile,
+    updateData,
+    ...zoneControls,
+    updateColorMapping
+  };
+}

+ 0 - 0
src/composables/useThreeData.js


+ 98 - 0
src/store/modules/pltData.js

@@ -0,0 +1,98 @@
+// store/modules/pltData.js
+import { defineStore } from 'pinia';
+
+export const usePltDataStore = defineStore('pltData', {
+  state: () => ({
+    zones: [],
+    domains: {},
+    activeVariables: ['CoefPressure'],
+    colorMap: 'rainbow',
+    lastUpdated: null
+  }),
+
+  getters: {
+    domainNames(state) {
+      return state.zones.map(zone => zone.name);
+    },
+    visibleZones(state) {
+      return state.zones.filter(zone =>
+        state.domains[zone.name]?.visible !== false
+      );
+    },
+    domainConfig(state) {
+      return (name) => state.domains[name] || {
+        visible: true,
+        color: '#FFFFFF',
+        opacity: 1.0,
+        drawType: 'Surface'
+      };
+    }
+  },
+
+  actions: {
+    initialize(zones) {
+      this.zones = zones;
+      this.initDomains();
+      this.setDefaultHiddenDomains();
+      this.lastUpdated = Date.now();
+    },
+
+    setDefaultHiddenDomains() {
+      const domainsToHide = [
+        'far',
+        // 'fuse',
+        'symm',
+        // 'tail',
+        // 'wingl',
+        // 'wingle',
+        // 'wingte',
+        // 'wingu',
+      ];
+
+      domainsToHide.forEach(name => {
+        if (this.domains[name]) {
+          this.domains[name].visible = false;
+        }
+      });
+    },
+
+    initDomains() {
+      const colorPalette = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F06292'];
+
+      this.zones.forEach((zone, index) => {
+        if (!this.domains[zone.name]) {
+          this.domains[zone.name] = {
+            visible: true,
+            color: colorPalette[index % colorPalette.length],
+            opacity: 1.0,
+            drawType: 'Surface'
+          };
+        }
+      });
+    },
+
+    setDomainVisibility(name, visible) {
+      if (this.domains[name]) {
+        this.domains[name].visible = visible;
+        this.updateTimestamp();
+        this.triggerVisibilityChange()
+      }
+    },
+
+    triggerVisibilityChange() {
+      // 可用于需要监听可见性变化的组件
+      this.updateTimestamp()
+    },
+
+    setAllDomainsVisibility(visible) {
+      Object.keys(this.domains).forEach(name => {
+        this.domains[name].visible = visible;
+      });
+      this.updateTimestamp();
+    },
+
+    updateTimestamp() {
+      this.lastUpdated = Date.now();
+    },
+  }
+});

+ 0 - 32
src/store/modules/pltData.ts

@@ -1,32 +0,0 @@
-import { defineStore } from 'pinia'
-
-export const usePltDataStore = defineStore('pltData', {
-  state: () => ({
-    domains: {} as Record<string, { visible: boolean }>,
-    zones: [] as Array<{ 
-      name: string 
-      vertices?: any[]
-      indices?: any[]
-    }>
-  }),
-  actions: {
-    initializeDomains(names: string[]) {
-      names.forEach(name => {
-        if (!this.domains[name]) {
-          this.domains[name] = { visible: true }
-        }
-      });
-    },
-    toggleDomain(name: string) {
-      if (this.domains[name]) {
-        this.domains[name].visible = !this.domains[name].visible
-      }
-    },
-    setZones(zones: any[]) {
-      this.zones = zones
-    },
-    getDomainNames() {
-      return this.zones.map(zone => zone.name)
-    }
-  }
-})

+ 73 - 0
src/utils/three/controls/orbitControls.js

@@ -0,0 +1,73 @@
+// src/utils/three/controls/orbitControls.js
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+
+/**
+ * 设置轨道控制器
+ * @param {THREE.Camera} camera - 相机实例
+ * @param {HTMLElement} domElement - 渲染器的DOM元素
+ * @param {Object} [config={}] - 控制器配置
+ * @returns {OrbitControls} 轨道控制器实例
+ */
+export const setupControls = (camera, domElement, config = {}) => {
+  const controls = new OrbitControls(camera, domElement);
+  
+  // 默认配置
+  const defaultConfig = {
+    enableDamping: true,
+    dampingFactor: 0.05,
+    minDistance: 1,
+    maxDistance: 100,
+    maxPolarAngle: Math.PI,
+    minPolarAngle: 0,
+    enablePan: true,
+    enableZoom: true,
+    enableRotate: true
+  };
+  
+  // 合并配置
+  const mergedConfig = { ...defaultConfig, ...config };
+  
+  // 应用配置
+  Object.keys(mergedConfig).forEach(key => {
+    if (controls[key] !== undefined) {
+      controls[key] = mergedConfig[key];
+    }
+  });
+  
+  // 禁用右键菜单
+  domElement.addEventListener('contextmenu', (e) => e.preventDefault());
+  
+  return controls;
+};
+
+/**
+ * 更新控制器配置
+ * @param {OrbitControls} controls - 轨道控制器实例
+ * @param {Object} config - 新配置
+ */
+export const updateControls = (controls, config) => {
+  if (!controls) return;
+  
+  Object.keys(config).forEach(key => {
+    if (controls[key] !== undefined) {
+      controls[key] = config[key];
+    }
+  });
+};
+
+/**
+ * 销毁控制器
+ * @param {OrbitControls} controls - 轨道控制器实例
+ */
+export const disposeControls = (controls) => {
+  if (controls) {
+    controls.dispose();
+  }
+};
+
+// 统一导出
+export default {
+  setupControls,
+  updateControls,
+  disposeControls
+};

+ 29 - 0
src/utils/three/core/animationLoop.js

@@ -0,0 +1,29 @@
+/**
+ * 启动动画循环
+ * @param {THREE.Scene} scene - Three.js 场景对象
+ * @param {THREE.Camera} camera - Three.js 相机对象
+ * @param {THREE.WebGLRenderer} renderer - Three.js 渲染器对象
+ * @param {Object} [controls] - 控制器对象
+ * @param {Stats} [stats] - 性能统计对象
+ * @returns {number} 动画帧ID
+ */
+export function startAnimationLoop(scene, camera, renderer, controls) {
+  const animate = () => {
+    requestAnimationFrame(animate);
+    
+    // 确保使用原始值
+    const sceneValue = scene.value;
+    const cameraValue = camera.value;
+    const rendererValue = renderer.value;
+    
+    if (controls?.value) {
+      controls.value.update();
+    }
+    
+    if (sceneValue && cameraValue && rendererValue) {
+      rendererValue.render(sceneValue, cameraValue);
+    }
+  };
+  
+  return animate();
+}

+ 129 - 0
src/utils/three/core/cameraManager.js

@@ -0,0 +1,129 @@
+import * as THREE from 'three';
+
+export function createCamera(config = {}) {
+  let camera;
+  
+  if (config.type === 'orthographic') {
+    camera = new THREE.OrthographicCamera(
+      config.left || -1,
+      config.right || 1,
+      config.top || 1,
+      config.bottom || -1,
+      config.near || 0.1,
+      config.far || 1000
+    );
+  } else {
+    // 默认透视相机
+    camera = new THREE.PerspectiveCamera(
+      config.fov || 45,
+      window.innerWidth / window.innerHeight,
+      config.near || 0.1,
+      config.far || 1000
+    );
+  }
+  
+  camera.position.set(
+    config.position?.x || 0,
+    config.position?.y || 0,
+    config.position?.z || 1
+  );
+  
+  if (config.lookAt) {
+    camera.lookAt(
+      config.lookAt.x || 0,
+      config.lookAt.y || 0,
+      config.lookAt.z || 0
+    );
+  }
+  
+  return camera;
+}
+
+export function updateCamera(camera, config) {
+  if (config.position) {
+    camera.position.set(
+      config.position.x || 0,
+      config.position.y || 0,
+      config.position.z || camera.position.z
+    );
+  }
+  
+  if (config.lookAt) {
+    camera.lookAt(
+      config.lookAt.x || 0,
+      config.lookAt.y || 0,
+      config.lookAt.z || 0
+    );
+  }
+  
+  if (camera.isPerspectiveCamera && config.fov) {
+    camera.fov = config.fov;
+    camera.updateProjectionMatrix();
+  }
+}
+
+export function fitCameraToScene(camera, scene, params = {}) {
+  const {
+    padding = 1.5,
+    forceTopView = false,
+    minNear = 0.1,
+    maxFar = 1000
+  } = params;
+
+  // 计算包含可见物体的包围盒
+  const box = new THREE.Box3();
+  let hasVisibleObjects = false;
+  
+  scene.traverse(child => {
+    if (child.isMesh && child.visible) {
+      child.updateWorldMatrix(true, false); // 确保世界矩阵最新
+      box.expandByObject(child);
+      hasVisibleObjects = true;
+    }
+  });
+
+  if (!hasVisibleObjects) {
+    console.warn('没有可见物体,使用默认视角');
+    camera.position.set(0, 0, 5);
+    camera.lookAt(0, 0, 0);
+    return;
+  }
+
+  const size = box.getSize(new THREE.Vector3());
+  const center = box.getCenter(new THREE.Vector3());
+  const maxDim = Math.max(size.x, size.y, size.z);
+  const distance = padding * maxDim;
+
+  // 智能视角选择
+  if (forceTopView || size.y > size.x * 1.5) {
+    // 高模型或强制顶视图
+    camera.position.set(center.x, center.y + distance, center.z);
+    camera.up.set(0, 0, 1); // Z轴朝上
+  } else {
+    // 默认斜45°视角
+    camera.position.set(
+      center.x + distance * 0.7,
+      center.y + distance * 0.5,
+      center.z + distance * 0.7
+    );
+    camera.up.set(0, 1, 0); // Y轴朝上
+  }
+
+  // 动态裁剪面
+  camera.near = Math.max(minNear, maxDim * 0.01);
+  camera.far = Math.min(maxFar, maxDim * 100);
+  camera.lookAt(center);
+  camera.updateProjectionMatrix();
+
+  console.debug('相机调整参数:', {
+    position: camera.position.toArray(),
+    center: center.toArray(),
+    size: size.toArray(),
+    near: camera.near,
+    far: camera.far
+  });
+}
+
+export function disposeCamera(camera) {
+  // 相机通常没有需要手动释放的资源
+}

+ 18 - 0
src/utils/three/core/rendererManager.js

@@ -0,0 +1,18 @@
+import * as THREE from 'three';
+
+export function createRenderer(canvas, config = {}) {
+  const renderer = new THREE.WebGLRenderer({
+    canvas,
+    antialias: config.antialias ?? true,
+  });
+  
+  renderer.setPixelRatio(window.devicePixelRatio);
+  
+  return renderer;
+}
+
+export function disposeRenderer(renderer) {
+  if (renderer) {
+    renderer.dispose();
+  }
+}

+ 59 - 0
src/utils/three/core/sceneManager.js

@@ -0,0 +1,59 @@
+// src/utils/three/core/sceneManager.js
+import * as THREE from 'three';
+
+// 使用命名导出
+const createScene = (config = {}) => {
+  const scene = new THREE.Scene();
+  
+  if (config.backgroundColor) {  
+    scene.background = new THREE.Color(config.backgroundColor);
+  }
+  
+  if (config.fog) {
+    scene.fog = new THREE.Fog(
+      config.fogOptions.color || 0xffffff,
+      config.fogOptions.near || 1,
+      config.fogOptions.far || 100
+    );
+  }
+  
+  return scene;
+};
+
+const updateScene = (scene, config) => {
+  if (config.backgroundColor) {
+    scene.background = new THREE.Color(config.backgroundColor);
+  }
+  
+  if (config.fog) {
+    if (!scene.fog) {
+      scene.fog = new THREE.Fog();
+    }
+    scene.fog.color.set(config.fogOptions?.color || 0x000000);
+    scene.fog.near = config.fogOptions?.near || 1;
+    scene.fog.far = config.fogOptions?.far || 100;
+  } else if (scene.fog) {
+    scene.fog = null;
+  }
+};
+
+const disposeScene = (scene) => {
+  if (!scene) return;
+  
+  scene.traverse(object => {
+    if (object.isMesh) {
+      object.geometry?.dispose();
+      
+      if (object.material) {
+        if (Array.isArray(object.material)) {
+          object.material.forEach(material => material.dispose());
+        } else {
+          object.material.dispose();
+        }
+      }
+    }
+  });
+};
+
+// 或者可以使用这种方式统一导出
+export { createScene, updateScene, disposeScene };

+ 72 - 0
src/utils/three/dataHandlers/BaseDataHandler.js

@@ -0,0 +1,72 @@
+export class BaseDataHandler {
+  constructor(scene) {
+    this.scene = scene
+    this.objectMap = new Map() // 存储生成的三维对象
+    this.zoneGroups = new Map();
+    this._eventListeners = {};
+  }
+
+  emit(eventType, detail) {
+    this.dispatchEvent(new CustomEvent(eventType, {detail}));
+  }
+
+
+
+  parse(data) {
+    throw new Error('必须实现parse方法')
+  }
+
+  update(data) {
+    // 默认更新逻辑
+  }
+
+  highlight(id) {
+    // 对象高亮逻辑
+  }
+  clearAll() {
+    console.log('[Base] 清除所有对象');
+    this.objectMap.forEach(obj => {
+      if (obj.isMesh) {
+        this.scene.remove(obj);
+        obj.geometry?.dispose();
+        obj.material?.dispose();
+      }
+    });
+    this.objectMap.clear();
+  }
+
+  // 添加事件监听
+  on(eventType, callback) {
+    if (!this._eventListeners[eventType]) {
+      this._eventListeners[eventType] = [];
+    }
+    this._eventListeners[eventType].push(callback);
+  }
+
+  // 移除事件监听
+  off(eventType, callback) {
+    const listeners = this._eventListeners[eventType];
+    if (listeners) {
+      this._eventListeners[eventType] = listeners.filter(fn => fn !== callback);
+    }
+  }
+
+  // 发射事件
+  emit(eventType, detail) {
+    const listeners = this._eventListeners[eventType];
+    if (listeners) {
+      listeners.forEach(callback => {
+        try {
+          callback({ type: eventType, detail });
+        } catch (e) {
+          console.error(`Error in ${eventType} listener:`, e);
+        }
+      });
+    }
+  }
+
+  // 清除所有监听器
+  clearAllListeners() {
+    this._eventListeners = {};
+  }
+}

+ 0 - 0
src/utils/three/dataHandlers/EdgeMeshHandler.js


+ 419 - 0
src/utils/three/dataHandlers/PltHandler.js

@@ -0,0 +1,419 @@
+import * as THREE from 'three';
+import { BaseDataHandler } from '@/utils/three/dataHandlers/BaseDataHandler.js';
+import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+export class PltHandler extends BaseDataHandler {
+    constructor(scene) {
+        // 添加场景验证
+        if (!scene?.isScene) {
+            throw new Error('无效的scene参数,必须传入THREE.Scene实例');
+        }
+        super(scene);
+        this.parse = this.parse.bind(this); // 确保方法绑定
+        this.createZoneMesh = this.createZoneMesh.bind(this);
+        this.originalData = null;
+        this.extremeMarkers = new THREE.Group();
+        this.extremeMarkers.name = 'ExtremeMarkers';
+        this.scene.add(this.extremeMarkers);
+    }
+    parse(data, options = {}) {
+        if (!data?.zones) return;
+        const globalRanges = this.calculateGlobalRanges(data);
+        console.log('全局变量范围:', JSON.stringify(globalRanges, null, 2));
+        // 保存原始数据
+        this.originalData = JSON.parse(JSON.stringify(data)); // 深拷贝
+        // 先清空旧数据
+        this.clearAll();
+
+        // 设置默认标量变量(可从options中获取或使用第一个可用的)
+        const scalarVariable = options.scalarVariable ||
+            Object.keys(data.zones[0]?.variables || {})[0] ||
+            'CoefPressure';
+
+        // 并行处理所有区域
+        data.zones.forEach(zone => {
+            const mesh = this.createZoneMesh(zone, {
+                scalarVariable,
+                colorMap: options.colorMap,
+                minValue: globalRanges[options.scalarVariable]?.min,
+                maxValue: globalRanges[options.scalarVariable]?.max
+            });
+            if (mesh) {
+                this.scene.add(mesh);
+                this.objectMap.set(zone.name, mesh);
+
+                // 初始位置调整(如果需要)
+                mesh.position.set(0, 0, 0);
+                mesh.rotation.set(0, 0, 0);
+            }
+        });
+
+        const bbox = new THREE.Box3().setFromObject(this.scene);
+        const center = bbox.getCenter(new THREE.Vector3());
+        const size = bbox.getSize(new THREE.Vector3());
+
+        // 计算合适的相机位置
+        const maxDim = Math.max(size.x, size.y, size.z);
+        const camera = this.scene.parent?.camera; // 假设相机已关联到场景
+
+        if (camera?.isPerspectiveCamera) {
+            const fov = camera.fov * (Math.PI / 180);
+            const distance = maxDim / (2 * Math.tan(fov / 2)) * 1.5;
+
+            camera.position.copy(center).add(new THREE.Vector3(0, 0, distance));
+            camera.lookAt(center);
+            camera.updateProjectionMatrix();
+
+            console.log('相机调整参数:', {
+                position: camera.position.toArray(),
+                lookAt: center.toArray(),
+                distance
+            });
+        }
+    }
+
+    findZoneData(zoneName) {
+        if (!this.originalData?.zones) return null;
+        return this.originalData.zones.find(zone => zone.name === zoneName) || null;
+    }
+
+    createZoneMesh(zone, options = {}) {
+        console.group(`创建区域网格: ${zone.name}`);
+        try {
+            // 1. 验证顶点数据
+            console.assert(zone.vertices.length > 0, '顶点数据为空');
+            console.assert(zone.indices.length > 0, '索引数据为空');
+
+            // 2. 创建几何体
+            const geometry = new THREE.BufferGeometry();
+            geometry.setAttribute('position', new THREE.BufferAttribute(zone.vertices, 3));
+            geometry.setIndex(new THREE.BufferAttribute(zone.indices, 1));
+            geometry.computeVertexNormals(); // 重要:确保法线存在
+
+
+            // 3. 创建测试材质
+            const material = new THREE.MeshPhongMaterial({
+                color: 0xffffff,
+                specular: 0x111111,
+                shininess: 30,
+                side: THREE.DoubleSide,
+                flatShading: false,
+                vertexColors: true, // 必须启用
+                transparent: true,
+                opacity: 1.0
+            });
+
+            // 如果数据有变量,应用颜色映射
+            if (zone.variables) {
+                // 使用传入的 scalarVariable 或第一个可用变量
+                const scalarVariable = options.scalarVariable ||
+                    Object.keys(zone.variables)[0];
+
+                if (scalarVariable && zone.variables[scalarVariable]) {
+                    const colors = this.generateVertexColors(
+                        zone.variables[scalarVariable],
+                        options.colorMap || 'rainbow',
+                        options.minValue,
+                        options.maxValue
+                    );
+                    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
+                }
+
+                Object.entries(zone.variables).forEach(([name, data]) => {
+                    const min = Math.min(...data);
+                    const max = Math.max(...data);
+                    console.log(`变量 ${name} 范围: [${min}, ${max}]`);
+                });
+            }
+
+            // 强制材质更新
+            material.needsUpdate = true;
+
+            // 4. 计算法线和包围盒
+            geometry.computeVertexNormals();
+            geometry.computeBoundingSphere();
+
+            // 4. 创建网格
+            const mesh = new THREE.Mesh(geometry, material);
+            mesh.userData = {
+                zoneName: zone.name,  // 保存区域名称用于后续查询
+                originalZone: zone    // 保存原始数据引用
+            };
+            return mesh;
+        } catch (e) {
+            console.error('创建网格失败:', e);
+            return null;
+        } finally {
+            console.groupEnd();
+        }
+    }
+
+    calculateGlobalRanges(data) {
+        const ranges = {};
+        data.zones.forEach(zone => {
+            Object.entries(zone.variables || {}).forEach(([name, values]) => {
+                if (!ranges[name]) {
+                    ranges[name] = { min: Infinity, max: -Infinity };
+                }
+
+                // 对速度场采用对称范围处理
+                if (name.startsWith('Velocity')) {
+                    const absMax = Math.max(...values.map(Math.abs));
+                    ranges[name].min = -absMax;
+                    ranges[name].max = absMax;
+                } else {
+                    ranges[name].min = Math.min(ranges[name].min, ...values);
+                    ranges[name].max = Math.max(ranges[name].max, ...values);
+                }
+            });
+        });
+        return ranges;
+    }
+
+    /**
+ * 生成顶点颜色数组
+ * @param {number[]} data - 标量数据数组
+ * @param {string} colorMap - 颜色映射方案 ('jet', 'hot', 'cool', 'rainbow', 'grayscale')
+ * @param {number} [min] - 数据最小值(可选)
+ * @param {number} [max] - 数据最大值(可选)
+ * @returns {Float32Array} - 顶点颜色数组(RGB格式)
+ */
+    generateVertexColors(data, colorMap = 'rainbow', min, max) {
+        // 特殊处理速度场
+        const isVelocity = colorMap.startsWith('Velocity');
+
+        // 强制增强速度场的颜色对比度
+        if (isVelocity) {
+            // 使用对数缩放增强小值差异
+            const absData = data.map(Math.abs);
+            const logMax = Math.log(Math.max(...absData.filter(v => v > 0))) || 1;
+            const scaledData = data.map(v => {
+                const sign = Math.sign(v);
+                const absV = Math.abs(v);
+                return sign * (absV > 0 ? Math.log(absV) / logMax : 0);
+            });
+
+            min = -1;
+            max = 1;
+            data = scaledData;
+            colorMap = 'diverging'; // 使用自定义发散色标
+        }
+
+        // 原始处理逻辑
+        min = min !== undefined ? min : Math.min(...data);
+        max = max !== undefined ? max : Math.max(...data);
+        const range = max - min || 1;
+
+        const colors = new Float32Array(data.length * 3);
+        for (let i = 0; i < data.length; i++) {
+            const normalized = (data[i] - min) / range;
+            const [r, g, b] = this.getColorByScheme(normalized, colorMap);
+            colors[i * 3] = r;
+            colors[i * 3 + 1] = g;
+            colors[i * 3 + 2] = b;
+        }
+
+        return colors;
+    }
+    /**
+ * 根据归一化值和颜色方案获取RGB颜色
+ * @param {number} t - 归一化值 [0, 1]
+ * @param {string} scheme - 颜色方案名称
+ * @returns {number[]} - [r, g, b] 范围[0,1]
+ */
+    getColorByScheme(t, scheme) {
+        // 确保t在[0,1]范围内
+        t = Math.max(0, Math.min(1, t));
+
+        switch (scheme.toLowerCase()) {
+            case 'jet':
+                return this.jetColorMap(t);
+            case 'hot':
+                return this.hotColorMap(t);
+            case 'cool':
+                return this.coolColorMap(t);
+            case 'rainbow':
+                return this.rainbowColorMap(t);
+            case 'grayscale':
+                return [t, t, t];
+            default:
+                return this.jetColorMap(t);
+        }
+    }
+    // Jet颜色映射 (蓝-青-黄-红)
+    jetColorMap(t) {
+        if (scheme === 'diverging') {
+            if (t < 0.5) {
+                return [0, 0, 0.5 + t * 1.5]; // 蓝到青
+            } else {
+                return [1.5 * (t - 0.5), 1.5 * (t - 0.5), 1 - 1.5 * (t - 0.5)]; // 青到红
+            }
+        }
+        if (t < 0.125) {
+            return [0, 0, 0.5 + t * 4];
+        } else if (t < 0.375) {
+            return [0, (t - 0.125) * 4, 1];
+        } else if (t < 0.625) {
+            return [(t - 0.375) * 4, 1, 1 - (t - 0.375) * 4];
+        } else if (t < 0.875) {
+            return [1, 1 - (t - 0.625) * 4, 0];
+        } else {
+            return [1 - (t - 0.875) * 4, 0, 0];
+        }
+    }
+
+    // Hot颜色映射 (黑-红-黄-白)
+    hotColorMap(t) {
+        if (t < 0.4) {
+            return [t / 0.4, 0, 0];
+        } else if (t < 0.8) {
+            return [1, (t - 0.4) / 0.4, 0];
+        } else {
+            return [1, 1, (t - 0.8) / 0.2];
+        }
+    }
+
+    // Cool颜色映射 (青-品红)
+    coolColorMap(t) {
+        return [t, 1 - t, 1];
+    }
+
+    // Rainbow颜色映射 (紫-蓝-青-绿-黄-红)
+    rainbowColorMap(t) {
+        if (t < 0.25) {
+            return [0, t * 4, 1];
+        } else if (t < 0.5) {
+            return [0, 1, 1 - (t - 0.25) * 4];
+        } else if (t < 0.75) {
+            return [(t - 0.5) * 4, 1, 0];
+        } else {
+            return [1, 1 - (t - 0.75) * 4, 0];
+        }
+    }
+
+    // 更新颜色映射方法
+    updateColorMapping(variableName, options = {}) {
+        if (!this.scene || !this.objectMap) {
+            console.error('Scene or objectMap not initialized');
+            return false;
+        }
+
+        let updated = false;
+
+        this.objectMap.forEach((mesh, zoneName) => {
+            const zone = mesh.userData.originalZone; // 直接从mesh中获取原始数据
+
+            if (!zone?.variables?.[variableName]) {
+                console.warn(`变量 ${variableName} 在区域 ${zoneName} 中不存在`);
+                return;
+            }
+
+            const colors = this.generateVertexColors(
+                zone.variables[variableName],
+                options.colorMap || 'rainbow',
+                options.min,
+                options.max
+            );
+
+            if (mesh.geometry) {
+                if (!mesh.geometry.attributes.color) {
+                    mesh.geometry.setAttribute(
+                        'color',
+                        new THREE.BufferAttribute(colors, 3)
+                    );
+                } else {
+                    mesh.geometry.attributes.color.array = colors;
+                    mesh.geometry.attributes.color.needsUpdate = true;
+                }
+
+                if (mesh.material) {
+                    mesh.material.vertexColors = true;
+                    mesh.material.needsUpdate = true;
+                }
+
+                updated = true;
+            }
+        });
+
+        return updated;
+    }
+
+
+    // 域控制
+    setZoneVisibility(zoneName, visible) {
+        const mesh = this.objectMap.get(zoneName);
+        if (!mesh) {
+            console.warn(`Zone ${zoneName} not found`);
+            return false;
+        }
+
+        // 避免不必要的更新
+        if (mesh.visible === visible) return true;
+
+        mesh.visible = visible;
+
+        // 触发事件(新增source标记)
+        this.emit('visibilityChange', {
+            zoneName,
+            visible,
+            source: 'manual',
+            timestamp: Date.now()
+        });
+
+        return true;
+    }
+    // 取单个域状态
+    getZoneVisibility(zoneName) {
+        const mesh = this.objectMap.get(zoneName);
+        return mesh?.visible ?? false;
+    }
+
+    setAllZonesVisibility(visible) {
+        let success = true;
+        this.objectMap.forEach((mesh, name) => {
+            if (!this.setZoneVisibility(name, visible)) {
+                success = false;
+            }
+        });
+        return success;
+    }
+
+    /**
+   * 批量控制域可见性
+   * @param {string[]} zoneNames 要控制的域名称数组
+   * @param {boolean} visible 是否显示
+   */
+    setZonesVisibility(zoneNames, visible) {
+        zoneNames.forEach(name => this.setZoneVisibility(name, visible));
+    }
+
+    /**
+   * 创建域分组(用于批量控制)
+   * @param {string} groupName 分组名称
+   * @param {string[]} zoneNames 包含的域名称数组
+   */
+    createZoneGroup(groupName, zoneNames) {
+        this.zoneGroups.set(groupName, new Set(zoneNames));
+    }
+
+    /**
+     * 控制分组可见性
+     * @param {string} groupName 分组名称
+     * @param {boolean} visible 是否显示
+     */
+    setGroupVisibility(groupName, visible) {
+        const zones = this.zoneGroups.get(groupName);
+        if (zones) {
+            zones.forEach(zone => this.setZoneVisibility(zone, visible));
+        }
+    }
+    /**
+     * 获取所有域状态
+     * @returns {Array<{name: string, visible: boolean}>}
+     */
+    getAllZonesVisibility() {
+        return Array.from(this.objectMap.entries()).map(([name, mesh]) => ({
+            name,
+            visible: mesh.visible
+        }));
+    }
+}

+ 0 - 0
src/utils/three/dataHandlers/PointCloudHandler.js


+ 20 - 0
src/utils/three/dataHandlers/TopologyHandler.js

@@ -0,0 +1,20 @@
+export class TopologyHandler extends BaseDataHandler {
+  parse(data) {
+    // 1. 根据拓扑数据生成颜色材质
+    const material = createMaterial({
+      type: 'meshPhong',
+      colorMap: data.colorMap
+    })
+    
+    // 2. 构建几何体
+    const geometry = new THREE.BufferGeometry()
+    geometry.setAttribute('position', new Float32BufferAttribute(data.vertices, 3))
+    
+    // 3. 生成可交互对象
+    const mesh = new THREE.Mesh(geometry, material)
+    mesh.userData = { originalData: data }
+    
+    this.scene.add(mesh)
+    this.objectMap.set(data.id, mesh)
+  }
+}

+ 178 - 0
src/utils/three/gui/guiManager.js

@@ -0,0 +1,178 @@
+import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
+
+export function setupGUI(scene, camera, objects = []) {
+  const gui = new GUI();
+  
+  // 场景设置
+  const sceneFolder = gui.addFolder('Scene');
+  if (scene.background) {
+    sceneFolder.addColor({ background: scene.background.getHex() }, 'background')
+      .name('Background')
+      .onChange(color => {
+        scene.background.setHex(color);
+      });
+  }
+  
+  if (scene.fog) {
+    sceneFolder.addColor({ fogColor: scene.fog.color.getHex() }, 'fogColor')
+      .name('Fog Color')
+      .onChange(color => {
+        scene.fog.color.setHex(color);
+      });
+    
+    sceneFolder.add(scene.fog, 'near', 0, 100).name('Fog Near');
+    sceneFolder.add(scene.fog, 'far', 0, 1000).name('Fog Far');
+  }
+  
+  // 相机设置
+  const cameraFolder = gui.addFolder('Camera');
+  cameraFolder.add(camera.position, 'x', -10, 10).name('Position X');
+  cameraFolder.add(camera.position, 'y', -10, 10).name('Position Y');
+  cameraFolder.add(camera.position, 'z', 0, 20).name('Position Z');
+  
+  if (camera.isPerspectiveCamera) {
+    cameraFolder.add(camera, 'fov', 20, 120).name('Field of View');
+  }
+  
+  // 对象控制
+  objects.forEach((obj, index) => {
+    // 处理ObjectManager返回的对象结构
+    const mesh = obj.mesh || obj;
+    const updateParams = obj.updateParams || (() => {});
+    
+    const objName = mesh.name || `Object ${index + 1}`;
+    const objFolder = gui.addFolder(objName);
+    objFolder.open();
+    
+    // 通用属性
+    objFolder.add({ name: objName }, 'name')
+      .name('Object Name')
+      .onChange(name => {
+        mesh.name = name;
+        objFolder.title = name;
+      });
+    
+    objFolder.add(mesh, 'visible').name('Visible');
+    
+    // 位置
+    const positionParams = {
+      x: mesh.position.x,
+      y: mesh.position.y,
+      z: mesh.position.z
+    };
+    
+    const positionFolder = objFolder.addFolder('Position');
+    positionFolder.add(positionParams, 'x', -10, 10).name('X')
+      .onChange(val => {
+        mesh.position.x = val;
+        updateParams({ position: positionParams });
+      });
+    positionFolder.add(positionParams, 'y', -10, 10).name('Y')
+      .onChange(val => {
+        mesh.position.y = val;
+        updateParams({ position: positionParams });
+      });
+    positionFolder.add(positionParams, 'z', -10, 10).name('Z')
+      .onChange(val => {
+        mesh.position.z = val;
+        updateParams({ position: positionParams });
+      });
+    
+    // 旋转
+    const rotationParams = {
+      x: mesh.rotation.x,
+      y: mesh.rotation.y,
+      z: mesh.rotation.z
+    };
+    
+    const rotationFolder = objFolder.addFolder('Rotation');
+    rotationFolder.add(rotationParams, 'x', 0, Math.PI * 2).name('X (rad)')
+      .onChange(val => {
+        mesh.rotation.x = val;
+        updateParams({ rotation: rotationParams });
+      });
+    rotationFolder.add(rotationParams, 'y', 0, Math.PI * 2).name('Y (rad)')
+      .onChange(val => {
+        mesh.rotation.y = val;
+        updateParams({ rotation: rotationParams });
+      });
+    rotationFolder.add(rotationParams, 'z', 0, Math.PI * 2).name('Z (rad)')
+      .onChange(val => {
+        mesh.rotation.z = val;
+        updateParams({ rotation: rotationParams });
+      });
+    
+    // 缩放
+    const scaleParams = {
+      x: mesh.scale.x,
+      y: mesh.scale.y,
+      z: mesh.scale.z
+    };
+    
+    const scaleFolder = objFolder.addFolder('Scale');
+    scaleFolder.add(scaleParams, 'x', 0.1, 3).name('X')
+      .onChange(val => {
+        mesh.scale.x = val;
+        updateParams({ scale: scaleParams });
+      });
+    scaleFolder.add(scaleParams, 'y', 0.1, 3).name('Y')
+      .onChange(val => {
+        mesh.scale.y = val;
+        updateParams({ scale: scaleParams });
+      });
+    scaleFolder.add(scaleParams, 'z', 0.1, 3).name('Z')
+      .onChange(val => {
+        mesh.scale.z = val;
+        updateParams({ scale: scaleParams });
+      });
+    
+    // 材质
+    if (mesh.material) {
+      const materialFolder = objFolder.addFolder('Material');
+      materialFolder.open();
+      
+      const materialParams = {
+        color: mesh.material.color.getHex(),
+        wireframe: mesh.material.wireframe,
+        shininess: mesh.material.shininess || 30,
+        opacity: mesh.material.opacity || 1.0,
+        transparent: mesh.material.transparent || false
+      };
+      
+      materialFolder.addColor(materialParams, 'color').name('Color')
+        .onChange(color => {
+          mesh.material.color.setHex(color);
+          updateParams({ color });
+        });
+      
+      materialFolder.add(materialParams, 'wireframe').name('Wireframe')
+        .onChange(val => {
+          mesh.material.wireframe = val;
+          updateParams({ wireframe: val });
+        });
+      
+      if (mesh.material.shininess !== undefined) {
+        materialFolder.add(materialParams, 'shininess', 0, 200).name('Shininess')
+          .onChange(val => {
+            mesh.material.shininess = val;
+            updateParams({ shininess: val });
+          });
+      }
+      
+      materialFolder.add(materialParams, 'opacity', 0, 1).name('Opacity')
+        .onChange(val => {
+          mesh.material.opacity = val;
+          updateParams({ opacity: val });
+        });
+      
+      materialFolder.add(materialParams, 'transparent').name('Transparent')
+        .onChange(val => {
+          mesh.material.transparent = val;
+          updateParams({ transparent: val });
+        });
+    }
+  });
+  
+  
+  return gui;
+}

+ 146 - 0
src/utils/three/helpers/axesHelper.js

@@ -0,0 +1,146 @@
+import * as THREE from 'three';
+import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+
+export class ScreenFixedArrow {
+  constructor(scene, camera, targetObject, options = {}) {
+    this.scene = scene;
+    this.camera = camera;
+    this.targetObject = targetObject;
+
+    // 默认配置
+    this.options = {
+      screenPosition: new THREE.Vector2(-0.8, -0.6), // 屏幕左下角
+      axisLength: 0.03,                              // 箭头长度
+      labelColor: 'white',                          // 标签颜色
+      labelSize: '16px',                           // 标签字体大小
+      ...options
+    };
+
+    // 创建三组箭头(X/Y/Z)
+    this.arrows = {
+      x: this._createArrow(0xff0000, new THREE.Vector3(1, 0, 0)), // 红-X
+      y: this._createArrow(0x00ff00, new THREE.Vector3(0, 1, 0)), // 绿-Y
+      z: this._createArrow(0x0000ff, new THREE.Vector3(0, 0, 1))  // 蓝-Z
+    };
+
+    // 创建文字标签
+    this.labels = this._createLabels();
+
+    this.update();
+  }
+
+  // 创建单根箭头
+  _createArrow(color, direction) {
+    const arrow = new THREE.ArrowHelper(
+      direction,
+      new THREE.Vector3(),
+      this.options.axisLength,
+      color,
+      this.options.axisLength * 0.3 // 头部长度
+    );
+    this.scene.add(arrow);
+    return arrow;
+  }
+
+  // 创建文字标签
+  _createLabels() {
+    const labels = {};
+    const labelOffset = 0.15;
+    ['x', 'y', 'z'].forEach(axis => {
+      const div = document.createElement('div');
+      div.textContent = axis.toUpperCase();
+      div.style.color = this.options.labelColor;
+      div.style.fontSize = this.options.labelSize;
+      div.style.fontFamily = 'Arial, sans-serif';
+      div.style.fontWeight = 'bold';
+      div.style.pointerEvents = 'none';
+
+      const label = new CSS2DObject(div);
+      label.userData.offset = labelOffset
+      labels[axis] = label;
+      this.scene.add(label);
+    });
+    return labels;
+  }
+
+  // 更新箭头和标签位置
+  update() {
+    // 1. 计算屏幕固定基点
+    const ndc = new THREE.Vector3(
+      this.options.screenPosition.x,
+      this.options.screenPosition.y + 0.15,
+      0.5
+    );
+    const worldPos = ndc.unproject(this.camera);
+
+    // 2. 更新箭头和标签
+    Object.keys(this.arrows).forEach(axis => {
+      const arrow = this.arrows[axis];
+      const label = this.labels?.[axis];
+
+      // --- 箭头更新 ---
+      arrow.position.copy(worldPos);
+      const dir = new THREE.Vector3()
+        .setComponent(['x', 'y', 'z'].indexOf(axis), 1)
+        .applyQuaternion(this.targetObject.quaternion)
+        .normalize();
+      arrow.setDirection(dir);
+
+      // --- 标签位置计算(精准版)---
+      if (label) {
+        // 基础位置计算(1.2倍箭头长度)
+        const labelPos = worldPos.clone()
+          .add(dir.clone().multiplyScalar(this.options.axisLength * 1.2));
+
+        // 应用位置
+        label.position.copy(labelPos);
+
+        // --- 动态微调(所有轴补偿)---
+        const labelEl = label.element;
+
+        // 重置样式
+        labelEl.style.transform = 'translate(-50%, -50%)'; // 中心对齐
+        labelEl.style.margin = '0';
+
+        // 各轴独立补偿(单位:px)
+        const compensations = {
+          x: { left: 40, top: 85 },   // X轴:右移10px
+          y: { left: 60, top: 88 }, // Y轴:上移10px 
+          z: { left: 45, top: 88 }    // Z轴:下移5px
+        };
+
+        // 获取当前主导轴(绝对值最大的分量)
+        const absDir = {
+          x: Math.abs(dir.x),
+          y: Math.abs(dir.y),
+          z: Math.abs(dir.z)
+        };
+        const dominantAxis = ['x', 'y', 'z'].reduce((a, b) =>
+          absDir[a] > absDir[b] ? a : b
+        );
+
+        // 应用补偿
+        labelEl.style.marginLeft = `${compensations[dominantAxis].left}px`;
+        labelEl.style.marginTop = `${compensations[dominantAxis].top}px`;
+      }
+    });
+  }
+
+  dispose() {
+    // 移除箭头
+    Object.values(this.arrows).forEach(arrow => {
+      this.scene.remove(arrow);
+    });
+
+    // 移除标签
+    Object.values(this.labels).forEach(label => {
+      this.scene.remove(label);
+      if (label.element.parentNode) {
+        label.element.parentNode.removeChild(label.element);
+      }
+    });
+
+    this.arrows = null;
+    this.labels = null;
+  }
+}

+ 14 - 0
src/utils/three/helpers/gridHelper.js

@@ -0,0 +1,14 @@
+import * as THREE from 'three';
+
+/**
+ * 设置网格辅助
+ * @param {THREE.Scene} scene - Three.js 场景对象
+ * @param {number} [size=10] - 网格大小
+ * @param {number} [divisions=10] - 分割数
+ * @returns {THREE.GridHelper} 网格辅助对象
+ */
+export function setupGridHelper(scene, size = 10, divisions = 10) {
+  const gridHelper = new THREE.GridHelper(size, divisions);
+  scene.add(gridHelper);
+  return gridHelper;
+}

+ 50 - 0
src/utils/three/helpers/index.js

@@ -0,0 +1,50 @@
+// src/utils/three/helpers/index.js
+import * as THREE from 'three';
+import { setupAxesHelper } from './axesHelper';
+import { setupGridHelper } from './gridHelper';
+import { setupStatsHelper } from './statsHelper';
+
+/**
+ * 设置场景辅助工具
+ * @param {THREE.Scene} scene 
+ * @param {Object} config 
+ * @returns {Object} 辅助工具实例
+ */
+export function setupHelpers(scene, config = {}) {
+  const helpers = {};
+  
+  if (config.axesHelper !== false) {
+    helpers.axesHelper = setupAxesHelper(scene, config.axesHelperSize);
+  }
+  
+  if (config.gridHelper !== false) {
+    helpers.gridHelper = setupGridHelper(scene, config.gridHelperSize);
+  }
+  
+  if (config.statsHelper) {
+    helpers.statsHelper = setupStatsHelper();
+  }
+  
+  return helpers;
+}
+
+/**
+ * 销毁辅助工具
+ * @param {THREE.Scene} scene 
+ * @param {Object} helpers 
+ */
+export function disposeHelpers(scene, helpers) {
+  if (!helpers) return;
+  
+  if (helpers.axesHelper) {
+    scene.remove(helpers.axesHelper);
+  }
+  
+  if (helpers.gridHelper) {
+    scene.remove(helpers.gridHelper);
+  }
+  
+  if (helpers.statsHelper) {
+    helpers.statsHelper.domElement.remove();
+  }
+}

+ 19 - 0
src/utils/three/helpers/statsHelper.js

@@ -0,0 +1,19 @@
+import Stats from 'three/examples/jsm/libs/stats.module.js';
+
+/**
+ * 设置性能统计辅助
+ * @returns {Stats} 性能统计对象
+ */
+export function setupStatsHelper() {
+  const stats = new Stats();
+  stats.showPanel(0); // 0: fps, 1: ms, 2: mb
+  document.body.appendChild(stats.dom);
+  
+  // 调整样式避免遮挡
+  stats.dom.style.position = 'absolute';
+  stats.dom.style.left = '0px';
+  stats.dom.style.top = '0px';
+  stats.dom.style.zIndex = '1000';
+  
+  return stats;
+}

+ 0 - 0
src/utils/three/interactivity/EventDispatcher.js


+ 17 - 0
src/utils/three/interactivity/SelectionManager.js

@@ -0,0 +1,17 @@
+export class SelectionManager {
+  constructor(renderer, camera) {
+    this.raycaster = new THREE.Raycaster()
+    this.selected = null
+  }
+
+  checkIntersection(scene, mousePosition) {
+    this.raycaster.setFromCamera(mousePosition, camera)
+    const intersects = this.raycaster.intersectObjects(scene.children)
+    
+    if (intersects.length > 0) {
+      this.selected = intersects[0].object
+      return intersects[0].object.userData.originalData
+    }
+    return null
+  }
+}

+ 17 - 0
src/utils/three/lights/ambientLight.js

@@ -0,0 +1,17 @@
+// 环境光
+
+import * as THREE from 'three';
+
+export function createAmbientLight(config = {}) {
+    const light = new THREE.AmbientLight(
+        config.color || 0x404040,  // 默认灰色环境光
+        config.intensity || 1      // 默认强度
+    );
+    light.name = config.name || 'ambientLight';
+    return light;
+}
+
+export function updateAmbientLight(light, config) {
+    if (config.color) light.color.set(config.color);
+    if (config.intensity !== undefined) light.intensity = config.intensity;
+}

+ 30 - 0
src/utils/three/lights/directionalLight.js

@@ -0,0 +1,30 @@
+// 平行光
+
+import * as THREE from 'three';
+
+export function createDirectionalLight(config = {}) {
+    const light = new THREE.DirectionalLight(
+        config.color || 0xffffff,
+        config.intensity || 0.5
+    );
+    light.name = config.name || 'directionalLight';
+    light.position.set(
+        config.position?.x || 1,
+        config.position?.y || 1,
+        config.position?.z || 1
+    );
+    light.castShadow = config.castShadow || true;
+    
+    // 阴影参数配置
+    if (light.castShadow) {
+        light.shadow.mapSize.width = config.shadow?.mapSize || 1024;
+        light.shadow.mapSize.height = config.shadow?.mapSize || 1024;
+    }
+    return light;
+}
+
+export function updateDirectionalLight(light, config) {
+    if (config.color) light.color.set(config.color);
+    if (config.intensity !== undefined) light.intensity = config.intensity;
+    if (config.position) light.position.set(config.position.x, config.position.y, config.position.z);
+}

+ 48 - 0
src/utils/three/lights/lightManager.js

@@ -0,0 +1,48 @@
+import { createAmbientLight, updateAmbientLight } from './ambientLight';
+import { createDirectionalLight, updateDirectionalLight } from './directionalLight';
+import { createPointLight } from './pointLight';
+import { createSpotLight } from './spotLight';
+
+export function setupLights(scene, config = {}) {
+    const lights = {};
+    
+    // 环境光(默认必加)
+    if (config.ambient !== false) {
+        lights.ambient = createAmbientLight(config.ambient || {});
+        scene.add(lights.ambient);
+    }
+
+    // 平行光(默认主光源)
+    if (config.directional !== false) {
+        lights.directional = createDirectionalLight(config.directional || {});
+        scene.add(lights.directional);
+        if (config.directional?.target) {
+            scene.add(lights.directional.target);
+        }
+    }
+
+    // 点光源(按需添加)
+    if (config.point) {
+        lights.point = createPointLight(config.point);
+        scene.add(lights.point);
+    }
+
+    // 聚光灯(按需添加)
+    if (config.spot) {
+        lights.spot = createSpotLight(config.spot);
+        scene.add(lights.spot);
+        scene.add(lights.spot.target);
+    }
+
+    return lights;
+}
+
+export function updateLights(lights, config) {
+    if (config.ambient && lights.ambient) {
+        updateAmbientLight(lights.ambient, config.ambient);
+    }
+    if (config.directional && lights.directional) {
+        updateDirectionalLight(lights.directional, config.directional);
+    }
+    // 其他光源更新逻辑...
+}

+ 17 - 0
src/utils/three/lights/pointLight.js

@@ -0,0 +1,17 @@
+import * as THREE from 'three';
+
+export function createPointLight(config = {}) {
+    const light = new THREE.PointLight(
+        config.color || 0xffffff,
+        config.intensity || 1,
+        config.distance || 0,
+        config.decay || 2
+    );
+    light.name = config.name || 'pointLight';
+    light.position.set(
+        config.position?.x || 0,
+        config.position?.y || 0,
+        config.position?.z || 0
+    );
+    return light;
+}

+ 24 - 0
src/utils/three/lights/spotLight.js

@@ -0,0 +1,24 @@
+import * as THREE from 'three';
+
+export function createSpotLight(config = {}) {
+    const light = new THREE.SpotLight(
+        config.color || 0xffffff,
+        config.intensity || 1,
+        config.distance || 0,
+        config.angle || Math.PI / 4,
+        config.penumbra || 0,
+        config.decay || 1
+    );
+    light.name = config.name || 'spotLight';
+    light.position.set(
+        config.position?.x || 0,
+        config.position?.y || 0,
+        config.position?.z || 0
+    );
+    light.target.position.set(
+        config.target?.x || 0,
+        config.target?.y || 0,
+        config.target?.z || 0
+    );
+    return light;
+}

+ 51 - 0
src/utils/three/materials/ContourMaterial.js

@@ -0,0 +1,51 @@
+import * as THREE from 'three';
+
+export class ContourMaterial extends THREE.ShaderMaterial {
+  constructor(options = {}) {
+    const uniforms = {
+      u_dataMin: { value: options.minValue || 0 },
+      u_dataMax: { value: options.maxValue || 1 },
+      u_spacing: { value: options.spacing || 0.1 },
+      u_width: { value: options.width || 0.02 },
+      u_color: { value: new THREE.Color(options.color || 0x000000) },
+      u_opacity: { value: options.opacity || 1.0 }
+    };
+
+    super({
+      uniforms,
+      vertexShader: `
+        attribute float a_variable; // 从几何体传递的标量值
+        varying float v_variable;
+        void main() {
+          v_variable = a_variable;
+          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+        }
+      `,
+      fragmentShader: `
+        uniform float u_dataMin;
+        uniform float u_dataMax;
+        uniform float u_spacing;
+        uniform float u_width;
+        uniform vec3 u_color;
+        uniform float u_opacity;
+        varying float v_variable;
+        
+        void main() {
+          // 归一化数据值
+          float normalized = (v_variable - u_dataMin) / (u_dataMax - u_dataMin);
+          // 计算等值线位置
+          float contourPos = fract(normalized / u_spacing);
+          // 判断是否在等值线范围内
+          if (contourPos < u_width || contourPos > (1.0 - u_width)) {
+            gl_FragColor = vec4(u_color, u_opacity);
+          } else {
+            discard;
+          }
+        }
+      `,
+      transparent: true,
+      side: THREE.DoubleSide,
+      depthTest: true
+    });
+  }
+}

+ 99 - 0
src/utils/three/materials/materialManager.js

@@ -0,0 +1,99 @@
+// src/utils/three/materials/materialManager.js
+import * as THREE from 'three';
+
+// 基础材质预设
+export const materialPresets = {
+  standard: (params = {}) => new THREE.MeshStandardMaterial({
+    color: 0xffffff,
+    roughness: 0.7,
+    metalness: 0.3,
+    ...params
+  }),
+  phong: (params = {}) => new THREE.MeshPhongMaterial({
+    color: 0xffffff,
+    specular: 0x111111,
+    shininess: 30,
+    ...params
+  }),
+  basic: (params = {}) => new THREE.MeshBasicMaterial({
+    color: 0xffffff,
+    ...params
+  }),
+  lambert: (params = {}) => new THREE.MeshLambertMaterial({
+    color: 0xffffff,
+    ...params
+  }),
+  physical: (params = {}) => new THREE.MeshPhysicalMaterial({
+    color: 0xffffff,
+    roughness: 0.5,
+    metalness: 0.5,
+    clearcoat: 1,
+    clearcoatRoughness: 0.1,
+    ...params
+  })
+};
+
+/**
+ * 创建材质
+ * @param {string} type - 材质类型 (standard/phong/basic/lambert/physical)
+ * @param {Object} params - 材质参数
+ * @returns {THREE.Material} 材质实例
+ */
+export const createMaterial = (type = 'standard', params = {}) => {
+  const preset = materialPresets[type.toLowerCase()];
+  if (!preset) {
+    console.warn(`Unknown material type: ${type}. Falling back to standard`);
+    return materialPresets.standard(params);
+  }
+  return preset(params);
+};
+
+/**
+ * 更新材质
+ * @param {THREE.Material} material - 要更新的材质
+ * @param {Object} params - 新参数
+ */
+export const updateMaterial = (material, params) => {
+  if (!material) return;
+  
+  Object.keys(params).forEach(key => {
+    if (material[key] !== undefined) {
+      // 特殊处理颜色属性
+      if (key === 'color' && params[key] !== undefined) {
+        material.color.set(params[key]);
+      } else {
+        material[key] = params[key];
+      }
+    }
+  });
+  
+  material.needsUpdate = true;
+};
+
+/**
+ * 克隆材质
+ * @param {THREE.Material} material - 要克隆的材质
+ * @returns {THREE.Material} 新材质实例
+ */
+export const cloneMaterial = (material) => {
+  return material.clone();
+};
+
+/**
+ * 销毁材质
+ * @param {THREE.Material} material - 要销毁的材质
+ */
+export const disposeMaterial = (material) => {
+  if (material) {
+    material.dispose();
+  }
+};
+
+// 统一导出
+export default {
+  createMaterial,
+  updateMaterial,
+  cloneMaterial,
+  disposeMaterial,
+  presets: materialPresets
+};

+ 0 - 0
src/utils/three/objects/fileHandlers/BdfHandler.js


+ 0 - 0
src/utils/three/objects/fileHandlers/CgnsHandler.js


+ 0 - 0
src/utils/three/objects/fileHandlers/PltHandler.js


+ 186 - 0
src/utils/three/objects/fileHandlers/XyzHandler.js

@@ -0,0 +1,186 @@
+// src/utils/three/objects/fileHandlers/XyzHandler.js
+import * as THREE from 'three';
+const DEFAULT_XYZ_DATA = {
+    "data": {
+        "uvw": [
+            4,
+            7,
+            2
+        ],
+        "datasetType": "xyz",
+        "points": [
+            -0.245139472120208, -0.2802128, -0.617476667954974,
+            3.850861272260981, -0.2802128, -1.909834309392576,
+            7.946862016642172, -0.2802128, -1.846004385590652,
+            12.042862761023361, -0.2802128, -1.460504680310172,
+            2.713975458046356, 3.908268028, -0.497897633101216,
+            6.04181267626649, 3.908268028, -1.210722046796919,
+            9.369649894486624, 3.908268028, -1.121004848757709,
+            12.69748711270676, 3.908268028, -0.870302205354601,
+            5.707765400602832, 7.8167506639782, -0.357545943582087,
+            8.267504393121383, 7.8167506639782, -0.744626752318642,
+            10.827243385639932, 7.8167506639782, -0.640702332530762,
+            13.38698237815848, 7.8167506639782, -0.42739495914176,
+            8.30799142943476, 11.208512, -0.151246223926708,
+            10.58515651761008, 11.208512, -0.403620953899546,
+            12.862321605785402, 11.208512, -0.260187234137928,
+            15.13948669396072, 11.208512, -0.071612411550862,
+            12.059619563521881, 16.112236, 0.312907147370755,
+            13.930883890055254, 16.112236, 0.155632219588651,
+            15.802148216588627, 16.112236, 0.316878285974105,
+            17.673412543122, 16.112236, 0.458144961714001,
+            15.810969838596522, 21.01596, 0.996976516169303,
+            17.27631399875155, 21.01596, 0.891361822158575,
+            18.741658158906574, 21.01596, 1.033368576330935,
+            20.2070023190616, 21.01596, 1.154191282753619,
+            19.85641804635804, 26.5851894, 1.939432599710228,
+            20.884242241859546, 26.5851894, 1.851773421428224,
+            21.912066437361055, 26.5851894, 1.97832125434272,
+            22.93989063286256, 26.5851894, 2.123049027519056,
+            -0.245139472120208, -0.2802128, 0.606625928694764,
+            3.850861272260981, -0.2802128, 0.855699489205117,
+            7.946862016642172, -0.2802128, 0.358285248930787,
+            12.042862761023361, -0.2802128, -0.336338120149948,
+            2.713975458046356, 3.908268028, 0.597738982147923,
+            6.04181267626649, 3.908268028, 0.918328417817198,
+            9.369649894486624, 3.908268028, 0.645559876597333,
+            12.69748711270676, 3.908268028, 0.160425568163699,
+            5.707765400602832, 7.8167506639782, 0.627582035287386,
+            8.267504393121383, 7.8167506639782, 0.940891763850826,
+            10.827243385639932, 7.8167506639782, 0.845345008983021,
+            13.38698237815848, 7.8167506639782, 0.509920987722646,
+            8.30799142943476, 11.208512, 0.736705567663439,
+            10.58515651761008, 11.208512, 1.066151869969516,
+            12.862321605785402, 11.208512, 1.054276880931633,
+            15.13948669396072, 11.208512, 0.772303913720452,
+            12.059619563521881, 16.112236, 1.099545224188703,
+            13.930883890055254, 16.112236, 1.403784820986931,
+            15.802148216588627, 16.112236, 1.422194287055911,
+            17.673412543122, 16.112236, 1.208650598401699,
+            15.810969838596522, 21.01596, 1.680585762777217,
+            17.27631399875155, 21.01596, 1.926468323809689,
+            18.741658158906574, 21.01596, 1.961057096213413,
+            20.2070023190616, 21.01596, 1.811292819501266,
+            19.85641804635804, 26.5851894, 2.516948316735412,
+            20.884242241859546, 26.5851894, 2.667841646476216,
+            21.912066437361055, 26.5851894, 2.707135014557344,
+            22.93989063286256, 26.5851894, 2.686770539509896
+        ]
+    }
+}
+export class XyzHandler {
+    constructor(scene) {
+        console.log('XyzHandler initialized with scene:', scene);
+        this.scene = scene;
+        this.currentObject = null;
+    }
+
+    async load(file) {
+        try {
+            console.log(`Processing XYZ file: ${file.name} (using default data)`);
+
+            // 清理旧对象
+            this.disposeCurrentObject();
+
+            // 创建可视化对象
+            this.currentObject = this.createVisualization(DEFAULT_XYZ_DATA);
+            return this.currentObject;
+        } catch (error) {
+            console.error('XYZ处理失败:', error);
+            throw error;
+        }
+    }
+
+    disposeCurrentObject() {
+        if (this.currentObject) {
+            this.currentObject.dispose();
+            this.currentObject = null;
+        }
+    }
+
+    createVisualization(xyzData) {
+        const parsed = this.parse(xyzData);
+        const geometry = new THREE.BufferGeometry();
+
+        geometry.setAttribute(
+            'position',
+            new THREE.Float32BufferAttribute(parsed.normalizedPoints, 3)
+        );
+        geometry.setIndex(parsed.indices);
+
+        // 线框材质
+        const lineMaterial = new THREE.LineBasicMaterial({
+            color: 0x00ff00,
+            linewidth: 2
+        });
+
+        // 点云材质
+        const pointMaterial = new THREE.PointsMaterial({
+            color: 0xff0000,
+            size: 0.15,
+            sizeAttenuation: true
+        });
+
+        const wireframe = new THREE.LineSegments(geometry, lineMaterial);
+        const points = new THREE.Points(geometry, pointMaterial);
+
+        const container = new THREE.Group();
+        container.add(wireframe);
+        container.add(points);
+        container.userData.isXYZ = true;
+
+        if (this.scene.isScene) {
+            this.scene.add(container);
+        } else {
+            throw new Error('Cannot add to invalid scene');
+        }
+
+        return {
+            type: 'xyz',
+            mesh: container,
+            rawData: xyzData,
+            dispose: () => {
+                // 确保场景存在再移除
+                if (this.scene?.isScene && container.parent === this.scene) {
+                    this.scene.remove(container);
+                }
+                geometry.dispose();
+                lineMaterial.dispose();
+                pointMaterial.dispose();
+            }
+        };
+    }
+
+    parse(xyzData) {
+        const points = xyzData.data.points;
+        const uvw = xyzData.data.uvw;
+        const uSize = uvw[0], vSize = uvw[1], wSize = uvw[2];
+
+        // 生成线框索引
+        const indices = [];
+        for (let u = 0; u < uSize - 1; u++) {
+            for (let v = 0; v < vSize - 1; v++) {
+                for (let w = 0; w < wSize - 1; w++) {
+                    const i0 = u + v * uSize + w * uSize * vSize;
+                    const i1 = i0 + 1;
+                    const i2 = i0 + uSize;
+                    const i3 = i2 + 1;
+                    const i4 = i0 + uSize * vSize;
+                    const i5 = i1 + uSize * vSize;
+                    const i6 = i2 + uSize * vSize;
+                    const i7 = i3 + uSize * vSize;
+
+                    // 只保留外框线条减少复杂度
+                    if (u === 0 || u === uSize - 2 || v === 0 || v === vSize - 2 || w === 0 || w === wSize - 2) {
+                        indices.push(i0, i1, i1, i3, i3, i2, i2, i0); // 前面
+                        indices.push(i4, i5, i5, i7, i7, i6, i6, i4); // 后面
+                        indices.push(i0, i4, i2, i6); // 左侧连接线
+                        indices.push(i1, i5, i3, i7); // 右侧连接线
+                    }
+                }
+            }
+        }
+
+        return { points, indices };
+    }
+}

+ 58 - 0
src/utils/three/objects/objectManager.js

@@ -0,0 +1,58 @@
+export class ObjectManager {
+  constructor(scene) {
+    this.scene = scene;
+    this.objects = new Map(); // 使用Map存储对象
+    this.typeIndex = new Map();
+  }
+
+  add(object, type = 'default') {
+    if (!object.mesh) {
+      console.error('Object must have mesh property');
+      return;
+    }
+
+    const id = object.id || Date.now().toString();
+    this.scene.add(object.mesh);
+    this.objects.set(id, { ...object, type });
+
+    // 维护类型索引
+    if (!this.typeIndex.has(type)) {
+      this.typeIndex.set(type, new Set());
+    }
+    this.typeIndex.get(type).add(id);
+
+    return id;
+  }
+
+  clearByType(type) {
+    if (!this.typeIndex.has(type)) return;
+
+    const ids = Array.from(this.typeIndex.get(type));
+    ids.forEach(id => this.remove(id));
+    this.typeIndex.delete(type);
+  }
+
+  get(id) {
+    return this.objects.get(id);
+  }
+
+  remove(id) {
+    const object = this.objects.get(id);
+    if (object) {
+      this.scene.remove(object.mesh);
+      object.dispose?.();
+      this.objects.delete(id);
+    }
+  }
+
+  dispose() {
+    this.objects.forEach(obj => obj.dispose?.());
+    this.objects.clear();
+  }
+
+  getObjectsByType(type) {
+    return this.typeIndex.has(type)
+      ? Array.from(this.typeIndex.get(type)).map(id => this.objects.get(id))
+      : [];
+  }
+}

+ 1 - 1
src/views/threejsView/utils/renderers/CgnsJSONRenderer.js

@@ -100,7 +100,7 @@ export class CgnsJSONRenderer {
     getZoneMaterial(zoneData) {
         // 可根据不同区域类型返回不同材质
         const material = this.defaultMaterial.clone();
-        
+        // material.wireframe = true;
         // 结构化网格使用线框模式便于调试
         if (zoneData.type === 'STRUCTURED') {
             material.wireframe = true;

+ 125 - 56
src/views/threejsView/utils/renderers/PltDataRenderer.js

@@ -1,34 +1,37 @@
 import * as THREE from 'three';
+import { usePltDataStore } from '@/store/modules/pltData';
 
 export class PltDataRenderer {
     constructor(updateProgress, onComplete) {
         this.updateProgress = updateProgress;
         this.onComplete = onComplete;
         this.meshGroup = new THREE.Group();
-        
+        this.zoneMeshes = new Map(); // 存储各zone的mesh
+        this.renderer = null;  // 需要从外部传入
+        this.camera = null;
+
         this.defaultMaterial = new THREE.MeshPhongMaterial({
-            color: 0xffffff, // 改为白色基础色
-            specular: 0x111111, // 适当的高光
+            color: 0xffffff, // 保持白色基础色
+            specular: 0x111111,
             shininess: 30,
             side: THREE.DoubleSide,
-            flatShading: false, // 改为false获得更平滑的 shading
+            flatShading: false,
             vertexColors: true,
             transparent: true,
-            opacity: 1.0 // 不透明度设为1
+            opacity: 1.0
         });
         this.defaultLights = [
-            new THREE.AmbientLight(0xffffff, 1.0), // 环境光(强度增强)
-            new THREE.DirectionalLight(0xffffff, 1.5), // 平行光(强度增强)
-            new THREE.HemisphereLight(0xffffbb, 0x080820, 0.8) // 天光
+            new THREE.AmbientLight(0xffffff, 1.0),
+            new THREE.DirectionalLight(0xffffff, 1.5),
+            new THREE.HemisphereLight(0xffffbb, 0x080820, 0.8)
         ];
     }
 
     async render(pltData, scene) {
-      // 清除旧光照
-        scene.traverse(obj => {
-            if (obj.isLight) scene.remove(obj);
-        });
-        
+        scene.remove(this.meshGroup);
+        // // 清除旧光照
+
+        scene.traverse(obj => { if (obj.isLight) scene.remove(obj); });
         // 添加新光照
         this.defaultLights.forEach(light => {
             if (light.isDirectionalLight) {
@@ -38,47 +41,62 @@ export class PltDataRenderer {
         });
         this.clearScene(scene);
         scene.add(this.meshGroup);
-
         try {
             await this.updateProgressAsync('开始渲染PLT模型...');
-            
-            // 处理每个zone
+            console.log('开始渲染PLT模型:');
+            const pltStore = usePltDataStore();
+            const visibleZoneNames = pltStore.visibleZones.map(z => z.name)
+            this.meshGroup.clear();
             for (const zone of pltData.zones) {
                 await this.updateProgressAsync(`正在处理区域: ${zone.name}...`);
-                
-                // 创建几何体
-                const geometry = new THREE.BufferGeometry();
-                
-                // 设置顶点属性
-                geometry.setAttribute(
-                    'position',
-                    new THREE.BufferAttribute(new Float32Array(zone.vertices), 3)
-                );
-
-                // 设置索引
-                if (zone.indices && zone.indices.length > 0) {
-                    geometry.setIndex(
-                        new THREE.BufferAttribute(new Uint32Array(zone.indices), 1)
-                    );
-                }
 
-                // 设置顶点颜色(使用CoefPressure变量)
-                if (zone.variables && zone.variables.CoefPressure) {
-                    const colors = this.generateVertexColors(zone.variables.CoefPressure);
+                // 如果已有mesh则复用
+                let mesh = this.zoneMeshes.get(zone.name);
+                const isVisible = visibleZoneNames.includes(zone.name)
+
+                if (!mesh) {
+                    // 创建几何体
+                    const geometry = new THREE.BufferGeometry();
                     geometry.setAttribute(
-                        'color',
-                        new THREE.BufferAttribute(new Float32Array(colors), 3)
+                        'position',
+                        new THREE.BufferAttribute(new Float32Array(zone.vertices), 3)
                     );
-                }
 
-                await this.updateProgressAsync('优化几何体...');
-                geometry.computeVertexNormals();
-                geometry.computeBoundingSphere();
+                    if (zone.indices && zone.indices.length > 0) {
+                        geometry.setIndex(
+                            new THREE.BufferAttribute(new Uint32Array(zone.indices), 1)
+                        );
+                    }
+
+                    if (zone.variables?.CoefPressure) {
+                        const colors = this.generateVertexColors(zone.variables.CoefPressure);
+                        geometry.setAttribute(
+                            'color',
+                            new THREE.BufferAttribute(new Float32Array(colors), 3)
+                        );
+                    }
+
+                    geometry.computeVertexNormals();
+                    geometry.computeBoundingSphere();
 
-                await this.updateProgressAsync('创建网格...');
-                const mesh = new THREE.Mesh(geometry, this.defaultMaterial);
-                mesh.name = zone.name;
-                this.meshGroup.add(mesh);
+                    mesh = new THREE.Mesh(geometry, this.defaultMaterial.clone());
+                    mesh.name = zone.name;
+                    this.zoneMeshes.set(zone.name, mesh);
+                }
+
+                // 设置可见性
+                mesh.visible = isVisible;
+                if (!isVisible && mesh.parent) {
+                    mesh.parent.remove(mesh);
+                }
+                if (isVisible) {
+                    if (!this.meshGroup.children.some(child => child.name === zone.name)) {
+                        this.meshGroup.add(mesh);
+                    }
+                } else {
+                    const toRemove = this.meshGroup.children.find(child => child.name === zone.name);
+                    if (toRemove) this.meshGroup.remove(toRemove);
+                }
             }
 
             if (this.onComplete) this.onComplete();
@@ -89,27 +107,24 @@ export class PltDataRenderer {
     }
 
     generateVertexColors(data) {
-        // 添加数据验证
         if (!data || !Array.isArray(data) || data.length === 0) {
             console.warn('无效的顶点颜色数据,使用默认颜色');
-            return new Array(data.length * 3).fill(0.5); // 返回灰色
+            return new Array(data.length * 3).fill(0.5); // 灰色
         }
 
         const min = Math.min(...data);
         const max = Math.max(...data);
-        const range = max - min || 1; // 避免除以0
-        
+        const range = max - min || 1;
+
         const colors = new Array(data.length * 3);
-        
+
         for (let i = 0; i < data.length; i++) {
             const normalized = (data[i] - min) / range;
-            
-            // 热力图颜色映射
             colors[i * 3] = normalized;         // R
             colors[i * 3 + 1] = 0;              // G
             colors[i * 3 + 2] = 1 - normalized; // B
         }
-        
+
         return colors;
     }
 
@@ -121,13 +136,67 @@ export class PltDataRenderer {
     }
 
     clearScene(scene) {
+        // 保留mesh缓存,只从场景移除
         this.meshGroup.traverse(child => {
             if (child.isMesh) {
-                child.geometry.dispose();
-                child.material.dispose();
+                this.meshGroup.remove(child);
             }
         });
         scene.remove(this.meshGroup);
-        this.meshGroup = new THREE.Group();
+    }
+
+    showAllDomains(scene) {
+        this.zoneMeshes.forEach(mesh => {
+            mesh.visible = true
+            if (!mesh.parent) {
+                this.meshGroup.add(mesh)
+            }
+        })
+        scene.add(this.meshGroup)
+    }
+
+    hideAllDomains(scene) {
+        this.zoneMeshes.forEach(mesh => {
+            mesh.visible = false
+            if (mesh.parent) {
+                this.meshGroup.remove(mesh)
+            }
+        })
+        scene.remove(this.meshGroup)
+    }
+
+    updateDomainVisibility(scene, visibleZones) {
+        const visibleZoneNames = visibleZones.map(z => z.name)
+        console.log("更新域可见性:", visibleZoneNames);
+        console.log("this.zoneMeshes", this.zoneMeshes);
+        
+        this.zoneMeshes.forEach((mesh, name) => {
+            console.log(`处理域 ${name} 的可见性:`, mesh.visible);
+
+            const shouldBeVisible = visibleZoneNames.includes(name)
+            console.log(`域 ${name} 应该可见:`, shouldBeVisible);
+
+            mesh.visible = shouldBeVisible
+
+            if (shouldBeVisible && !mesh.parent) {
+                this.meshGroup.add(mesh)
+                console.log(`域 ${name} 已添加到 meshGroup`);
+
+            } else if (!shouldBeVisible && mesh.parent) {
+                this.meshGroup.remove(mesh)
+                console.log(`域 ${name} 已从 meshGroup 移除`);
+
+            }
+        })
+        console.log('zoneMeshes keys:', [...this.zoneMeshes.keys()])
+
+        if (this.meshGroup.children.length > 0) {
+            
+            scene.remove(this.meshGroup)
+            console.log('meshGroup 已添加到场景');
+        } else {
+            scene.add(this.meshGroup)
+            console.log('meshGroup 已从场景移除');
+        }
     }
 }

+ 1 - 1
tsconfig.json

@@ -38,7 +38,7 @@
     "src/**/*.d.ts",
     "src/**/*.tsx",
     "src/**/*.vue"
-  ],
+, "src/store/modules/pltData.js"  ],
   "references": [
     {
       "path": "./tsconfig.node.json"