diff --git a/src/modules/cockpit/components/MapInfoPanel.vue b/src/modules/cockpit/components/MapInfoPanel.vue index 8668e75..b91d3f1 100644 --- a/src/modules/cockpit/components/MapInfoPanel.vue +++ b/src/modules/cockpit/components/MapInfoPanel.vue @@ -43,0 +44,9 @@ +

{{ PIPE_MODEL_NOTE }}

+

+ 管道模型 {{ pipeModelState.instances }} 实例 / {{ pipeModelState.batches }} 批 · + 折线/符号 {{ pipeModelState.fallback }} 对象 + + + +

+ @@ -59,0 +69,5 @@ import MapCoordinateBlocked from './MapCoordinateBlocked.vue' +import { PIPE_MODEL_NOTE } from '../domain/pipeModels.js' + +const pipeModelState = ref(null) +let modelHost = null +const updatePipeModelState = (event) => { pipeModelState.value = event.detail } @@ -102,0 +117,2 @@ onMounted(() => { + modelHost = host + host.addEventListener('cockpit-pipe-model-state', updatePipeModelState) @@ -107,0 +124,2 @@ onBeforeUnmount(() => { + if (modelHost) modelHost.removeEventListener('cockpit-pipe-model-state', updatePipeModelState) + modelHost = null diff --git a/src/modules/cockpit/services/cesiumPipeLayer.js b/src/modules/cockpit/services/cesiumPipeLayer.js index ddb0620..6dcb74a 100644 --- a/src/modules/cockpit/services/cesiumPipeLayer.js +++ b/src/modules/cockpit/services/cesiumPipeLayer.js @@ -12,5 +12,4 @@ - * 渲染口径(与 `domain/pipeNetwork.js` 同源,引擎不再做第二次判定): - * · 管段:`clampToGround` 贴地折线,管径映射线宽,行业映射颜色; - * · 构件:阀门 / 角管道 / 支管管道各用**可辨识符号 + 中文标签**表达。 - * glTF 预制构件实例化需要素材库,仓库内暂无管线素材,故先用符号表达并保留扩展位—— - * **不拿临时符号冒充"已建成的管道模型"**。 + * 渲染口径(与领域建模计划同源,引擎不再做第二次准入判定): + * · 已知管径/类型:独立 Cesium GPU 批次、地表采样减虚拟埋深 2m; + * · 模型未就绪、缺管径/类型/素材/高程:保留贴地折线或符号标签。 + * glTF 在 readyEvent 后替代降级表达,示意外形与近似朝向不冒充真实安装信息。 @@ -20,0 +20,5 @@ import { pipeWidth } from '../domain/pipeNetwork.js' +// Viewer-scoped batches; loaded lazily so symbol-only/headless paths do not load the model adapter. +const modelLayers = new WeakMap() +const layerVersions = new WeakMap() +let modelAdapterPromise = null + @@ -124,2 +152,4 @@ function componentEntity(code, component) { -export async function syncPipeLayer(viewer, network) { - if (!viewer || !viewer.dataSources) return { pipes: 0, components: 0, rendered: false } +export async function syncPipeLayer(viewer, network, { onModelChange = () => {} } = {}) { + if (!viewer || !viewer.dataSources) return { pipes: 0, components: 0, rendered: false, added: 0, updated: 0, removed: 0, unchanged: 0 } + const version = (layerVersions.get(viewer) || 0) + 1 + layerVersions.set(viewer, version) @@ -128,0 +159 @@ export async function syncPipeLayer(viewer, network) { + if (layerVersions.get(viewer) !== version) return { pipes: 0, components: 0, rendered: false } @@ -170 +221,14 @@ export async function syncPipeLayer(viewer, network) { - return { pipes: pipes.length, components: components.length, rendered: true } + // Keep fallback entities until ALL model spans for that business object signal ready. + // The model adapter alone controls entity.show; unchanged geometries still stay untouched (ticket 33). + if (viewer.scene && viewer.scene.primitives && !viewer.isDestroyed()) { + modelAdapterPromise ||= import('./cesiumPipeModels.js') + const { createCesiumPipeModels } = await modelAdapterPromise + if (viewer.isDestroyed() || layerVersions.get(viewer) !== version) return { pipes: 0, components: 0, rendered: false } + let layer = modelLayers.get(viewer) + if (!layer) { + layer = createCesiumPipeModels(viewer, { source, onChange: onModelChange }) + modelLayers.set(viewer, layer) + } + layer.sync({ pipes, components }) + } + return { pipes: pipes.length, components: components.length, rendered: true, ...stats } @@ -174,0 +239,3 @@ export async function clearPipeLayer(viewer) { + if (viewer) layerVersions.set(viewer, (layerVersions.get(viewer) || 0) + 1) + const layer = viewer && modelLayers.get(viewer) + if (layer) { layer.destroy(); modelLayers.delete(viewer) } diff --git a/src/modules/cockpit/services/mapService.js b/src/modules/cockpit/services/mapService.js index 2eaa74c..065ceac 100644 --- a/src/modules/cockpit/services/mapService.js +++ b/src/modules/cockpit/services/mapService.js @@ -1004,0 +1005 @@ export function unmountMap() { + if (record.pipeLayer) record.pipeLayer.clearPipeLayer(record.viewer) @@ -2383,2 +2385,18 @@ export async function syncPipeLayerForPlan(plan) { - await engine.syncPipeLayer(instance.viewer, layer) - const host = instance.host + if (instance !== record || !record.viewer) return false + record.pipeLayer = engine + await engine.syncPipeLayer(record.viewer, layer, { onModelChange: (state) => { + if (instance !== record || !record.host) return + const host = record.host + if (host.dataset) { + host.dataset.mapPipeModelBatches = String(state.batches) + host.dataset.mapPipeModelInstances = String(state.instances) + host.dataset.mapPipeModelFallback = String(state.fallback) + host.dataset.mapPipeModelPending = String(state.pending) + host.dataset.mapPipeModelFailed = String(state.failed) + host.dataset.mapPipeModelKinds = JSON.stringify(state.byKind) + host.dataset.mapPipeVirtualDepth = '2' + } + host.dispatchEvent(new CustomEvent('cockpit-pipe-model-state', { detail: state })) + } }) + if (instance !== record) return false + const host = record.host