@eturnity/eturnity_3d 9.31.0 → 9.31.1-9.25-dev-03.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/package.json +2 -2
  2. package/src/Overlay/ThreeDModelOverlay.js +23 -16
  3. package/src/components/ThreeCanvasViewer.vue +0 -2
  4. package/src/config.js +1 -10
  5. package/src/helper/UpdateRoofModuleFieldRelations.js +75 -36
  6. package/src/helper/cameraMixin.js +17 -7
  7. package/src/helper/materialMixin.js +0 -7
  8. package/src/helper/render/clear.js +7 -1
  9. package/src/helper/render/edge.js +1 -0
  10. package/src/helper/render/index.js +2 -0
  11. package/src/helper/render/moduleField.js +149 -27
  12. package/src/helper/render/panel.js +21 -11
  13. package/src/helper/render/pullHandle.js +1000 -0
  14. package/src/helper/renderMixin.js +40 -1
  15. package/src/helper/threeMixin.js +2 -1
  16. package/src/store/hydrateData.js +24 -30
  17. package/src/store/three-d-module.js +0 -3
  18. package/dist/assets/images/panels/longiSolarLR4_60HPH_350M.png +0 -0
  19. package/dist/assets/images/panels/longiSolarLR4_60HPH_370M.png +0 -0
  20. package/dist/assets/images/panels/reneSola_Virtus_2_JC320S_24_Bbw copy.png +0 -0
  21. package/dist/assets/images/panels/reneSola_Virtus_2_JC320S_24_Bbw.png +0 -0
  22. package/dist/assets/images/panels/uv.png +0 -0
  23. package/dist/assets/panels/longiSolarLR4_60HPH_350M.png +0 -0
  24. package/dist/assets/panels/longiSolarLR4_60HPH_370M.png +0 -0
  25. package/dist/assets/panels/reneSola_Virtus_2_JC320S_24_Bbw.png +0 -0
  26. package/dist/assets/panels/uv.png +0 -0
  27. package/dist/assets/theme.js +0 -43
  28. package/dist/assets/vue.svg +0 -1
  29. package/dist/main.es.js +0 -43662
  30. package/dist/main.umd.js +0 -3542
  31. package/dist/style.css +0 -1
  32. package/dist/vite.svg +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_3d",
3
3
  "private": false,
4
- "version": "9.31.0",
4
+ "version": "9.31.1-9.25-dev-03.0",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
@@ -16,7 +16,7 @@
16
16
  "merge-remote-master": "node scripts/merge-remote-master.js"
17
17
  },
18
18
  "dependencies": {
19
- "@eturnity/eturnity_maths": "9.31.0",
19
+ "@eturnity/eturnity_maths": "9.31.0-9.25-dev-03.0",
20
20
  "@originjs/vite-plugin-commonjs": "1.0.3",
21
21
  "core-js": "3.30.2",
22
22
  "cors": "2.8.5",
@@ -66,28 +66,31 @@ export default class ThreeDModelOverlay extends Overlay {
66
66
  threeJSComponent.scene.add(waitingBall)
67
67
  }
68
68
  threeJSComponent.meshes.overlayMeshes[this.id] = waitingBall
69
- this.hasPlaceholderMesh = true
70
69
  return
71
70
  }
72
- let meshId = this.id
71
+ // Each canvas renders its own clone: an Object3D belongs to a single scene,
72
+ // so sharing this.overlayMesh made one canvas steal (and later dispose) the
73
+ // mesh another canvas was still rendering. this.overlayMesh stays a load
74
+ // cache and is never added to a scene.
73
75
  let overlayMesh = threeJSComponent.meshes.overlayMeshes[this.id]
