fdsreader 0.9.20__tar.gz → 1.2.8__tar.gz

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 (65) hide show
  1. {fdsreader-0.9.20 → fdsreader-1.2.8}/LICENSE +0 -0
  2. {fdsreader-0.9.20 → fdsreader-1.2.8}/MANIFEST.in +0 -0
  3. fdsreader-1.2.8/PKG-INFO +86 -0
  4. {fdsreader-0.9.20 → fdsreader-1.2.8}/README.md +1 -1
  5. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/__init__.py +0 -0
  6. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/_version.py +1 -1
  7. fdsreader-1.2.8/fdsreader/bndf/ObstructionCollection.py +53 -0
  8. fdsreader-1.2.8/fdsreader/bndf/__init__.py +3 -0
  9. fdsreader-1.2.8/fdsreader/bndf/obstruction.py +348 -0
  10. fdsreader-1.2.8/fdsreader/bndf/utils.py +33 -0
  11. fdsreader-1.2.8/fdsreader/evac/EvacCollection.py +453 -0
  12. fdsreader-1.2.8/fdsreader/evac/__init__.py +3 -0
  13. fdsreader-1.2.8/fdsreader/evac/evacuation.py +196 -0
  14. fdsreader-1.2.8/fdsreader/fds_classes/MeshCollection.py +21 -0
  15. fdsreader-1.2.8/fdsreader/fds_classes/__init__.py +7 -0
  16. {fdsreader-0.9.20/fdsreader/utils → fdsreader-1.2.8/fdsreader}/fds_classes/mesh.py +56 -23
  17. {fdsreader-0.9.20/fdsreader/utils → fdsreader-1.2.8/fdsreader}/fds_classes/surface.py +0 -0
  18. {fdsreader-0.9.20/fdsreader/utils → fdsreader-1.2.8/fdsreader}/fds_classes/ventilation.py +2 -1
  19. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/geom/GeometryCollection.py +8 -4
  20. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/geom/__init__.py +0 -0
  21. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/geom/geometry.py +108 -13
  22. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/isof/IsosurfaceCollection.py +7 -3
  23. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/isof/__init__.py +0 -0
  24. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/isof/isosurface.py +7 -8
  25. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/part/ParticleCollection.py +37 -26
  26. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/part/__init__.py +0 -0
  27. fdsreader-1.2.8/fdsreader/part/particle.py +113 -0
  28. fdsreader-0.9.20/fdsreader/pl3d/Plot3dCollection.py → fdsreader-1.2.8/fdsreader/pl3d/Plot3DCollection.py +11 -3
  29. fdsreader-1.2.8/fdsreader/pl3d/__init__.py +3 -0
  30. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/pl3d/pl3d.py +23 -7
  31. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/settings.py +3 -2
  32. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/simulation.py +344 -159
  33. fdsreader-1.2.8/fdsreader/slcf/GeomSliceCollection.py +62 -0
  34. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/slcf/SliceCollection.py +9 -5
  35. fdsreader-1.2.8/fdsreader/slcf/__init__.py +7 -0
  36. fdsreader-1.2.8/fdsreader/slcf/geomslice.py +458 -0
  37. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/slcf/slice.py +150 -42
  38. fdsreader-1.2.8/fdsreader/smoke3d/Smoke3DCollection.py +28 -0
  39. fdsreader-1.2.8/fdsreader/smoke3d/__init__.py +3 -0
  40. fdsreader-1.2.8/fdsreader/smoke3d/smoke3d.py +250 -0
  41. fdsreader-1.2.8/fdsreader/utils/__init__.py +7 -0
  42. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/data.py +64 -17
  43. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/dimension.py +0 -0
  44. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/extent.py +13 -7
  45. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/fortran_data.py +3 -3
  46. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/misc.py +0 -0
  47. fdsreader-1.2.8/fdsreader.egg-info/PKG-INFO +86 -0
  48. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/SOURCES.txt +16 -6
  49. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/dependency_links.txt +0 -0
  50. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/requires.txt +0 -0
  51. {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/top_level.txt +0 -0
  52. fdsreader-1.2.8/requirements.txt +113 -0
  53. {fdsreader-0.9.20 → fdsreader-1.2.8}/setup.cfg +0 -0
  54. {fdsreader-0.9.20 → fdsreader-1.2.8}/setup.py +0 -0
  55. fdsreader-0.9.20/PKG-INFO +0 -83
  56. fdsreader-0.9.20/fdsreader/bndf/ObstructionCollection.py +0 -51
  57. fdsreader-0.9.20/fdsreader/bndf/__init__.py +0 -3
  58. fdsreader-0.9.20/fdsreader/bndf/obstruction.py +0 -376
  59. fdsreader-0.9.20/fdsreader/part/particle.py +0 -71
  60. fdsreader-0.9.20/fdsreader/pl3d/__init__.py +0 -3
  61. fdsreader-0.9.20/fdsreader/slcf/__init__.py +0 -3
  62. fdsreader-0.9.20/fdsreader/utils/__init__.py +0 -13
  63. fdsreader-0.9.20/fdsreader/utils/fds_classes/__init__.py +0 -1
  64. fdsreader-0.9.20/fdsreader.egg-info/PKG-INFO +0 -83
  65. fdsreader-0.9.20/requirements.txt +0 -48
File without changes
File without changes
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.1
2
+ Name: fdsreader
3
+ Version: 1.2.8
4
+ Summary: Python reader for data generated by FDS.
5
+ Home-page: https://github.com/JanVogelsang/fdsreader
6
+ Author: FZJ IAS-7 (Prof. Dr. Lukas Arnold, Jan Vogelsang)
7
+ Author-email: j.vogelsang@fz-juelich.de
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+
17
+ # FDSReader
18
+ > Fast and easy-to-use Python reader for FDS data
19
+
20
+ [![Build Status](https://travis-ci.com/FireDynamics/fdsreader.svg?branch=master)](https://travis-ci.com/FireDynamics/fdsreader)
21
+ [![PyPI version](https://badge.fury.io/py/fdsreader.png)](https://badge.fury.io/py/fdsreader)
22
+
23
+
24
+ ## Installation
25
+
26
+ The package is available on PyPI and can be installed using pip:
27
+ ```sh
28
+ pip install fdsreader
29
+ ```
30
+
31
+ ## Usage example
32
+ ```python
33
+ import fdsreader as fds
34
+
35
+ # Creates an instance of a simulation master-class which manages all data for a given simulation
36
+ sim = fds.Simulation("./sample_data")
37
+
38
+ # Examples of data that can be easily accessed
39
+ print(sim.meshes, sim.surfaces, sim.slices, sim.data_3d, sim.isosurfaces, sim.particles, sim.obstructions)
40
+ ```
41
+
42
+ More advanced examples can be found in the respective data type directories inside of the examples directory.
43
+
44
+ ### Configuration
45
+ The package provides a few configuration options that can be set using the `settings` module.
46
+ ```python
47
+ fds.settings.KEY = VALUE
48
+
49
+ # Example
50
+ fds.settings.DEBUG = True
51
+ ```
52
+
53
+ | KEY | VALUE | Default | Description |
54
+ |----------------|---------|---------|-------------|
55
+ | LAZY_LOAD | boolean | True | Load all data when initially loading the simulation (False) or only when specific data is needed (True). |
56
+ | ENABLE_CACHING | boolean | True | Cache the loaded simulation to reduce startup times when loading the same simulation again. |
57
+ | DEBUG | boolean | False | Crash on non-critical errors with an exception (True) or hide non-critical errors (False). |
58
+
59
+
60
+ ### Data structure
61
+ ![Data structure](https://raw.githubusercontent.com/FireDynamics/fdsreader/master/docs/img/data-structure.svg)
62
+
63
+ Beware that not all attributes and methods are covered in this diagram. For a complete
64
+ documentation of all classes check the API Documentation below.
65
+
66
+ ## API Documentation
67
+ [https://firedynamics.github.io/fdsreader/](https://firedynamics.github.io/fdsreader/)
68
+
69
+ ## Meta
70
+
71
+ * Jan Vogelsang – j.vogelsang@fz-juelich.de
72
+ * Prof. Dr. Lukas Arnold - l.arnold@fz-juelich.de
73
+
74
+ Distributed under the LGPLv3 (GNU Lesser General Public License v3) license. See ``LICENSE`` for more information.
75
+
76
+ [https://github.com/FireDynamics/fdsreader](https://github.com/FireDynamics/fdsreader)
77
+
78
+ ## Contributing
79
+
80
+ 1. Fork it (<https://github.com/FireDynamics/fdsreader/fork>)
81
+ 2. Create your feature branch (`git checkout -b feature/fooBar`)
82
+ 3. Commit your changes (`git commit -am 'Add some fooBar'`)
83
+ 4. Push to the branch (`git push origin feature/fooBar`)
84
+ 5. Create a new Pull Request
85
+
86
+
@@ -20,7 +20,7 @@ import fdsreader as fds
20
20
  sim = fds.Simulation("./sample_data")
21
21
 
22
22
  # Examples of data that can be easily accessed
23
- print(sim.meshes, sim.surfaces, sim.slices, sim.data_3d, sim.isosurfaces, sim.particles, sim.obstructions, sim.obstructions[0].get_boundary_data("temperature"))
23
+ print(sim.meshes, sim.surfaces, sim.slices, sim.data_3d, sim.isosurfaces, sim.particles, sim.obstructions)
24
24
  ```
25
25
 
26
26
  More advanced examples can be found in the respective data type directories inside of the examples directory.
@@ -7,5 +7,5 @@ Provides fdsreader version information.
7
7
 
8
8
  from incremental import Version
9
9
 
10
- __version__ = Version('fdsreader', 0, 9, 20)
10
+ __version__ = Version('fdsreader', 1, 2, 8)
11
11
  __all__ = ["__version__"]
@@ -0,0 +1,53 @@
1
+ from typing import Iterable, Tuple, List
2
+ import numpy as np
3
+
4
+ from fdsreader.bndf import Obstruction
5
+ from fdsreader.utils.data import FDSDataCollection, Quantity
6
+
7
+
8
+ class ObstructionCollection(FDSDataCollection):
9
+ """Collection of :class:`Obstruction` objects. Offers extensive functionality for filtering and
10
+ using obstructions as well as dependent such as :class:`Boundary`.
11
+ """
12
+
13
+ def __init__(self, *obstructions: Iterable[Obstruction]):
14
+ super().__init__(*obstructions)
15
+
16
+ @property
17
+ def quantities(self) -> List[Quantity]:
18
+ qs = set()
19
+ for obst in self:
20
+ for q in obst.quantities:
21
+ qs.add(q)
22
+ return list(qs)
23
+
24
+ def filter_by_boundary_data(self):
25
+ """Filters all obstructions for which output data exists.
26
+ """
27
+ return ObstructionCollection(x for x in self if x.has_boundary_data)
28
+
29
+ def get_by_id(self, obst_id: str):
30
+ """Get the obstruction with corresponding id if it exists.
31
+ """
32
+ return next((obst for obst in self if obst.id == obst_id), None)
33
+
34
+ def get_nearest_obstruction(self, point: Tuple[float, float, float]) -> Obstruction:
35
+ """Filters the obstruction with the shortest distance to the given point.
36
+ """
37
+ d_min = np.finfo(float).max
38
+ obst_min = None
39
+
40
+ for obst in self:
41
+ for subobst in obst:
42
+ dx = max(subobst.extent.x_start - point[0], 0, point[0] - subobst.extent.x_end)
43
+ dy = max(subobst.extent.y_start - point[1], 0, point[1] - subobst.extent.y_end)
44
+ dz = max(subobst.extent.z_start - point[2], 0, point[2] - subobst.extent.z_end)
45
+ d = np.sqrt(dx * dx + dy * dy + dz * dz)
46
+ if d < d_min:
47
+ d_min = d
48
+ obst_min = obst
49
+
50
+ return obst_min
51
+
52
+ def __repr__(self):
53
+ return "ObstructionCollection(" + super(ObstructionCollection, self).__repr__() + ")"
@@ -0,0 +1,3 @@
1
+ from .obstruction import Obstruction, SubObstruction, Patch, Boundary
2
+
3
+ from .ObstructionCollection import ObstructionCollection
@@ -0,0 +1,348 @@
1
+ import os
2
+ from typing import List, Dict, Tuple, Union, Sequence
3
+ from typing_extensions import Literal
4
+ import numpy as np
5
+
6
+ from fdsreader.utils import Extent, Quantity, Dimension
7
+ import fdsreader.utils.fortran_data as fdtype
8
+ from fdsreader import settings
9
+
10
+
11
+ class Patch:
12
+ """Container for the actual data which is stored as rectangular plane with specific orientation
13
+ and extent.
14
+
15
+ :ivar dimension: :class:`Dimension` object containing information about steps in each dimension.
16
+ :ivar extent: :class:`Extent` object containing 3-dimensional extent information.
17
+ :ivar orientation: The direction the patch is facing (x={-1;1}, y={-2;2}, z={-3;3}).
18
+ :ivar cell_centered: Indicates whether centered positioning for data is used.
19
+ :ivar n_t: Total number of time steps for which output data has been written.
20
+ """
21
+
22
+ def __init__(self, file_path: str, dimension: Dimension, extent: Extent, orientation: int, cell_centered: bool,
23
+ patch_offset: int, initial_offset: int, n_t: int):
24
+ self.file_path = file_path
25
+ self.dimension = dimension
26
+ self.extent = extent
27
+ self.orientation = orientation
28
+ self.cell_centered = cell_centered
29
+ self._patch_offset = patch_offset
30
+ self._initial_offset = initial_offset
31
+ self._time_offset = -1
32
+ self.n_t = n_t
33
+
34
+ @property
35
+ def shape(self) -> Tuple:
36
+ """Convenience function to calculate the shape of the array containing data for this patch.
37
+ """
38
+ return self.dimension.shape(self.cell_centered)
39
+
40
+ @property
41
+ def size(self) -> int:
42
+ """Convenience function to calculate the number of data points in the array for this patch.
43
+ """
44
+ return self.dimension.size(self.cell_centered)
45
+
46
+ def _post_init(self, time_offset: int):
47
+ """Fully initialize the patch as soon as the number of timesteps is known.
48
+ """
49
+ self._time_offset = time_offset
50
+
51
+ @property
52
+ def data(self):
53
+ """Method to load the quantity data for a single patch.
54
+ """
55
+ if not hasattr(self, "_data"):
56
+ dtype_data = fdtype.new((('f', self.dimension.size(cell_centered=False)),))
57
+
58
+ if self.n_t == -1:
59
+ self.n_t = (os.stat(self.file_path).st_size - self._initial_offset) // self._time_offset
60
+
61
+ self._data = np.empty((self.n_t,) + self.shape)
62
+ with open(self.file_path, 'rb') as infile:
63
+ for t in range(self.n_t):
64
+ infile.seek(self._initial_offset + self._patch_offset + t * self._time_offset)
65
+ data = np.fromfile(infile, dtype_data, 1)[0][1].reshape(
66
+ self.dimension.shape(cell_centered=False), order='F')
67
+ if self.cell_centered:
68
+ self._data[t, :] = data[:-1, :-1]
69
+ else:
70
+ self._data[t, :] = data
71
+ return self._data
72
+
73
+ def clear_cache(self):
74
+ """Remove all data from the internal cache that has been loaded so far to free memory.
75
+ """
76
+ if hasattr(self, "_data"):
77
+ del self._data
78
+
79
+ def __repr__(self, *args, **kwargs):
80
+ return f"Patch(shape={self.shape}, orientation={self.orientation}, extent={self.extent})"
81
+
82
+
83
+ class Boundary:
84
+ """Container for boundary data specific to one quantity.
85
+
86
+ :ivar quantity: Quantity object containing information about the quantity calculated for this
87
+ :class:`Obstruction` with the corresponding short_name and unit.
88
+ :ivar times: Numpy array containing all times for which data has been recorded.
89
+ :ivar cell_centered: Indicates whether centered positioning for data is used.
90
+ :ivar lower_bounds: Dictionary with lower bounds for each timestep with meshes as keys.
91
+ :ivar upper_bounds: Dictionary with upper bounds for each timestep with meshes as keys.
92
+ :ivar n_t: Total number of time steps for which output data has been written.
93
+ """
94
+
95
+ def __init__(self, quantity: Quantity, cell_centered: bool, times: np.ndarray, n_t: int, patches: List[Patch],
96
+ lower_bounds: np.ndarray, upper_bounds: np.ndarray):
97
+ self.quantity = quantity
98
+ self.cell_centered = cell_centered
99
+ self.patches = patches
100
+ self.times = times
101
+ self.n_t = n_t
102
+ self.lower_bounds = lower_bounds
103
+ self.upper_bounds = upper_bounds
104
+
105
+ @property
106
+ def data(self) -> Dict[int, Patch]:
107
+ """The :class:`Patch` in each direction (-3=-z, -2=-y, -1=-x, 1=x, 2=y, 3=y).
108
+ """
109
+ return {p.orientation: p for p in self.patches}
110
+
111
+ def vmin(self, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
112
+ """Minimum value of all patches at any time.
113
+
114
+ :param orientation: Optionally filter by patches with a specific orientation.
115
+ """
116
+ if orientation == 0:
117
+ curr_min = np.min(self.lower_bounds)
118
+ if curr_min == 0.0:
119
+ return min(np.min(p.data) for p in self.patches)
120
+ return curr_min
121
+ else:
122
+ return np.min(self.data[orientation].data)
123
+
124
+ def vmax(self, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
125
+ """Maximum value of all patches at any time.
126
+
127
+ :param orientation: Optionally filter by patches with a specific orientation.
128
+ """
129
+ if orientation == 0:
130
+ curr_max = np.max(self.upper_bounds)
131
+ if curr_max == np.float32(-1e33):
132
+ return max(np.max(p.data) for p in self.patches)
133
+ return curr_max
134
+ else:
135
+ return np.max(self.data[orientation].data)
136
+
137
+ def clear_cache(self):
138
+ """Remove all data from the internal cache that has been loaded so far to free memory.
139
+ """
140
+ for p in self.patches:
141
+ p.clear_cache()
142
+
143
+ def __repr__(self):
144
+ return f"Boundary(Quantity={self.quantity}, Patches={len(self.patches)})"
145
+
146
+
147
+ class SubObstruction:
148
+ """An :class:`Obstruction` consists of 1 or more SubObstructions which can be hidden at specific points in time.
149
+
150
+ :ivar extent: :class:`Extent` object containing 3-dimensional extent information.
151
+ :ivar bound_indices: Indices used to define obstruction bounds in terms of mesh locations.
152
+ :ivar side_surfaces: Tuple of six :class:`Surface`s for each side of the cuboid.
153
+ :ivar hide_times: List with points in time from when on the SubObstruction will be hidden.
154
+ :ivar show_times: List with points in time from when on the SubObstruction will be shown.
155
+ """
156
+
157
+ def __init__(self, side_surfaces: Tuple, bound_indices: Tuple[int, int, int, int, int, int],
158
+ extent: Extent):
159
+ self.extent = extent
160
+ self.side_surfaces = side_surfaces
161
+ self.bound_indices = {'x': (bound_indices[0], bound_indices[1]),
162
+ 'y': (bound_indices[2], bound_indices[3]),
163
+ 'z': (bound_indices[4], bound_indices[5])}
164
+
165
+ self._boundary_data: Dict[int, Boundary] = dict()
166
+
167
+ self.hide_times = list()
168
+ self.show_times = list()
169
+
170
+ def _add_patches(self, bid: int, cell_centered: bool, quantity: str, short_name: str, unit: str,
171
+ patches: List[Patch], times: np.ndarray, n_t: int, lower_bounds: np.ndarray,
172
+ upper_bounds: np.ndarray):
173
+ if bid not in self._boundary_data:
174
+ self._boundary_data[bid] = Boundary(Quantity(quantity, short_name, unit), cell_centered, times, n_t,
175
+ patches, lower_bounds, upper_bounds)
176
+
177
+ if not settings.LAZY_LOAD:
178
+ _ = self._boundary_data[bid].data
179
+
180
+ def get_data(self, quantity: Union[str, Quantity]):
181
+ if type(quantity) == Quantity:
182
+ quantity = quantity.name
183
+ return next(b for b in self._boundary_data.values() if
184
+ b.quantity.name.lower() == quantity.lower() or b.quantity.short_name.lower() == quantity.lower())
185
+
186
+ def __getitem__(self, item):
187
+ if type(item) == int:
188
+ return self._boundary_data[item]
189
+ return self.get_data(item)
190
+
191
+ def _hide(self, time: float):
192
+ self.hide_times.append(time)
193
+ self.hide_times.sort()
194
+
195
+ def _show(self, time: float):
196
+ self.show_times.append(time)
197
+ self.show_times.sort()
198
+
199
+ def visible_times(self, times: Sequence[float]) -> np.ndarray:
200
+ """Returns an ndarray containing all time steps when there is data available for the SubObstruction. Will return an
201
+ empty list when no data is output at all.
202
+
203
+ :param times: All timesteps of the simulation.
204
+ """
205
+ ret = list()
206
+
207
+ hidden = False
208
+ for time in times:
209
+ if time in self.show_times:
210
+ hidden = False
211
+ if time in self.hide_times:
212
+ hidden = True
213
+ if not hidden:
214
+ ret.append(time)
215
+
216
+ return np.array(ret)
217
+
218
+ def vmin(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
219
+ """Minimum value of all patches at any time for a specific quantity.
220
+
221
+ :param orientation: Optionally filter by patches with a specific orientation.
222
+ """
223
+ return self.get_data(quantity).vmin(orientation)
224
+
225
+ def vmax(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
226
+ """Maximum value of all patches at any time for a specific quantity.
227
+
228
+ :param orientation: Optionally filter by patches with a specific orientation.
229
+ """
230
+ return self.get_data(quantity).vmax(orientation)
231
+
232
+ def clear_cache(self):
233
+ """Remove all data from the internal cache that has been loaded so far to free memory.
234
+ """
235
+ for bndf in self._boundary_data.values():
236
+ bndf.clear_cache()
237
+
238
+ def __repr__(self):
239
+ return f"SubObstruction(Extent={self.extent})"
240
+
241
+
242
+ class Obstruction:
243
+ """A box-shaped obstruction with specific surfaces (materials) on each side.
244
+
245
+ :ivar id: ID of the obstruction.
246
+ :ivar color_index: Type of coloring used to color obstruction.
247
+ \n-1 - default color
248
+ \n-2 - invisible
249
+ \n-3 - use red, green, blue and alpha (rgba attribute)
250
+ \nn>0 - use n’th color table entry
251
+ :ivar block_type: Defines how the obstruction is drawn.
252
+ \n-1 - use surface to obtain blocktype
253
+ \n0 - regular block
254
+ \n2 - outline
255
+ :ivar texture_origin: Origin position of the texture provided by the surface. When the texture
256
+ does have a pattern, for example windows or bricks, the texture_origin specifies where the
257
+ pattern should begin.
258
+ :ivar rgba: Optional color of the obstruction in form of a 4-element tuple
259
+ (ranging from 0.0 to 1.0).
260
+ """
261
+
262
+ def __init__(self, oid: int, color_index: int, block_type: int, texture_origin: Tuple[float, float, float],
263
+ rgba: Union[Tuple[()], Tuple[float, float, float, float]] = ()):
264
+ self.id = oid
265
+ self.color_index = color_index
266
+ self.block_type = block_type
267
+ self.texture_origin = texture_origin
268
+ if len(rgba) != 0:
269
+ self.rgba = rgba
270
+
271
+ self._subobstructions: List[SubObstruction] = list()
272
+
273
+ @property
274
+ def bounding_box(self) -> Extent:
275
+ """:class:`Extent` object representing the bounding box around the Obstruction.
276
+ """
277
+ extents = [sub.extent for sub in self._subobstructions]
278
+
279
+ return Extent(min(extents, key=lambda e: e.x_start).x_start, max(extents, key=lambda e: e.x_end).x_end,
280
+ min(extents, key=lambda e: e.y_start).y_start, max(extents, key=lambda e: e.y_end).y_end,
281
+ min(extents, key=lambda e: e.z_start).z_start, max(extents, key=lambda e: e.z_end).z_end)
282
+
283
+ @property
284
+ def quantities(self) -> List[Quantity]:
285
+ """Get a list of all quantities for which boundary data exists.
286
+ """
287
+ return [b.quantity for b in self._subobstructions[0]._boundary_data.values()]
288
+
289
+ def filter_by_orientation(self, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> List[SubObstruction]:
290
+ """Filter all SubObstructions by a specific orientation. All returned SubObstructions will contain boundary data
291
+ in the specified orientation.
292
+ """
293
+ return [subobst for subobst in self._subobstructions if
294
+ orientation in next(iter(subobst._boundary_data.values())).data.keys()]
295
+
296
+ def get_boundary_data(self, quantity: Union[Quantity, str],
297
+ orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> List[Boundary]:
298
+ """Gets the boundary data for a specific quantity of all SubObstructions.
299
+
300
+ :param quantity: The quantity to filter by.
301
+ :param orientation: Optionally filter by a specific orientation as well (-3=-z, -2=-y, -1=-x, 1=x, 2=y, 3=z).
302
+ A value of 0 indicates to no filter.
303
+ """
304
+ if type(quantity) == Quantity:
305
+ quantity = quantity.name
306
+
307
+ ret = [subobst.get_data(quantity) for subobst in self._subobstructions]
308
+ if orientation == 0:
309
+ return ret
310
+ return [bndf for bndf in ret if orientation in bndf.data.keys()]
311
+
312
+ @property
313
+ def has_boundary_data(self):
314
+ """Whether boundary data has been output in the simulation.
315
+ """
316
+ return len(self._subobstructions[0]._boundary_data) != 0
317
+
318
+ def clear_cache(self):
319
+ """Remove all data from the internal cache that has been loaded so far to free memory.
320
+ """
321
+ for s in self._subobstructions:
322
+ s.clear_cache()
323
+
324
+ def vmin(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
325
+ """Minimum value of all patches at any time for a specific quantity.
326
+
327
+ :param orientation: Optionally filter by patches with a specific orientation.
328
+ """
329
+ return min(s.vmin(quantity, orientation) for s in self._subobstructions)
330
+
331
+ def vmax(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
332
+ """Maximum value of all patches at any time for a specific quantity.
333
+
334
+ :param orientation: Optionally filter by patches with a specific orientation.
335
+ """
336
+ return max(s.vmax(quantity, orientation) for s in self._subobstructions)
337
+
338
+ def __getitem__(self, index):
339
+ """Gets the nth :class:`SubObstruction`.
340
+ """
341
+ return self._subobstructions[index]
342
+
343
+ def __eq__(self, other):
344
+ return self.id == other.id
345
+
346
+ def __repr__(self, *args, **kwargs):
347
+ return f"Obstruction(id={self.id}, Bounding-Box={self.bounding_box}, SubObstructions={len(self._subobstructions)}" + (
348
+ f", Quantities={[q.short_name for q in self.quantities]}" if self.has_boundary_data else "") + ")"
@@ -0,0 +1,33 @@
1
+ from typing import List
2
+
3
+ from fdsreader.bndf import Patch
4
+
5
+
6
+ def sort_patches_cartesian(patches_in: List[Patch]):
7
+ """Returns all patches (of same orientation!) sorted in cartesian coordinates.
8
+ """
9
+ patches = patches_in.copy()
10
+ if len(patches) != 0:
11
+ patches_cart = [[patches[0]]]
12
+ orientation = abs(patches[0].orientation)
13
+ if orientation == 1: # x
14
+ patches.sort(key=lambda p: (p.extent.y_start, p.extent.z_start))
15
+ elif orientation == 2: # y
16
+ patches.sort(key=lambda p: (p.extent.x_start, p.extent.z_start))
17
+ elif orientation == 3: # z
18
+ patches.sort(key=lambda p: (p.extent.x_start, p.extent.y_start))
19
+
20
+ if orientation == 1:
21
+ for patch in patches[1:]:
22
+ if patch.extent.y_start == patches_cart[-1][-1].extent.y_start:
23
+ patches_cart[-1].append(patch)
24
+ else:
25
+ patches_cart.append([patch])
26
+ else:
27
+ for patch in patches[1:]:
28
+ if patch.extent.x_start == patches_cart[-1][-1].extent.x_start:
29
+ patches_cart[-1].append(patch)
30
+ else:
31
+ patches_cart.append([patch])
32
+ return patches_cart
33
+ return patches