Beispiel #1
0
    def trim(self, points, radius):
        """
        remove points too close to the cut curve. they dont add anything, and only lead to awkward faces
        """
        #some precomputations
        tree   = KDTree(points)
        cp     = self.vertices[self.faces]
        normal = util.normalize(np.cross(cp[:,0], cp[:,1]))
        mid    = util.normalize(cp.sum(axis=1))
        diff   = np.diff(cp, axis=1)[:,0,:]
        edge_radius = np.sqrt(util.dot(diff, diff)/4 + radius**2)

        index = np.ones(len(points), np.bool)

        #eliminate near edges
        def near_edge(e, p):
            return np.abs(np.dot(points[p]-mid[e], normal[e])) < radius
        for i,(p,r) in enumerate(izip(mid, edge_radius)):
            coarse = tree.query_ball_point(p, r)
            index[[c for c in coarse if near_edge(i, c)]] = 0
        #eliminate near points
        for p in self.vertices:
            coarse = tree.query_ball_point(p, radius)
            index[coarse] = 0

        return points[index]
    def redraw(self):
        """
        set coords and scalar data of roots
        """
        hf = self.datamodel.heightfield
        hfl, hfh = hf.min(), hf.max()
        if hfh==hfl:
            hf[:] = 1
        else:
            hf = (hf-hfl) / (hfh-hfl)       #norm to 0-1 range

        hmin, hmax = 0.95, 1.05
        radius = hf*(hmax-hmin)+hmin

        #precompute normal information
        if self.parent.mapping_height:
            if self.parent.vertex_normal:
                normals = self.complex.normals(radius)
        else:
            N = util.normalize(self.complex.geometry.primal)
            normals = np.array([np.dot( N, T) for T in self.group.transforms.reshape(-1,3,3) ])

        if not self.parent.mapping_color:
            hf = np.ones_like(hf)

        for index,T in enumerate( self.root):
            for mirror,M in enumerate(T):
                _, source, mapper = M

##                mapper.lookup_table = None
                #set positions
                B = self.group.basis[index,mirror,0]
                B = util.normalize(B.T).T
                PP = np.dot(B, self.complex.geometry.decomposed.T).T       #primal coords transformed into current frame
                if self.parent.mapping_height: PP *= radius[:, index][:, None]
                source.mlab_source.set(points=PP)

                #set colors
                source.mlab_source.set(scalars=hf[:,index])

                #set normals
                if self.parent.vertex_normal:
                    M = self.group.transforms[mirror,0]
                    source.data.point_data.normals = np.dot(M, normals[index,:,:].T).T
                    source.data.cell_data.normals = None
                else:
                    source.data.point_data.normals = None
                    source.data.cell_data.normals = None

                source.mlab_source.update()


        for i in self.instances:
            i.property.representation = 'wireframe' if self.parent.wireframe else 'surface'
    def redraw_control(self):
        """redraw control mesh"""
        edge, index = self.selected_edge

        cp = edge.edge.curve.controlpoints()[index]
        if not self.height_visible: cp = util.normalize(cp)
        self.control_points.mlab_source.reset(points=cp[1:-1])

        cm = edge.edge.curve.controlmesh()[index]
        if not self.height_visible: cm = util.normalize(cm)
        self.control_mesh.mlab_source.reset(points=cm)
    def __init__(self, hierarchy, points):
        """pick a set of worldcoords on the sphere

        Parameters
        ----------
        hierarchy : list of MultiComplex
        points : ndarray, [n, 3], float

        Notes
        -----
        hierarchical structure of the triangles allows finding intersection triangle quickly
        """
        self.hierarchy = hierarchy
        self.points = util.normalize(np.atleast_2d(points))

        complex = hierarchy[-1]
        group = complex.group

        domains, baries = group.find_support(self.points)    #find out which fundamental domain tile each point is in
        local = np.dot(group.basis[0,0,0],baries.T).T   #map all points to the root domain for computations; all domains have identical tesselation anyway

        # get the face index for each point
        faces = pick_primal_triangles(hierarchy, local)

        #calc baries; simple linear bary computation is good enough for these purposes, no?
        baries = np.einsum('ijk,ik->ij', complex.geometry.inverted_triangle[faces], local)
        self.baries = baries / baries.sum(axis=1)[:, None]

        self.raveled_indices = np.ravel_multi_index(
            (complex.topology.FV[faces].ravel(), np.repeat(domains[0],3)),
            complex.shape)
        self.complex = complex