74
- if (this.hasPlaceholderMesh) {
75
- threeJSComponent.clearMeshFromScene(overlayMesh, threeJSComponent.scene)
76
+ if (overlayMesh && overlayMesh.name == 'waitingBall') {
77
+ // Placeholder spheres are per-canvas objects, so disposing them is safe.
78
+ threeJSComponent.scene.remove(overlayMesh)
79
+ threeJSComponent.disposeMesh(overlayMesh)
80
+ delete threeJSComponent.meshes.overlayMeshes[this.id]
76
81
  overlayMesh = null
77
- this.hasPlaceholderMesh = false
78
82
  }
79
- if (!overlayMesh && this.overlayMesh) {
80
- threeJSComponent.meshes.overlayMeshes[this.id] = this.overlayMesh
81
- overlayMesh = this.overlayMesh
82
- overlayMesh.children.forEach((mesh) => {
83
+ if (!overlayMesh) {
84
+ if (!this.overlayMesh) {
85
+ this.overlayMesh = await this.loadFile()
86
+ }
87
+ this.overlayMesh.children.forEach((mesh) => {
83
88
  mesh.position.set(-this.boxCenter.x, -this.boxCenter.y, 0)
84
89
  })
90
+ overlayMesh = this.overlayMesh.clone(true)
91
+ threeJSComponent.meshes.overlayMeshes[this.id] = overlayMesh
85
92
  threeJSComponent.scene.add(overlayMesh)
86
93
  }
87
- if (!overlayMesh) {
88
- overlayMesh = await this.loadFile()
89
- threeJSComponent.meshes.overlayMeshes[meshId] = overlayMesh
90
- }
91
94
  const positionOffset = multiplyVector(1 / 1000, this.positionOffset)
92
95
  overlayMesh.position.set(0, 0, 0)
93
96
  overlayMesh.rotateZ(-overlayMesh.rotation.z)
@@ -104,9 +107,13 @@ export default class ThreeDModelOverlay extends Overlay {
104
107
  threeJSComponent.render()
105
108
  }
106
109
 
107
- this.overlayMesh = overlayMesh
108
- this.overlayMesh.frustumCulled = false
109
- this.set3DObjectMaterialOpacity(this.overlayMesh)
110
+ overlayMesh.frustumCulled = false
111
+ this.set3DObjectMaterialOpacity(overlayMesh)
112
+ // Keep the master aligned with the rendered clones: it is still what
113
+ // getProjectedPlanesOnOverlay raycasts and what bounding-box consumers
114
+ // measure.
115
+ this.overlayMesh.position.copy(overlayMesh.position)
116
+ this.overlayMesh.rotation.copy(overlayMesh.rotation)
110
117
  return overlayMesh
111
118
  }
112
119
  renderOnPaperJS(paperJSComponent) {
@@ -26,7 +26,6 @@
26
26
  layers: null,
27
27
  provider: null,
28
28
  pointer: { x: 0, y: 0 },
29
- areUserDeactivatedPanelsVisible: false,
30
29
  animationFrameId: null,
31
30
  intervalId: null,
32
31
  intervalEventId: null,
@@ -55,7 +54,6 @@
55
54
  obstacles: 'getObstacles',
56
55
  moduleFields: 'getModuleFields',
57
56
  panels: 'getPanels',
58
- userDeactivatedPanels: 'getUserDeactivatedPanels',
59
57
  }),
60
58
  },
61
59
  watch: {
package/src/config.js CHANGED
@@ -8,6 +8,7 @@ export const mmTolerance = 5
8
8
  export const mmTolerance3dNode = 50
9
9
  export const earthRadius = 6371008
10
10
  export const marginAroundBuildingForCameraViewInMeter = 5
11
+ export const DEFAULT_FOV = 50
11
12
  export const layerColors = {
12
13
  construction: {
13
14
  fillColor: 'transparent',
@@ -49,11 +50,6 @@ export const layerColors = {
49
50
  strokeColor: '#ffffff',
50
51
  strokeWidth: 1,
51
52
  },
52
- user_deactivated_panel: {
53
- fillColor: 'rgba(115, 115, 229, 0.1)',
54
- strokeColor: 'white',
55
- strokeWidth: 2,
56
- },
57
53
  selectedPanel: {
58
54
  fillColor: '#0068DE80',
59
55
  strokeColor: theme.colors.blue,
@@ -126,11 +122,6 @@ export const layerColors = {
126
122
  strokeColor: theme.colors.blueElectric,
127
123
  strokeWidth: 5,
128
124
  },
129
- tmpModuleField: {
130
- fillColor: 'rgba(115, 115, 229, 0.2)',
131
- strokeColor: theme.colors.blue,
132
- strokeWidth: 1,
133
- },
134
125
  selectedModuleField: {
135
126
  fillColor: '#0068DEA0',
136
127
  strokeColor: theme.colors.blue,
@@ -9,35 +9,51 @@ import {
9
9
  } from '@eturnity/eturnity_maths'
10
10
  import { defaultTextureColorMap, fittingPanelsColorMap } from '../config'
11
11
  //this function just update the "roof" and "moduleField(s)" properties of panels,moduleFields and roofs
12
+ // The roof underneath an outline: biggest footprint overlap wins, ties go to
13
+ // the smaller roof. Returns null when no roof intersects the outline.
14
+ export function findRoofWithBiggestOverlap(roofs, outline) {
15
+ const intersections = []
16
+ roofs.forEach((roof) => {
17
+ const intersection = intersectOutlines(roof.outline, outline)
18
+ if (intersection.length) {
19
+ const intersectionArea = intersection.reduce(
20
+ (acc, cur) => acc + calculateArea(cur),
21
+ 0
22
+ )
23
+ intersections.push({ roof, intersectionArea })
24
+ }
25
+ })
26
+ intersections.sort((a, b) => a.roof.area - b.roof.area)
27
+ intersections.sort((a, b) => b.intersectionArea - a.intersectionArea)
28
+ return intersections.length ? intersections[0].roof : null
29
+ }
30
+
12
31
  export function UpdateRoofModuleFieldRelations(state) {
13
32
  let moduleFields = state.polygons.filter(
14
33
  (poly) => poly.layer == 'moduleField'
15
34
  )
16
35
  let roofs = state.polygons.filter((poly) => poly.layer == 'roof')
17
36
  const panels = state.polygons.filter((poly) =>
18
- ['panel', 'user_deactivated_panel'].includes(poly.layer)
37
+ ['panel'].includes(poly.layer)
19
38
  )
20
-
21
39
  let roofUnderPerModuleFieldId = {}
22
40
  moduleFields.forEach((moduleField) => {
23
- let roofModuleFieldIntersections = []
24
- roofs.forEach((roof) => {
25
- let intersection = intersectOutlines(roof.outline, moduleField.outline)
26
- if (intersection.length) {
27
- const intersectionArea = intersection.reduce((acc, cur) => {
28
- acc += calculateArea(cur)
29
- return acc
30
- }, 0)
31
- roofModuleFieldIntersections.push({ roof, intersectionArea })
32
- }
33
- })
34
- roofModuleFieldIntersections.sort((a, b) => a.roof.area - b.roof.area)
35
- roofModuleFieldIntersections.sort(
36
- (a, b) => b.intersectionArea - a.intersectionArea
37
- )
38
- if (roofModuleFieldIntersections.length) {
39
- roofUnderPerModuleFieldId[moduleField.id] =
40
- roofModuleFieldIntersections[0].roof
41
+ // A module field keeps its roof for as long as that roof exists (matched
42
+ // by id: polygons are recreated on hydration, so the reference may be
43
+ // stale). It is only reassigned — to the roof it overlaps most — when its
44
+ // previous roof was deleted. Flows that deliberately move a field to
45
+ // another roof (drag replant, MoveModuleFields) set moduleField.roof
46
+ // themselves before the relations update.
47
+ const previousRoof = moduleField.roof
48
+ ? roofs.find((roof) => roof.id == moduleField.roof.id)
49
+ : null
50
+ if (previousRoof) {
51
+ roofUnderPerModuleFieldId[moduleField.id] = previousRoof
52
+ return
53
+ }
54
+ const roofUnder = findRoofWithBiggestOverlap(roofs, moduleField.outline)
55
+ if (roofUnder) {
56
+ roofUnderPerModuleFieldId[moduleField.id] = roofUnder
41
57
  }
42
58
  })
43
59
  roofs.forEach((roof) => (roof.moduleFields = []))
@@ -47,6 +63,11 @@ export function UpdateRoofModuleFieldRelations(state) {
47
63
  moduleFields.forEach((moduleField) => {
48
64
  let roof = roofUnderPerModuleFieldId[moduleField.id]
49
65
  if (roof) {
66
+ if (moduleField.hasError) {
67
+ moduleField.hasError = false
68
+ moduleField.warnings = []
69
+ moduleField.hasWarnings = false
70
+ }
50
71
  moduleField.roof = roof
51
72
  roof.moduleFields.push(moduleField)
52
73
  panels.forEach((panel) => {
@@ -54,6 +75,13 @@ export function UpdateRoofModuleFieldRelations(state) {
54
75
  panel.roof = roof
55
76
  }
56
77
  })
78
+ } else if (moduleField.panels && moduleField.panels.length > 0) {
79
+ moduleField.roof = null
80
+ if (!moduleField.hasError) {
81
+ moduleField.hasError = true
82
+ moduleField.warnings = ['module_field_warning_roof_missing']
83
+ moduleField.hasWarnings = true
84
+ }
57
85
  }
58
86
  })
59
87
  state = removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state)
@@ -65,36 +93,46 @@ export function removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state) {
65
93
  ['roof', 'obstacle'].includes(polygon.layer)
66
94
  )
67
95
  const remainingModuleFields = state.polygons.filter(
68
- (polygon) => polygon.layer == 'moduleField' && !!polygon.roof
96
+ (polygon) =>
97
+ polygon.layer == 'moduleField' && (!!polygon.roof || polygon.hasError)
69
98
  )
70
- const remainingPanels = state.polygons.filter(
71
- (polygon) => polygon.layer == 'panel' && !!polygon.roof
99
+ const retainedModuleFieldIds = new Set(
100
+ remainingModuleFields.map((mf) => mf.id)
72
101
  )
73
- const remainingUserDeactivatedPanels = state.polygons.filter(
74
- (polygon) => polygon.layer == 'user_deactivated_panel' && !!polygon.roof
102
+ const remainingPanels = state.polygons.filter(
103
+ (polygon) =>
104
+ polygon.layer == 'panel' &&
105
+ (!!polygon.roof || retainedModuleFieldIds.has(polygon.moduleField?.id))
75
106
  )
76
-
77
107
  state.polygons = [
78
108
  ...remainingPolygons,
79
109
  ...remainingModuleFields,
80
110
  ...remainingPanels,
81
- ...remainingUserDeactivatedPanels,
82
111
  ]
83
112
  return state
84
113
  }
85
114
 
86
115
  export function updateModuleFieldFittingPanels(moduleField) {
116
+ const { has_fitting_panels, fitting_panel_area_m2 } = moduleField.data || {}
117
+ if (!(has_fitting_panels && fitting_panel_area_m2 > 0)) {
118
+ delete moduleField.fittingPanels
119
+ return
120
+ }
121
+ clipModuleFieldFittingPanels(moduleField)
122
+ }
123
+
124
+ export function clipModuleFieldFittingPanels(moduleField) {
125
+ const prevVisible = moduleField?.fittingPanels?.visible
87
126
  delete moduleField.fittingPanels
88
- const { has_fitting_panels, fitting_panel_area_m2 } = moduleField.data
89
- const hasFittingPanels = has_fitting_panels && fitting_panel_area_m2 > 0
90
- if (!hasFittingPanels) return
91
- const polygons = []
127
+ if (!moduleField?.panels?.length) return
128
+ if (!moduleField?.roof || !moduleField.data?.has_fitting_panels) return
129
+ const innerOutline = moduleField.roof.margins?.innerOutline
130
+ if (!innerOutline?.length) return
92
131
 
93
- const outlines = getModuleFieldRoofIntersections(
94
- moduleField,
95
- moduleField.roof.margins.innerOutline
96
- )
132
+ const outlines = getModuleFieldRoofIntersections(moduleField, innerOutline)
133
+ if (!outlines?.length) return
97
134
  const holes = getModuleFieldHoleOutlines(moduleField)
135
+ const polygons = []
98
136
 
99
137
  outlines.forEach((outline) => {
100
138
  polygons.push({
@@ -105,6 +143,7 @@ export function updateModuleFieldFittingPanels(moduleField) {
105
143
  .filter(Boolean),
106
144
  })
107
145
  })
146
+ if (!polygons.length) return
108
147
 
109
148
  const hasCustomTexture = !!moduleField.pvData?.texture_img_url
110
149
  const defaultTextureIndex = moduleField.pvData?.module_texture_version_id || 0
@@ -115,7 +154,7 @@ export function updateModuleFieldFittingPanels(moduleField) {
115
154
 
116
155
  moduleField.fittingPanels = {
117
156
  polygons,
118
- visible: true,
157
+ visible: prevVisible !== false,
119
158
  color: mappedColor,
120
159
  }
121
160
  }
@@ -6,13 +6,21 @@ import {
6
6
  multiplyVector,
7
7
  substractVector,
8
8
  } from '@eturnity/eturnity_maths'
9
- import { marginAroundBuildingForCameraViewInMeter } from '../config'
9
+ import {
10
+ DEFAULT_FOV,
11
+ marginAroundBuildingForCameraViewInMeter,
12
+ } from '../config'
10
13
  import OverlayLayer from '../Overlay/OverlayLayer'
11
14
  export default {
12
15
  data() {
13
16
  return {
14
17
  animationFrameId: null,
15
- perspectiveCamera: new THREE.PerspectiveCamera(80, 300 / 300, 1, 50000),
18
+ perspectiveCamera: new THREE.PerspectiveCamera(
19
+ DEFAULT_FOV,
20
+ 300 / 300,
21
+ 1,
22
+ 50000
23
+ ),
16
24
  orthographicCamera: new THREE.OrthographicCamera(
17
25
  -50,
18
26
  50,
@@ -398,7 +406,7 @@ export default {
398
406
  },
399
407
  setCameraInitialPosition() {
400
408
  if (this.roofs.length == 0) return
401
- let newFov = 50
409
+ let newFov = DEFAULT_FOV
402
410
  if (
403
411
  this.roofsBounds.xMax === Infinity ||
404
412
  this.roofsBounds.xMin === -Infinity ||
@@ -462,7 +470,7 @@ export default {
462
470
  if (this.camera.isOrthographicCamera) {
463
471
  this.setOrthographicCameraGlobalPosition()
464
472
  } else {
465
- let newFov = 50
473
+ let newFov = DEFAULT_FOV
466
474
  let bounds = this.roofsBounds
467
475
  if (
468
476
  bounds.xMax === Infinity ||
@@ -618,7 +626,7 @@ export default {
618
626
  const {
619
627
  position,
620
628
  target,
621
- fov = 50,
629
+ fov = DEFAULT_FOV,
622
630
  duration = 1500,
623
631
  callback = () => {},
624
632
  } = payload
@@ -713,7 +721,9 @@ export default {
713
721
  callback()
714
722
  })
715
723
  } else {
716
- this.camera.up = { x: 0, y: 0, z: 1 }
724
+ // must stay a real Vector3: OrbitControls' zoomToCursor calls
725
+ // camera.up.dot() on wheel zoom (a plain object crashes it)
726
+ this.camera.up = new THREE.Vector3(0, 0, 1)
717
727
  this.tweenCamera = new TWEEN.Tween(currentTweenVar)
718
728
  .to({ t: 1 }, duration)
719
729
  .easing(TWEEN.Easing.Sinusoidal.Out)
@@ -778,7 +788,7 @@ export default {
778
788
  },
779
789
  },
780
790
 
781
- beforeDestroy() {
791
+ beforeUnmount() {
782
792
  TWEEN.removeAll()
783
793
  cancelAnimationFrame(this.animationFrameId)
784
794
  },
@@ -80,13 +80,6 @@ export default {
80
80
  flatShading: true,
81
81
  })
82
82
  }
83
- this.material.panel['userDeactivatedPanel'] = new THREE.MeshBasicMaterial({
84
- color: 0xffffff,
85
- transparent: true,
86
- alphaTest: 0,
87
- opacity: 0.2,
88
- side: THREE.DoubleSide,
89
- })
90
83
  this.material.projectionTiles = []
91
84
 
92
85
  this.material.roof = [this.material.wall]
@@ -33,7 +33,13 @@ export default {
33
33
  return
34
34
  }
35
35
  scene.remove(mesh)
36
- this.disposeMesh(mesh)
36
+ // Overlay meshes are per-canvas clones sharing geometry/material with the
37
+ // overlay's master mesh and with the other canvases' clones: disposing
38
+ // here would blank the overlay on every other canvas. Scene removal plus
39
+ // registry delete only; the master's resources go with the overlay.
40
+ if (mesh.userData.type != 'overlayMeshes') {
41
+ this.disposeMesh(mesh)
42
+ }
37
43
  if (
38
44
  mesh.userData.meshId &&
39
45
  this.meshes[mesh.userData.type][mesh.userData.meshId]
@@ -48,6 +48,7 @@ export default {
48
48
  if (cylinder) {
49
49
  cylinder.visible = false
50
50
  }
51
+ if (edge.layer === 'moduleField') return
51
52
  if (['obstacle', 'construction'].includes(edge.layer)) return
52
53
  if (
53
54
  ['selectRoof'].includes(this.selectedTool) &&
@@ -18,6 +18,7 @@ import mergedGeometry from './mergedGeometry'
18
18
  import building from './building'
19
19
  import marker from './marker'
20
20
  import scene from './scene'
21
+ import pullHandle from './pullHandle'
21
22
  export default {
22
23
  mixins: [
23
24
  margin,
@@ -40,5 +41,6 @@ export default {
40
41
  building,
41
42
  marker,
42
43
  scene,
44
+ pullHandle,
43
45
  ],
44
46
  }
@@ -3,6 +3,7 @@ import {
3
3
  getBufferModuleFieldGeometry,
4
4
  getFittingPanelsBufferGeometry,
5
5
  } from './geometryHandler'
6
+ import { clipModuleFieldFittingPanels } from '../UpdateRoofModuleFieldRelations'
6
7
 
7
8
  export default {
8
9
  data() {
@@ -11,35 +12,149 @@ export default {
11
12
  }
12
13
  },
13
14
  methods: {
15
+ refreshModuleFieldFittingPanels(moduleFieldId = null) {
16
+ this.polygons
17
+ .filter((p) => p.layer === 'moduleField')
18
+ .forEach((moduleField) => {
19
+ if (
20
+ moduleField.data &&
21
+ (moduleFieldId == null || moduleField.id == moduleFieldId)
22
+ ) {
23
+ clipModuleFieldFittingPanels(moduleField)
24
+ }
25
+ })
26
+ this.renderModuleFields()
27
+ },
14
28
  renderModuleFields() {
15
- this.clearByType('moduleFieldsMeshes')
16
- const moduleFieldPolygons = this.polygons.filter((p) =>
17
- ['moduleField', 'tmpModuleField'].includes(p.layer)
29
+ console.log('renderModuleFields')
30
+ this.clearRemovedModuleFieldGroups()
31
+ const moduleFieldPolygons = this.polygons.filter(
32
+ (p) => p.layer === 'moduleField'
18
33
  )
19
34
  moduleFieldPolygons.forEach((moduleFieldPolygon) => {
20
- const panelOverrideMesh =
21
- this.renderModuleFieldPanelOverride(moduleFieldPolygon)
22
- const fittingPanelsMesh =
23
- this.renderModuleFieldFittingPanels(moduleFieldPolygon)
35
+ let group = this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id]
36
+ if (!group) {
37
+ group = new THREE.Group()
38
+ group.matrixAutoUpdate = false
39
+ group.userData.type = 'moduleFieldsMeshes'
40
+ group.userData.layer = moduleFieldPolygon.layer
41
+ group.userData.meshId = moduleFieldPolygon.id
42
+ group.userData.polygonId = moduleFieldPolygon.id
43
+ this.scene.add(group)
44
+ this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id] = group
45
+ }
24
46
 
25
- if (!panelOverrideMesh && !fittingPanelsMesh) return
47
+ this.updateModuleFieldPanelOverrideMesh(group, moduleFieldPolygon)
48
+ this.updateModuleFieldFittingPanelsMesh(group, moduleFieldPolygon)
26
49
 
27
- const meshList = [panelOverrideMesh, fittingPanelsMesh]
28
- const group = new THREE.Group()
29
- meshList.forEach((mesh) => {
30
- if (mesh) group.add(mesh)
31
- })
32
- group.matrixAutoUpdate = false
33
50
  group.userData.version = moduleFieldPolygon.version
34
- group.userData.type = 'moduleFieldsMeshes'
35
- group.userData.layer = moduleFieldPolygon.layer
36
- group.userData.meshId = moduleFieldPolygon.id
37
- group.userData.polygonId = moduleFieldPolygon.id
38
51
  group.visible = this.areModuleFieldsVisible && this.panelOpacity === 1
39
- this.scene.add(group)
40
- this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id] = group
52
+
53
+ if (group.children.length === 0) {
54
+ this.scene.remove(group)
55
+ delete this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id]
56
+ }
57
+ })
58
+ },
59
+ clearRemovedModuleFieldGroups() {
60
+ Object.values(this.meshes.moduleFieldsMeshes).forEach((group) => {
61
+ if (!this.polygonIds.includes(group.userData.polygonId)) {
62
+ this.disposeModuleFieldGroupChildren(group)
63
+ this.scene.remove(group)
64
+ delete this.meshes.moduleFieldsMeshes[group.userData.meshId]
65
+ }
66
+ })
67
+ },
68
+ disposeModuleFieldGroupChildren(group) {
69
+ group.children.forEach((child) => {
70
+ if (child.geometry?.dispose) child.geometry.dispose()
71
+ if (
72
+ child.userData.type === 'moduleFieldsMeshes_fittingPanels' &&
73
+ child.material?.dispose
74
+ ) {
75
+ child.material.dispose()
76
+ }
77
+ })
78
+ },
79
+ updateModuleFieldPanelOverrideMesh(group, moduleFieldPolygon) {
80
+ const type = 'moduleFieldsMeshes_panelOverride'
81
+ let mesh = group.children.find((c) => c.userData.type === type)
82
+ const shouldExist = !!moduleFieldPolygon?.data?.number_of_panels_override
83
+
84
+ if (!shouldExist) {
85
+ if (mesh) {
86
+ group.remove(mesh)
87
+ if (mesh.geometry?.dispose) mesh.geometry.dispose()
88
+ }
89
+ return
90
+ }
91
+
92
+ if (!mesh) {
93
+ mesh = this.renderModuleFieldPanelOverride(moduleFieldPolygon)
94
+ if (mesh) group.add(mesh)
95
+ return
96
+ }
97
+
98
+ if (mesh.userData.version < moduleFieldPolygon.version) {
99
+ const geometry = getBufferModuleFieldGeometry(moduleFieldPolygon)
100
+ if (geometry) {
101
+ if (mesh.geometry?.dispose) mesh.geometry.dispose()
102
+ mesh.geometry = geometry
103
+ mesh.userData.version = moduleFieldPolygon.version
104
+ mesh.updateMatrix()
105
+ }
106
+ }
107
+ },
108
+ updateModuleFieldFittingPanelsMesh(group, moduleFieldPolygon) {
109
+ const type = 'moduleFieldsMeshes_fittingPanels'
110
+ let mesh = group.children.find((c) => c.userData.type === type)
111
+ const fittingPanels = moduleFieldPolygon?.fittingPanels
112
+
113
+ const removeMesh = () => {
114
+ if (mesh) {
115
+ group.remove(mesh)
116
+ if (mesh.geometry?.dispose) mesh.geometry.dispose()
117
+ if (mesh.material?.dispose) mesh.material.dispose()
118
+ }
119
+ }
120
+
121
+ if (!fittingPanels?.polygons?.length) {
122
+ removeMesh()
123
+ return
124
+ }
125
+
126
+ if (!mesh) {
127
+ mesh = this.renderModuleFieldFittingPanels(moduleFieldPolygon)
128
+ if (mesh) group.add(mesh)
41
129
  return
130
+ }
131
+
132
+ const { polygons, visible, color } = fittingPanels
133
+ const geometries = []
134
+ polygons.forEach((polygon) => {
135
+ const geometry = getFittingPanelsBufferGeometry(
136
+ polygon.outline,
137
+ polygon.holes,
138
+ moduleFieldPolygon
139
+ )
140
+ if (geometry) geometries.push(geometry)
42
141
  })
142
+
143
+ if (!geometries.length) {
144
+ removeMesh()
145
+ return
146
+ }
147
+
148
+ mesh.geometry = this.updateMergedGeometry(geometries, mesh.geometry)
149
+ const isTransparent = color === 'transparent'
150
+ mesh.material.color.set(isTransparent ? '#ffffff' : color)
151
+ mesh.material.transparent = true
152
+ mesh.material.opacity = isTransparent ? 0.2 : 0.8
153
+ mesh.material.needsUpdate = true
154
+ mesh.userData.fittingPanelsVisible = visible
155
+ mesh.userData.version = moduleFieldPolygon.version
156
+ mesh.visible =
157
+ visible && this.areModuleFieldsVisible && this.panelOpacity === 1
43
158
  },
44
159
  renderModuleFieldPanelOverride(moduleFieldPolygon) {
45
160
  if (!moduleFieldPolygon?.data?.number_of_panels_override) return
@@ -60,7 +175,6 @@ export default {
60
175
  renderModuleFieldFittingPanels(moduleFieldPolygon) {
61
176
  if (!moduleFieldPolygon?.fittingPanels?.polygons?.length) return
62
177
  const { polygons, visible, color } = moduleFieldPolygon.fittingPanels
63
-
64
178
  const geometries = []
65
179
  polygons.forEach((polygon) => {
66
180
  const { outline, holes } = polygon
@@ -84,8 +198,10 @@ export default {
84
198
  })
85
199
 
86
200
  const mesh = new THREE.Mesh(mergedGeometry, material)
87
- mesh.visible = visible
201
+ mesh.visible =
202
+ visible && this.areModuleFieldsVisible && this.panelOpacity === 1
88
203
  mesh.matrixAutoUpdate = false
204
+ mesh.userData.fittingPanelsVisible = visible
89
205
  mesh.userData.version = moduleFieldPolygon.version
90
206
  mesh.userData.type = 'moduleFieldsMeshes_fittingPanels'
91
207
  mesh.userData.layer = moduleFieldPolygon.layer
@@ -96,11 +212,17 @@ export default {
96
212
  },
97
213
  updateModuleFieldsVisibility(isVisible) {
98
214
  this.areModuleFieldsVisible = isVisible
99
- Object.values(this.meshes['moduleFieldsMeshes']).forEach(
100
- (mesh) =>
101
- (mesh.visible =
102
- this.areModuleFieldsVisible && this.panelOpacity === 1)
103
- )
215
+ const moduleFieldsVisible =
216
+ this.areModuleFieldsVisible && this.panelOpacity === 1
217
+ Object.values(this.meshes['moduleFieldsMeshes']).forEach((group) => {
218
+ group.visible = moduleFieldsVisible
219
+ group.children.forEach((child) => {
220
+ if (child.userData.type === 'moduleFieldsMeshes_fittingPanels') {
221
+ child.visible =
222
+ moduleFieldsVisible && child.userData.fittingPanelsVisible
223
+ }
224
+ })
225
+ })
104
226
  },
105
227
  hideModuleFields() {
106
228
  this.updateModuleFieldsVisibility(false)