code-only.patch 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. diff --git a/src/modules/cockpit/components/MapInfoPanel.vue b/src/modules/cockpit/components/MapInfoPanel.vue
  2. index 8668e75..b91d3f1 100644
  3. --- a/src/modules/cockpit/components/MapInfoPanel.vue
  4. +++ b/src/modules/cockpit/components/MapInfoPanel.vue
  5. @@ -43,0 +44,9 @@
  6. + <p class="map-info__note" data-pipe-model-note>{{ PIPE_MODEL_NOTE }}</p>
  7. + <p v-if="pipeModelState" class="map-info__line" data-pipe-model-status role="status">
  8. + 管道模型 {{ pipeModelState.instances }} 实例 / {{ pipeModelState.batches }} 批 ·
  9. + 折线/符号 {{ pipeModelState.fallback }} 对象
  10. + <template v-if="pipeModelState.planning || pipeModelState.pending"> · 模型加载中(保留降级表达)</template>
  11. + <template v-if="pipeModelState.failed"> · {{ pipeModelState.failed }} 批素材失败,已降级</template>
  12. + <template v-if="pipeModelState.terrainFailed"> · 地表高程采样失败,已保留折线/符号,稍后刷新重试</template>
  13. + </p>
  14. +
  15. @@ -59,0 +69,5 @@ import MapCoordinateBlocked from './MapCoordinateBlocked.vue'
  16. +import { PIPE_MODEL_NOTE } from '../domain/pipeModels.js'
  17. +
  18. +const pipeModelState = ref(null)
  19. +let modelHost = null
  20. +const updatePipeModelState = (event) => { pipeModelState.value = event.detail }
  21. @@ -102,0 +117,2 @@ onMounted(() => {
  22. + modelHost = host
  23. + host.addEventListener('cockpit-pipe-model-state', updatePipeModelState)
  24. @@ -107,0 +124,2 @@ onBeforeUnmount(() => {
  25. + if (modelHost) modelHost.removeEventListener('cockpit-pipe-model-state', updatePipeModelState)
  26. + modelHost = null
  27. diff --git a/src/modules/cockpit/services/cesiumPipeLayer.js b/src/modules/cockpit/services/cesiumPipeLayer.js
  28. index ddb0620..6dcb74a 100644
  29. --- a/src/modules/cockpit/services/cesiumPipeLayer.js
  30. +++ b/src/modules/cockpit/services/cesiumPipeLayer.js
  31. @@ -12,5 +12,4 @@
  32. - * 渲染口径(与 `domain/pipeNetwork.js` 同源,引擎不再做第二次判定):
  33. - * · 管段:`clampToGround` 贴地折线,管径映射线宽,行业映射颜色;
  34. - * · 构件:阀门 / 角管道 / 支管管道各用**可辨识符号 + 中文标签**表达。
  35. - * glTF 预制构件实例化需要素材库,仓库内暂无管线素材,故先用符号表达并保留扩展位——
  36. - * **不拿临时符号冒充"已建成的管道模型"**。
  37. + * 渲染口径(与领域建模计划同源,引擎不再做第二次准入判定):
  38. + * · 已知管径/类型:独立 Cesium GPU 批次、地表采样减虚拟埋深 2m;
  39. + * · 模型未就绪、缺管径/类型/素材/高程:保留贴地折线或符号标签。
  40. + * glTF 在 readyEvent 后替代降级表达,示意外形与近似朝向不冒充真实安装信息。
  41. @@ -20,0 +20,5 @@ import { pipeWidth } from '../domain/pipeNetwork.js'
  42. +// Viewer-scoped batches; loaded lazily so symbol-only/headless paths do not load the model adapter.
  43. +const modelLayers = new WeakMap()
  44. +const layerVersions = new WeakMap()
  45. +let modelAdapterPromise = null
  46. +
  47. @@ -124,2 +152,4 @@ function componentEntity(code, component) {
  48. -export async function syncPipeLayer(viewer, network) {
  49. - if (!viewer || !viewer.dataSources) return { pipes: 0, components: 0, rendered: false }
  50. +export async function syncPipeLayer(viewer, network, { onModelChange = () => {} } = {}) {
  51. + if (!viewer || !viewer.dataSources) return { pipes: 0, components: 0, rendered: false, added: 0, updated: 0, removed: 0, unchanged: 0 }
  52. + const version = (layerVersions.get(viewer) || 0) + 1
  53. + layerVersions.set(viewer, version)
  54. @@ -128,0 +159 @@ export async function syncPipeLayer(viewer, network) {
  55. + if (layerVersions.get(viewer) !== version) return { pipes: 0, components: 0, rendered: false }
  56. @@ -170 +221,14 @@ export async function syncPipeLayer(viewer, network) {
  57. - return { pipes: pipes.length, components: components.length, rendered: true }
  58. + // Keep fallback entities until ALL model spans for that business object signal ready.
  59. + // The model adapter alone controls entity.show; unchanged geometries still stay untouched (ticket 33).
  60. + if (viewer.scene && viewer.scene.primitives && !viewer.isDestroyed()) {
  61. + modelAdapterPromise ||= import('./cesiumPipeModels.js')
  62. + const { createCesiumPipeModels } = await modelAdapterPromise
  63. + if (viewer.isDestroyed() || layerVersions.get(viewer) !== version) return { pipes: 0, components: 0, rendered: false }
  64. + let layer = modelLayers.get(viewer)
  65. + if (!layer) {
  66. + layer = createCesiumPipeModels(viewer, { source, onChange: onModelChange })
  67. + modelLayers.set(viewer, layer)
  68. + }
  69. + layer.sync({ pipes, components })
  70. + }
  71. + return { pipes: pipes.length, components: components.length, rendered: true, ...stats }
  72. @@ -174,0 +239,3 @@ export async function clearPipeLayer(viewer) {
  73. + if (viewer) layerVersions.set(viewer, (layerVersions.get(viewer) || 0) + 1)
  74. + const layer = viewer && modelLayers.get(viewer)
  75. + if (layer) { layer.destroy(); modelLayers.delete(viewer) }
  76. diff --git a/src/modules/cockpit/services/mapService.js b/src/modules/cockpit/services/mapService.js
  77. index 2eaa74c..065ceac 100644
  78. --- a/src/modules/cockpit/services/mapService.js
  79. +++ b/src/modules/cockpit/services/mapService.js
  80. @@ -1004,0 +1005 @@ export function unmountMap() {
  81. + if (record.pipeLayer) record.pipeLayer.clearPipeLayer(record.viewer)
  82. @@ -2383,2 +2385,18 @@ export async function syncPipeLayerForPlan(plan) {
  83. - await engine.syncPipeLayer(instance.viewer, layer)
  84. - const host = instance.host
  85. + if (instance !== record || !record.viewer) return false
  86. + record.pipeLayer = engine
  87. + await engine.syncPipeLayer(record.viewer, layer, { onModelChange: (state) => {
  88. + if (instance !== record || !record.host) return
  89. + const host = record.host
  90. + if (host.dataset) {
  91. + host.dataset.mapPipeModelBatches = String(state.batches)
  92. + host.dataset.mapPipeModelInstances = String(state.instances)
  93. + host.dataset.mapPipeModelFallback = String(state.fallback)
  94. + host.dataset.mapPipeModelPending = String(state.pending)
  95. + host.dataset.mapPipeModelFailed = String(state.failed)
  96. + host.dataset.mapPipeModelKinds = JSON.stringify(state.byKind)
  97. + host.dataset.mapPipeVirtualDepth = '2'
  98. + }
  99. + host.dispatchEvent(new CustomEvent('cockpit-pipe-model-state', { detail: state }))
  100. + } })
  101. + if (instance !== record) return false
  102. + const host = record.host