Beispiel #5
0
def generate(group, levels):
    """
    create geometry hierarchy from topology hierarchy
    and group and a pmd basis of its first fundamental domain
    """
    T = topology.generate(levels)

    pmd = util.normalize( group.basis[0,0,0].T)

    _t = T[0]
    G = [Geometry(_t, pmd, None)]
    _g = G[0]
    for t in T[1:]:
        position, planes = _t.subdivide_position(_g.primal)
        _g = Geometry(t, position, planes)
        _t = t
        G.append(_g)

    #hook up parent-child relations in geometry list
    for parent,child in zip(G[:-1],G[1:]):
        parent.child = child
        child.parent = parent
        parent.transfer_operators()


    return G
    def __init__(self, datamodel, points):
        """precomputations which are identical for all mappings"""
        self.datamodel = datamodel
        self.hierarchy = self.datamodel.hierarchy
        self.complex   = self.hierarchy[-1]
        self.group     = self.complex.group

        #cache the index a point is in. if it remains unchanged, no need to update
        count = len(points)
        self.index = np.zeros((3, count), np.int8)

        #precompute subtriangles
        primal = self.complex.geometry.primal[ self.complex.topology.FV]
        mid    = util.normalize(np.roll(primal, +1, 1) + np.roll(primal, -1, 1))
        dual   = self.complex.geometry.dual

        basis = np.empty((self.complex.topology.D0, 3, 2, 3, 3))
        for i in range(3):
            basis[:,i,0,0,:] = primal[:,i  ,:]
            basis[:,i,1,0,:] = primal[:,i  ,:]
            basis[:,i,0,1,:] = mid   [:,i-2,:]
            basis[:,i,1,1,:] = mid   [:,i-1,:]
        basis[:,:,:,2,:]     = dual  [:,None, None,:]     #each subtri shares the dual vert

        self.subdomain = util.adjoint(basis)                #precompute all we can for subdomain compu
        self.subdomain[:,:,1,:,:] *= -1                     #flip sign
        self.subdomain = self.subdomain.reshape(-1,6,3,3)   #fold sign axis

        self.update(points)
        def add_base(index, mirror, B):
            """
            build pipeline, for given basis
            """
            B = util.normalize(B.T).T

            PP = np.dot(B, self.complex.geometry.decomposed.T).T       #primal coords transformed into current frame


            x,y,z = PP.T
            FV = self.complex.topology.FV[:,::np.sign(np.linalg.det(B))]        #reverse vertex order depending on orientation
            source  = self.scene.mlab.pipeline.triangular_mesh_source(x,y,z, FV)

            #put these guys under a ui list
            l=lut_manager.tvtk.LookupTable()
            lut_manager.set_lut(l, lut_manager.pylab_luts['jet'])
##            lut_manager.set_lut(l, lut_manager.pylab_luts.values()[0])

            #add polydatamapper, to control color mapping and interpolation
            mapper = tvtk.PolyDataMapper(lookup_table=l)

            from tvtk.common import configure_input
            configure_input(mapper, source.outputs[0])

            ##            mapper = tvtk.PolyDataMapper(input=source.outputs[0])
            mapper.interpolate_scalars_before_mapping = True
            mapper.immediate_mode_rendering = False

            return mirror, source, mapper
Beispiel #8
0
 def compute_angles(self):
     """compute angles for each triangle-vertex"""
     edges = self.edges().reshape(-1, 3, 2)
     vecs = np.diff(self.vertices[edges], axis=2)[:, :, 0]
     vecs = util.normalize(vecs)
     angles = np.arccos(-util.dot(vecs[:, [1, 2, 0]], vecs[:, [2, 0, 1]]))
     assert np.allclose(angles.sum(axis=1), np.pi, rtol=1e-3)
     return angles
