@eturnity/eturnity_3d 8.16.1 → 8.16.2-EPDM-14200.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_3d",
3
3
  "private": false,
4
- "version": "8.16.1",
4
+ "version": "8.16.2-EPDM-14200.0",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
@@ -26,7 +26,6 @@
26
26
  layers: null,
27
27
  provider: null,
28
28
  pointer: { x: 0, y: 0 },
29
- areModuleFieldsVisible: false,
30
29
  areUserDeactivatedPanelsVisible: false,
31
30
  animationFrameId: null,
32
31
  intervalId: null,
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: '#0068DE',
289
+ brown: '#5C4033',
290
+ 'rustic red': '#8B0000',
291
+ terracotta: '#E35336',
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,168 @@ export function removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state) {
78
82
  ]
79
83
  return state
80
84
  }
85
+
86
+ export function updateModuleFieldFittingPanels({
87
+ moduleField,
88
+ state,
89
+ preComputed = false,
90
+ outline = [],
91
+ holes = [],
92
+ }) {
93
+ delete moduleField.fittingPanels
94
+ const { has_fitting_panels, fitting_panel_area_m2 } = moduleField.data
95
+ const hasFittingPanels = has_fitting_panels && fitting_panel_area_m2 > 0
96
+ if (!hasFittingPanels) return
97
+
98
+ const computedOutline = preComputed
99
+ ? outline
100
+ : getModuleFieldRoofIntersection(
101
+ moduleField,
102
+ getRoofInnerMarginOutline(moduleField.roof, moduleField, state).outline
103
+ )
104
+
105
+ const computedHoles = preComputed
106
+ ? holes
107
+ : getModuleFieldHoleOutlines(moduleField)
108
+
109
+ // Clean up holes outside of the outline
110
+ const holesClean = computedHoles
111
+ .map((hole) => intersectOutlines(hole, computedOutline)[0])
112
+ .filter(Boolean)
113
+
114
+ // map backsheet color to hex color
115
+ const backSheetColor = moduleField.pvData?.backsheet_color || 'default'
116
+ const mappedColor = fittingPanelsColorMap[backSheetColor]
117
+
118
+ moduleField.fittingPanels = {
119
+ outline: computedOutline,
120
+ holes: holesClean,
121
+ visible: true,
122
+ color: mappedColor,
123
+ }
124
+ }
125
+
126
+ export function getRoofInnerMarginOutline(roof, moduleField, state) {
127
+ let roofInnerMarginOutline = roof.margins.innerOutline
128
+ let isOutlineSelfIntersecting = false
129
+ let sumAllArea = 0
130
+ if (isSelfIntersecting(roofInnerMarginOutline)) {
131
+ let selfIntersections = intersectOutlines(
132
+ roof.margins.innerOutline,
133
+ roof.margins.innerOutline
134
+ )
135
+ let sumAllArea = selfIntersections.reduce((acc, cur) => {
136
+ acc += calculateArea(cur)
137
+ return acc
138
+ }, 0)
139
+ if (moduleField) {
140
+ selfIntersections = selfIntersections.filter((i) => {
141
+ if (calculateArea(i) < 0.05 * sumAllArea) {
142
+ return false
143
+ }
144
+ let MFintersections = intersectOutlines(moduleField.outline, i)
145
+ if (MFintersections.length > 0) {
146
+ return true
147
+ }
148
+ })
149
+ } else {
150
+ if (state.pointer && state.pointer.reality_x && state.pointer.reality_y) {
151
+ let P = {
152
+ x: state.pointer.reality_x,
153
+ y: state.pointer.reality_y,
154
+ z: 0,
155
+ }
156
+ let pointerIntersections = selfIntersections.filter((outline) =>
157
+ isInsidePolygon(P, outline)
158
+ )
159
+ if (pointerIntersections.length) {
160
+ selfIntersections = pointerIntersections
161
+ }
162
+ }
163
+ }
164
+ let sortedSelfIntersections = selfIntersections.sort(
165
+ (a, b) => calculateArea(b) - calculateArea(a)
166
+ )
167
+ if (sortedSelfIntersections.length) {
168
+ roofInnerMarginOutline = sortedSelfIntersections[0]
169
+ isOutlineSelfIntersecting = true
170
+ }
171
+ }
172
+ return {
173
+ isSelfIntersecting: isOutlineSelfIntersecting,
174
+ outline: roofInnerMarginOutline,
175
+ totalArea: sumAllArea,
176
+ }
177
+ }
178
+
179
+ export function getModuleFieldRoofIntersection(
180
+ moduleField,
181
+ roofInnerMarginOutline
182
+ ) {
183
+ const roof = moduleField.roof
184
+ let intersection = intersectOutlines(
185
+ roofInnerMarginOutline,
186
+ moduleField.outline
187
+ )[0]
188
+
189
+ intersection = intersection?.map((v) => {
190
+ return {
191
+ ...v,
192
+ z: verticalProjectionOnPlane(v, roof.normalVector, roof.flatOutline[0]).z,
193
+ }
194
+ })
195
+ return intersection
196
+ }
197
+
198
+ export function getModuleFieldHoleOutlines(moduleField) {
199
+ const obstacles = []
200
+ const roof = moduleField.roof
201
+ if (roof.moduleFields && roof.moduleFields.length > 0) {
202
+ obstacles.push(
203
+ ...roof.moduleFields.filter((obstacle) => {
204
+ return (
205
+ obstacle.id != moduleField.id && //obstacle cannot be MF itself
206
+ obstacle.panels &&
207
+ obstacle.panels.length > 0 && //obstacle cannot be a moduleField without panels on
208
+ !isPolygonInsidePolygon(moduleField.outline, obstacle.outline) && //obstacle cannot be a container of the polygon we try to compute
209
+ (obstacle.priority >= moduleField.priority ||
210
+ isPolygonInsidePolygon(obstacle.outline, moduleField.outline)) //obstacle cannot be a MF with lower prio level (except if fully inside)
211
+ )
212
+ })
213
+ )
214
+ }
215
+
216
+ let groupedConcavePanelOutlines = []
217
+ obstacles.forEach((moduleField) => {
218
+ if (moduleField.panels.length) {
219
+ let groupedConcavePanelData = getConcaveOutlines(
220
+ moduleField.panels,
221
+ moduleField.panels[0].outline
222
+ )
223
+ groupedConcavePanelOutlines.push(...groupedConcavePanelData.outlines)
224
+ }
225
+ })
226
+
227
+ function verticalProjectionOnRoofFlatFace(v) {
228
+ const projectedZ = verticalProjectionOnPlane(
229
+ v,
230
+ roof.normalVector,
231
+ roof.flatOutline[0]
232
+ ).z
233
+ return { ...v, z: projectedZ }
234
+ }
235
+
236
+ groupedConcavePanelOutlines = groupedConcavePanelOutlines.map((outline) =>
237
+ outline.map((v) => verticalProjectionOnRoofFlatFace(v)).reverse()
238
+ )
239
+
240
+ const roofObstacleOutlines = roof.holes.map((hole) =>
241
+ hole.margins.outterOutline.map((v) => verticalProjectionOnRoofFlatFace(v))
242
+ )
243
+ const obstacleOutLine = [
244
+ ...roofObstacleOutlines,
245
+ ...groupedConcavePanelOutlines,
246
+ ]
247
+
248
+ return obstacleOutLine
249
+ }
@@ -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
+ const vertexList = [outline, ...croppedHolesOutline].flat()
780
+ vertexList.forEach((point) => {
781
+ const offset = addVector(point, vectorOffset)
782
+ Object.assign(point, offset)
783
+ })
784
+
785
+ const geometry = new THREE.BufferGeometry()
786
+ const triangleVertices = getTriangleVerticesFromOutline(
787
+ outline,
788
+ croppedHolesOutline
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
  methods: {
5
9
  data() {
@@ -13,27 +17,78 @@ 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
39
+ this.scene.add(group)
40
+ this.meshes.moduleFieldsMeshes[moduleFieldPolygon.id] = group
41
+ return
35
42
  })
36
43
  },
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?.outline) return
62
+ const { outline, holes, visible, color } =
63
+ moduleFieldPolygon.fittingPanels
64
+
65
+ const geometry = getFittingPanelsBufferGeometry(
66
+ outline,
67
+ holes,
68
+ moduleFieldPolygon
69
+ )
70
+
71
+ const isTransparent = color === 'transparent'
72
+ const material = new THREE.MeshBasicMaterial({
73
+ color: isTransparent ? '#ffffff' : color,
74
+ side: THREE.DoubleSide,
75
+ transparent: true,
76
+ opacity: isTransparent ? 0.2 : 0.8,
77
+ })
78
+
79
+ if (geometry) {
80
+ const mesh = new THREE.Mesh(geometry, material)
81
+ mesh.visible = visible
82
+ mesh.matrixAutoUpdate = false
83
+ mesh.userData.version = moduleFieldPolygon.version
84
+ mesh.userData.type = 'moduleFieldsMeshes_fittingPanels'
85
+ mesh.userData.layer = moduleFieldPolygon.layer
86
+ mesh.userData.meshId = moduleFieldPolygon.id
87
+ mesh.userData.polygonId = moduleFieldPolygon.id
88
+ mesh.renderOrder = 5
89
+ return mesh
90
+ }
91
+ },
37
92
  setModuleFieldsVisibility(isVisible) {
38
93
  this.areModuleFieldsVisible = isVisible
39
94
  Object.values(this.meshes['moduleFieldsMeshes']).forEach(
@@ -36,6 +36,7 @@ export default {
36
36
  const texturedRoof = mesh.getObjectByName('textured_roof')
37
37
  if (texturedRoof) {
38
38
  if (
39
+ yearlySunShadingOverlay &&
39
40
  yearlySunShadingOverlay.isActive &&
40
41
  yearlySunShadingMaterial
41
42
  ) {
@@ -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 (
@@ -138,7 +140,8 @@ export async function hydratePolygons({
138
140
  moduleField.data.row_spacing_valley_mm =
139
141
  moduleFieldResp.row_spacing_valley_mm
140
142
  moduleField.data.offset_percent = moduleFieldResp.offset_percent
141
- moduleField.data.fitting_panel_area_m2 = moduleFieldResp.fitting_panel_area_m2
143
+ moduleField.data.fitting_panel_area_m2 =
144
+ moduleFieldResp.fitting_panel_area_m2
142
145
  moduleField.data.has_fitting_panels = moduleFieldResp.has_fitting_panels
143
146
  moduleField.data.panel_orientation = moduleFieldResp.panel_orientation
144
147
  moduleField.data.panel_direction_degrees =
@@ -295,4 +298,13 @@ export async function hydrateData({ state, commit }, data) {
295
298
  state.polygons = hydratationResult.polygons
296
299
 
297
300
  state = UpdateRoofObstacleRelations(state)
301
+ state = UpdateRoofModuleFieldRelations(state)
302
+ state.polygons
303
+ .filter((poly) => poly.layer == 'moduleField')
304
+ .forEach((moduleField) => {
305
+ updateModuleFieldFittingPanels({
306
+ moduleField,
307
+ state,
308
+ })
309
+ })
298
310
  }