Here are two similar versions. The idea of both is that you always return
a BBox
object and only alter a variable x
which indicates which dimensions you have specified via left
, right
, ...
Finally you have a function which uses x
to calculate the center of the
remaining corners.
The first approach uses functions so you have to call them bbox.bottom().front().left().c()
. The main difference here is that not all the combinations
top
top left
top right
top left front
...
are computed when creating the object, but only when you call them.
import numpy as np
import itertools
class BBox:
"""
("left", "right"), -x, +x
("front", "back"), -y, +y
("bottom", "top"), -z, +z
"""
def __init__(self, bfl, tbr):
self.bfl = bfl
self.tbr = tbr
self.g = np.array((bfl, tbr)).T
self.x = [[0, 1], [0, 1], [0, 1]]
def c(self): # get center coordinates
return np.mean([i for i in itertools.product(*[self.g[i][self.x[i]] for i in range(3)])], axis=0)
def part(self, i, xi):
assert len(self.x[i]) == 2
b2 = BBox(bfl=self.bfl, tbr=self.tbr)
b2.x = self.x.copy()
b2.x[i] = [xi]
return b2
def left(self):
return self.part(i=0, xi=0)
def right(self):
return self.part(i=0, xi=1)
def front(self):
return self.part(i=1, xi=0)
def back(self):
return self.part(i=1, xi=1)
def bottom(self):
return self.part(i=2, xi=0)
def top(self):
return self.part(i=2, xi=1)
bbox = BBox(bfl=[-1, -1, -1], tbr=[1, 1, 1])
>>> bbox.bottom().front().left().c()
(-1, -1, -1)
>>> bbox.top().front().c()
(0, -1, 1)
>>> bbox.bottom().c()
(0, 0, -1)
The second approach uses attributes which are in itself BBox
objects.
When you uncomment the print statement in the init
function you get an idea of all the recursive calls which are happening during construction.
So while it might be more complicated to see what is going on here, you have more convenience when accessing the attributes.
class BBox:
def __init__(self, bfl, tbr, x=None):
self.bfl = bfl
self.tbr = tbr
self.g = np.array((bfl, tbr)).T
self.x = [[0, 1], [0, 1], [0, 1]] if x is None else x
# print(self.x) # Debugging
self.left = self.part(i=0, xi=0)
self.right = self.part(i=0, xi=1)
self.front = self.part(i=1, xi=0)
self.back = self.part(i=1, xi=1)
self.bottom = self.part(i=2, xi=0)
self.top = self.part(i=2, xi=1)
def c(self): # get center coordinates
return np.mean([i for i in itertools.product(*[self.g[i][self.x[i]]
for i in range(3)])], axis=0)
def part(self, i, xi):
if len(self.x[i]) < 2:
return None
x2 = self.x.copy()
x2[i] = [xi]
return BBox(bfl=self.bfl, tbr=self.tbr, x=x2)
bbox = BBox(bfl=[-1, -1, -1], tbr=[1, 1, 1])
>>> bbox.bottom.front.left.c()
(-1, -1, -1)
You could also add something like this at the end of the constructor, to remove the invalid attributes. (to prevent stuff like bbox.right.left.c()
). They were None
before but AttributeError
might be more appropriate.
def __init__(self, bfl, tbr, x=None):
...
for name in ['left', 'right', 'front', 'back', 'bottom', 'top']:
if getattr(self, name) is None:
delattr(self, name)
And you could add a __repr__()
method as well:
def __repr__(self):
return repr(self.get_vertices())
def get_vertices(self):
return [i for i in itertools.product(*[self.g[i][self.x[i]]
for i in range(3)])]
def c(self): # get center coordinates
return np.mean(self.get_vertices(), axis=0)
bbox.left.front
# [(-1, -1, -1), (-1, -1, 1)]
bbox.left.front.c()
# array([-1., -1., 0.])
EDIT
After coming back to this after a while I think it is better to only add the relevant attributes and not add all and than delete half of them afterwards. So the most compact / convenient class I can come up with is:
class BBox:
def __init__(self, bfl, tbr, x=None):
self.bfl, self.tbr = bfl, tbr
self.g = np.array((bfl, tbr)).T
self.x = [[0, 1], [0, 1], [0, 1]] if x is None else x
for j, name in enumerate(['left', 'right', 'front', 'back', 'bottom', 'top']):
temp = self.part(i=j//2, xi=j%2)
if temp is not None:
setattr(self, name, temp)
def c(self): # get center coordinates
return np.mean([x for x in itertools.product(*[self.g[i][self.x[i]]
for i in range(3)])], axis=0)
def part(self, i, xi):
if len(self.x[i]) == 2:
x2, x2[i] = self.x.copy(), [xi]
return BBox(bfl=self.bfl, tbr=self.tbr, x=x2)