Beispiel #9
0
    def from_pair(old, new):
        """
        find minimal rotation that maps one direction unto the other
        """
        axis = normalize( np.cross(old, new))
        angle = np.arccos( np.dot(old, new) / np.linalg.norm(old) / np.linalg.norm(new))

        return Quaternion._from_axis_angle(axis, angle)
Beispiel #10
0
def test_triangulation():

    #random points on the sphere
    points = util.normalize(np.random.randn(10000,3))

    #build curve. add sharp convex corners, as well as additional cuts
    N = 267#9
    radius = np.cos( np.linspace(0,np.pi*2*12,N, False)) +1.1
    curve = np.array([(np.cos(a)*r,np.sin(a)*r,1) for a,r in zip( np.linspace(0,np.pi*2,N, endpoint=False), radius)])
    curve = np.append(curve, [[1,0,-4],[-1,0,-4]], axis=0)      #add bottom slit
    curve = util.normalize(curve)
    curve = cg.Curve(curve)
#    print curve

    #do triangulation
    mesh, curve = cg.triangulate(points, curve)
    #add partitioning of points here too?
    partitions = mesh.partition(curve)
Beispiel #11
0
 def dual_position(self):
     """calc dual coords from primal; interestingly, this is idential to computing a triangle normal"""
     #calc direction orthogonal to normal of intesecting plane
     diff = self.topology.T10 * self.primal
     #collect these on per-tri basis, including weights, so ordering is correct
     tri_edge = util.gather(self.topology.FEi, diff) * self.topology.FEs[:, :, None]
     #for above, could also do cyclical diff on grab(FV, primal)
     #dual vert les where three mid edge planes intesect
     self.dual = util.normalize(-util.null(tri_edge))
 def redraw(self, reset=True):
     """only need to update the root datasource"""
     coords = self.edge.instantiate()
     for mirror, coord in zip(self.mirrors, coords):
         if not self.parent.height_visible:
             coord = util.normalize(coord)
         x,y,z = coord[0].T
         func = mirror.mlab_source.reset if reset else mirror.mlab_source.set
         func(x=x,y=y,z=z)
Beispiel #13
0
    def compute_gradient(self, field):
        """compute gradient of scalar function on vertices on faces"""
        normals = self.face_normals()
        face_area = np.linalg.norm(normals, axis=1)
        normals = util.normalize(normals)

        edges = self.edges().reshape(-1, 3, 2)
        vecs = np.diff(self.vertices[edges], axis=2)[:, :, 0, :]
        gradient = (field[self.faces][:, :, None] * np.cross(normals[:, None, :], vecs)).sum(axis=1)
        return gradient / (2 * face_area[:, None])
 def constrain(self, position):
     """
     constrain bary coords, given world coords of zero transform point
     return constrained point in world coords
     """
     B = self.group.basis[self.domain]
     B = B * self.get_constraint()[np.newaxis,:]      #zero out deactived basis points
     bary = np.linalg.lstsq(B, position)[0]
     self.bary = self.normalize(bary)
     return normalize( np.dot(B, self.bary))
Beispiel #15
0
def test_sphere():
    """some tests on a sphere"""
    vertices = util.normalize(np.random.normal(0, 1, (10000, 3)))
    mesh = cg.Mesh(vertices, scipy.spatial.ConvexHull(vertices).simplices)

    seed = np.zeros_like(mesh.vertices[:, 0])
    seed[np.argmax(vertices[:, 0])] = 1
    seed[np.argmax(vertices[:, 1])] = 1
    distance = mesh.geodesic(seed)
    mesh.plot(color=np.cos(distance*10))
    print(distance.max())
