@@ -10,6 +10,7 @@ def __init__(
1010 self ,
1111 origin : Optional [np .ndarray ] = None ,
1212 maximum : Optional [np .ndarray ] = None ,
13+ global_origin : Optional [np .ndarray ] = None ,
1314 nsteps : Optional [np .ndarray ] = None ,
1415 step_vector : Optional [np .ndarray ] = None ,
1516 dimensions : int = 3 ,
@@ -28,13 +29,25 @@ def __init__(
2829 nsteps : Optional[np.ndarray], optional
2930 _description_, by default None
3031 """
32+ # reproject relative to the global origin, if origin is not provided.
33+ # we want the local coordinates to start at 0
34+ # otherwise uses provided origin. This is useful for having multiple bounding boxes rela
35+ if global_origin is not None and origin is None :
36+ origin = np .zeros (3 )
37+
3138 if maximum is None and nsteps is not None and step_vector is not None :
3239 maximum = origin + nsteps * step_vector
40+
3341 self ._origin = np .array (origin )
3442 self ._maximum = np .array (maximum )
43+ if global_origin is None :
44+ global_origin = np .zeros (3 )
45+
46+ self ._global_origin = global_origin
3547 self .dimensions = dimensions
36- if nsteps is None :
37- self .nsteps = np .array ([50 , 50 , 25 ])
48+ self .nsteps = np .array ([50 , 50 , 25 ])
49+ if nsteps is not None :
50+ self .nsteps = np .array (nsteps )
3851 self .name_map = {
3952 "xmin" : (0 , 0 ),
4053 "ymin" : (0 , 1 ),
@@ -54,11 +67,15 @@ def __init__(
5467
5568 @property
5669 def global_origin (self ):
57- return self ._origin
70+ return self ._global_origin
71+
72+ @global_origin .setter
73+ def global_origin (self , global_origin ):
74+ self ._global_origin = global_origin
5875
5976 @property
6077 def global_maximum (self ):
61- return self ._maximum
78+ return self .maximum - self . origin + self . _global_origin
6279
6380 @property
6481 def valid (self ):
@@ -108,7 +125,7 @@ def nelements(self, nelements):
108125 self .nsteps = nsteps
109126
110127 @property
111- def corners (self ):
128+ def corners (self ) -> np . ndarray :
112129 """Returns the corners of the bounding box
113130
114131
@@ -130,6 +147,29 @@ def corners(self):
130147 ]
131148 )
132149
150+ @property
151+ def corners_global (self ) -> np .ndarray :
152+ """Returns the corners of the bounding box
153+
154+
155+ Returns
156+ -------
157+ np.ndarray
158+ corners of the bounding box
159+ """
160+ return np .array (
161+ [
162+ self .global_origin .tolist (),
163+ [self .global_maximum [0 ], self .global_origin [1 ], self .global_origin [2 ]],
164+ [self .global_maximum [0 ], self .global_maximum [1 ], self .global_origin [2 ]],
165+ [self .global_origin [0 ], self .global_maximum [1 ], self .global_origin [2 ]],
166+ [self .global_origin [0 ], self .global_origin [1 ], self .global_maximum [2 ]],
167+ [self .global_maximum [0 ], self .global_origin [1 ], self .global_maximum [2 ]],
168+ self .global_maximum .tolist (),
169+ [self .global_origin [0 ], self .global_maximum [1 ], self .global_maximum [2 ]],
170+ ]
171+ )
172+
133173 @property
134174 def step_vector (self ):
135175 return (self .maximum - self .origin ) / self .nsteps
@@ -138,21 +178,67 @@ def step_vector(self):
138178 def length (self ):
139179 return self .maximum - self .origin
140180
141- def fit (self , locations : np .ndarray ):
181+ def fit (self , locations : np .ndarray , local_coordinate : bool = False ) -> BoundingBox :
182+ """Initialise the bounding box from a set of points.
183+
184+ Parameters
185+ ----------
186+ locations : np.ndarray
187+ xyz locations of the points to fit the bbox
188+ local_coordinate : bool, optional
189+ whether to set the origin to [0,0,0], by default False
190+
191+ Returns
192+ -------
193+ BoundingBox
194+ A reference to the bounding box object, note this is not a new bounding box
195+ it updates the current one in place.
196+
197+ Raises
198+ ------
199+ LoopValueError
200+ _description_
201+ """
142202 if locations .shape [1 ] != self .dimensions :
143203 raise LoopValueError (
144204 f"locations array is { locations .shape [1 ]} D but bounding box is { self .dimensions } "
145205 )
146- self .origin = locations .min (axis = 0 )
147- self .maximum = locations .max (axis = 0 )
206+ origin = locations .min (axis = 0 )
207+ maximum = locations .max (axis = 0 )
208+ if local_coordinate :
209+ self .global_origin = origin
210+ self .origin = np .zeros (3 )
211+ self .maximum = maximum - origin
212+ else :
213+ self .origin = origin
214+ self .maximum = maximum
215+ self .global_origin = np .zeros (3 )
148216 return self
149217
150218 def with_buffer (self , buffer : float = 0.2 ) -> BoundingBox :
219+ """Create a new bounding box with a buffer around the existing bounding box
220+
221+ Parameters
222+ ----------
223+ buffer : float, optional
224+ percentage to expand the dimensions by, by default 0.2
225+
226+ Returns
227+ -------
228+ BoundingBox
229+ The new bounding box object.
230+
231+ Raises
232+ ------
233+ LoopValueError
234+ if the current bounding box is invalid
235+ """
151236 if self .origin is None or self .maximum is None :
152237 raise LoopValueError ("Cannot create bounding box with buffer, no origin or maximum" )
238+ # local coordinates, rescale into the original bounding boxes global coordinates
153239 origin = self .origin - buffer * (self .maximum - self .origin )
154240 maximum = self .maximum + buffer * (self .maximum - self .origin )
155- return BoundingBox (origin = origin , maximum = maximum )
241+ return BoundingBox (origin = origin , maximum = maximum , global_origin = self . global_origin )
156242
157243 def get_value (self , name ):
158244 ix , iy = self .name_map .get (name , (- 1 , - 1 ))
@@ -187,12 +273,16 @@ def is_inside(self, xyz):
187273 inside = np .logical_and (inside , xyz [:, 2 ] < self .maximum [2 ])
188274 return inside
189275
190- def regular_grid (self , nsteps = None , shuffle = False , order = "C" ):
276+ def regular_grid (self , nsteps = None , shuffle = False , order = "C" , local = True ):
191277 if nsteps is None :
192278 nsteps = self .nsteps
193279 x = np .linspace (self .origin [0 ], self .maximum [0 ], nsteps [0 ])
194280 y = np .linspace (self .origin [1 ], self .maximum [1 ], nsteps [1 ])
195281 z = np .linspace (self .origin [2 ], self .maximum [2 ], nsteps [2 ])
282+ if not local :
283+ x = np .linspace (self .global_origin [0 ], self .global_maximum [0 ], nsteps [0 ])
284+ y = np .linspace (self .global_origin [1 ], self .global_maximum [1 ], nsteps [1 ])
285+ z = np .linspace (self .global_origin [2 ], self .global_maximum [2 ], nsteps [2 ])
196286 xx , yy , zz = np .meshgrid (x , y , z , indexing = "ij" )
197287 locs = np .array (
198288 [xx .flatten (order = order ), yy .flatten (order = order ), zz .flatten (order = order )]
@@ -202,6 +292,13 @@ def regular_grid(self, nsteps=None, shuffle=False, order="C"):
202292 rng .shuffle (locs )
203293 return locs
204294
295+ def to_dict (self ):
296+ return {
297+ "origin" : self .origin .tolist (),
298+ "maximum" : self .maximum .tolist (),
299+ "nsteps" : self .nsteps .tolist (),
300+ }
301+
205302 @property
206303 def vtk (self ):
207304
@@ -217,3 +314,7 @@ def vtk(self):
217314 y ,
218315 z ,
219316 )
317+
318+ @property
319+ def structured_grid (self ):
320+ pass
0 commit comments