"""
Add positioning to the Tree elements.
"""
from math import ceil, copysign
from ..core.trageom import Vector3
from ..core.trageom.vector import _are_close
from .tree import Tree
[docs]class RGrid(object):
def __init__(self, container, x=1, y=1, z=1, origin=(0, 0, 0)):
self.__x = x
self.__y = y
self.__z = z
self.__o = Vector3(origin)
self.__c = container # link to the solid containing the grid.
return
def copy(self, container):
new = self.__class__(container, self.x, self.y, self.z, self.__o.car)
return new
@property
def x(self):
"""
Grid pitch along x axis.
"""
return self.__x
@x.setter
[docs] def x(self, val):
self.__x = val
@property
def y(self):
"""
Grid pitch along y axis.
"""
return self.__y
@y.setter
[docs] def y(self, val):
self.__y = val
@property
def z(self):
"""
Grid pitch along z axis.
"""
return self.__z
@z.setter
[docs] def z(self, val):
self.__z = val
@property
[docs] def container(self):
"""
Link to the solid containing the grid.
"""
return self.__c
[docs] def position(self, i, j, k, coordinate=None):
"""
Returns position of element ijk with respect to the grid's container.
"""
if coordinate is None:
return self.__o + Vector3((self.x*i, self.y*j, self.z*k))
else:
if coordinate == 'x':
return self.__o.x + self.x*i
elif coordinate == 'y':
return self.__o.y + self.y*j
elif coordinate == 'z':
return self.__o.z + self.z*k
else:
raise NotImplementedError('coordinate {} not implemented'.format(coordinate))
[docs] def index(self, x, y, z):
"""
Returns index i,j,k of the element containing point p (with respect to the grid's container)
index(x=5, y=7, z=8) # returns tuple (i, j, k)
"""
ox, oy, oz = self.__o.car
res = []
for (o , a, d) in ((ox, x, self.x), (oy, y, self.y), (oz, z, self.z)):
l = a - o
N = copysign(ceil(abs(l)/d - 0.5), l)
res.append(int(N))
return res
# return NotImplementedError
@property
def origin(self):
"""
Position of the grid's central element (0,0,0) with respect to the container.
"""
return self.__o
@origin.setter
[docs] def origin(self, value):
self.__o = value # TODO: check type of value.
[docs] def set_origin(self, (i, j, k), (x, y, z)):
"""
Sets the grid origin so that the grid element (i,j,k) has position (x, y, z)
with respect to the grid's container.
"""
self.__o.x = x - i*self.x
self.__o.y = y - j*self.y
self.__o.z = z - k*self.z
def __eq__(self, othr):
if self is othr:
return True
else:
return (self.x == othr.x and
self.y == othr.y and
self.z == othr.z and
self.__o == othr.__o)
def __ne__(self, othr):
return not self == othr
def __str__(self):
return 'grid x={0} y={1} z={2} origin={3}'.format(self.x, self.y, self.z, self.__o.car)
def __getitem__(self, index):
"""
Returns grid dimension along axis specifyed by the character attribute 'index'.
This syntax is similar to Vector3 class.
"""
if index == 'x':
return self.x
elif index == 'y':
return self.y
elif index == 'z':
return self.z
else:
raise IndexError('Unsupported index ', index)
[docs] def extension(self, a=None):
"""
Returns tuple (Imin, Imax) of minimal and maximal grid element indices
in the direction along axis a.
The argument a can be 'x', 'y' or 'z'.
UPD: the default a is None; in this case the tuple (Imin,
Imax, Jmin, Jmax, Kmin, Kmax) is returned.
"""
if a is None:
ii = self.extension('x')
jj = self.extension('y')
kk = self.extension('z')
return ii + jj + kk
else:
step = self[a]
orig = self.origin[a]
amin, amax = self.__c.extension(a, 'rel')
# float values:
fmax = (amax - orig - 0.5*step)/step
fmin = (orig - amin - 0.5*step)/step
# check if they are close to integers:
Nmax = ceil(fmax) - 1.
if _are_close(fmax, Nmax):
imax = int(Nmax)
else:
imax = int(Nmax) + 1
Nmin = ceil(fmin) - 1.
if _are_close(fmin, Nmin):
imin = int(Nmin)
else:
imin = int(Nmin) + 1
imin = -imin
return (imin, imax)
def __extension(self, a):
dx = self[a]
dx2 = dx/2.
x, X = self.__c.extension(a, 'rel')
o = self.origin[a]
n = -int(ceil((o - dx2 - x)/dx))
N = int(ceil((X - o - dx2)/dx))
return (n, N)
[docs] def used(self):
"""
Returns True if at least one of the grid container's local children has not None i,j,k attributes.
"""
for e in self.__c.children: #### .values():
if e.indexed():
return True
return False
[docs] def elements(self):
"""
Iterates over index-positioned elements.
"""
for e in self.__c.children:
if e.indexed():
yield e
[docs] def center(self, log=False):
"""
Positions the central element of the grid so that the box circumscribing all inserted grid elements is centered
with repsect to the container.
"""
elst = list(self.elements())
if len(elst) > 0:
Imin, Jmin, Kmin = elst.pop(0).ijk
Imax, Jmax, Kmax = Imin, Jmin, Kmin
else:
# there are no index-positioned elements.
return
for e in elst:
if Imin > e.i: Imin = e.i
if Imax < e.i: Imax = e.i
if Jmin > e.j: Jmin = e.j
if Jmax < e.j: Jmax = e.j
if Kmin > e.k: Kmin = e.k
if Kmax < e.k: Kmax = e.k
if log:
print 'Imin: ', Imin
print 'Imax: ', Imax
print 'Jmin: ', Jmin
print 'Jmax: ', Jmax
print 'Kmin: ', Kmin
print 'Kmax: ', Kmax
self.origin.x = -(Imin + Imax)/2. * self.x
self.origin.y = -(Jmin + Jmax)/2. * self.y
self.origin.z = -(Kmin + Kmax)/2. * self.z
return
[docs] def insert(self, ijk, element, i=None):
"""
Inserts element into the grid's container and specifies for the inserted element
that it should be positioned with respect to the ijk-th grid element.
"""
self.__c.insert(element, i)
element.i, element.j, element.k = map(int, ijk)
return element
def _append(self, ijk, element):
self.__c._append(element)
element.i, element.j, element.k = map(int, ijk)
[docs] def boundaries(self, d='x'):
"""
Returns coordinates of boundaries in direction d with respect to the grid's container.
"""
dmin, dmax = self.__c.extension(d, 'rel')
b = [dmin]
Imin, Imax = self.extension(d)
if d == 'x':
# along x axis.
o = self.__o.x
dd = self.x
elif d == 'y':
# along y axis.
o = self.__o.y
dd = self.y
elif d == 'z':
# along z axis.
o = self.__o.z
dd = self.z
else:
raise ValueError('Unsupported value of d ', d)
for i in range(Imin, Imax):
x = o + dd*i + dd*0.5
b.append(x)
b.append(dmax)
return b
class PositionedTree(Tree):
def __init__(self, **kwargs):
super(PositionedTree, self).__init__()
self.__pos = Vector3((0,0,0)) # position of element with respect to its parent
self.__grd = None ### RGrid(self) # grid parameters, used to position children
self.__i = None # indices used to position element in the grid of its parent.
self.__j = None
self.__k = None
self.setp(**kwargs)
## self.__abspos = None # variable to store computed abspos(). See compile() method
return
@property
def i(self):
"""Index to position solid in the parent's grid along x axis.
"""
return self.__i
@i.setter
def i(self, value):
self.__i = value
@property
def j(self):
"""Index to position solid in the parent's grid along x axis.
"""
return self.__j
@j.setter
def j(self, value):
self.__j = value
@property
def k(self):
"""Index to position solid in the parent's grid along x axis.
"""
return self.__k
@k.setter
def k(self, value):
self.__k = value
@property
def pos(self):
"""
Position of the element with respect to its parent. By default at the origin.
"""
return self.__pos
@pos.setter
def pos(self, value):
if isinstance(value, Vector3):
self.__pos = value
else:
raise TypeError
@property
def ijk(self):
"""
Tuple with element indices spcifying the parent's grid element where the element
is positioned.
"""
return (self.i, self.j, self.k)
@ijk.setter
def ijk(self, value):
self.i, self.j, self.k = value
return
@property
def grid(self):
"""
Grid description. Its parameters are used to compute absolute position
of children, if they have other than None i,j,k attributes.
"""
if self.__grd is None:
self.__grd = RGrid(self)
return self.__grd
def indexed(self):
"""
Returns Ture if self is positioned using the indices i, j and k.
"""
return (not (self.i, self.j, self.k) == (None, None, None))
def abspos(self, cs='abs', coordinate=None):
"""
Returns absolute position of the element with respect to the tree's
root.
Optional argument cs (by default 'abs') specifies the coordinate
system. Can be 'abs', 'rel', or an instance of the PositionedTree
class. In the latter case, it must be a direct or indirect parent of
self; the returned position is with respect to this parent.
Optional argument coordinate defines the coordinate's name returned by
the method. By default, coordinate is None and the vector itself is
returned. If coordinate is one of 'x', 'y', 'z', 'r', etc. (the
complete list of variables see in the description of the Vector3
class), the correspondent coordinate is returned.
"""
if cs == 'abs':
ref = self.root
elif cs == 'rel':
ref = self
elif isinstance(cs, PositionedTree):
ref = cs
else:
raise TypeError('Unsupported type {} or value of cs: {}'.format(cs.__class__.__name__, repr(cs)))
if ref is self or self.parent is None:
if coordinate is None:
pp = Vector3((0,0,0))
else:
pp = 0.
else:
pp = self.parent.abspos(cs=ref, coordinate=coordinate)
if self.parent is not None and self.indexed():
pp += self.parent.__grd.position(self.i, self.j, self.k, coordinate)
### res = pp + self.pos
if coordinate is None:
return pp + self.pos
elif coordinate == 'x':
return pp + self.pos.x
elif coordinate == 'y':
return pp + self.pos.y
elif coordinate == 'z':
return pp + self.pos.z
else:
raise ValueError("Unknown value of the 'coordinate' argument: ", coordinate)
def copy_node(self):
new = self.__class__()
new.__pos = self.__pos.copy()
if self.__grd is not None:
new.__grd = self.__grd.copy(new)
return new
def copy_tree(self):
new = self.copy_node()
for c in self.children:
newc = c.copy_tree()
new._append(newc)
newc.i = c.i
newc.j = c.j
newc.k = c.k
return new