Beispiel #16
0
def triangulate(points, curve):
    """
    return a triangulation of the pointset points,
    while being constrained by the boundary dicated by curve
    """
    #test curve for self-intersection
    print 'testing curve for self-intersection'
    curve.self_intersect()

    #trim the pointset, to eliminate points co-linear with the cutting curve
    print 'trimming dataset'
    diff   = np.diff(curve.vertices[curve.faces], axis=1)[:,0,:]
    length = np.linalg.norm(diff, axis=1)
    points = curve.trim(points, length.mean()/4)

    #refine curve iteratively. new points may both obsolete or require novel insertions themselves
    #so only do the most pressing ones first, then iterate to convergence
    while True:
        newcurve = curve.refine(points)
        if len(newcurve.vertices)==len(curve.vertices):
            break
        print 'curve refined'
        curve = newcurve


    """
    we use the nifty property, that a convex hull of a sphere equals a delauney triangulation of its surface
    if we have cleverly refined our boundary curve, this trinagulation should also be 'constrained', in the sense
    of respecting that original boundary curve
    this is the most computationally expensive part of this function, but we should be done in a minute or so

    qhull performance; need 51 sec and 2.7gb for 4M points
    that corresponds to an icosahedron with level 8 subdivision; not too bad
    editor is very unresponsive at this level anyway
    """
    print 'triangulating'
    allpoints = np.concatenate((curve.vertices, points))    #include origin; facilitates clipping
    hull = scipy.spatial.ConvexHull(util.normalize(allpoints))
    triangles = hull.simplices

    #order faces coming from the convex hull
    print 'ordering faces'
    FP        = util.gather(triangles, allpoints)
    mid       = FP.sum(axis=1)
    normal    = util.normals(FP)
    sign      = util.dot(normal, mid) > 0
    triangles = np.where(sign[:,None], triangles[:,::+1], triangles[:,::-1])

    mesh = Mesh(allpoints, triangles)
    assert mesh.is_orientated()

    return mesh, curve
Beispiel #17
0
    def refine(self, points):
        """
        refine the contour such as to maintain it as a constrained boundary under triangulation using a convex hull
        this is really the crux of the method pursued in this module
        we need to 'shield off' any points that lie so close to the edge such as to threaten our constrained boundary
        by adding a split at the projection of the point on the line, for all vertices within the swept circle of the edge,
        we may guarantee that a subsequent convex hull of the sphere respects our original boundary
        """
        allpoints = np.vstack((self.vertices, points))
        tree = KDTree(allpoints)

        cp     = util.gather(self.faces, self.vertices)
        normal = util.normalize(np.cross(cp[:,0], cp[:,1]))
        mid    = util.normalize(cp.sum(axis=1))
        diff   = np.diff(cp, axis=1)[:,0,:]
        radius = np.linalg.norm(diff, axis=1) / 2

        def insertion_point(e, c):
            """calculate insertion point"""
            coeff = np.dot( np.linalg.pinv(cp[e].T), allpoints[c])
            coeff = coeff / coeff.sum()
            return coeff[0], np.dot(cp[e].T, coeff)

        #build new curves
        _curve_p = [c for c in self.vertices]
        _curve_idx = []
        for e,(m,r,cidx) in enumerate(izip( mid, radius, self.faces)):
            try:
                d,ip = min(     #codepath for use in iterative scheme; only insert the most balanced split; probably makes more awkward ones obsolete anyway
                    [insertion_point(e,v) for v in tree.query_ball_point(m, r) if not v in cidx],
                    key=lambda x:(x[0]-0.5)**2)     #sort on distance from midpoint
                nidx = len(_curve_p)
                _curve_idx.append((cidx[0], nidx))  #attach on both ends
                _curve_idx.append((nidx, cidx[1]))
                _curve_p.append(ip)                   #append insertion point
            except:
                _curve_idx.append(cidx)             #if edge is not split, just copy it

        return Curve(_curve_p, _curve_idx)
