def sphere(ndiv=6, base='icosa', equiv='max'): """Create a triangulated approximation of a spherical surface. A (possibly high quality) approximation of a spherical surface is constructed as follows. First a simple base triangulated surface is created. Its triangular facets are subdivided by dividing all edges in `ndiv` parts. The resulting mesh is then projected on a sphere with unit radius. The higher `ndiv` is taken, the better the approximation. For `ndiv=1`, the base surface is returned. Parameters: - `ndiv`: number of divisions along the edges of the base surface. - `base`: the type of base surface. One of the following: - 'icosa': icosahedron (20 faces): this offers the highest quality with triangles of almost same size ans shape. - 'octa': octahedron (8 faces): this model will have the same mesh on each of the quadrants. The coordinate planes do not cut any triangle. This model is this fit to be subdivided along coordinate planes. Returns a TriSurface, representing a triangulated approximation of a spherical surface with radius 1 and center at the origin. """ from pyformex import elements from pyformex.trisurface import TriSurface base = getattr(elements, base.capitalize()) M = TriSurface(base.vertices, base.faces) M = M.subdivide(ndiv).fuse() M = M.projectOnSphere() return M
def fill(self): """Fill the surface inside the polygon with triangles. Returns a TriSurface filling the surface inside the polygon. """ print("AREA(self) %s" % self.area()) # creating elems array at once (more efficient than appending) from pyformex.gui.draw import draw, pause, undraw from pyformex.geomtools import insideTriangle x = self.coords n = x.shape[0] tri = -ones((n-2, 3), dtype=Int) # compute all internal angles e = arange(x.shape[0]) c = self.internalAngles() # loop in order of smallest angles itri = 0 while n > 3: #print("ANGLES",c) # try minimal angle srt = c.argsort() for j in srt: #print("ANGLE: %s" % c[j]) if c[j] > 180.: print("OOPS, I GOT STUCK!\nMaybe the curve is self-intersecting?") #print("Remaining points: %s" % e) #raise # # We could return here also the remaining part # return TriSurface(x, tri[:itri]) i = (j - 1) % n k = (j + 1) % n newtri = [ e[i], e[j], e[k]] # remove the point j of triangle i,j,k # recompute adjacent angles of edge i,k ii = (i-1) % n kk = (k+1) % n iq = e[[ii, i, k, kk]] PQ = Polygon(x[iq]) cn = PQ.internalAngles() cnew = cn[1:3] reme = roll(e, -j)[2:-1] T = x[newtri].reshape(1, 3, 3) P = x[reme].reshape(-1, 1, 3) check = insideTriangle(T, P) if not check.any(): # Triangle is ok break #draw(TriSurface(x,newtri),bbox='last',color='red') # accept new triangle tri[itri] = newtri c = roll(concatenate([cnew, roll(c, 1-j)[3:]]), j-1) e = roll(roll(e, -j)[1:], j) n -= 1 itri += 1 tri[itri] = e return TriSurface(x, tri)
def vmtkDistanceOfPoints(self, X, tryfixsign=True, nproc=1): """Find the distances of points X to the TriSurface self. - `X` is a (nX,3) shaped array of points. - `nproc`: number of parallel processes to use. On multiprocessor machines this may be used to speed up the processing. If <= 0 , the number of processes will be set equal to the number of processors, to achieve a maximal speedup. Retuns a tuple of vector and signed scalar distances for all points. The signed distance is positive if the distance vector and the surface normal have negative dot product, i.e. if X is outer with respect to self. """ if nproc < 1: nproc = multi.cpu_count() if nproc == 1: S = TriSurface( X, arange(X.shape[0]).reshape(-1, 1) * ones(3, dtype=int).reshape(1, -1)) return vmtkDistanceOfSurface(self, S, tryfixsign) else: datablocks = multi.splitar(X, nproc, close=False) print('-----distance of %d points from %d triangles with %d proc' % (len(X), self.nelems(), nproc)) tasks = [(vmtkDistanceOfPoints, (self, d, tryfixsign, 1)) for d in datablocks] ind = multi.multitask(tasks, nproc) vdist, sdist = list(zip(*ind)) return concatenate(vdist), concatenate(sdist)
def Cube(): """Create the surface of a cube Returns a TriSurface representing the surface of a unit cube. Each face of the cube is represented by two triangles. """ from pyformex.trisurface import TriSurface back = Formex('3:012934') fb = back.reverse() + back.translate(2, 1) faces = fb + fb.rollAxes(1) + fb.rollAxes(2) return TriSurface(faces)
def vmtkDistancePointsToSegments(X, L, atol=1.e-4): """Find the shortest distances from points X to segments L. X is a (nX,3) shaped array of points. L is a line2 Mesh. atol is the height of the triangles built from lines L (use lines as non-degenerated triangles to vmtk) Retuns a tuple of vector and scalar distances for all points. Points and lines are first converted into nearly degenerate triangles which are then used by vmtkDistanceOfSurface. """ SX = TriSurface( X, arange(X.shape[0]).reshape(-1, 1) * ones(3, dtype=int).reshape(1, -1)) from pyformex.geomtools import anyPerpendicularVector Lf = L.coords[L.elems] L0, L1 = Lf[:, 0], Lf[:, 1] perp = anyPerpendicularVector(L1 - L0) L2 = 0.5 * (L0 + L1) + normalize(perp) * atol SL = TriSurface( concatenate([L0, L1, L2], axis=1).reshape(-1, 3, 3) ) #NB they should not be fully degenerate otherwise VMTK will not consider them! vdist, dist = vmtkDistanceOfSurface(SL, SX, tryfixsign=False) return vdist, abs(dist)
def create_points(color=0, obj=None): """Create points on visible objects. Points are picked on the surface of a single object, a list of objects or of all visible objects (default). It returns all the points in the order they were picked and a variable indicating how you have exited: - you can escape the create_points by pushing ESC on Keyboard: None is returned - right mouse click or ENTER returns an empty list [] """ # build one surface around all visible objects or the given objects if obj is None: canvas = pf.GUI.viewports.current obj = [a.object for a in canvas.actors] if type(obj)!=list: obj = [obj] S = [] for o in obj: try: S.append(o.toSurface()) except: pass if len(S)==0: warning('there are no objects on the screen') return [] S = TriSurface.concatenate(S) # the surface of all visible objects # now pick points on the surface D = [] P = [] while True: col = mycolor(color) drawopt = dict(color=col,bbox='last', view=None) p = create_point(S) if isNoneOrEmpty(p): undraw(D) break s = "*** Point 3D report ***\n" s += '%s'%p cprint (s, color=col) # print in color on pyFormex message board D+=[ draw(p, **drawopt), drawMarks([p,], [color,], size=20, mode='smooth',**drawopt) # smooth is needed for drawMarks ] P.append(p) color+=1 return Coords.concatenate(P), p
def gtsset(self,surf,op,filt='',ext='.tmp',curve=False,check=False,verbose=False): """_Perform the boolean/intersection methods. See the boolean/intersection methods for more info. Parameters not explained there: - curve: if True, an intersection curve is computed, else the surface. Returns the resulting TriSurface (curve=False), or a plex-2 Formex (curve=True), or None if the input surfaces do not intersect. """ # import here to avoid circular import from pyformex.trisurface import TriSurface op = {'+':'union', '-':'diff', '*':'inter'}[op] options = '' if curve: options += '-i' if check: options += ' -s' if not verbose: options += ' -v' tmp = utils.tempFile(suffix='.gts').name tmp1 = utils.tempFile(suffix='.gts').name tmp2 = utils.tempFile(suffix=ext).name print("Writing temp file %s" % tmp) self.write(tmp, 'gts') print("Writing temp file %s" % tmp1) surf.write(tmp1, 'gts') print("Performing boolean operation") cmd = "gtsset %s %s %s %s %s" % (options, op, tmp, tmp1, filt) P = utils.system(cmd, stdout=open(tmp2, 'w')) os.remove(tmp) os.remove(tmp1) if P.sta or verbose: print(P.out) if P.sta: print(P.err) return None print("Reading result from %s" % tmp2) if curve: res = read_gts_intersectioncurve(tmp2) else: res = TriSurface.read(tmp2) os.remove(tmp2) return res
def voronoi(fn): """Determine the voronoi diagram corresponding with a triangulated surface. fn is the file name of a surface, including the extension (.off, .stl, .gts, .neu or .smesh) The voronoi diagram is determined by Tetgen. The output are the voronoi nodes and the corresponding radii of the voronoi spheres. """ S = TriSurface.read(fn) fn, ftype = os.path.splitext(fn) ftype = ftype.strip('.').lower() if ftype != 'smesh': S.write('%s.smesh' % fn) P = utils.command('tetgen -zpv %s.smesh' % fn) #information tetrahedra elems = tetgen.readElems('%s.1.ele' % fn)[0] nodes = tetgen.readNodes('%s.1.node' % fn)[0] #voronoi information nodesVor = tetgen.readNodes('%s.1.v.node' % fn)[0] #calculate the radii of the voronoi spheres vec = nodesVor[:] - nodes[elems[:, 0]] radii = sqrt((vec * vec).sum(axis=-1)) return nodesVor, radii
def voronoiInner(fn): """Determine the inner voronoi diagram corresponding with a triangulated surface. fn is the file name of a surface, including the extension (.off, .stl, .gts, .neu or .smesh) The output are the voronoi nodes and the corresponding radii of the voronoi spheres. """ S = TriSurface.read(fn) fn, ftype = os.path.splitext(fn) ftype = ftype.strip('.').lower() if ftype != 'smesh': S.write('%s.smesh' % fn) P = utils.command('tetgen -zp %s.smesh' % fn) #information tetrahedra elems = tetgen.readElems('%s.1.ele' % fn)[0] nodes = tetgen.readNodes('%s.1.node' % fn)[0].astype(float64) #calculate surface normal for each point elemsS = array(S.elems) NT = S.areaNormals()[1] NP = zeros([nodes.shape[0], 3]) for i in [0, 1, 2]: NP[elemsS[:, i]] = NT #calculate centrum circumsphere of each tetrahedron centers = circumcenter(nodes, elems)[0] #check if circumcenter falls within the geomety described by the surface ie = column_stack([ ((nodes[elems[:, j]] - centers[:]) * NP[elems[:, j]]).sum(axis=-1) for j in [0, 1, 2, 3] ]) ie = ie[:, :] >= 0 w = where(ie.all(1))[0] elemsInner = elems[w] nodesVorInner = centers[w] #calculate the radii of the voronoi spheres vec = nodesVorInner[:] - nodes[elemsInner[:, 0]] radii = sqrt((vec * vec).sum(axis=-1)) return nodesVorInner, radii
def remesh(self, elementsizemode='edgelength', edgelength=None, area=None, areaarray=None, aspectratio=None, excludeprop=None, includeprop=None, preserveboundary=False, conformal='border', options=''): """Remesh a TriSurface. Returns the remeshed TriSurface. If the TriSurface has property numbers the property numbers will be inherited by the remeshed surface. Parameters: - `elementsizemode`: str: metric that is used for remeshing. `edgelength`, `area` and `areaarray` allow to specify a global target edgelength, area and areaarray (area at each node), respectively. - `edgelength`: float: global target triangle edgelength - `area`: float: global target triangle area - `areaarray`: array of float: nodal target triangle area - `aspectratio`: float: upper threshold for aspect ratio (default=1.2) - `includeprop`: either a single integer, or a list/array of integers. Only the regions with these property number(s) will be remeshed. This option is not compatible with `exludeprop`. - `excludeprop`: either a single integer, or a list/array of integers. The regions with these property number(s) will not be remeshed. This option is not compatible with `includeprop`. - `preserveboundary`: if True vmtk tries to keep the shape of the border. - `conformal`: None, `border` or `regionsborder`. If there is a border (ie. the surface is open) conformal=`border` preserves the border line (both points and connectivity); conformal=`regionsborder` preserves both border line and lines between regions with different property numbers. Detected BUGS: - preserveboundary = True With VMTK 1.3 and VTK6 there is a bug: if surface has a boundary (is not closed) and you want preserveboundary=False you can an error: python: /build/vtk6-E5SYwm/vtk6-6.3.0+dfsg1/Common/Core/vtkDataArrayTemplate.h:191: T vtkDataArrayTemplate<T>::GetValue(vtkIdType) [with T = int; vtkIdType = long long int]: Assertion `id >= 0 && id < this->Size' failed. Aborted As a workaround, you can - either close the surface (so there is no boundary, and preserveboundary will be ignored) - or add some extensions (with different property number) to the borders and use preserveboundary=True After remeshing you can clip the added parts using their property number. """ if includeprop is not None: if excludeprop is not None: raise ValueError('you cannot use both excludeprop and includeprop') else: ps = self.propSet() mask = in1d(ar1=ps, ar2=checkArray1D(includeprop)) if sum(mask) == 0: utils.warn("warn_vmtk_includeprop", data=(includeprop, ps)) return self excludeprop = ps[~mask] if conformal == 'border' or conformal == 'regionsborder': if elementsizemode == 'areaarray': raise ValueError( 'conformal (regions)border and areaarray cannot be used together (yet)!' ) #conformalBorder alters the node list. Afterwards, the nodes do not correspond with pointdata. if self.isClosedManifold() == False: if conformal == 'regionsborder': if self.propSet() is not None: if len(self.propSet()) > 1: return TriSurface.concatenate([ remesh(s2, elementsizemode=elementsizemode, edgelength=edgelength, area=area, areaarray=None, aspectratio=aspectratio, excludeprop=excludeprop, preserveboundary=preserveboundary, conformal='border') for s2 in self.splitProp() ]) added = TriSurface( self.getBorderMesh().convert('line3').setType('tri3')) s1 = self + added.setProp(-1) + added.setProp( -2) #add triangles on the border s1 = s1.fuse().compact().renumber( ) #this would mix up the areaarray!! excludeprop1 = array([-1, -2]) if excludeprop is not None: excludeprop1 = append(excludeprop1, asarray(excludeprop).reshape(-1)) return remesh(s1, elementsizemode=elementsizemode, edgelength=edgelength, area=area, areaarray=None, aspectratio=aspectratio, excludeprop=excludeprop1, preserveboundary=preserveboundary, conformal=None).cselectProp([-1, -2]).compact() else: if conformal is not None: raise ValueError( 'conformal should be either None, border or regionsborder') from pyformex.plugins.vtk_itf import writeVTP, checkClean, readVTKObject tmp = utils.tempFile(suffix='.vtp').name tmp1 = utils.tempFile(suffix='.vtp').name fielddata, celldata, pointdata = {}, {}, {} cmd = 'vmtk vmtksurfaceremeshing -ifile %s -ofile %s' % (tmp, tmp1) if elementsizemode == 'edgelength': if edgelength is None: self.getElemEdges() E = Mesh(self.coords, self.edges, eltype='line2') edgelength = E.lengths().mean() cmd += ' -elementsizemode edgelength -edgelength %f' % edgelength elif elementsizemode == 'area': if area is None: area = self.areas().mean() cmd += ' -elementsizemode area -area %f' % area elif elementsizemode == 'areaarray': if not checkClean(self): raise ValueError( "Mesh is not clean: vtk will alter the node numbering and the areaarray will not correspond to the node numbering. To clean: mesh.fuse().compact().renumber()" ) cmd += ' -elementsizemode areaarray -areaarray nodalareas ' pointdata['nodalareas'] = areaarray if aspectratio is not None: cmd += ' -aspectratio %f' % aspectratio if excludeprop is not None: cmd += ' -exclude ' + ' '.join([ '%d' % i for i in checkArray1D(excludeprop, kind='i', allow=None) ]) if preserveboundary: cmd += ' -preserveboundary 1' if self.prop is not None: cmd += ' -entityidsarray prop' print("Writing temp file %s" % tmp) cmd += ' %s' % options writeVTP(mesh=self, fn=tmp, pointdata=pointdata) print("Remeshing with command\n %s" % cmd) P = utils.command(cmd) os.remove(tmp) if P.sta: print("An error occurred during the remeshing.") print(P.out) return None [coords, cells, polys, lines, verts], fielddata, celldata, pointdata = readVTKObject(tmp1) S = TriSurface(coords, polys) if self.prop is not None: S = S.setProp(celldata['prop']) os.remove(tmp1) return S
def Horse(): return TriSurface.read(getcfg('datadir') + '/horse.off')