def interpolate(clas,F,G,div):
        """Create interpolations between two Coords.

        F and G are two Coords with the same shape.
        v is a list of floating point values.
        The result is the concatenation of the interpolations of F and G at all
        the values in div.
        An interpolation of F and G at value v is a Coords H where each
        coordinate Hijk is obtained from:  Hijk = Fijk + v * (Gijk-Fijk).
        Thus, a Coords interpolate(F,G,[0.,0.5,1.0]) will contain all points of
        F and G and all points with mean coordinates between those of F and G.

        As a convenience, if an integer is specified for div, it is taken as a
        number of divisions for the interval [0..1].
        Thus, interpolate(F,G,n) is equivalent with
        interpolate(F,G,arange(0,n+1)/float(n))

        The resulting Coords array has an extra axis (the first). Its shape is
        (n,) + F.shape, where n is the number of divisions.
        """
        if F.shape != G.shape:
            raise RuntimeError,"Expected Coords objects with equal shape!"
        if type(div) == int:
            div = arange(div+1) / float(div)
        else:
            div = array(div).ravel()
        return F + outer(div,G-F).reshape((-1,)+F.shape)
    def __new__(cls, data=None, dtyp=Float, copy=False):
        """Create a new instance of class Coords.

        If no data are given, a single point (0.,0.,0.) will be created.
        If specified, data should evaluate to an (...,3) shaped array of floats.
        If copy==True, the data are copied.
        If no dtyp is given that of data are used, or float32 by default.
        """
        if data is None:
            data = zeros((3,),dtype=Float)

        # Turn the data into an array, and copy if requested
        ar = array(data, dtype=dtyp, copy=copy)
        if ar.shape[-1] == 3:
            pass
        elif ar.shape[-1] in [1,2]:
            ar = concatenate([ar,zeros(ar.shape[:-1]+(3-ar.shape[-1],))],axis=-1)
        else:
            raise ValueError,"Expected a length 1,2 or 3 for last array axis"


        # Make sure dtype is a float type
        if ar.dtype.kind != 'f':
            ar = ar.astype(Float)
 
        # Transform 'subarr' from an ndarray to our new subclass.
        ar = ar.view(cls)

        return ar
def rotationMatrix(angle,axis=None):
    """Return a rotation matrix over angle, optionally around axis.

    The angle is specified in degrees.
    If axis==None (default), a 2x2 rotation matrix is returned.
    Else, axis should specifying the rotation axis in a 3D world. It is either
    one of 0,1,2, specifying a global axis, or a vector with 3 components
    specifying an axis through the origin.
    In either case a 3x3 rotation matrix is returned.
    Note that:
      rotationMatrix(angle,[1,0,0]) == rotationMatrix(angle,0) 
      rotationMatrix(angle,[0,1,0]) == rotationMatrix(angle,1) 
      rotationMatrix(angle,[0,0,1]) == rotationMatrix(angle,2)
    but the latter functions calls are more efficient.
    The result is returned as an array.
    """
    a = angle*rad
    c = cos(a)
    s = sin(a)
    if axis==None:
        f = [[c,s],[-s,c]]
    elif type(axis) == int:
        f = [[0.0 for i in range(3)] for j in range(3)]
        axes = range(3)
        i,j,k = axes[axis:]+axes[:axis]
        f[i][i] = 1.0
        f[j][j] = c
        f[j][k] = s
        f[k][j] = -s
        f[k][k] = c
    else:
        t = 1-c
        X,Y,Z = axis
        f = [ [ t*X*X + c  , t*X*Y + s*Z, t*X*Z - s*Y ],
              [ t*Y*X - s*Z, t*Y*Y + c  , t*Y*Z + s*X ],
              [ t*Z*X + s*Y, t*Z*Y - s*X, t*Z*Z + c   ] ]
        
    return array(f)
    def fuse(self,nodesperbox=1,shift=0.5,rtol=1.e-5,atol=1.e-5):
        """Find (almost) identical nodes and return a compressed set.

        This method finds the points that are very close and replaces them
        with a single point. The return value is a tuple of two arrays:
        - the unique points as a Coords object,
        - an integer (nnod) array holding an index in the unique
        coordinates array for each of the original nodes. This index will
        have the same shape as the pshape() of the coords array.

        The procedure works by first dividing the 3D space in a number of
        equally sized boxes, with a mean population of nodesperbox.
        The boxes are numbered in the 3 directions and a unique integer scalar
        is computed, that is then used to sort the nodes.
        Then only nodes inside the same box are compared on almost equal
        coordinates, using the numpy allclose() function. Two coordinates are
        considered close if they are within a relative tolerance rtol or absolute
        tolerance atol. See numpy for detail. The default atol is set larger than
        in numpy, because pyformex typically runs with single precision.
        Close nodes are replaced by a single one.

        Currently the procedure does not guarantee to find all close nodes:
        two close nodes might be in adjacent boxes. The performance hit for
        testing adjacent boxes is rather high, and the probability of separating
        two close nodes with the computed box limits is very small. Nevertheless
        we intend to access this problem by repeating the procedure with the
        boxes shifted in space.
        """
        x = self.points()
        nnod = x.shape[0]
        # Calculate box size
        lo = array([ x[:,i].min() for i in range(3) ])
        hi = array([ x[:,i].max() for i in range(3) ])
        sz = hi-lo
        esz = sz[sz > 0.0]  # only keep the nonzero dimensions
        if esz.size == 0:
            # All points are coincident
            x = x[:1]
            e = zeros(nnod,dtype=int32)
            return x,e
            
        vol = esz.prod()
        nboxes = nnod / nodesperbox # ideal total number of boxes
        boxsz = (vol/nboxes) ** (1./esz.shape[0])
        nx = (sz/boxsz).astype(int32)
        # avoid error message on the global sz/nx calculation
        errh = seterr(all='ignore')
        dx = where(nx>0,sz/nx,boxsz)
        seterr(**errh)
        #
        nx = array(nx) + 1
        ox = lo - dx*shift # origin :  0 < shift < 1
        # Create box coordinates for all nodes
        ind = floor((x-ox)/dx).astype(int32)
        # Create unique box numbers in smallest direction first
        o = argsort(nx)
        val = ( ind[:,o[2]] * nx[o[2]] + ind[:,o[1]] ) * nx[o[1]] + ind[:,o[0]]
        # sort according to box number
        srt = argsort(val)
        # rearrange the data according to the sort order
        val = val[srt]
        x = x[srt]
        # now compact
        # make sure we use int32 (for the fast fuse function)
        # Using int32 limits this procedure to 10**9 points, which is more
        # than enough for all practical purposes
        x = x.astype(float32)
        val = val.astype(int32)
        flag = ones((nnod,),dtype=int32)   # 1 = new, 0 = existing node
        sel = arange(nnod).astype(int32)   # replacement unique node nr
        tol = float32(max(abs(rtol*self.sizes()).max(),atol))
        if hasattr(misc,'fuse'):
            # use the lib
            misc.fuse(x,val,flag,sel,tol)
        else:
            # !!!! this code should be moved to the emulated lib !
            for i in range(nnod):
                j = i-1
                while j>=0 and val[i]==val[j]:
                    if allclose(x[i],x[j],rtol=rtol,atol=atol):
                        # node i is same as node j
                        flag[i] = 0
                        sel[i] = sel[j]
                        sel[i+1:nnod] -= 1
                        break
                    j = j-1
                    
        x = x[flag>0]          # extract unique nodes
        s = sel[argsort(srt)]  # and indices for old nodes
        return (x,s.reshape(self.shape[:-1]))