Beispiel #18
0
    def self_intersect(self):
        """
        test curve of arc-segments for intersection
        raises exception in case of intersection
        alternatively, we might resolve intersections by point insertion
        but this is unlikely to have any practical utility, and more likely to be annoying
        """
        vertices = self.vertices
        faces = self.faces
        tree   = KDTree(vertices)
        # curve points per edge, [n, 2, 3]
        cp     = util.gather(faces, vertices)
        # normal rotating end unto start
        normal = util.normalize(np.cross(cp[:,0], cp[:,1]))
        # midpoints of edges; [n, 3]
        mid    = util.normalize(cp.sum(axis=1))
        # vector from end to start, [n, 3]
        diff   = np.diff(cp, axis=1)[:,0,:]
        # radius of sphere needed to contain edge, [n]
        radius = np.linalg.norm(diff, axis=1) / 2 * 1.01

        # FIXME: this can be vectorized by adapting pinv
        projector = [np.linalg.pinv(q) for q in np.swapaxes(cp, 1, 2)]

        # incident[vertex_index] gives a list of all indicent edge indices
        incident = npi.group_by(faces.flatten(), np.arange(faces.size))

        def intersect(i,j):
            """test if spherical line segments intersect. bretty elegant"""
            intersection = np.cross(normal[i], normal[j])                               #intersection direction of two great circles; sign may go either way though!
            return all(np.prod(np.dot(projector[e], intersection)) > 0 for e in (i,j))  #this direction must lie within the cone spanned by both sets of endpoints
        for ei,(p,r,cidx) in enumerate(izip(mid, radius, faces)):
            V = [v for v in tree.query_ball_point(p, r) if v not in cidx]
            edges = np.unique([ej for v in V for ej in incident[v]])
            for ej in edges:
                if len(np.intersect1d(faces[ei], faces[ej])) == 0:      #does not count if edges touch
                    if intersect(ei, ej):
                        raise Exception('The boundary curves intersect. Check your geometry and try again')
    def subdivide_position(self, position):
        """calc primal coords from parent"""
        #one child for each parent
        vertex_vertex = position
        #each new vert lies at midpoint
        edge_vertex = util.normalize(self.edge_mid*vertex_vertex)
        #ordering convention is vertex-vertex + edge-vertex
        position = np.vstack((vertex_vertex, edge_vertex))

        #calc subdivision planes
        central = util.gather(self.FEi, edge_vertex)
        planes  = util.adjoint(central)

        return position, planes
Beispiel #20
0
def triangle_normals(complex, radius, index):
    """triangle normals for a root index. do for each index?"""
    group = complex.group
    geometry = complex.geometry
    topology = geometry.topology

    FV = topology.FV
    PP = geometry.decomposed
    B = group.basis[:,0,0]      #grab all root bases

    b = util.normalize(B[index].T).T                   #now every row is a normalized vertex
    P = np.dot(b, PP.T).T * radius[:,index][:, None]   #go from decomposed coords to local coordinate system
    fv = FV[:,::np.sign(np.linalg.det(b))]         #flip sign for mirrored domains
    return np.cross(P[fv[:,1]]-P[fv[:,0]], P[fv[:,2]]-P[fv[:,0]])
Beispiel #21
0
def save_STL(filename, mesh):
    """save a mesh to plain stl. vertex ordering is assumed to be correct"""
    header      = np.zeros(80, '<c')
    triangles   = np.array(len(mesh.faces), '<u4')
    dtype       = [('normal', '<f4', 3,),('vertex', '<f4', (3,3)), ('abc', '<u2', 1,)]
    data        = np.empty(triangles, dtype)

    data['abc']    = 0     #standard stl cruft
    data['vertex'] = mesh.vertices[mesh.faces]
    data['normal'] = util.normalize(mesh.face_normals())

    with open(filename, 'wb') as fh:
        header.   tofile(fh)
        triangles.tofile(fh)
        data.     tofile(fh)
