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 gui.draw import draw, pause, undraw from 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 run(): reset() smooth() lights(True) S = TriSurface.read(getcfg('datadir')+'/horse.off') SA = draw(S) res = askItems([ ('direction',[1.,0.,0.]), ('number of sections',20), ('color','red'), ('ontop',False), ('remove surface',False), ]) if not res: return d = res['direction'] n = res['number of sections'] c = res['color'] slices = S.slice(dir=d,nplanes=n) linewidth(2) draw(slices,color=c,view=None,bbox='last',nolight=True,ontop=res['ontop']) export({'_HorseSlice_slices':slices}) if res['remove surface']: undraw(SA) zoomAll()
def remesh(self, edgelen=None): """Remesh a TriSurface. edgelen is the suggested edge length """ if edgelen is None: self.getElemEdges() E = Mesh(self.coords, self.edges, eltype='line2') edgelen = E.lengths().mean() tmp = utils.tempFile(suffix='.stl').name tmp1 = utils.tempFile(suffix='.stl').name pf.message("Writing temp file %s" % tmp) self.write(tmp, 'stl') pf.message("Remeshing using VMTK (edge length = %s)" % edgelen) cmd = "vmtk vmtksurfaceremeshing -ifile %s -ofile %s -edgelength %s" % ( tmp, tmp1, edgelen) sta, out = utils.runCommand(cmd) os.remove(tmp) if sta: pf.message("An error occurred during the remeshing.") pf.message(out) return None S = TriSurface.read(tmp1) os.remove(tmp1) return S
def run(): global F, G clear() smooth() view('iso') F = cylinder(L=8., D=2., nt=36, nl=20, diag='u').centered() F = TriSurface(F).setProp(3).close(method='planar').fixNormals() G = F.rotate(90., 0).trl(0, 1.).setProp(1) export({'F': F, 'G': G}) draw([F, G]) res = askItems([ _I('op', text='Operation', choices=[ '+ (Union)', '- (Difference)', '* Intersection', 'Intersection Curve', ], itemtype='vradio'), _I('verbose', False, text='Show stats'), ]) if not res: return op = res['op'][0] verbose = res['verbose'] if op in '+-*': I = F.boolean(G, op, verbose=verbose) else: I = F.intersection(G, verbose=verbose) clear() draw(I) if op in '+-*': return else: if ack('Create a surface inside the curve ?'): I = I.toMesh() e = connectedLineElems(I.elems) I = Mesh(I.coords, connectedLineElems(I.elems)[0]) clear() draw(I, color=red, linewidth=3) S = fillBorder(I, method='planar') draw(S)
def delaunay(X): """Return a Delaunay triangulation of the specified Coords. While the Coords are 3d, only the first 2 components are used. Returns a TriSurface with the Delaunay trinagulation in the x-y plane. """ from voronoi import voronoi return TriSurface(X, voronoi(X[:, :2]).triangles)
def run(): global F,G clear() smooth() view('iso') F = cylinder(L=8.,D=2.,nt=36,nl=20,diag='u').centered() F = TriSurface(F).setProp(3).close(method='planar').fixNormals() G = F.rotate(90.,0).trl(0,1.).setProp(1) export({'F':F,'G':G}) draw([F,G]) res = askItems( [ _I('op',text='Operation',choices=[ '+ (Union)', '- (Difference)', '* Intersection', 'Intersection Curve', ],itemtype='vradio'), _I('verbose',False,text='Show stats'), ]) if not res: return op = res['op'][0] verbose = res['verbose'] if op in '+-*': I = F.boolean(G,op,verbose=verbose) else: I = F.intersection(G,verbose=verbose) clear() draw(I) if op in '+-*': return else: if ack('Create a surface inside the curve ?'): I = I.toMesh() e = connectedLineElems(I.elems) I = Mesh(I.coords,connectedLineElems(I.elems)[0]) clear() draw(I,color=red,linewidth=3) S = fillBorder(I,method='planar') draw(S)
def smallestDirection(x,method='inertia',return_size=False): """Return the direction of the smallest dimension of a Coords - `x`: a Coords-like array - `method`: one of 'inertia' or 'random' - return_size: if True and `method` is 'inertia', a tuple of a direction vector and the size along that direction and the cross directions; else, only return the direction vector. """ x = x.reshape(-1,3) if method == 'inertia': # The idea is to take the smallest dimension in a coordinate # system aligned with the global axes. C,r,Ip,I = x.inertia() X = x.trl(-C).rot(r) sizes = X.sizes() i = sizes.argmin() # r gives the directions as column vectors! # TODO: maybe we should change that N = r[:,i] if return_size: return N,sizes[i] else: return N elif method == 'random': # Take the mean of the normals on randomly created triangles from plugins.trisurface import TriSurface n = x.shape[0] m = 3 * (n // 3) e = arange(m) random.shuffle(e) if n > m: e = concatenate([e,[0,1,n-1]]) el = e[-3:] S = TriSurface(x,e.reshape(-1,3)) A,N = S.areaNormals() ok = where(isnan(N).sum(axis=1) == 0)[0] N = N[ok] N = N*N N = N.mean(axis=0) N = sqrt(N) N = normalize(N) return N
def run(): reset() smooth() lights(True) S = TriSurface.read(getcfg('datadir') + '/horse.off') SA = draw(S) bb = S.bbox() bb1 = [1.1 * bb[0] - 0.1 * bb[1], 1.1 * bb[1] - 0.1 * bb[0]] print(bb) print(bb1) res = askItems([ _I('Resolution', 100), ]) if not res: return nmax = res['Resolution'] sz = bb1[1] - bb1[0] step = sz.max() / (nmax - 1) n = (sz / step).astype(Int) print(n) P = Formex(simple.regularGrid(bb1[0], bb1[0] + n * step, n).reshape(-1, 3)) draw(P, marksize=1, color='black') #drawNumbers(P) zoomAll() ind = S.inside(P) vox = zeros(n + 1, dtype=uint8) print(vox.shape) vox1 = vox.reshape(-1) print(vox1.shape, ind.max()) vox1[ind] = 1 print(vox.max()) P.setProp(vox1) draw(P, marksize=8) dirname = askDirname() chdir(dirname) # Create output file if not checkWorkdir(): print("Could not open a directory for writing. I have to stop here") return fs = utils.NameSequence('horse', '.png') clear() flat() A = None for frame in vox: B = showGreyImage(frame) saveBinaryImage(frame * 255, fs.next()) undraw(A) A = B
def run(): reset() smooth() lights(True) S = TriSurface.read(getcfg('datadir')+'/horse.off') SA = draw(S) bb = S.bbox() bb1 = [ 1.1*bb[0]-0.1*bb[1], 1.1*bb[1]-0.1*bb[0]] print(bb) print(bb1) res = askItems([ _I('Resolution',100), ]) if not res: return nmax = res['Resolution'] sz = bb1[1]-bb1[0] step = sz.max() / (nmax-1) n = (sz / step).astype(Int) print(n) P = Formex(simple.regularGrid(bb1[0],bb1[0]+n*step,n).reshape(-1,3)) draw(P, marksize=1, color='black') #drawNumbers(P) zoomAll() ind = S.inside(P) vox = zeros(n+1,dtype=uint8) print(vox.shape) vox1 = vox.reshape(-1) print(vox1.shape,ind.max()) vox1[ind] = 1 print(vox.max()) P.setProp(vox1) draw(P, marksize=8) dirname = askDirname() chdir(dirname) # Create output file if not checkWorkdir(): print("Could not open a directory for writing. I have to stop here") return fs = utils.NameSequence('horse','.png') clear() flat() A = None for frame in vox: B = showGreyImage(frame) saveBinaryImage(frame*255,fs.next()) undraw(A) A = B
def sphere(ndiv=6): """Create a triangulated spherical surface. A high quality approximation of a spherical surface is constructed as follows. First an icosahedron is constructed. 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. `ndiv=1` results in an icosahedron. Returns: A TriSurface, representing a triangulated approximation of a spherical surface with radius 1 and center at the origin. """ from elements import Icosa from plugins.trisurface import TriSurface M = TriSurface(Icosa.vertices,Icosa.faces) M = M.subdivide(ndiv).fuse() M = M.projectOnSphere() return M
def run(): reset() smooth() lights(True) transparent(False) setView('horse', [20, 20, 0]) S = TriSurface.read(getcfg('datadir') + '/horse.off') bb = S.bbox() t = -0.3 bb[0] = (1.0 - t) * bb[0] + t * bb[1] draw(S, bbox=bb, view='front') try: P, n, t = askSlices(S.bbox()) except: return a = t / len(P) F = S.toFormex() G = [] old = seterr(all='ignore') setDrawOptions({'bbox': None}) clear() A = None for i, p in enumerate(P): F1, F = F.cutWithPlane(p, -n) if F1.nelems() > 0: F1.setProp(i) G = [g.rot(a, around=p) for g in G] G.append(F1) #clear() B = draw([F, G]) if A: undraw(A) A = B seterr(**old) x = pf.canvas.width() / 2 y = pf.canvas.height() - 40 T = drawText("No animals got hurt during the making of this movie!", x, y, size=18, gravity='C') for i in range(10): sleep(0.3) undecorate(T) sleep(0.3) decorate(T)
def run(): reset() smooth() lights(True) transparent(False) setView('horse',[20,20,0]) S = TriSurface.read(getcfg('datadir')+'/horse.off') bb = S.bbox() t = -0.3 bb[0] = (1.0-t)*bb[0] + t*bb[1] draw(S,bbox=bb,view='front') try: P,n,t = askSlices(S.bbox()) except: return a = t/len(P) F = S.toFormex() G = [] old = seterr(all='ignore') setDrawOptions({'bbox':None}) clear() A = None for i,p in enumerate(P): F1,F = F.cutWithPlane(p,-n) if F1.nelems() > 0: F1.setProp(i) G = [ g.rot(a,around=p) for g in G ] G.append(F1) #clear() B = draw([F,G]) if A: undraw(A) A = B seterr(**old) x = pf.canvas.width()/2 y = pf.canvas.height() - 40 T = drawText("No animals got hurt during the making of this movie!",x,y,size=18,gravity='C') for i in range(10): sleep(0.3) undecorate(T) sleep(0.3) decorate(T)
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: - filt: a filter command to be executed on the gtsset output - ext: extension of the result file - curve: if True, an intersection curve is computed, else the surface. Returns the name of the (temporary) results file. """ 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 pf.message("Writing temp file %s" % tmp) self.write(tmp, 'gts') pf.message("Writing temp file %s" % tmp1) surf.write(tmp1, 'gts') pf.message("Performing boolean operation") cmd = "gtsset %s %s %s %s %s > %s" % (options, op, tmp, tmp1, filt, tmp2) sta, out = utils.runCommand(cmd) os.remove(tmp) os.remove(tmp1) if sta or verbose: pf.message(out) pf.message("Reading result from %s" % tmp2) if curve: res = read_gts_intersectioncurve(tmp2) else: res = TriSurface.read(tmp2) os.remove(tmp2) return res
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: - filt: a filter command to be executed on the gtsset output - ext: extension of the result file - curve: if True, an intersection curve is computed, else the surface. Returns the name of the (temporary) results file. """ 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 pf.message("Writing temp file %s" % tmp) self.write(tmp,'gts') pf.message("Writing temp file %s" % tmp1) surf.write(tmp1,'gts') pf.message("Performing boolean operation") cmd = "gtsset %s %s %s %s %s > %s" % (options,op,tmp,tmp1,filt,tmp2) sta,out = utils.runCommand(cmd) os.remove(tmp) os.remove(tmp1) if sta or verbose: pf.message(out) pf.message("Reading result from %s" % tmp2) if curve: res = read_gts_intersectioncurve(tmp2) else: res = TriSurface.read(tmp2) os.remove(tmp2) return res
def run(): reset() smooth() lights(True) S = TriSurface.read(getcfg('datadir') + '/horse.off') SA = draw(S) res = askItems([ ('direction', [1., 0., 0.]), ('number of sections', 20), ('color', 'red'), ('ontop', False), ('remove surface', False), ]) if not res: return d = res['direction'] n = res['number of sections'] c = res['color'] slices = S.slice(dir=d, nplanes=n) linewidth(2) draw(slices, color=c, view=None, bbox='last', nolight=True, ontop=res['ontop']) export({'_HorseSlice_slices': slices}) if res['remove surface']: undraw(SA) zoomAll()
def remesh(self,edgelen=None): """Remesh a TriSurface. edgelen is the suggested edge length """ if edgelen is None: self.getElemEdges() E = Mesh(self.coords,self.edges,eltype='line2') edgelen = E.lengths().mean() tmp = utils.tempFile(suffix='.stl').name tmp1 = utils.tempFile(suffix='.stl').name pf.message("Writing temp file %s" % tmp) self.write(tmp,'stl') pf.message("Remeshing using VMTK (edge length = %s)" % edgelen) cmd = "vmtk vmtksurfaceremeshing -ifile %s -ofile %s -edgelength %s" % (tmp,tmp1,edgelen) sta,out = utils.runCommand(cmd) os.remove(tmp) if sta: pf.message("An error occurred during the remeshing.") pf.message(out) return None S = TriSurface.read(tmp1) os.remove(tmp1) return S
totalrot = res['total rot'] xmin,xmax = bb[:,axis] dx = (xmax-xmin) / nslices x = arange(nslices+1) * dx N = unitVector(axis) P = [ bb[0]+N*s for s in x ] return P,N,totalrot else: return None reset() smooth() lights(True) transparent(False) setView('horse',[20,20,0]) S = TriSurface.read(getcfg('datadir')+'/horse.off') bb = S.bbox() t = -0.3 bb[0] = (1.0-t)*bb[0] + t*bb[1] draw(S,bbox=bb,view='front') try: P,n,t = askSlices(S.bbox()) except: exit() a = t/len(P) F = S.toFormex() G = []
def drawSurf(F, surface=False, **kargs): """Draw a Formex as surface or not.""" if surface: F = TriSurface(F) return draw(F, **kargs)
sw=sp[w] return px[sw], w, itx[sw], delete( arange(len(segm)), w) else:return px, ilx, itx clear() smooth() lights(True) transparent(False) view('iso') image = None scaled_image = None # read the teapot surface T = TriSurface.read(getcfg('datadir')+'/teapot.off') xmin,xmax = T.bbox() T= T.trl(-T.center()).scale(4./(xmax[0]-xmin[0])).setProp(2) draw(T) # default image file filename = getcfg('datadir')+'/benedict_6.jpg' viewer = ImageView(filename) px,py = 5,5 #control points for projection of patches kx,ky = 60,50 #number of cells in each patch scale = 0.8 method='projection' res = askItems([ I('filename',filename,text='Image file',itemtype='button',func=selectImage),
def run(): clear() linewidth(2) flatwire() setDrawOptions({'bbox': 'auto'}) # A tapered grid of points F = Formex([0.]).replic2(10, 5, taper=-1) draw(F) # Split in parts by testing y-position; note use of implicit loop! G = [F.clip(F.test(dir=1, min=i - 0.5, max=i + 0.5)) for i in range(5)] print([Gi.nelems() for Gi in G]) def annot(char): [ drawText3D(G[i][0, 0] + [-0.5, 0., 0.], "%s%s" % (char, i)) for i, Gi in enumerate(G) ] # Apply a general mapping function : x,y,x -> [ newx, newy, newz ] G = [ Gi.map(lambda x, y, z: [x, y + 0.01 * float(i + 1)**1.5 * x**2, z]) for i, Gi in enumerate(G) ] clear() draw(G) annot('G') setDrawOptions({'bbox': 'last'}) # Connect G0 with G1 H1 = connect([G[0], G[1]]) draw(H1, color=blue) # Connect G1 with G2 with a 2-element bias H2 = connect([G[1], G[2]], bias=[0, 2]) draw(H2, color=green) # Connect G3 with G4 with a 1-element bias plus loop H2 = connect([G[3], G[4]], bias=[1, 0], loop=True) draw(H2, color=red) # Create a triangular grid of bars clear() draw(G) annot('G') # Connect Gi[j] with Gi[j+1] to create horizontals K1 = [connect([i, i], bias=[0, 1]) for i in G] draw(K1, color=blue) # Connect Gi[j] with Gi+1[j] to create verticals K2 = [connect([i, j]) for i, j in zip(G[:-1], G[1:])] draw(K2, color=red) # Connect Gi[j+1] with Gi+1[j] to create diagonals K3 = [connect([i, j], bias=[1, 0]) for i, j in zip(G[:-1], G[1:])] draw(K3, color=green) # Create triangles clear() draw(G) annot('G') L1 = [connect([i, i, j], bias=[0, 1, 0]) for i, j in zip(G[:-1], G[1:])] draw(L1, color=red) L2 = [connect([i, j, j], bias=[1, 0, 1]) for i, j in zip(G[:-1], G[1:])] draw(L2, color=green) # Connecting multiplex Formices using bias clear() annot('K') draw(K1) L1 = [connect([i, i, j], bias=[0, 1, 0]) for i, j in zip(K1[:-1], K1[1:])] draw(L1, color=red) L2 = [connect([i, j, j], bias=[1, 0, 1]) for i, j in zip(K1[:-1], K1[1:])] draw(L2, color=green) # Connecting multiplex Formices using nodid clear() draw(K1) annot('K') L1 = [connect([i, i, j], nodid=[0, 1, 0]) for i, j in zip(K1[:-1], K1[1:])] draw(L1, color=red) L2 = [connect([i, j, j], nodid=[1, 0, 1]) for i, j in zip(K1[:-1], K1[1:])] draw(L2, color=green) # Add the missing end triangles L3 = [ connect([i, i, j], nodid=[0, 1, 1], bias=[i.nelems() - 1, i.nelems() - 1, j.nelems() - 1]) for i, j in zip(K1[:-1], K1[1:]) ] draw(L3, color=magenta) # Collect all triangles in a single Formex L = (Formex.concatenate(L1) + Formex.concatenate(L3)).setProp(1) + Formex.concatenate(L2).setProp(2) clear() draw(L) # Convert to a Mesh print("nelems = %s, nplex = %s, coords = %s" % (L.nelems(), L.nplex(), L.coords.shape)) M = L.toMesh() print("nelems = %s, nplex = %s, coords = %s" % (M.nelems(), M.nplex(), M.coords.shape)) clear() draw(M, color=yellow, mode='flatwire') drawNumbers(M) draw(M.getBorderMesh(), color=black, linewidth=6) # Convert to a surface from plugins.trisurface import TriSurface S = TriSurface(M) print("nelems = %s, nplex = %s, coords = %s" % (S.nelems(), S.nplex(), S.coords.shape)) clear() draw(S) print("Total surface area: %s" % S.area()) export({'surface-1': S}) setDrawOptions({'bbox': 'auto'})
def createShellModel(): """Create the Finite Element Model. It is supposed here that the Geometry has been created and is available as a global variable F. """ # Turn the Formex structure into a TriSurface # This guarantees that element i of the Formex is element i of the TriSurface S = TriSurface(F) print "The structure has %s nodes, %s edges and %s faces" % (S.ncoords(),S.nedges(),S.nfaces()) nodes = S.coords elems = S.elems # the triangles clear() draw(F) # Shell section and material properties # VALUES SHOULD BE SET CORRECTLY glass_plate = { 'name': 'glass_plate', 'sectiontype': 'shell', 'thickness': 18, 'material': 'glass', } glass = { 'name': 'glass', 'young_modulus': 72000, 'shear_modulus': 26200, 'density': 2.5e-9, # T/mm**3 } print glass_plate print glass glasssection = ElemSection(section=glass_plate,material=glass) PDB = PropertyDB() # All elements have same property: PDB.elemProp(set=arange(len(elems)),section=glasssection,eltype='STRI3') # Calculate the nodal loads # Area of triangles area,normals = S.areaNormals() print "Area:\n%s" % area ### DEFINE LOAD CASE (ask user) ### res = askItems([('Glass',True),('Snow',False)]) if not res: return step = 0 if res['Glass']: step += 1 NODLoad = zeros((S.ncoords(),3)) # add the GLASS weight wgt = 450e-6 # N/mm**2 # Or, calculate weight from density: # wgt = glass_plate['thickness'] * glass['density'] * 9810 # assemble uniform glass load for e,a in zip(S.elems,area): NODLoad[e] += [ 0., 0., - a * wgt / 3 ] # Put the nodal loads in the properties database for i,P in enumerate(NODLoad): PDB.nodeProp(tag=step,set=i,cload=[P[0],P[1],P[2],0.,0.,0.]) if res['Snow']: step += 1 NODLoad = zeros((S.ncoords(),3)) # add NON UNIFORM SNOW fn = 'hesperia-nieve.prop' snowp = fromfile(fn,sep=',') snow_uniform = 320e-6 # N/mm**2 snow_non_uniform = { 1:333e-6, 2:133e-6, 3:133e-6, 4:266e-6, 5:266e-6, 6:667e-6 } # assemble non-uniform snow load for e,a,p in zip(S.elems,area,snowp): NODLoad[e] += [ 0., 0., - a * snow_non_uniform[p] / 3] # Put the nodal loads in the properties database for i,P in enumerate(NODLoad): PDB.nodeProp(tag=step,set=[i],cload=[P[0],P[1],P[2],0.,0.,0.]) # Get support nodes botnodes = where(isClose(nodes[:,2], 0.0))[0] bot = nodes[botnodes].reshape((-1,1,3)) pf.message("There are %s support nodes." % bot.shape[0]) botofs = bot + [ 0.,0.,-0.2] bbot2 = concatenate([bot,botofs],axis=1) print bbot2.shape S = Formex(bbot2) draw(S) ## np_central_loaded = NodeProperty(3, displacement=[[1,radial_displacement]],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## #np_transf = NodeProperty(0,coords='cylindrical',coordset=[0,0,0,0,0,1]) ## # Radial movement only ## np_fixed = NodeProperty(1,bound=[0,1,1,0,0,0],coords='cylindrical',coordset=[0,0,0,0,0,1]) # Since we left out the ring beam, we enforce no movement at the botnodes bc = PDB.nodeProp(set=botnodes,bound=[1,1,1,0,0,0],csys=CoordSystem('C',[0,0,0,0,0,1])) # And we record the name of the bottom nodes set botnodeset = Nset(bc.nr) fe_model = Dict(dict(nodes=nodes,elems=elems,prop=PDB,botnodeset=botnodeset,nsteps=step)) export({'fe_model':fe_model}) smooth() lights(False)
def createFrameModel(): """Create the Finite Element Model. It is supposed here that the Geometry has been created and is available as a global variable F. """ wireframe() lights(False) # Turn the Formex structure into a TriSurface # This guarantees that element i of the Formex is element i of the TriSurface S = TriSurface(F) nodes = S.coords elems = S.elems # the triangles # Create edges and faces from edges print "The structure has %s nodes, %s edges and %s faces" % (S.ncoords(),S.nedges(),S.nfaces()) # Create the steel structure E = Formex(nodes[S.getEdges()]) clear() draw(E) # Get the tri elements that are part of a quadrilateral: prop = F.prop quadtri = S.getFaceEdges()[prop==6] nquadtri = quadtri.shape[0] print "%s triangles are part of quadrilateral faces" % nquadtri if nquadtri > 0: # Create triangle definitions of the quadtri faces tri = Connectivity.tangle(quadtri,S.getEdges()) D = Formex(nodes[tri]) clear() flatwire() draw(D,color='yellow') conn = connections(quadtri) print conn # Filter out the single connection edges internal = [ c[0] for c in conn if len(c[1]) > 1 ] print "Internal edges in quadrilaterals: %s" % internal E = Formex(nodes[S.getEdges()],1) E.prop[internal] = 6 wireframe() clear() draw(E) # Remove internal edges tubes = S.getEdges()[E.prop != 6] print "Number of tube elements after removing %s internals: %s" % (len(internal),tubes.shape[0]) D = Formex(nodes[tubes],1) clear() draw(D) # Beam section and material properties b = 60 h = 100 t = 4 b1 = b-2*t h1 = h-2*t A = b*h - b1*h1 print b*h**3 I1 = (b*h**3 - b1*h1**3) / 12 I2 = (h*b**3 - h1*b1**3) / 12 I12 = 0 J = 4 * A**2 / (2*(b+h)/t) tube = { 'name':'tube', 'cross_section': A, 'moment_inertia_11': I1, 'moment_inertia_22': I2, 'moment_inertia_12': I12, 'torsional_constant': J } steel = { 'name':'steel', 'young_modulus' : 206000, 'shear_modulus' : 81500, 'density' : 7.85e-9, } print tube print steel tubesection = ElemSection(section=tube,material=steel) # Calculate the nodal loads # Area of triangles area,normals = S.areaNormals() print "Area:\n%s" % area # compute bar lengths bars = nodes[tubes] barV = bars[:,1,:] - bars[:,0,:] barL = sqrt((barV*barV).sum(axis=-1)) print "Member length:\n%s" % barL ### DEFINE LOAD CASE (ask user) ### res = askItems([('Steel',True), ('Glass',True), ('Snow',False), ('Solver',None,'select',['Calpy','Abaqus']), ]) if not res: return nlc = 0 for lc in [ 'Steel','Glass','Snow' ]: if res[lc]: nlc += 1 NODLoad = zeros((nlc,S.ncoords(),3)) nlc = 0 if res['Steel']: # the STEEL weight lwgt = steel['density'] * tube['cross_section'] * 9810 # mm/s**2 print "Weight per length %s" % lwgt # assemble steel weight load for e,L in zip(tubes,barL): NODLoad[nlc,e] += [ 0., 0., - L * lwgt / 2 ] nlc += 1 if res['Glass']: # the GLASS weight wgt = 450e-6 # N/mm**2 # assemble uniform glass load for e,a in zip(S.elems,area): NODLoad[nlc,e] += [ 0., 0., - a * wgt / 3 ] nlc += 1 if res['Snow']: # NON UNIFORM SNOW fn = 'hesperia-nieve.prop' snowp = fromfile(fn,sep=',') snow_uniform = 320e-6 # N/mm**2 snow_non_uniform = { 1:333e-6, 2:133e-6, 3:133e-6, 4:266e-6, 5:266e-6, 6:667e-6 } # assemble non-uniform snow load for e,a,p in zip(S.elems,area,snowp): NODLoad[nlc,e] += [ 0., 0., - a * snow_non_uniform[p] / 3] nlc += 1 # For Abaqus: put the nodal loads in the properties database print NODLoad PDB = PropertyDB() for lc in range(nlc): for i,P in enumerate(NODLoad[lc]): PDB.nodeProp(tag=lc,set=i,cload=[P[0],P[1],P[2],0.,0.,0.]) # Get support nodes botnodes = where(isClose(nodes[:,2], 0.0))[0] bot = nodes[botnodes] pf.message("There are %s support nodes." % bot.shape[0]) # Upper structure nnodes = nodes.shape[0] # node number offset ntubes = tubes.shape[0] # element number offset PDB.elemProp(set=arange(ntubes),section=tubesection,eltype='FRAME3D') # Create support systems (vertical beams) bot2 = bot + [ 0.,0.,-200.] # new nodes 200mm below bot botnodes2 = arange(botnodes.shape[0]) + nnodes # node numbers nodes = concatenate([nodes,bot2]) supports = column_stack([botnodes,botnodes2]) elems = concatenate([tubes,supports]) ## !!! ## THIS SHOULD BE FIXED !!! supportsection = ElemSection(material=steel,section={ 'name':'support', 'cross_section': A, 'moment_inertia_11': I1, 'moment_inertia_22': I2, 'moment_inertia_12': I12, 'torsional_constant': J }) PDB.elemProp(set=arange(ntubes,elems.shape[0]),section=supportsection,eltype='FRAME3D') # Finally, the botnodes2 get the support conditions botnodes = botnodes2 ## # Radial movement only ## np_fixed = NodeProperty(1,bound=[0,1,1,0,0,0],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## # No movement, since we left out the ring beam ## for i in botnodes: ## NodeProperty(i,bound=[1,1,1,0,0,0],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## np_central_loaded = NodeProperty(3, displacement=[[1,radial_displacement]],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## #np_transf = NodeProperty(0,coords='cylindrical',coordset=[0,0,0,0,0,1]) # Draw the supports S = connect([Formex(bot),Formex(bot2)]) draw(S,color='black') if res['Solver'] == 'Calpy': fe_model = Dict(dict(solver='Calpy',nodes=nodes,elems=elems,prop=PDB,loads=NODLoad,botnodes=botnodes,nsteps=nlc)) else: fe_model = Dict(dict(solver='Abaqus',nodes=nodes,elems=elems,prop=PDB,botnodes=botnodes,nsteps=nlc)) export({'fe_model':fe_model}) print "FE model created and exported as 'fe_model'"
# Add the missing end triangles L3 = [ connect([i,i,j],nodid=[0,1,1],bias=[i.nelems()-1,i.nelems()-1,j.nelems()-1]) for i,j in zip(K1[:-1],K1[1:]) ] draw(L3,color=magenta) # Collect all triangles in a single Formex L = (Formex.concatenate(L1)+Formex.concatenate(L3)).setProp(1) + Formex.concatenate(L2).setProp(2) clear() draw(L) # Convert to a Mesh print "nelems = %s, nplex = %s, coords = %s" % (L.nelems(),L.nplex(),L.coords.shape) M = L.toMesh() print "nelems = %s, nplex = %s, coords = %s" % (M.nelems(),M.nplex(),M.coords.shape) clear() draw(M,color=yellow,mode=flatwire) drawNumbers(M) draw(M.getBorderMesh(),color=black,linewidth=6) # Convert to a surface from plugins.trisurface import TriSurface S = TriSurface(M) print "nelems = %s, nplex = %s, coords = %s" % (S.nelems(),S.nplex(),S.coords.shape) clear() draw(S) print "Total surface area: %s" % S.area() export({'surface-1':S}) setDrawOptions({'bbox':'auto'}) # End
def createFrameModel(): """Create the Finite Element Model. It is supposed here that the Geometry has been created and is available as a global variable F. """ wireframe() lights(False) # Turn the Formex structure into a TriSurface # This guarantees that element i of the Formex is element i of the TriSurface S = TriSurface(F) nodes = S.coords elems = S.elems # the triangles # Create edges and faces from edges print("The structure has %s nodes, %s edges and %s faces" % (S.ncoords(), S.nedges(), S.nfaces())) # Remove the edges between to quad triangles drawNumbers(S.coords) quadtri = where(S.prop == 6)[0] nquadtri = quadtri.shape[0] print("%s triangles are part of quadrilateral faces" % nquadtri) faces = S.getElemEdges()[quadtri] cnt, ind, xbin = histogram2(faces.reshape(-1), arange(faces.max() + 1)) rem = where(cnt == 2)[0] print("Total edges %s" % len(S.edges)) print("Removing %s edges" % len(rem)) edges = S.edges[complement(rem, n=len(S.edges))] print("Remaining edges %s" % len(edges)) # Create the steel structure E = Formex(nodes[edges]) clear() draw(E) warning("Beware! This script is currently under revision.") conn = connections(quadtri) print(conn) # Filter out the single connection edges internal = [c[0] for c in conn if len(c[1]) > 1] print("Internal edges in quadrilaterals: %s" % internal) E = Formex(nodes[edges], 1) E.prop[internal] = 6 wireframe() clear() draw(E) # Remove internal edges tubes = edges[E.prop != 6] print("Number of tube elements after removing %s internals: %s" % (len(internal), tubes.shape[0])) D = Formex(nodes[tubes], 1) clear() draw(D) # Beam section and material properties b = 60 h = 100 t = 4 b1 = b - 2 * t h1 = h - 2 * t A = b * h - b1 * h1 print(b * h**3) I1 = (b * h**3 - b1 * h1**3) / 12 I2 = (h * b**3 - h1 * b1**3) / 12 I12 = 0 J = 4 * A**2 / (2 * (b + h) / t) tube = { 'name': 'tube', 'cross_section': A, 'moment_inertia_11': I1, 'moment_inertia_22': I2, 'moment_inertia_12': I12, 'torsional_constant': J } steel = { 'name': 'steel', 'young_modulus': 206000, 'shear_modulus': 81500, 'density': 7.85e-9, } print(tube) print(steel) tubesection = ElemSection(section=tube, material=steel) # Calculate the nodal loads # Area of triangles area, normals = S.areaNormals() print("Area:\n%s" % area) # compute bar lengths bars = nodes[tubes] barV = bars[:, 1, :] - bars[:, 0, :] barL = sqrt((barV * barV).sum(axis=-1)) print("Member length:\n%s" % barL) ### DEFINE LOAD CASE (ask user) ### res = askItems([ _I('Steel', True), _I('Glass', True), _I('Snow', False), _I('Solver', choices=['Calpy', 'Abaqus']), ]) if not res: return nlc = 0 for lc in ['Steel', 'Glass', 'Snow']: if res[lc]: nlc += 1 NODLoad = zeros((nlc, S.ncoords(), 3)) nlc = 0 if res['Steel']: # the STEEL weight lwgt = steel['density'] * tube['cross_section'] * 9810 # mm/s**2 print("Weight per length %s" % lwgt) # assemble steel weight load for e, L in zip(tubes, barL): NODLoad[nlc, e] += [0., 0., -L * lwgt / 2] nlc += 1 if res['Glass']: # the GLASS weight wgt = 450e-6 # N/mm**2 # assemble uniform glass load for e, a in zip(S.elems, area): NODLoad[nlc, e] += [0., 0., -a * wgt / 3] nlc += 1 if res['Snow']: # NON UNIFORM SNOW fn = '../data/hesperia-nieve.prop' snowp = fromfile(fn, sep=',') snow_uniform = 320e-6 # N/mm**2 snow_non_uniform = { 1: 333e-6, 2: 133e-6, 3: 133e-6, 4: 266e-6, 5: 266e-6, 6: 667e-6 } # assemble non-uniform snow load for e, a, p in zip(S.elems, area, snowp): NODLoad[nlc, e] += [0., 0., -a * snow_non_uniform[p] / 3] nlc += 1 # For Abaqus: put the nodal loads in the properties database print(NODLoad) PDB = PropertyDB() for lc in range(nlc): for i, P in enumerate(NODLoad[lc]): PDB.nodeProp(tag=lc, set=i, cload=[P[0], P[1], P[2], 0., 0., 0.]) # Get support nodes botnodes = where(isClose(nodes[:, 2], 0.0))[0] bot = nodes[botnodes] pf.message("There are %s support nodes." % bot.shape[0]) # Upper structure nnodes = nodes.shape[0] # node number offset ntubes = tubes.shape[0] # element number offset PDB.elemProp(set=arange(ntubes), section=tubesection, eltype='FRAME3D') # Create support systems (vertical beams) bot2 = bot + [0., 0., -200.] # new nodes 200mm below bot botnodes2 = arange(botnodes.shape[0]) + nnodes # node numbers nodes = concatenate([nodes, bot2]) supports = column_stack([botnodes, botnodes2]) elems = concatenate([tubes, supports]) ## !!! ## THIS SHOULD BE FIXED !!! supportsection = ElemSection(material=steel, section={ 'name': 'support', 'cross_section': A, 'moment_inertia_11': I1, 'moment_inertia_22': I2, 'moment_inertia_12': I12, 'torsional_constant': J }) PDB.elemProp(set=arange(ntubes, elems.shape[0]), section=supportsection, eltype='FRAME3D') # Finally, the botnodes2 get the support conditions botnodes = botnodes2 ## # Radial movement only ## np_fixed = NodeProperty(1,bound=[0,1,1,0,0,0],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## # No movement, since we left out the ring beam ## for i in botnodes: ## NodeProperty(i,bound=[1,1,1,0,0,0],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## np_central_loaded = NodeProperty(3, displacement=[[1,radial_displacement]],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## #np_transf = NodeProperty(0,coords='cylindrical',coordset=[0,0,0,0,0,1]) # Draw the supports S = connect([Formex(bot), Formex(bot2)]) draw(S, color='black') if res['Solver'] == 'Calpy': fe_model = Dict( dict(solver='Calpy', nodes=nodes, elems=elems, prop=PDB, loads=NODLoad, botnodes=botnodes, nsteps=nlc)) else: fe_model = Dict( dict(solver='Abaqus', nodes=nodes, elems=elems, prop=PDB, botnodes=botnodes, nsteps=nlc)) export({'fe_model': fe_model}) print("FE model created and exported as 'fe_model'")
def createShellModel(): """Create the Finite Element Model. It is supposed here that the Geometry has been created and is available as a global variable F. """ # Turn the Formex structure into a TriSurface # This guarantees that element i of the Formex is element i of the TriSurface S = TriSurface(F) print("The structure has %s nodes, %s edges and %s faces" % (S.ncoords(), S.nedges(), S.nfaces())) nodes = S.coords elems = S.elems # the triangles clear() draw(F) # Shell section and material properties # VALUES SHOULD BE SET CORRECTLY glass_plate = { 'name': 'glass_plate', 'sectiontype': 'shell', 'thickness': 18, 'material': 'glass', } glass = { 'name': 'glass', 'young_modulus': 72000, 'shear_modulus': 26200, 'density': 2.5e-9, # T/mm**3 } print(glass_plate) print(glass) glasssection = ElemSection(section=glass_plate, material=glass) PDB = PropertyDB() # All elements have same property: PDB.elemProp(set=arange(len(elems)), section=glasssection, eltype='STRI3') # Calculate the nodal loads # Area of triangles area, normals = S.areaNormals() print("Area:\n%s" % area) ### DEFINE LOAD CASE (ask user) ### res = askItems([('Glass', True), ('Snow', False)]) if not res: return step = 0 if res['Glass']: step += 1 NODLoad = zeros((S.ncoords(), 3)) # add the GLASS weight wgt = 450e-6 # N/mm**2 # Or, calculate weight from density: # wgt = glass_plate['thickness'] * glass['density'] * 9810 # assemble uniform glass load for e, a in zip(S.elems, area): NODLoad[e] += [0., 0., -a * wgt / 3] # Put the nodal loads in the properties database for i, P in enumerate(NODLoad): PDB.nodeProp(tag=step, set=i, cload=[P[0], P[1], P[2], 0., 0., 0.]) if res['Snow']: step += 1 NODLoad = zeros((S.ncoords(), 3)) # add NON UNIFORM SNOW fn = '../data/hesperia-nieve.prop' snowp = fromfile(fn, sep=',') snow_uniform = 320e-6 # N/mm**2 snow_non_uniform = { 1: 333e-6, 2: 133e-6, 3: 133e-6, 4: 266e-6, 5: 266e-6, 6: 667e-6 } # assemble non-uniform snow load for e, a, p in zip(S.elems, area, snowp): NODLoad[e] += [0., 0., -a * snow_non_uniform[p] / 3] # Put the nodal loads in the properties database for i, P in enumerate(NODLoad): PDB.nodeProp(tag=step, set=[i], cload=[P[0], P[1], P[2], 0., 0., 0.]) # Get support nodes botnodes = where(isClose(nodes[:, 2], 0.0))[0] bot = nodes[botnodes].reshape((-1, 1, 3)) pf.message("There are %s support nodes." % bot.shape[0]) botofs = bot + [0., 0., -0.2] bbot2 = concatenate([bot, botofs], axis=1) print(bbot2.shape) S = Formex(bbot2) draw(S) ## np_central_loaded = NodeProperty(3, displacement=[[1,radial_displacement]],coords='cylindrical',coordset=[0,0,0,0,0,1]) ## #np_transf = NodeProperty(0,coords='cylindrical',coordset=[0,0,0,0,0,1]) ## # Radial movement only ## np_fixed = NodeProperty(1,bound=[0,1,1,0,0,0],coords='cylindrical',coordset=[0,0,0,0,0,1]) # Since we left out the ring beam, we enforce no movement at the botnodes bc = PDB.nodeProp(set=botnodes, bound=[1, 1, 1, 0, 0, 0], csys=CoordSystem('C', [0, 0, 0, 0, 0, 1])) # And we record the name of the bottom nodes set botnodeset = Nset(bc.nr) fe_model = Dict( dict(nodes=nodes, elems=elems, prop=PDB, botnodeset=botnodeset, nsteps=step)) export({'fe_model': fe_model}) smooth() lights(False)
def run(): global wviewer,pcolor,px,py clear() smooth() lights(True) transparent(False) view('iso') image = None scaled_image = None # read the teapot surface T = TriSurface.read(getcfg('datadir')+'/teapot.off') xmin,xmax = T.bbox() T= T.trl(-T.center()).scale(4./(xmax[0]-xmin[0])).setProp(2) draw(T) # default image file dfilename = getcfg('datadir')+'/benedict_6.jpg' wviewer = ImageView(dfilename,maxheight=200) res = askItems([ _I('filename',dfilename,text='Image file',itemtype='button',func=selectImage), wviewer, _I('px',4,text='Number of patches in x-direction'), _I('py',6,text='Number of patches in y-direction'), _I('kx',30,text='Width of a patch in pixels'), _I('ky',30,text='Height of a patch in pixels'), _I('scale',1.0,text='Scale factor'), _I('trl',[-0.4,-0.1,2.],itemtype='point',text='Translation'), _I('method',choices=['projection','intersection']), ]) if not res: return globals().update(res) nx,ny = px*kx,py*ky # pixels print('The image is reconstructed with %d x %d pixels'%(nx, ny)) F = Formex('4:0123').replic2(nx,ny).centered() if image is None: print("Loading image") image = loadImage(filename) wpic,hpic = image.width(),image.height() print("Image size is %sx%s" % (wpic,hpic)) if image is None: return # Create the colors color,colortable = image2glcolor(image.scaled(nx,ny)) # Reorder by patch pcolor = color.reshape((py,ky,px,kx,3)).swapaxes(1,2).reshape(-1,kx*ky,3) print("Shape of the colors array: %s" % str(pcolor.shape)) mH = makeGrid(px,py,'Quad8') try: ratioYX = float(hpic)/wpic mH = mH.scale(ratioYX,1) # Keep original aspect ratio except: pass mH0 = mH.scale(scale).translate(trl) dg0 = draw(mH0,mode='wireframe') zoomAll() zoom(0.5) print("Create %s x %s patches" % (px,py)) # Create the transforms base = makeGrid(1,1,'Quad8').coords[0] patch = makeGrid(kx,ky,'Quad4').toMesh() d0 = drawImage(mH0,base,patch) if method == 'projection': pts = mH0.coords.projectOnSurface(T,[0.,0.,1.],'-f') dg1 = d1 = [] # to allow dummy undraw else: mH1 = mH.rotate(-30.,0).scale(0.5).translate([0.,-.7,-2.]) dg1 = draw(mH1,mode='wireframe') d1 = drawImage(mH1,base,patch) x = connect([mH0.asPoints(),mH1.asPoints()]) dx = draw(x) print("Creating intersection with surface") pts, il, it, mil=intersectSurfaceWithSegments2(T, x, max1xperline=True) if len(x) != len(pts): print("Some of the lines do not intersect the surface:") print(" %d lines, %d intersections %d missing" % (len(x),len(pts),len(mil))) return dp = draw(pts, marksize=6, color='white') #print pts.shape mH2 = Formex(pts.reshape(-1,8,3)) if method == 'projection': x = connect([mH0.points(),mH2.points()]) dx = draw(x) print("Create projection mapping using the grid points") d2 = drawImage(mH2.trl([0.,0.,0.01]),base,patch) # small translation to make sure the image is above the surface, not cutting it print("Finally show the finished image") undraw(dp); undraw(dx); undraw(d0); undraw(d1); undraw(dg0); undraw(dg1); view('front') zoomAll() sleep(1) transparent()
## """HorseTorse level = 'advanced' topics = ['geometry','surface','mesh'] techniques = ['intersection'] """ from plugins.trisurface import TriSurface from mesh import Mesh reset() smooth() lights(True) S = TriSurface.read(getcfg("datadir") + "/horse.off") SA = draw(S) res = askItems([("direction", [1.0, 0.0, 0.0]), ("number of sections", 20), ("color", "red"), ("ontop", False)]) if not res: exit() d = res["direction"] n = res["number of sections"] c = res["color"] slices = S.slice(dir=d, nplanes=n) linewidth(2) draw(slices, color=c, view=None, bbox="last", nolight=True, ontop=res["ontop"]) draw(slices[0])