@eturnity/eturnity_3d 8.19.2 → 8.19.3-EPDM-14200.1

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_3d",
3
3
  "private": false,
4
- "version": "8.19.2",
4
+ "version": "8.19.3-EPDM-14200.1",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
package/src/config.js CHANGED
@@ -282,3 +282,14 @@ export const hitOptionStroke = {
282
282
 
283
283
  export const dragHeightSensitivity = 20
284
284
  export const snapLimit = 100
285
+
286
+ export const fittingPanelsColorMap = {
287
+ black: '#000000',
288
+ blue: '#2B3A67',
289
+ brown: '#623A36',
290
+ 'rustic red': '#713229',
291
+ terracotta: '#A76448',
292
+ transparent: 'transparent',
293
+ white: '#ffffff',
294
+ default: '#0068DE',
295
+ }
@@ -3,7 +3,11 @@ import {
3
3
  verticalProjectionOnPlane,
4
4
  intersectOutlines,
5
5
  calculateArea,
6
+ isSelfIntersecting,
7
+ isPolygonInsidePolygon,
8
+ getConcaveOutlines,
6
9
  } from '@eturnity/eturnity_maths'
10
+ import { fittingPanelsColorMap } from '../config'
7
11
  //this function just update the "roof" and "moduleField(s)" properties of panels,moduleFields and roofs
8
12
  export function UpdateRoofModuleFieldRelations(state) {
9
13
  let moduleFields = state.polygons.filter(
@@ -78,3 +82,170 @@ export function removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state) {
78
82
  ]
79
83
  return state
80
84
  }
85
+
86
+ export function updateModuleFieldFittingPanels(moduleField) {
87
+ 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 = []
92
+
93
+ const outlines = getModuleFieldRoofIntersections(
94
+ moduleField,
95
+ moduleField.roof.margins.innerOutline
96
+ )
97
+ const holes = getModuleFieldHoleOutlines(moduleField)
98
+
99
+ outlines.forEach((outline) => {
100
+ polygons.push({
101
+ outline,
102
+ // Clean up holes outside of the outline
103
+ holes: holes
104
+ .flatMap((hole) => intersectOutlines(hole, outline))
105
+ .filter(Boolean),
106
+ })
107
+ })
108
+
109
+ // map backsheet color to hex color
110
+ const backSheetColor = moduleField.pvData?.backsheet_color || 'default'
111
+ const mappedColor = fittingPanelsColorMap[backSheetColor]
112
+
113
+ moduleField.fittingPanels = {
114
+ polygons,
115
+ visible: true,
116
+ color: mappedColor,
117
+ }
118
+ }
119
+
120
+ export function getRoofInnerMarginOutline({
121
+ roof,
122
+ moduleField,
123
+ pointer = null,
124
+ } = {}) {
125
+ let roofInnerMarginOutline = roof.margins.innerOutline
126
+ let isOutlineSelfIntersecting = false
127
+ let sumAllArea = 0
128
+ if (isSelfIntersecting(roofInnerMarginOutline)) {
129
+ let selfIntersections = intersectOutlines(
130
+ roof.margins.innerOutline,
131
+ roof.margins.innerOutline
132
+ )
133
+ let sumAllArea = selfIntersections.reduce((acc, cur) => {
134
+ acc += calculateArea(cur)
135
+ return acc
136
+ }, 0)
137
+ if (moduleField) {
138
+ selfIntersections = selfIntersections.filter((i) => {
139
+ if (calculateArea(i) < 0.05 * sumAllArea) {
140
+ return false
141
+ }
142
+ let MFintersections = intersectOutlines(moduleField.outline, i)
143
+ if (MFintersections.length > 0) {
144
+ return true
145
+ }
146
+ })
147
+ } else {
148
+ if (pointer && pointer.reality_x && pointer.reality_y) {
149
+ let P = {
150
+ x: pointer.reality_x,
151
+ y: pointer.reality_y,
152
+ z: 0,
153
+ }
154
+ let pointerIntersections = selfIntersections.filter((outline) =>
155
+ isInsidePolygon(P, outline)
156
+ )
157
+ if (pointerIntersections.length) {
158
+ selfIntersections = pointerIntersections
159
+ }
160
+ }
161
+ }
162
+ let sortedSelfIntersections = selfIntersections.sort(
163
+ (a, b) => calculateArea(b) - calculateArea(a)
164
+ )
165
+ if (sortedSelfIntersections.length) {
166
+ roofInnerMarginOutline = sortedSelfIntersections[0]
167
+ isOutlineSelfIntersecting = true
168
+ }
169
+ }
170
+ return {
171
+ isSelfIntersecting: isOutlineSelfIntersecting,
172
+ outline: roofInnerMarginOutline,
173
+ totalArea: sumAllArea,
174
+ }
175
+ }
176
+
177
+ export function getModuleFieldRoofIntersections(
178
+ moduleField,
179
+ roofInnerMarginOutline
180
+ ) {
181
+ const roof = moduleField.roof
182
+ let intersections = intersectOutlines(
183
+ roofInnerMarginOutline,
184
+ moduleField.outline
185
+ )
186
+
187
+ intersections = intersections.map((i) =>
188
+ i.map((v) => {
189
+ return {
190
+ ...v,
191
+ z: verticalProjectionOnPlane(v, roof.normalVector, roof.flatOutline[0])
192
+ .z,
193
+ }
194
+ })
195
+ )
196
+
197
+ return intersections
198
+ }
199
+
200
+ export function getModuleFieldHoleOutlines(moduleField) {
201
+ const obstacles = []
202
+ const roof = moduleField.roof
203
+ if (roof.moduleFields && roof.moduleFields.length > 0) {
204
+ obstacles.push(
205
+ ...roof.moduleFields.filter((obstacle) => {
206
+ return (
207
+ obstacle.id != moduleField.id && //obstacle cannot be MF itself
208
+ obstacle.panels &&
209
+ obstacle.panels.length > 0 && //obstacle cannot be a moduleField without panels on
210
+ !isPolygonInsidePolygon(moduleField.outline, obstacle.outline) && //obstacle cannot be a container of the polygon we try to compute
211
+ (obstacle.priority >= moduleField.priority ||
212
+ isPolygonInsidePolygon(obstacle.outline, moduleField.outline)) //obstacle cannot be a MF with lower prio level (except if fully inside)
213
+ )
214
+ })
215
+ )
216
+ }
217
+
218
+ let groupedConcavePanelOutlines = []
219
+ obstacles.forEach((moduleField) => {
220
+ if (moduleField.panels.length) {
221
+ let groupedConcavePanelData = getConcaveOutlines(
222
+ moduleField.panels,
223
+ moduleField.panels[0].outline
224
+ )
225
+ groupedConcavePanelOutlines.push(...groupedConcavePanelData.outlines)
226
+ }
227
+ })
228
+
229
+ function verticalProjectionOnRoofFlatFace(v) {
230
+ const projectedZ = verticalProjectionOnPlane(
231
+ v,
232
+ roof.normalVector,
233
+ roof.flatOutline[0]
234
+ ).z
235
+ return { ...v, z: projectedZ }
236
+ }
237
+
238
+ groupedConcavePanelOutlines = groupedConcavePanelOutlines.map((outline) =>
239
+ outline.map((v) => verticalProjectionOnRoofFlatFace(v)).reverse()
240
+ )
241
+
242
+ const roofObstacleOutlines = roof.holes.map((hole) =>
243
+ hole.margins.outterOutline.map((v) => verticalProjectionOnRoofFlatFace(v))
244
+ )
245
+ const obstacleOutLine = [
246
+ ...roofObstacleOutlines,
247
+ ...groupedConcavePanelOutlines,
248
+ ]
249
+
250
+ return obstacleOutLine
251
+ }
@@ -1,3 +1,5 @@
1
+ import * as THREE from 'three'
2
+
1
3
  export default {
2
4
  methods: {
3
5
  clearThreeObjects(obj) {
@@ -31,18 +33,7 @@ export default {
31
33
  return
32
34
  }
33
35
  scene.remove(mesh)
34
- if (mesh.geometry && mesh.geometry.dispose) {
35
- mesh.geometry.dispose()
36
- mesh.geometry = null
37
- }
38
- if (mesh.texture && mesh.texture.dispose) {
39
- mesh.texture.dispose()
40
- mesh.texture = null
41
- }
42
- if (mesh.material && mesh.material.dispose) {
43
- mesh.material.dispose()
44
- mesh.material = null
45
- }
36
+ this.disposeMesh(mesh)
46
37
  if (
47
38
  mesh.userData.meshId &&
48
39
  this.meshes[mesh.userData.type][mesh.userData.meshId]
@@ -68,19 +59,24 @@ export default {
68
59
  })
69
60
  },
70
61
  clearByType(type) {
71
- Object.values(this.meshes[type]).forEach((mesh) => {
72
- mesh.geometry.dispose()
73
- mesh.geometry = null
74
- if (mesh.material && mesh.material.dispose) {
75
- mesh.material.dispose()
76
- }
77
- mesh.material = null
78
- this.scene.remove(mesh)
62
+ Object.values(this.meshes[type]).forEach((object) => {
63
+ object instanceof THREE.Group
64
+ ? object.children.forEach((c) => this.disposeMesh(c))
65
+ : this.disposeMesh(object)
66
+ this.scene.remove(object)
79
67
  })
80
68
  this.meshes[type] = {}
81
69
  if (this.renderer) {
82
70
  this.renderer.renderLists.dispose()
83
71
  }
84
72
  },
73
+ disposeMesh(mesh) {
74
+ if (mesh.geometry && mesh.geometry.dispose) mesh.geometry.dispose()
75
+ if (mesh.material && mesh.material.dispose) mesh.material.dispose()
76
+ if (mesh.texture && mesh.texture.dispose) mesh.texture.dispose()
77
+ mesh.geometry = null
78
+ mesh.material = null
79
+ mesh.texture = null
80
+ },
85
81
  },
86
82
  }
@@ -88,10 +88,9 @@ export default {
88
88
  cylinder.userData.type = 'edgeMeshes'
89
89
  cylinder.userData.layer = edge.layer
90
90
  cylinder.frustumCulled = false
91
- cylinder.renderOrder = 10
92
91
 
93
92
  cylinder.frustumCulled = false
94
- cylinder.renderOrder = 10
93
+ cylinder.renderOrder = 15
95
94
  this.meshes.edgeMeshes[edge.id] = cylinder
96
95
  this.scene.add(cylinder)
97
96
  }
@@ -410,7 +410,7 @@ export function getBufferObstacleSideGeometry(obstaclePolygon, roofPolygon) {
410
410
 
411
411
  export function getBufferModuleFieldGeometry(moduleFieldPolygon) {
412
412
  const roofPolygon = moduleFieldPolygon.roof
413
- const moduleHeight = 10
413
+ const moduleHeight = 20
414
414
  //get lowest verticalProjection
415
415
  const normalVector = roofPolygon.normalVector
416
416
 
@@ -747,3 +747,55 @@ export function getExtrusionGeometry(
747
747
  const geometry = new THREE.ExtrudeGeometry(shapes, settings)
748
748
  return geometry
749
749
  }
750
+
751
+ export function getFittingPanelsBufferGeometry(
752
+ outline,
753
+ holes = [],
754
+ moduleFieldPolygon
755
+ ) {
756
+ const croppedHolesOutline = holes
757
+ .map((hole) => {
758
+ const cropedHoleOutline = intersectOutlines(hole, outline)[0] || []
759
+ return cropedHoleOutline.map((p) => {
760
+ return {
761
+ x: p.x,
762
+ y: p.y,
763
+ z: verticalProjectionOnPlane(
764
+ p,
765
+ moduleFieldPolygon.normalVector,
766
+ moduleFieldPolygon.flatOutline[0]
767
+ ).z,
768
+ }
769
+ })
770
+ })
771
+ .filter((outline) => !!outline)
772
+
773
+ // add offset to vertices for rendering
774
+ const FITTING_PANELS_OFFSET = 2
775
+ const vectorOffset = multiplyVector(
776
+ FITTING_PANELS_OFFSET,
777
+ moduleFieldPolygon.normalVector
778
+ )
779
+
780
+ const offsetOutline = outline.map((point) => addVector(point, vectorOffset))
781
+ const offsetHolesOutline = croppedHolesOutline.map((outline) =>
782
+ outline.map((point) => addVector(point, vectorOffset))
783
+ )
784
+
785
+ const geometry = new THREE.BufferGeometry()
786
+ const triangleVertices = getTriangleVerticesFromOutline(
787
+ offsetOutline,
788
+ offsetHolesOutline
789
+ )
790
+ if (hasNaN(triangleVertices)) {
791
+ return null
792
+ }
793
+ const vertices = new Float32Array(triangleVertices)
794
+
795
+ const uvs = new Float32Array([1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0])
796
+ geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
797
+ geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2))
798
+ geometry.computeVertexNormals()
799
+ geometry.userData.version = 0
800
+ return geometry
801
+ }
@@ -1,5 +1,9 @@
1
1
  import * as THREE from 'three'
2
- import { getBufferModuleFieldGeometry } from './geometryHandler'
2
+ import {
3
+ getBufferModuleFieldGeometry,
4
+ getFittingPanelsBufferGeometry,
5
+ } from './geometryHandler'
6
+
3
7
  export default {
4
8
  data() {
5
9
  return {
@@ -13,31 +17,89 @@ export default {
13
17
  ['moduleField', 'tmpModuleField'].includes(p.layer)
14
18
  )
15
19
  moduleFieldPolygons.forEach((moduleFieldPolygon) => {
16
- let material
17
- if (moduleFieldPolygon.data.number_of_panels_override) {
18
- material = this.material.moduleFieldWithPanelOveride
19
- } else {
20
- return
21
- }
22
- const geometry = getBufferModuleFieldGeometry(moduleFieldPolygon)
23
- if (geometry) {
24
- const mesh = new THREE.Mesh(geometry, material)
25
- mesh.matrixAutoUpdate = false
26
- mesh.userData.version = moduleFieldPolygon.version
27
- mesh.userData.type = 'moduleFieldsMeshes'
28
- mesh.userData.layer = moduleFieldPolygon.layer
29
- mesh.userData.meshId = moduleFieldPolygon.id
30
- mesh.userData.polygonId = moduleFieldPolygon.id
31
- mesh.visible = this.areModuleFieldsVisible
32
- this.scene.add(mesh)
33
- this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id] = mesh
34
- }
20
+ const panelOverrideMesh =
21
+ this.renderModuleFieldPanelOverride(moduleFieldPolygon)
22
+ const fittingPanelsMesh =
23
+ this.renderModuleFieldFittingPanels(moduleFieldPolygon)
24
+
25
+ if (!panelOverrideMesh && !fittingPanelsMesh) return
26
+
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
+ 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
+ group.visible = this.areModuleFieldsVisible && this.panelOpacity === 1
39
+ this.scene.add(group)
40
+ this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id] = group
41
+ return
35
42
  })
36
43
  },
37
- setModuleFieldsVisibility(isVisible) {
44
+ renderModuleFieldPanelOverride(moduleFieldPolygon) {
45
+ if (!moduleFieldPolygon?.data?.number_of_panels_override) return
46
+ const geometry = getBufferModuleFieldGeometry(moduleFieldPolygon)
47
+ const material = this.material.moduleFieldWithPanelOveride
48
+ if (geometry) {
49
+ const mesh = new THREE.Mesh(geometry, material)
50
+ mesh.matrixAutoUpdate = false
51
+ mesh.userData.version = moduleFieldPolygon.version
52
+ mesh.userData.type = 'moduleFieldsMeshes_panelOverride'
53
+ mesh.userData.layer = moduleFieldPolygon.layer
54
+ mesh.userData.meshId = moduleFieldPolygon.id
55
+ mesh.userData.polygonId = moduleFieldPolygon.id
56
+ mesh.renderOrder = 5
57
+ return mesh
58
+ }
59
+ },
60
+ renderModuleFieldFittingPanels(moduleFieldPolygon) {
61
+ if (!moduleFieldPolygon?.fittingPanels?.polygons?.length) return
62
+ const { polygons, visible, color } = moduleFieldPolygon.fittingPanels
63
+
64
+ const geometries = []
65
+ polygons.forEach((polygon) => {
66
+ const { outline, holes } = polygon
67
+ const geometry = getFittingPanelsBufferGeometry(
68
+ outline,
69
+ holes,
70
+ moduleFieldPolygon
71
+ )
72
+ geometries.push(geometry)
73
+ })
74
+
75
+ if (!geometries.length) return
76
+
77
+ const mergedGeometry = this.createMergedGeometry(geometries)
78
+ const isTransparent = color === 'transparent'
79
+ const material = new THREE.MeshBasicMaterial({
80
+ color: isTransparent ? '#ffffff' : color,
81
+ side: THREE.DoubleSide,
82
+ transparent: true,
83
+ opacity: isTransparent ? 0.2 : 0.8,
84
+ })
85
+
86
+ const mesh = new THREE.Mesh(mergedGeometry, material)
87
+ mesh.visible = visible
88
+ mesh.matrixAutoUpdate = false
89
+ mesh.userData.version = moduleFieldPolygon.version
90
+ mesh.userData.type = 'moduleFieldsMeshes_fittingPanels'
91
+ mesh.userData.layer = moduleFieldPolygon.layer
92
+ mesh.userData.meshId = moduleFieldPolygon.id
93
+ mesh.userData.polygonId = moduleFieldPolygon.id
94
+ mesh.renderOrder = 5
95
+ return mesh
96
+ },
97
+ updateModuleFieldsVisibility(isVisible) {
38
98
  this.areModuleFieldsVisible = isVisible
39
99
  Object.values(this.meshes['moduleFieldsMeshes']).forEach(
40
- (mesh) => (mesh.visible = isVisible)
100
+ (mesh) =>
101
+ (mesh.visible =
102
+ this.areModuleFieldsVisible && this.panelOpacity === 1)
41
103
  )
42
104
  },
43
105
  },
@@ -302,6 +302,11 @@ export default {
302
302
  method: this.renderPanels,
303
303
  arg: [],
304
304
  })
305
+ methodList.push({
306
+ name: 'updateModuleFieldsVisibility',
307
+ method: this.updateModuleFieldsVisibility,
308
+ arg: [this.areModuleFieldsVisible],
309
+ })
305
310
  break
306
311
  case 'addPointToPolygon': //params.polygon,index
307
312
  case 'removeObstacle': //params:obstacle
@@ -420,11 +425,11 @@ export default {
420
425
  arg: [],
421
426
  })
422
427
  break
423
- case 'updateIsYearlyShadingHeatmapActive':
428
+ case 'updateModuleFieldsVisibility':
424
429
  methodList.push({
425
- name: 'setModuleFieldsVisibility',
426
- method: this.setModuleFieldsVisibility,
427
- arg: [!params.value],
430
+ name: 'updateModuleFieldsVisibility',
431
+ method: this.updateModuleFieldsVisibility,
432
+ arg: [params.value],
428
433
  })
429
434
  break
430
435
  case 'selectedPanelChange':
@@ -2,13 +2,15 @@
2
2
  import {
3
3
  Polygon,
4
4
  updateComputedGeometryPolygon,
5
- getRoofMarginOutline,
6
5
  getObstacleMarginOutline,
7
- midPoint,
8
6
  } from '@eturnity/eturnity_maths'
9
7
  import OverlayLayer from '../Overlay/OverlayLayer'
10
8
  import getLayers from '../utils/layers'
11
9
  import { UpdateRoofObstacleRelations } from '../helper/UpdateRoofObstacleRelations'
10
+ import {
11
+ updateModuleFieldFittingPanels,
12
+ UpdateRoofModuleFieldRelations,
13
+ } from '../helper/UpdateRoofModuleFieldRelations'
12
14
  function sanitizedOutline(outline) {
13
15
  return outline.reduce((accumulator, currentValue) => {
14
16
  if (
@@ -128,6 +130,7 @@ export async function hydratePolygons({
128
130
  moduleField.mountingData = {}
129
131
  moduleField.pvData.module_texture_version_id =
130
132
  moduleFieldResp.module_texture_version_id
133
+ moduleField.pvData.backsheet_color = moduleFieldResp.backsheet_color
131
134
  moduleField.data = {}
132
135
  moduleField.data.texture_img_url = moduleFieldResp.texture_img_url
133
136
  moduleField.data.col_spacing_mm = moduleFieldResp.col_spacing_mm
@@ -138,7 +141,8 @@ export async function hydratePolygons({
138
141
  moduleField.data.row_spacing_valley_mm =
139
142
  moduleFieldResp.row_spacing_valley_mm
140
143
  moduleField.data.offset_percent = moduleFieldResp.offset_percent
141
- moduleField.data.fitting_panel_area_m2 = moduleFieldResp.fitting_panel_area_m2
144
+ moduleField.data.fitting_panel_area_m2 =
145
+ moduleFieldResp.fitting_panel_area_m2
142
146
  moduleField.data.has_fitting_panels = moduleFieldResp.has_fitting_panels
143
147
  moduleField.data.panel_orientation = moduleFieldResp.panel_orientation
144
148
  moduleField.data.panel_direction_degrees =
@@ -295,4 +299,10 @@ export async function hydrateData({ state, commit }, data) {
295
299
  state.polygons = hydratationResult.polygons
296
300
 
297
301
  state = UpdateRoofObstacleRelations(state)
302
+ state = UpdateRoofModuleFieldRelations(state)
303
+ state.polygons
304
+ .filter((poly) => poly.layer == 'moduleField')
305
+ .forEach((moduleField) => {
306
+ updateModuleFieldFittingPanels(moduleField)
307
+ })
298
308
  }