Beispiel #22
0
def generate_trace(trace):
    """
    convert picked points to well sampled trace of positions and weights
    """
    edges = np.array( zip(trace[1:], trace[:-1]))
    edges = util.normalize(edges)
    samples = np.linspace(0, 1, 11)
    samples = (samples[1:] + samples[:-1])/2
    weights = []
    positions = []
    for l,r in edges:
        L = np.linalg.norm(l-r)
        weights.extend([L]*len(samples))
        positions.extend( l[None, :] * samples[:, None] + r[None, :] * (1-samples[:, None]))
    weights = np.array(weights)
    positions = np.array(positions)#.reshape((-1,3))
    return positions, weights
Beispiel #23
0
def triangle_area_from_normals(*edge_planes):
    """compute spherical area from triplet of great circles

    Parameters
    ----------
    edge_planes : 3 x ndarray, [n, 3], float
        edge normal vectors of great circles

    Returns
    -------
    areas : ndarray, [n], float
        spherical area enclosed by the input planes
    """
    edge_planes = [util.normalize(ep) for ep in edge_planes ]
    angles      = [util.dot(edge_planes[p-2], edge_planes[p-1]) for p in xrange(3)] #a3 x [faces, c3]
    areas       = sum(np.arccos(-a) for a in angles) - np.pi                        #faces
    return areas
Beispiel #24
0
    def metric(self):
        """
        calc metric properties and hodges; nicely vectorized
        """
        topology = self.topology

        #metrics
        MP0 = np.ones (topology.P0)
        MP1 = np.zeros(topology.P1)
        MP2 = np.zeros(topology.P2)
        MD0 = np.ones (topology.D0)
        MD1 = np.zeros(topology.D1)
        MD2 = np.zeros(topology.D2)

        #precomputations
        EVP  = util.gather(topology.EVi, self.primal)
        FEVP = util.gather(topology.FEi, EVP)         #[faces, e3, v2, c3]
        FEM  = util.normalize(FEVP.sum(axis=2))
        FEV  = util.gather(topology.FEi, topology.EVi)

        #calculate areas; devectorization over e makes things a little more elegant, by avoiding superfluous stacking
        for e in xrange(3):
            areas = triangle_area_from_corners(FEVP[:,e,0,:], FEVP[:,e,1,:], self.dual)
            MP2 += areas                    #add contribution to primal face
            util.scatter(                   #add contributions divided over left and right dual face
                FEV[:,e,:],                 #get both verts of each edge
                np.repeat(areas/2, 2),      #half of domain area for both verts
                MD2)

        #calc edge lengths
        MP1 += edge_length(EVP[:,0,:], EVP[:,1,:])
        for e in xrange(3):
            util.scatter(
                topology.FEi[:,e],
                edge_length(FEM[:,e,:], self.dual),
                MD1)

        #hodge operators
        self.D2P0 = MD2 / MP0
        self.P0D2 = MP0 / MD2

        self.D1P1 = MD1 / MP1
        self.P1D1 = MP1 / MD1

        self.D0P2 = MD0 / MP2
        self.P2D0 = MP2 / MD0
    def apply(self, tcr):
        """
        apply the edge basis to the abstract subdivided curve
        output is mirror x rotations x subpoints x 3
        """
        t, c, r = tcr.T     #length equal to subpoints
        t = t[None, None, :, None]
        c = c[None, None, :, None]
        r = r[None, None, :, None]

        L, R, C = self.edge.basis()     #edges x 3
##        print L.shape, 'what?'
        L = L[:,:,None,:]
        R = R[:,:,None,:]
        C = C[:,:,None,:]

        P = L*t + R*(1-t) + C*c
        return normalize(P) * r
Beispiel #26
0
    def generate_vertices(self, group):
        """instantiate a full sphere by repeating the transformed fundamental domain

        Returns
        -------
        ndarray, [n, 3], float
            all points in the geometry, on a unit sphere
        """
        points = np.empty((group.index, group.order, self.topology.P0, 3), np.float)
        PP = self.decomposed
        for i, B in enumerate(group.basis):
            for t, b in enumerate(B.reshape(-1, 3, 3)):
                b = util.normalize(b.T).T  # now every row is a normalized vertex
                P = np.dot(b, PP.T).T  # go from decomposed coords to local coordinate system
                points[i, t] = P

        # make single unique point list
        return npi.unique(points.reshape(-1, 3))
        def add_base(mirror, B):
            """
            build pipeline of mesh mappers for given basis
            """
            B = util.normalize(B.T).T
            mirror = np.sign(np.linalg.det(B))

            PP = np.dot(B, self.triangle.decomposed.T).T
            x,y,z = PP.T
            FV = self.triangle.topology.FV[:,::mirror]
            source  = self.scene.mlab.pipeline.triangular_mesh_source(x,y,z, FV)
            source.data.point_data.normals = PP     #perfect sphere

            #add polydatamapper, to control color mapping and interpolation; could add col based on
            mapper = tvtk.PolyDataMapper(input=source.outputs[0])
            mapper.lookup_table = None
            mapper.scalar_visibility = False

            return mirror, source, mapper
Beispiel #28
0
    def geodesic(self, seed, m=1):
        """Compute geodesic distance map

        Notes
        -----
        http://www.multires.caltech.edu/pubs/GeodesicsInHeat.pdf
        """
        laplacian = self.laplacian_vertex()
        mass = self.vertex_areas()
        t = self.edge_lengths().mean() ** 2 * m
        heat = lambda x : mass * x - laplacian * (x * t)
        operator = scipy.sparse.linalg.LinearOperator(shape=laplacian.shape, matvec=heat)

        diffused = scipy.sparse.linalg.minres(operator, seed.astype(np.float64), tol=1e-5)[0]
        gradient = -util.normalize(self.compute_gradient(diffused))
        # self.plot(facevec=gradient)
        rhs = self.compute_divergence(gradient)
        phi = scipy.sparse.linalg.minres(laplacian, rhs)[0]
        return phi - phi.min()
Beispiel #29
0
def save_STL_complete(complex, radius, filename):
    """
    split this as mesh_from_datamodel

    save a mesh to binary STL format
    the number of triangles grows quickly
    shapeway and solidworks tap out at a mere 1M and 20k triangles respectively...
    or 100k for sw surface
    """
    data        = np.empty((complex.group.index, complex.group.order, complex.topology.P2, 3, 3), np.float)

    #essence here is in exact transformations given by the basis trnasforms. this gives a guaranteed leak-free mesh
    PP = complex.geometry.decomposed
    FV = complex.topology.FV
    for i,B in enumerate(complex.group.basis):
        for t, b in enumerate(B.reshape(-1,3,3)):
            b = util.normalize(b.T).T                      #now every row is a normalized vertex
            P = np.dot(b, PP.T).T * radius[:,i][:, None]   #go from decomposed coords to local coordinate system
            fv = FV[:,::np.sign(np.linalg.det(b))]
            data[i,t] = util.gather(fv, P)

    save_STL(filename, data.reshape(-1,3,3))
    def redraw(self):
        """
        reset height and related info (normals)
        """
        radius = self.datamodel.heightfield
        if not self.parent.height_visible:
            radius = np.ones_like(radius)

        #precompute normal information
        if self.parent.vertex_normal:
            normals = self.complex.normals(radius)

        self.recolor()

        for index,T in enumerate( self.root):
            for mirror,M in enumerate(T):
                _, source, mapper = M

##                mapper.lookup_table = None

                #set positions
                B = self.group.basis[index,mirror,0]
                B = util.normalize(B.T).T
                PP = np.dot(B, self.complex.geometry.decomposed.T).T       #primal coords transformed into current frame
                if self.parent.mapping_height: PP *= radius[:, index][:, None]
                source.mlab_source.set(points=PP)

                #set normals
                if self.parent.vertex_normal:
                    M = self.group.transforms[mirror,0]
                    source.data.point_data.normals = np.dot(M, normals[index,:,:].T).T
                    source.data.cell_data.normals = None
                else:
                    source.data.point_data.normals = None
                    source.data.cell_data.normals = None

                # needed to force a redraw of these elements
                source.mlab_source.update()