├── Axisimetrico ├── deformada.png ├── ejemplo.png ├── ejemplo_Q8_axisimetrico_modificado.py ├── ejemplo_Q8_axisimetrico_original.py ├── funciones.py ├── leer_GMSH.py ├── malla.png ├── malla_boussinesq.msh ├── malla_boussinesq.py ├── malla_grupos_fisicos.png └── readme.md ├── Mallas_GMSH ├── 1_Malla-simple_2d.geo ├── 2_Malla-estructurada_2d.geo ├── 3_Malla-estructurada-extrude_2d.geo ├── 4_Scordelis-lo-roof_shell.geo ├── 6_Paraboloide_hiperbolico.geo ├── 7_Ejemplos_curvas_spline.geo ├── 7_Malla_con_splines.geo ├── malla_1.png ├── malla_2.png ├── malla_3.png ├── malla_4.png ├── malla_5.png ├── malla_6.jpg ├── malla_7.png ├── malla_7_2.png └── readme.md ├── Mallas_python ├── .gitignore ├── 1_Malla-simple_2d.py ├── 2_Malla-estructurada_2d.py ├── 3_Malla-estructurada-extrude_2d.py ├── 4_Scordelis-lo-roof_shell.py ├── 5_Twisted-beam_shell.py ├── 6_Paraboloide-hiperbolico_shell.py ├── 7_Malla_con_splines.py ├── leer_GMSH.py ├── malla_1.png ├── malla_2.png ├── malla_3.png ├── malla_4.png ├── malla_5.png ├── malla_6.jpg ├── malla_7.png ├── malla_7_2.png └── readme.md ├── README.md ├── area_malla.py ├── grafico_malla2.png ├── leer_GMSH.py ├── obtener_grupos_fisicos.py └── scordelis.msh /Axisimetrico/deformada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Axisimetrico/deformada.png -------------------------------------------------------------------------------- /Axisimetrico/ejemplo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Axisimetrico/ejemplo.png -------------------------------------------------------------------------------- /Axisimetrico/ejemplo_Q8_axisimetrico_modificado.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | #%% 4 | ''' 5 | ------------------------------------------------------------------------------- 6 | NOTA: este código SOLO es apropiado para el caso AXISIMÉTRICO usando elementos 7 | rectangulares serendípitos de 8 nodos 8 | ------------------------------------------------------------------------------- 9 | 10 | DEFINICIÓN DEL PROBLEMA: 11 | Calcule los desplazamientos y las reacciones en los empotramientos, las 12 | deformaciones y los esfuerzos de la estructura mostrada en la figura adjunta 13 | ''' 14 | 15 | import numpy as np 16 | import pandas as pd 17 | import matplotlib.pyplot as plt 18 | from funciones import t2ft_R89_axisimetrico, compartir_variables, plot_esf_def 19 | from leer_GMSH import * 20 | 21 | 22 | # %% constantes que ayudarán en la lectura del código 23 | X, Y = 0, 1 24 | NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8 = range(8) 25 | g = 9.81 # [m/s²] aceleración de la gravedad 26 | 27 | # %% seleccione la malla a emplear: 28 | 29 | malla = 'malla_boussinesq.msh' 30 | 31 | # %% Se obtienen los grupos físicos de la malla: 32 | # dict_nombres: Diccionario de la forma {tag : nombre_fisico} 33 | # dict_nodos: Diccionario de la forma {tag : (dim, nodos_grupo_fisico)} 34 | 35 | dict_nombres, dict_nodos = grupos_fisicos(malla) 36 | 37 | # %% Opción para leer las propiedades del material: 38 | 39 | # Se leen directamente desde la malla: 40 | Ee = np.array([]) # Módulo de elasticidad [Pa] 41 | nue = np.array([]) # Relación de Poisson 42 | rhoe = np.array([]) # Densidad [kg/m³] 43 | 44 | for tag in dict_nombres.keys(): 45 | nombre_grupo = dict_nombres[tag] # Nombre del grupo físico 46 | if nombre_grupo[:3] == 'mat': 47 | # Se identifican posiciones de los separadores 48 | g1 = nombre_grupo.index('_') 49 | g2 = nombre_grupo.index('_', g1+1) 50 | g3 = nombre_grupo.index('_', g2+1) 51 | 52 | # Se leen las propiedades entre separadores 53 | E = float(nombre_grupo[g1+1:g2]) 54 | nu = float(nombre_grupo[g2+1:g3]) 55 | rho = float(nombre_grupo[g3+1:]) 56 | 57 | # Y se agregan a los vectores respectivos 58 | Ee = np.append(Ee, E) 59 | nue = np.append(nue, nu) 60 | rhoe = np.append(rhoe, rho) 61 | 62 | nmat = Ee.size # Número de materiales distintos en la malla 63 | 64 | 65 | # %% posición de los nodos: 66 | 67 | # xnod: fila=número del nodo, columna=coordenada X=0 o Y=1 68 | xnod = xnod_from_msh(malla, 2) 69 | nno = xnod.shape[0] # número de nodos (número de filas de la matriz xnod) 70 | 71 | # %% definición de los grados de libertad 72 | 73 | ngdl = 2*nno # número de grados de libertad por nodo = [X, Y] 74 | gdl = np.reshape(np.arange(ngdl), (nno, 2)) # nodos vs grados de libertad 75 | 76 | # %% definición de elementos finitos con respecto a nodos 77 | 78 | # LaG: fila=número del elemento, columna=número del nodo local 79 | LaG_mat = LaG_from_msh(malla) 80 | 81 | mat = LaG_mat[:, 0] # Material asociado a cada EF 82 | LaG = LaG_mat[:, 1:] # Nodos asociados a cada EF 83 | 84 | # Se reorganizan los nodos en los EF (ya que GMSH los reporta en un orden 85 | # distinto) 86 | LaG = LaG[:, [NL1, NL5, NL2, NL6, NL3, NL7, NL4, NL8]] 87 | 88 | nef = LaG.shape[0] # número de EFs (número de filas de la matriz LaG) 89 | 90 | # %% Se leen condiciones de frontera de la malla y se aplican restricciones: 91 | 92 | # Se inicializa vector de GDL restringidos 93 | c = np.array([], dtype=int) # gdl del desplazamiento conocidos (c) 94 | 95 | for tag in dict_nombres.keys(): 96 | nombre_fis = dict_nombres[tag] # Se lee el nombre del grupo físico 97 | 98 | # Si se trata de puntos individuales con restricciones: 99 | if nombre_fis[:5] == 'punto': 100 | if nombre_fis[5:] =='_res_xy': 101 | nodos_pxy = dict_nodos[tag][1] - 1 102 | c = np.append(c, gdl[nodos_pxy, :].flatten()) 103 | elif nombre_fis[5:] =='_res_x': 104 | nodos_px = dict_nodos[tag][1] - 1 105 | c = np.append(c, gdl[nodos_px, X]) 106 | elif nombre_fis[5:] =='_res_y': 107 | nodos_py = dict_nodos[tag][1] - 1 108 | c = np.append(c, gdl[nodos_py, Y]) 109 | 110 | # Si se trata de bordes con restricciones: 111 | if nombre_fis[:5] == 'borde': 112 | if nombre_fis[5:] =='_res_xy': 113 | nodos_bxy = dict_nodos[tag][1] - 1 114 | c = np.append(c, gdl[nodos_bxy, :].flatten()) 115 | elif nombre_fis[5:] =='_res_x': 116 | nodos_bx = dict_nodos[tag][1] - 1 117 | c = np.append(c, gdl[nodos_bx, X]) 118 | elif nombre_fis[5:] =='_res_y': 119 | nodos_by = dict_nodos[tag][1] - 1 120 | c = np.append(c, gdl[nodos_by, Y]) 121 | 122 | c = np.unique(c) # Se descartan valores repetidos en c 123 | ac = np.zeros_like(c) # Desplazamientos conocidos (nulos) 124 | 125 | # grados de libertad del desplazamiento desconocidos 126 | d = np.setdiff1d(range(ngdl), c) 127 | 128 | # %% Relación de cargas puntuales: 129 | f = np.zeros(ngdl) 130 | 131 | for tag in dict_nombres.keys(): 132 | nombre_grupo = dict_nombres[tag] 133 | if nombre_grupo[:7] == 'puntual': 134 | nodos_puntual = dict_nodos[tag][1] - 1 135 | 136 | # Se identifican posiciones de los separadores 137 | g1 = nombre_grupo.index('_') 138 | g2 = nombre_grupo.index('_', g1+1) 139 | 140 | # Se leen las cargas y se agregan al vector f 141 | Px = float(nombre_grupo[g1+1:g2]) 142 | Py = float(nombre_grupo[g2+1:]) 143 | f[gdl[nodos_puntual, X]] += Px 144 | f[gdl[nodos_puntual, Y]] += Py 145 | 146 | 147 | # %% Se dibuja la malla de elementos finitos 148 | 149 | mostrar_nodos = False 150 | mostrar_num_nodo = False 151 | mostrar_num_elem = False 152 | 153 | plot_msh(malla, '2D', mostrar_nodos, mostrar_num_nodo, mostrar_num_elem) 154 | 155 | #%% Funciones de forma (serendípitas) y sus derivadas del elemento rectangular 156 | # de 8 nodos: 157 | Nforma = lambda xi,eta: np.array( 158 | [-((eta - 1)*(xi - 1)*(eta + xi + 1))/4, # N1 159 | ((xi**2 - 1)*(eta - 1))/2, # N2 160 | ((eta - 1)*(xi + 1)*(eta - xi + 1))/4, # N3 161 | -((eta**2 - 1)*(xi + 1))/2, # N4 162 | ((eta + 1)*(xi + 1)*(eta + xi - 1))/4, # N5 163 | -((xi**2 - 1)*(eta + 1))/2, # N6 164 | ((eta + 1)*(xi - 1)*(xi - eta + 1))/4, # N7 165 | ((eta**2 - 1)*(xi - 1))/2 ]) # N8 166 | 167 | # derivadas de las funciones de forma con respecto a xi 168 | dN_dxi = lambda xi,eta: np.array( 169 | [-((eta + 2*xi)*(eta - 1))/4, # dN1_dxi 170 | eta*xi - xi, # dN2_dxi 171 | ((eta - 2*xi)*(eta - 1))/4, # dN3_dxi 172 | 1/2 - eta**2/2, # dN4_dxi 173 | ((eta + 2*xi)*(eta + 1))/4, # dN5_dxi 174 | -xi*(eta + 1), # dN6_dxi 175 | -((eta - 2*xi)*(eta + 1))/4, # dN7_dxi 176 | eta**2/2 - 1/2 ]) # dN8_dxi 177 | 178 | # derivadas de N con respecto a eta 179 | dN_deta = lambda xi,eta: np.array( 180 | [-((2*eta + xi)*(xi - 1))/4, # dN1_deta 181 | xi**2/2 - 1/2, # dN2_deta 182 | ((xi + 1)*(2*eta - xi))/4, # dN3_deta 183 | -eta*(xi + 1), # dN4_deta 184 | ((2*eta + xi)*(xi + 1))/4, # dN5_deta 185 | 1/2 - xi**2/2, # dN6_deta 186 | -((xi - 1)*(2*eta - xi))/4, # dN7_deta 187 | eta*(xi - 1) ]) # dN8_deta 188 | 189 | #%% Cuadratura de Gauss-Legendre 190 | # NOTA: se asumirá aquí el mismo orden de la cuadratura tanto en la dirección 191 | # de xi como en la dirección de eta 192 | n_gl = 2 # orden de la cuadratura de Gauss-Legendre 193 | x_gl, w_gl = np.polynomial.legendre.leggauss(n_gl) 194 | 195 | # %% Ensamblaje la matriz de rigidez global y el vector de fuerzas másicas 196 | # nodales equivalentes global 197 | 198 | # se inicializan la matriz de rigidez global y los espacios en memoria que 199 | # almacenarán las matrices de forma y de deformación 200 | K = np.zeros((ngdl, ngdl)) # matriz de rigidez global 201 | N = np.empty((nef,n_gl,n_gl,2,2*8)) # matriz de forma en cada punto de GL 202 | B = np.empty((nef,n_gl,n_gl,4,2*8)) # matriz de deformaciones en cada punto de GL 203 | idx = nef * [None] # indices asociados a los gdl del EF e 204 | 205 | # matriz constitutiva del elemento para el caso AXISIMETRICO 206 | De = nmat * [ None ] 207 | be = nmat * [ None ] 208 | for i in range(nmat): 209 | De[i] = (Ee[i]/((1+nue[i])*(1-2*nue[i])))*\ 210 | np.array([[1 - nue[i], nue[i], nue[i], 0 ], 211 | [nue[i], 1-nue[i], nue[i], 0 ], 212 | [nue[i], nue[i], 1-nue[i], 0 ], 213 | [0, 0, 0, (1-2*nue[i])/2]]) 214 | be[i] = np.array([0, -rhoe[i]*g]) # [kgf/m³] vector de fuerzas másicas 215 | 216 | # para cada elemento finito en la malla: 217 | for e in range(nef): 218 | # se calculan con el siguiente ciclo las matrices de rigidez y el vector de 219 | # fuerzas nodales equivalentes del elemento usando las cuadraturas de GL 220 | Ke = np.zeros((16, 16)) 221 | fe = np.zeros(16) 222 | det_Je = np.empty((n_gl, n_gl)) # matriz para almacenar los jacobianos 223 | 224 | for p in range(n_gl): 225 | for q in range(n_gl): 226 | # en cada punto de la cuadratura de Gauss-Legendre se evalúan las 227 | # funciones de forma y sus derivadas 228 | xi_gl, eta_gl = x_gl[p], x_gl[q] 229 | 230 | NNforma = Nforma (xi_gl, eta_gl) 231 | ddN_dxi = dN_dxi (xi_gl, eta_gl) 232 | ddN_deta = dN_deta(xi_gl, eta_gl) 233 | 234 | # se llaman las coordenadas nodales del elemento para calcular las 235 | # derivadas de la función de transformación 236 | xe, ye = xnod[LaG[e], X], xnod[LaG[e], Y] 237 | 238 | dx_dxi = np.sum(ddN_dxi * xe); dy_dxi = np.sum(ddN_dxi * ye) 239 | dx_deta = np.sum(ddN_deta * xe); dy_deta = np.sum(ddN_deta * ye) 240 | 241 | # se calcula el radio del punto de Gauss 242 | r = np.sum(NNforma * xe) 243 | 244 | # con ellas se ensambla la matriz Jacobiana del elemento y se 245 | # calcula su determinante 246 | Je = np.array([[dx_dxi, dy_dxi ], 247 | [dx_deta, dy_deta]]) 248 | det_Je[p, q] = np.linalg.det(Je) 249 | 250 | # las matrices de forma y de deformación se evalúan y se ensamblan 251 | # en el punto de Gauss 252 | Npq = np.empty((2, 2*8)) 253 | Bpq = np.empty((4, 2*8)) 254 | for i in range(8): 255 | Npq[:,[2*i, 2*i+1]] = np.array([[NNforma[i], 0 ], 256 | [0, NNforma[i]]]) 257 | 258 | dNi_dx = (+dy_deta*ddN_dxi[i] - dy_dxi*ddN_deta[i])/det_Je[p,q] 259 | dNi_dy = (-dx_deta*ddN_dxi[i] + dx_dxi*ddN_deta[i])/det_Je[p,q] 260 | Bpq[:,[2*i, 2*i+1]] = np.array([[dNi_dx, 0 ], 261 | [0, dNi_dy], 262 | [NNforma[i]/r, 0 ], 263 | [dNi_dy, dNi_dx]]) 264 | N[e,p,q] = Npq 265 | B[e,p,q] = Bpq 266 | 267 | # se ensamblan la matriz de rigidez del elemento y el vector de 268 | # fuerzas nodales equivalentes del elemento 269 | Ke += Bpq.T @ De[mat[e]] @ Bpq * det_Je[p,q]*r*w_gl[p]*w_gl[q] 270 | fe += Npq.T @ be[mat[e]] * det_Je[p,q]*r*w_gl[p]*w_gl[q] 271 | Ke *= 2*np.pi 272 | fe *= 2*np.pi 273 | 274 | # se determina si hay puntos con jacobiano negativo, en caso tal se termina 275 | # el programa y se reporta 276 | if np.any(det_Je <= 0): 277 | raise Exception(f'Hay puntos con det_Je negativo en el elemento {e+1}') 278 | 279 | # y se añaden la matriz de rigidez del elemento y el vector de fuerzas 280 | # nodales del elemento a sus respectivos arreglos de la estructura 281 | idx[e] = gdl[LaG[e]].flatten() # se obtienen los grados de libertad 282 | K[np.ix_(idx[e], idx[e])] += Ke 283 | f[np.ix_(idx[e])] += fe 284 | 285 | # %% Muestro la configuración de la matriz K (K es rala) 286 | plt.figure() 287 | plt.spy(K) 288 | plt.title('Los puntos representan los elementos diferentes de cero') 289 | plt.show() 290 | 291 | # %% Se obtienen las cargas distribuidas de la malla: 292 | 293 | cd = pd.DataFrame() # se inicializa el dataframe 294 | 295 | for tag in dict_nombres.keys(): 296 | nombre_grupo = dict_nombres[tag] 297 | if nombre_grupo[:5] == 'carga': 298 | nodos_lado = dict_nodos[tag][1] - 1 299 | 300 | # Se leen posiciones de los separadores en el nombre 301 | g1 = 5 302 | g2 = nombre_grupo.index('_', g1+1) 303 | 304 | # Se leen las cargas y se agregan a un DataFrame 305 | Fx = float(nombre_grupo[g1+1:g2]) 306 | Fy = float(nombre_grupo[g2+1:]) 307 | df = aplicar_fsuperf(LaG, nodos_lado, Fx, Fy) 308 | cd = cd.append(df, ignore_index=True) 309 | 310 | nlcd = cd.shape[0] # número de lados con carga distribuida 311 | 312 | #%% Cálculo de las cargas nodales equivalentes de las cargas distribuidas: 313 | 314 | ft = np.zeros(ngdl) # fuerzas nodales equivalentes de cargas superficiales 315 | 316 | # por cada lado cargado se obtienen las fuerzas nodales equivalentes en los 317 | # nodos y se añaden al vector de fuerzas superficiales 318 | for i in range(nlcd): 319 | e = cd['elemento'][i] - 1 320 | lado = cd['lado'][i] 321 | carga = cd[['tix', 'tiy', 'tjx', 'tjy', 'tkx', 'tky']].loc[i].to_numpy() 322 | fte = t2ft_R89_axisimetrico(xnod[LaG[e,:],:], lado, carga) 323 | 324 | ft[np.ix_(idx[e])] += fte 325 | 326 | 327 | # %% agrego al vector de fuerzas nodales equivalentes las fuerzas 328 | # superficiales calculadas 329 | f += ft 330 | 331 | 332 | # %% extraigo las submatrices y especifico las cantidades conocidas 333 | # f = vector de fuerzas nodales equivalentes 334 | # q = vector de fuerzas nodales de equilibrio del elemento 335 | # a = desplazamientos 336 | 337 | #| qd | | Kcc Kcd || ac | | fd | # recuerde que qc=0 (siempre) 338 | #| | = | || | - | | 339 | #| qc | | Kdc Kdd || ad | | fc | 340 | Kcc = K[np.ix_(c,c)]; Kcd = K[np.ix_(c,d)]; fd = f[c] 341 | Kdc = K[np.ix_(d,c)]; Kdd = K[np.ix_(d,d)]; fc = f[d] 342 | 343 | # %% resuelvo el sistema de ecuaciones 344 | ad = np.linalg.solve(Kdd, fc - Kdc@ac) # desplazamientos desconocidos 345 | qd = Kcc@ac + Kcd@ad - fd # fuerzas de equilibrio desconocidas 346 | 347 | # armo los vectores de desplazamientos (a) y fuerzas (q) 348 | a = np.zeros(ngdl); q = np.zeros(ngdl) # separo la memoria 349 | a[c] = ac; a[d] = ad # desplazamientos 350 | q[c] = qd # q[d] = qc = 0 # fuerzas nodales de equilibrio 351 | 352 | # %% Dibujo la malla de elementos finitos y las deformada de esta 353 | delta = np.reshape(a, (nno,2)) 354 | escala = 1000 # factor de escalamiento de la deformada 355 | xdef = xnod + escala*delta # posición de la deformada 356 | 357 | plt.figure() 358 | for e in range(nef): 359 | nod_ef = LaG[e, [NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8, NL1]] 360 | plt.plot(xnod[nod_ef, X], xnod[nod_ef, Y], 'r', 361 | label='Posición original' if e == 0 else "", lw=0.5) 362 | plt.plot(xdef[nod_ef, X], xdef[nod_ef, Y], 'b', 363 | label='Posición deformada' if e == 0 else "") 364 | plt.gca().set_aspect('equal', adjustable='box') 365 | plt.legend() 366 | plt.xlabel('$r$ [m]') 367 | plt.ylabel('$z$ [m]') 368 | plt.title(f'Deformada escalada {escala} veces') 369 | plt.tight_layout() 370 | plt.show() 371 | 372 | #%% Deformaciones y los esfuerzos en los puntos de Gauss 373 | deform = np.zeros((nef,n_gl,n_gl,4)) # deformaciones en cada punto de GL 374 | esfuer = np.zeros((nef,n_gl,n_gl,4)) # esfuerzos en cada punto de GL 375 | 376 | for e in range(nef): 377 | ae = a[idx[e]] # desplazamientos nodales del elemento e 378 | for pp in range(n_gl): 379 | for qq in range(n_gl): 380 | deform[e,pp,qq] = B[e,pp,qq] @ ae # calculo las deformaciones 381 | esfuer[e,pp,qq] = De[mat[e]] @ deform[e,pp,qq] # calculo los esfuerzos 382 | 383 | #%% Esfuerzos y deformaciones en los nodos: 384 | num_elem_ady = np.zeros(nno) 385 | sr = np.zeros(nno); er = np.zeros(nno) 386 | sz = np.zeros(nno); ez = np.zeros(nno) 387 | st = np.zeros(nno); et = np.zeros(nno) 388 | trz = np.zeros(nno); grz = np.zeros(nno) 389 | 390 | # matriz de extrapolación 391 | A = np.array([ 392 | [ 3**(1/2)/2 + 1, -1/2, -1/2, 1 - 3**(1/2)/2], 393 | [3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4], 394 | [ -1/2, 1 - 3**(1/2)/2, 3**(1/2)/2 + 1, -1/2], 395 | [1/4 - 3**(1/2)/4, 1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4, 3**(1/2)/4 + 1/4], 396 | [ 1 - 3**(1/2)/2, -1/2, -1/2, 3**(1/2)/2 + 1], 397 | [1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4], 398 | [ -1/2, 3**(1/2)/2 + 1, 1 - 3**(1/2)/2, -1/2], 399 | [3**(1/2)/4 + 1/4, 3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4, 1/4 - 3**(1/2)/4]]) 400 | 401 | # se hace la extrapolación de los esfuerzos y las deformaciones en cada elemento 402 | # a partir de las lecturas en los puntos de Gauss 403 | for e in range(nef): 404 | #sr[LaG[e]] += A @ np.array([esfuer[e,0,0,0], # I = (p=0, q=0) 405 | # esfuer[e,0,1,0], # II = (p=0, q=1) 406 | # esfuer[e,1,0,0], # III = (p=1, q=0) 407 | # esfuer[e,1,1,0]]) # IV = (p=1, q=1) 408 | sr [LaG[e]] += A @ esfuer[e,:,:,0].ravel() 409 | sz [LaG[e]] += A @ esfuer[e,:,:,1].ravel() 410 | st [LaG[e]] += A @ esfuer[e,:,:,2].ravel() 411 | trz[LaG[e]] += A @ esfuer[e,:,:,3].ravel() 412 | er [LaG[e]] += A @ deform[e,:,:,0].ravel() 413 | ez [LaG[e]] += A @ deform[e,:,:,1].ravel() 414 | et [LaG[e]] += A @ deform[e,:,:,2].ravel() 415 | grz[LaG[e]] += A @ deform[e,:,:,3].ravel() 416 | 417 | # se lleva un conteo de los elementos adyacentes a un nodo 418 | num_elem_ady[LaG[e]] += 1 419 | 420 | # en todos los nodos se promedia los esfuerzos y las deformaciones de los 421 | # elementos, se alisa la malla de resultados 422 | sr /= num_elem_ady; er /= num_elem_ady 423 | sz /= num_elem_ady; ez /= num_elem_ady 424 | st /= num_elem_ady; et /= num_elem_ady 425 | trz /= num_elem_ady; grz /= num_elem_ady 426 | trt = 0 427 | ttz = 0 428 | 429 | 430 | # %% Se calculan para cada nodo los esfuerzos principales y sus direcciones 431 | s1 = np.zeros(nno); n1 = np.zeros((nno, 3)) 432 | s2 = np.zeros(nno); n2 = np.zeros((nno, 3)) 433 | s3 = np.zeros(nno); n3 = np.zeros((nno, 3)) 434 | for i in range(nno): 435 | esfppales, dirppales = np.linalg.eigh( 436 | [[sr[i], trt, trz[i]], # matriz de esfuerzos 437 | [trt, st[i], ttz ], # de Cauchy para 438 | [trz[i], ttz, sz[i] ]]) # theta = grados 439 | 440 | idx_esf = esfppales.argsort()[::-1] # ordene de mayor a menor 441 | s1[i], s2[i], s3[i] = esfppales[idx_esf] 442 | n1[i] = dirppales[:,idx_esf[0]] 443 | n2[i] = dirppales[:,idx_esf[1]] 444 | n3[i] = dirppales[:,idx_esf[2]] 445 | 446 | # Esfuerzo cortante máximo 447 | tmax = (s1-s3)/2 # esfuerzo cortante máximo 448 | 449 | # %% Calculo de los esfuerzos de von Mises 450 | sv = np.sqrt(((s1-s2)**2 + (s2-s3)**2 + (s1-s3)**2)/2) 451 | 452 | # %% Gráfica del post-proceso: 453 | # las matrices xnod y LaG se vuelven variables globales por facilidad 454 | compartir_variables(xnod, LaG) 455 | 456 | # deformaciones 457 | plot_esf_def(er, r'$\epsilon_r$') 458 | plot_esf_def(ez, r'$\epsilon_z$') 459 | plot_esf_def(et, r'$\epsilon_\theta$') 460 | plot_esf_def(grz, r'$\gamma_{rz}$ [rad]') 461 | 462 | # esfuerzos 463 | plot_esf_def(sr, r'$\sigma_r$ [Pa]') 464 | plot_esf_def(sz, r'$\sigma_z$ [Pa]') 465 | plot_esf_def(st, r'$\sigma_\theta$ [Pa]') 466 | plot_esf_def(trz, r'$\tau_{rz}$ [Pa]') 467 | 468 | # esfuerzos principales con sus orientaciones 469 | # plot_esf_def(s1, r'$\sigma_1$ [Pa]', ang ) 470 | # plot_esf_def(s2, r'$\sigma_2$ [Pa]', ang+np.pi/2 ) 471 | # plot_esf_def(tmax, r'$\tau_{máx}$ [Pa]', [ ang-np.pi/4, ang+np.pi/4 ]) 472 | 473 | # esfuerzos de von Mises 474 | # plot_esf_def(sv, r'$\sigma_{VM}$ [Pa]') 475 | 476 | # %% Reporte de los resultados: 477 | 478 | # se crean tablas para reportar los resultados nodales de: desplazamientos (a), 479 | # fuerzas nodales equivalentes (f) y fuerzas nodales de equilibrio (q) 480 | tabla_afq = pd.DataFrame( 481 | data=np.c_[a.reshape((nno,2)), f.reshape((nno,2)), q.reshape((nno,2))], 482 | index=np.arange(nno)+1, 483 | columns=['ur [m]', 'w [m]', 'fr [N]', 'fz [N]', 'qr [N]', 'qz [N]']) 484 | tabla_afq.index.name = '# nodo' 485 | 486 | # deformaciones 487 | tabla_def = pd.DataFrame(data = np.c_[er, ez, et, grz], 488 | index = np.arange(nno) + 1, 489 | columns = ['er', 'ez', 'et', 'grz [rad]']) 490 | tabla_def.index.name = '# nodo' 491 | 492 | # esfuerzos 493 | tabla_esf = pd.DataFrame(data = np.c_[sr, sz, st, trz], 494 | index = np.arange(nno) + 1, 495 | columns = ['sr [Pa]', 'sz [Pa]', 'st [Pa]', 'trz [Pa]']) 496 | tabla_esf.index.name = '# nodo' 497 | 498 | # esfuerzos principales y de von Misses: 499 | tabla_epv = pd.DataFrame( 500 | data = np.c_[s1, s2, s3, tmax, sv, n1, n2, n3], 501 | index = np.arange(nno) + 1, 502 | columns = ['s1 [Pa]', 's2 [Pa]', 's3 [Pa]', 'tmax [Pa]', 'sv [Pa]', 503 | 'n1x', 'n1y', 'n1z', 504 | 'n2x', 'n2y', 'n2z', 505 | 'n3x', 'n3y', 'n3z']) 506 | tabla_epv.index.name = '# nodo' 507 | # 508 | ## se crea un archivo de MS EXCEL 509 | #writer = pd.ExcelWriter(f"resultados_{nombre_archivo}.xlsx", engine = 'xlsxwriter') 510 | # 511 | ## cada tabla hecha previamente es guardada en una hoja del archivo de Excel 512 | #tabla_afq.to_excel(writer, sheet_name = 'afq') 513 | #tabla_def.to_excel(writer, sheet_name = 'deformaciones') 514 | #tabla_esf.to_excel(writer, sheet_name = 'esfuerzos') 515 | #tabla_epv.to_excel(writer, sheet_name = 'esf_ppales') 516 | #writer.save() 517 | # 518 | #print(f'Cálculo finalizado. En "resultados_{nombre_archivo}.xlsx" se guardaron los resultados.') 519 | 520 | # %% Se genera un archivo .VTK para visualizar en Paraview 521 | # Instale meshio (https://github.com/nschloe/meshio) con: 522 | # ! pip install meshio[all] --user 523 | 524 | import meshio 525 | meshio.write_points_cells( 526 | "resultados.vtk", 527 | points = xnod, 528 | cells = {"quad8": LaG[:,[0,2,4,6,1,3,5,7]] }, 529 | point_data = { 530 | 'er':er, 'ez':ez, 'et':et, 'grz':grz, 531 | 'sr':sr, 'sz':sz, 'st':st, 'trz':trz, 532 | 's1':s1, 's2':s2, 's3':s3, 'tmax':tmax, 'sv':sv, 533 | 'uv' : a.reshape((nno,2)), 534 | 'n1' : n1, 535 | 'n2' : n2, 536 | 'n3' : n3 537 | } 538 | # cell_data = {"quad8" : {"material" : mat}} 539 | # field_data=field_data 540 | ) 541 | 542 | # %% Pasando los resultados a GiD 543 | # Pasando los esfuerzos ya promediados: 544 | # export_to_GiD('c5_ejemplo_a',xnod,LaG,a,q,[sr sz st trz]); 545 | 546 | # Pasando los puntos de Gauss [RECOMENDADO] !!! 547 | # export_to_GiD('c5_ejemplo_b',xnod,LaG,a,q,esf); 548 | 549 | a2 = tabla_afq.loc[5] 550 | d2 = tabla_def.loc[5] 551 | s2 = tabla_esf.loc[5] 552 | 553 | a1 = np.loadtxt('a1.csv') 554 | d1 = np.loadtxt('d1.csv') 555 | s1 = np.loadtxt('s1.csv') 556 | 557 | a = pd.DataFrame(np.zeros((2, a2.size)), columns=a2.index, index=['original', 'modificado']); a.iloc[0,:] = a1; a.iloc[1,:] = a2 558 | d = pd.DataFrame(np.zeros((2, d2.size)), columns=d2.index, index=['original', 'modificado']); d.iloc[0,:] = d1; d.iloc[1,:] = d2 559 | s = pd.DataFrame(np.zeros((2, s2.size)), columns=s2.index, index=['original', 'modificado']); s.iloc[0,:] = s1; s.iloc[1,:] = s2 560 | 561 | # %%bye, bye! 562 | -------------------------------------------------------------------------------- /Axisimetrico/ejemplo_Q8_axisimetrico_original.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | #%% 4 | ''' 5 | ------------------------------------------------------------------------------- 6 | NOTA: este código SOLO es apropiado para el caso AXISIMÉTRICO usando elementos 7 | rectangulares serendípitos de 8 nodos 8 | ------------------------------------------------------------------------------- 9 | 10 | DEFINICIÓN DEL PROBLEMA: 11 | Calcule los desplazamientos y las reacciones en los empotramientos, las 12 | deformaciones y los esfuerzos de la estructura mostrada en la figura adjunta 13 | ''' 14 | 15 | import numpy as np 16 | import pandas as pd 17 | import matplotlib.pyplot as plt 18 | from funciones import t2ft_R89_axisimetrico, compartir_variables, plot_esf_def 19 | 20 | # %% constantes que ayudarán en la lectura del código 21 | X, Y = 0, 1 22 | NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8 = range(8) 23 | g = 9.81 # [m/s²] aceleración de la gravedad 24 | 25 | # %% seleccione la malla a emplear: 26 | nombre_archivo = 'boussinesq' 27 | df = pd.read_excel(f'{nombre_archivo}.xlsx', sheet_name=None) 28 | 29 | # %% posición de los nodos: 30 | # xnod: fila=número del nodo, columna=coordenada X=0 o Y=1 31 | xnod = df['xnod'][['x', 'y']].to_numpy() 32 | nno = xnod.shape[0] # número de nodos (número de filas de la matriz xnod) 33 | 34 | # %% definición de los grados de libertad 35 | ngdl = 2*nno # número de grados de libertad por nodo = [X, Y] 36 | gdl = np.reshape(np.arange(ngdl), (nno, 2)) # nodos vs grados de libertad 37 | 38 | # %% definición de elementos finitos con respecto a nodos 39 | # LaG: fila=número del elemento, columna=número del nodo local 40 | LaG = df['LaG_mat'][['NL1', 'NL2', 'NL3', 'NL4', 'NL5', 'NL6', 'NL7', 'NL8']].to_numpy() - 1 41 | # se carga el número del material 42 | mat = df['LaG_mat']['material'].to_numpy() - 1 43 | nef = LaG.shape[0] # número de EFs (número de filas de la matriz LaG) 44 | 45 | # %% material 46 | Ee = df['prop_mat']['E'].to_numpy() # [Pa] módulo de elasticidad del sólido 47 | nue = df['prop_mat']['nu'].to_numpy() # [-] coeficiente de Poisson 48 | rhoe = df['prop_mat']['rho'].to_numpy() # [kg/m³] densidad 49 | nmat = Ee.shape[0] # número de materiales 50 | 51 | # %% relación de cargas puntuales 52 | cp = df['carga_punt'] 53 | ncp = cp.shape[0] # número de cargas puntuales 54 | f = np.zeros(ngdl) # vector de fuerzas nodales equivalentes global 55 | for i in range(ncp): 56 | f[gdl[cp['nodo'][i]-1, cp['dirección'][i]-1]] = cp['fuerza puntual'][i] 57 | 58 | # %% Se dibuja la malla de elementos finitos 59 | cg = np.zeros((nef,2)) # almacena el centro de gravedad de los EF 60 | plt.figure() 61 | for e in range(nef): 62 | # se dibujan las aristas 63 | nod_ef = LaG[e, [NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8, NL1]] 64 | plt.plot(xnod[nod_ef, X], xnod[nod_ef, Y], 'b') 65 | # se calcula la posición del centro de gravedad 66 | cg[e] = np.mean(xnod[LaG[e]], axis = 0) 67 | # y se reporta el número del elemento actual 68 | plt.text(cg[e,X], cg[e,Y], f'{e+1}', horizontalalignment='center', 69 | verticalalignment='center', color='b') 70 | 71 | # en todos los nodos se dibuja un marcador y se reporta su numeración 72 | plt.plot(xnod[:, X], xnod[:, Y], 'r*') 73 | for i in range(nno): 74 | plt.text(xnod[i, X], xnod[i, Y], f'{i+1}', color = 'r') 75 | 76 | plt.gca().set_aspect('equal', adjustable = 'box') 77 | plt.tight_layout() 78 | plt.title('Malla de elementos finitos') 79 | plt.show() 80 | 81 | #%% Funciones de forma (serendípitas) y sus derivadas del elemento rectangular 82 | # de 8 nodos: 83 | Nforma = lambda xi,eta: np.array( 84 | [-((eta - 1)*(xi - 1)*(eta + xi + 1))/4, # N1 85 | ((xi**2 - 1)*(eta - 1))/2, # N2 86 | ((eta - 1)*(xi + 1)*(eta - xi + 1))/4, # N3 87 | -((eta**2 - 1)*(xi + 1))/2, # N4 88 | ((eta + 1)*(xi + 1)*(eta + xi - 1))/4, # N5 89 | -((xi**2 - 1)*(eta + 1))/2, # N6 90 | ((eta + 1)*(xi - 1)*(xi - eta + 1))/4, # N7 91 | ((eta**2 - 1)*(xi - 1))/2 ]) # N8 92 | 93 | # derivadas de las funciones de forma con respecto a xi 94 | dN_dxi = lambda xi,eta: np.array( 95 | [-((eta + 2*xi)*(eta - 1))/4, # dN1_dxi 96 | eta*xi - xi, # dN2_dxi 97 | ((eta - 2*xi)*(eta - 1))/4, # dN3_dxi 98 | 1/2 - eta**2/2, # dN4_dxi 99 | ((eta + 2*xi)*(eta + 1))/4, # dN5_dxi 100 | -xi*(eta + 1), # dN6_dxi 101 | -((eta - 2*xi)*(eta + 1))/4, # dN7_dxi 102 | eta**2/2 - 1/2 ]) # dN8_dxi 103 | 104 | # derivadas de N con respecto a eta 105 | dN_deta = lambda xi,eta: np.array( 106 | [-((2*eta + xi)*(xi - 1))/4, # dN1_deta 107 | xi**2/2 - 1/2, # dN2_deta 108 | ((xi + 1)*(2*eta - xi))/4, # dN3_deta 109 | -eta*(xi + 1), # dN4_deta 110 | ((2*eta + xi)*(xi + 1))/4, # dN5_deta 111 | 1/2 - xi**2/2, # dN6_deta 112 | -((xi - 1)*(2*eta - xi))/4, # dN7_deta 113 | eta*(xi - 1) ]) # dN8_deta 114 | 115 | #%% Cuadratura de Gauss-Legendre 116 | # NOTA: se asumirá aquí el mismo orden de la cuadratura tanto en la dirección 117 | # de xi como en la dirección de eta 118 | n_gl = 2 # orden de la cuadratura de Gauss-Legendre 119 | x_gl, w_gl = np.polynomial.legendre.leggauss(n_gl) 120 | 121 | # %% Ensamblaje la matriz de rigidez global y el vector de fuerzas másicas 122 | # nodales equivalentes global 123 | 124 | # se inicializan la matriz de rigidez global y los espacios en memoria que 125 | # almacenarán las matrices de forma y de deformación 126 | K = np.zeros((ngdl, ngdl)) # matriz de rigidez global 127 | N = np.empty((nef,n_gl,n_gl,2,2*8)) # matriz de forma en cada punto de GL 128 | B = np.empty((nef,n_gl,n_gl,4,2*8)) # matriz de deformaciones en cada punto de GL 129 | idx = nef * [None] # indices asociados a los gdl del EF e 130 | 131 | # matriz constitutiva del elemento para el caso AXISIMETRICO 132 | De = nmat * [ None ] 133 | be = nmat * [ None ] 134 | for i in range(nmat): 135 | De[i] = (Ee[i]/((1+nue[i])*(1-2*nue[i])))*\ 136 | np.array([[1 - nue[i], nue[i], nue[i], 0 ], 137 | [nue[i], 1-nue[i], nue[i], 0 ], 138 | [nue[i], nue[i], 1-nue[i], 0 ], 139 | [0, 0, 0, (1-2*nue[i])/2]]) 140 | be[i] = np.array([0, -rhoe[i]*g]) # [kgf/m³] vector de fuerzas másicas 141 | 142 | # para cada elemento finito en la malla: 143 | for e in range(nef): 144 | # se calculan con el siguiente ciclo las matrices de rigidez y el vector de 145 | # fuerzas nodales equivalentes del elemento usando las cuadraturas de GL 146 | Ke = np.zeros((16, 16)) 147 | fe = np.zeros(16) 148 | det_Je = np.empty((n_gl, n_gl)) # matriz para almacenar los jacobianos 149 | 150 | for p in range(n_gl): 151 | for q in range(n_gl): 152 | # en cada punto de la cuadratura de Gauss-Legendre se evalúan las 153 | # funciones de forma y sus derivadas 154 | xi_gl, eta_gl = x_gl[p], x_gl[q] 155 | 156 | NNforma = Nforma (xi_gl, eta_gl) 157 | ddN_dxi = dN_dxi (xi_gl, eta_gl) 158 | ddN_deta = dN_deta(xi_gl, eta_gl) 159 | 160 | # se llaman las coordenadas nodales del elemento para calcular las 161 | # derivadas de la función de transformación 162 | xe, ye = xnod[LaG[e], X], xnod[LaG[e], Y] 163 | 164 | dx_dxi = np.sum(ddN_dxi * xe); dy_dxi = np.sum(ddN_dxi * ye) 165 | dx_deta = np.sum(ddN_deta * xe); dy_deta = np.sum(ddN_deta * ye) 166 | 167 | # se calcula el radio del punto de Gauss 168 | r = np.sum(NNforma * xe) 169 | 170 | # con ellas se ensambla la matriz Jacobiana del elemento y se 171 | # calcula su determinante 172 | Je = np.array([[dx_dxi, dy_dxi ], 173 | [dx_deta, dy_deta]]) 174 | det_Je[p, q] = np.linalg.det(Je) 175 | 176 | # las matrices de forma y de deformación se evalúan y se ensamblan 177 | # en el punto de Gauss 178 | Npq = np.empty((2, 2*8)) 179 | Bpq = np.empty((4, 2*8)) 180 | for i in range(8): 181 | Npq[:,[2*i, 2*i+1]] = np.array([[NNforma[i], 0 ], 182 | [0, NNforma[i]]]) 183 | 184 | dNi_dx = (+dy_deta*ddN_dxi[i] - dy_dxi*ddN_deta[i])/det_Je[p,q] 185 | dNi_dy = (-dx_deta*ddN_dxi[i] + dx_dxi*ddN_deta[i])/det_Je[p,q] 186 | Bpq[:,[2*i, 2*i+1]] = np.array([[dNi_dx, 0 ], 187 | [0, dNi_dy], 188 | [NNforma[i]/r, 0 ], 189 | [dNi_dy, dNi_dx]]) 190 | N[e,p,q] = Npq 191 | B[e,p,q] = Bpq 192 | 193 | # se ensamblan la matriz de rigidez del elemento y el vector de 194 | # fuerzas nodales equivalentes del elemento 195 | Ke += Bpq.T @ De[mat[e]] @ Bpq * det_Je[p,q]*r*w_gl[p]*w_gl[q] 196 | fe += Npq.T @ be[mat[e]] * det_Je[p,q]*r*w_gl[p]*w_gl[q] 197 | Ke *= 2*np.pi 198 | fe *= 2*np.pi 199 | 200 | # se determina si hay puntos con jacobiano negativo, en caso tal se termina 201 | # el programa y se reporta 202 | if np.any(det_Je <= 0): 203 | raise Exception(f'Hay puntos con det_Je negativo en el elemento {e+1}') 204 | 205 | # y se añaden la matriz de rigidez del elemento y el vector de fuerzas 206 | # nodales del elemento a sus respectivos arreglos de la estructura 207 | idx[e] = gdl[LaG[e]].flatten() # se obtienen los grados de libertad 208 | K[np.ix_(idx[e], idx[e])] += Ke 209 | f[np.ix_(idx[e])] += fe 210 | 211 | # %% Muestro la configuración de la matriz K (K es rala) 212 | plt.figure() 213 | plt.spy(K) 214 | plt.title('Los puntos representan los elementos diferentes de cero') 215 | plt.show() 216 | 217 | #%% Cálculo de las cargas nodales equivalentes de las cargas distribuidas: 218 | cd = df['carga_distr'] 219 | nlcd = cd.shape[0] # número de lados con carga distribuida 220 | ft = np.zeros(ngdl) # fuerzas nodales equivalentes de cargas superficiales 221 | 222 | # por cada lado cargado se obtienen las fuerzas nodales equivalentes en los 223 | # nodos y se añaden al vector de fuerzas superficiales 224 | for i in range(nlcd): 225 | e = cd['elemento'][i] - 1 226 | lado = cd['lado'][i] 227 | carga = cd[['tix', 'tiy', 'tjx', 'tjy', 'tkx', 'tky']].loc[i].to_numpy() 228 | fte = t2ft_R89_axisimetrico(xnod[LaG[e,:],:], lado, carga) 229 | 230 | ft[np.ix_(idx[e])] += fte 231 | 232 | # %% agrego al vector de fuerzas nodales equivalentes las fuerzas 233 | # superficiales calculadas 234 | f += ft 235 | 236 | # %% restricciones y los grados de libertad del desplazamiento conocidos (c) 237 | restric = df['restric'] 238 | nres = restric.shape[0] 239 | c = np.empty(nres, dtype=int) 240 | for i in range(nres): 241 | c[i] = gdl[restric['nodo'][i]-1, restric['dirección'][i]-1] 242 | 243 | # desplazamientos conocidos 244 | ac = restric['desplazamiento'].to_numpy() 245 | 246 | # grados de libertad del desplazamiento desconocidos 247 | d = np.setdiff1d(range(ngdl), c) 248 | 249 | # %% extraigo las submatrices y especifico las cantidades conocidas 250 | # f = vector de fuerzas nodales equivalentes 251 | # q = vector de fuerzas nodales de equilibrio del elemento 252 | # a = desplazamientos 253 | 254 | #| qd | | Kcc Kcd || ac | | fd | # recuerde que qc=0 (siempre) 255 | #| | = | || | - | | 256 | #| qc | | Kdc Kdd || ad | | fc | 257 | Kcc = K[np.ix_(c,c)]; Kcd = K[np.ix_(c,d)]; fd = f[c] 258 | Kdc = K[np.ix_(d,c)]; Kdd = K[np.ix_(d,d)]; fc = f[d] 259 | 260 | # %% resuelvo el sistema de ecuaciones 261 | ad = np.linalg.solve(Kdd, fc - Kdc@ac) # desplazamientos desconocidos 262 | qd = Kcc@ac + Kcd@ad - fd # fuerzas de equilibrio desconocidas 263 | 264 | # armo los vectores de desplazamientos (a) y fuerzas (q) 265 | a = np.zeros(ngdl); q = np.zeros(ngdl) # separo la memoria 266 | a[c] = ac; a[d] = ad # desplazamientos 267 | q[c] = qd # q[d] = qc = 0 # fuerzas nodales de equilibrio 268 | 269 | # %% Dibujo la malla de elementos finitos y las deformada de esta 270 | delta = np.reshape(a, (nno,2)) 271 | escala = 1000 # factor de escalamiento de la deformada 272 | xdef = xnod + escala*delta # posición de la deformada 273 | 274 | plt.figure() 275 | for e in range(nef): 276 | nod_ef = LaG[e, [NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8, NL1]] 277 | plt.plot(xnod[nod_ef, X], xnod[nod_ef, Y], 'r', 278 | label='Posición original' if e == 0 else "", lw=0.5) 279 | plt.plot(xdef[nod_ef, X], xdef[nod_ef, Y], 'b', 280 | label='Posición deformada' if e == 0 else "") 281 | plt.gca().set_aspect('equal', adjustable='box') 282 | plt.legend() 283 | plt.xlabel('$r$ [m]') 284 | plt.ylabel('$z$ [m]') 285 | plt.title(f'Deformada escalada {escala} veces') 286 | plt.tight_layout() 287 | plt.show() 288 | 289 | #%% Deformaciones y los esfuerzos en los puntos de Gauss 290 | deform = np.zeros((nef,n_gl,n_gl,4)) # deformaciones en cada punto de GL 291 | esfuer = np.zeros((nef,n_gl,n_gl,4)) # esfuerzos en cada punto de GL 292 | 293 | for e in range(nef): 294 | ae = a[idx[e]] # desplazamientos nodales del elemento e 295 | for pp in range(n_gl): 296 | for qq in range(n_gl): 297 | deform[e,pp,qq] = B[e,pp,qq] @ ae # calculo las deformaciones 298 | esfuer[e,pp,qq] = De[mat[e]] @ deform[e,pp,qq] # calculo los esfuerzos 299 | 300 | #%% Esfuerzos y deformaciones en los nodos: 301 | num_elem_ady = np.zeros(nno) 302 | sr = np.zeros(nno); er = np.zeros(nno) 303 | sz = np.zeros(nno); ez = np.zeros(nno) 304 | st = np.zeros(nno); et = np.zeros(nno) 305 | trz = np.zeros(nno); grz = np.zeros(nno) 306 | 307 | # matriz de extrapolación 308 | A = np.array([ 309 | [ 3**(1/2)/2 + 1, -1/2, -1/2, 1 - 3**(1/2)/2], 310 | [3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4], 311 | [ -1/2, 1 - 3**(1/2)/2, 3**(1/2)/2 + 1, -1/2], 312 | [1/4 - 3**(1/2)/4, 1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4, 3**(1/2)/4 + 1/4], 313 | [ 1 - 3**(1/2)/2, -1/2, -1/2, 3**(1/2)/2 + 1], 314 | [1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4, 3**(1/2)/4 + 1/4], 315 | [ -1/2, 3**(1/2)/2 + 1, 1 - 3**(1/2)/2, -1/2], 316 | [3**(1/2)/4 + 1/4, 3**(1/2)/4 + 1/4, 1/4 - 3**(1/2)/4, 1/4 - 3**(1/2)/4]]) 317 | 318 | # se hace la extrapolación de los esfuerzos y las deformaciones en cada elemento 319 | # a partir de las lecturas en los puntos de Gauss 320 | for e in range(nef): 321 | #sr[LaG[e]] += A @ np.array([esfuer[e,0,0,0], # I = (p=0, q=0) 322 | # esfuer[e,0,1,0], # II = (p=0, q=1) 323 | # esfuer[e,1,0,0], # III = (p=1, q=0) 324 | # esfuer[e,1,1,0]]) # IV = (p=1, q=1) 325 | sr [LaG[e]] += A @ esfuer[e,:,:,0].ravel() 326 | sz [LaG[e]] += A @ esfuer[e,:,:,1].ravel() 327 | st [LaG[e]] += A @ esfuer[e,:,:,2].ravel() 328 | trz[LaG[e]] += A @ esfuer[e,:,:,3].ravel() 329 | er [LaG[e]] += A @ deform[e,:,:,0].ravel() 330 | ez [LaG[e]] += A @ deform[e,:,:,1].ravel() 331 | et [LaG[e]] += A @ deform[e,:,:,2].ravel() 332 | grz[LaG[e]] += A @ deform[e,:,:,3].ravel() 333 | 334 | # se lleva un conteo de los elementos adyacentes a un nodo 335 | num_elem_ady[LaG[e]] += 1 336 | 337 | # en todos los nodos se promedia los esfuerzos y las deformaciones de los 338 | # elementos, se alisa la malla de resultados 339 | sr /= num_elem_ady; er /= num_elem_ady 340 | sz /= num_elem_ady; ez /= num_elem_ady 341 | st /= num_elem_ady; et /= num_elem_ady 342 | trz /= num_elem_ady; grz /= num_elem_ady 343 | trt = 0 344 | ttz = 0 345 | 346 | 347 | # %% Se calculan para cada nodo los esfuerzos principales y sus direcciones 348 | s1 = np.zeros(nno); n1 = np.zeros((nno, 3)) 349 | s2 = np.zeros(nno); n2 = np.zeros((nno, 3)) 350 | s3 = np.zeros(nno); n3 = np.zeros((nno, 3)) 351 | for i in range(nno): 352 | esfppales, dirppales = np.linalg.eigh( 353 | [[sr[i], trt, trz[i]], # matriz de esfuerzos 354 | [trt, st[i], ttz ], # de Cauchy para 355 | [trz[i], ttz, sz[i] ]]) # theta = grados 356 | 357 | idx_esf = esfppales.argsort()[::-1] # ordene de mayor a menor 358 | s1[i], s2[i], s3[i] = esfppales[idx_esf] 359 | n1[i] = dirppales[:,idx_esf[0]] 360 | n2[i] = dirppales[:,idx_esf[1]] 361 | n3[i] = dirppales[:,idx_esf[2]] 362 | 363 | # Esfuerzo cortante máximo 364 | tmax = (s1-s3)/2 # esfuerzo cortante máximo 365 | 366 | # %% Calculo de los esfuerzos de von Mises 367 | sv = np.sqrt(((s1-s2)**2 + (s2-s3)**2 + (s1-s3)**2)/2) 368 | 369 | # %% Gráfica del post-proceso: 370 | # las matrices xnod y LaG se vuelven variables globales por facilidad 371 | compartir_variables(xnod, LaG) 372 | 373 | # deformaciones 374 | plot_esf_def(er, r'$\epsilon_r$') 375 | plot_esf_def(ez, r'$\epsilon_z$') 376 | plot_esf_def(et, r'$\epsilon_\theta$') 377 | plot_esf_def(grz, r'$\gamma_{rz}$ [rad]') 378 | 379 | # esfuerzos 380 | plot_esf_def(sr, r'$\sigma_r$ [Pa]') 381 | plot_esf_def(sz, r'$\sigma_z$ [Pa]') 382 | plot_esf_def(st, r'$\sigma_\theta$ [Pa]') 383 | plot_esf_def(trz, r'$\tau_{rz}$ [Pa]') 384 | 385 | # esfuerzos principales con sus orientaciones 386 | # plot_esf_def(s1, r'$\sigma_1$ [Pa]', ang ) 387 | # plot_esf_def(s2, r'$\sigma_2$ [Pa]', ang+np.pi/2 ) 388 | # plot_esf_def(tmax, r'$\tau_{máx}$ [Pa]', [ ang-np.pi/4, ang+np.pi/4 ]) 389 | 390 | # esfuerzos de von Mises 391 | # plot_esf_def(sv, r'$\sigma_{VM}$ [Pa]') 392 | 393 | # %% Reporte de los resultados: 394 | 395 | # se crean tablas para reportar los resultados nodales de: desplazamientos (a), 396 | # fuerzas nodales equivalentes (f) y fuerzas nodales de equilibrio (q) 397 | tabla_afq = pd.DataFrame( 398 | data=np.c_[a.reshape((nno,2)), f.reshape((nno,2)), q.reshape((nno,2))], 399 | index=np.arange(nno)+1, 400 | columns=['ur [m]', 'w [m]', 'fr [N]', 'fz [N]', 'qr [N]', 'qz [N]']) 401 | tabla_afq.index.name = '# nodo' 402 | 403 | # deformaciones 404 | tabla_def = pd.DataFrame(data = np.c_[er, ez, et, grz], 405 | index = np.arange(nno) + 1, 406 | columns = ['er', 'ez', 'et', 'grz [rad]']) 407 | tabla_def.index.name = '# nodo' 408 | 409 | # esfuerzos 410 | tabla_esf = pd.DataFrame(data = np.c_[sr, sz, st, trz], 411 | index = np.arange(nno) + 1, 412 | columns = ['sr [Pa]', 'sz [Pa]', 'st [Pa]', 'trz [Pa]']) 413 | tabla_esf.index.name = '# nodo' 414 | 415 | # esfuerzos principales y de von Misses: 416 | tabla_epv = pd.DataFrame( 417 | data = np.c_[s1, s2, s3, tmax, sv, n1, n2, n3], 418 | index = np.arange(nno) + 1, 419 | columns = ['s1 [Pa]', 's2 [Pa]', 's3 [Pa]', 'tmax [Pa]', 'sv [Pa]', 420 | 'n1x', 'n1y', 'n1z', 421 | 'n2x', 'n2y', 'n2z', 422 | 'n3x', 'n3y', 'n3z']) 423 | tabla_epv.index.name = '# nodo' 424 | 425 | # se crea un archivo de MS EXCEL 426 | writer = pd.ExcelWriter(f"resultados_{nombre_archivo}.xlsx", engine = 'xlsxwriter') 427 | 428 | # cada tabla hecha previamente es guardada en una hoja del archivo de Excel 429 | tabla_afq.to_excel(writer, sheet_name = 'afq') 430 | tabla_def.to_excel(writer, sheet_name = 'deformaciones') 431 | tabla_esf.to_excel(writer, sheet_name = 'esfuerzos') 432 | tabla_epv.to_excel(writer, sheet_name = 'esf_ppales') 433 | writer.save() 434 | 435 | print(f'Cálculo finalizado. En "resultados_{nombre_archivo}.xlsx" se guardaron los resultados.') 436 | 437 | # %% Se genera un archivo .VTK para visualizar en Paraview 438 | # Instale meshio (https://github.com/nschloe/meshio) con: 439 | # ! pip install meshio[all] --user 440 | 441 | import meshio 442 | meshio.write_points_cells( 443 | f"resultados_{nombre_archivo}.vtk", 444 | points = xnod, 445 | cells = {"quad8": LaG[:,[0,2,4,6,1,3,5,7]] }, 446 | point_data = { 447 | 'er':er, 'ez':ez, 'et':et, 'grz':grz, 448 | 'sr':sr, 'sz':sz, 'st':st, 'trz':trz, 449 | 's1':s1, 's2':s2, 's3':s3, 'tmax':tmax, 'sv':sv, 450 | 'uv' : a.reshape((nno,2)), 451 | 'n1' : n1, 452 | 'n2' : n2, 453 | 'n3' : n3 454 | }, 455 | # cell_data = {"quad8" : {"material" : mat}} 456 | # field_data=field_data 457 | ) 458 | # %% Resultados en punto de referencia 459 | a1 = tabla_afq.loc[4].to_numpy() 460 | d1 = tabla_def.loc[4].to_numpy() 461 | s1 = tabla_esf.loc[4].to_numpy() 462 | 463 | np.savetxt('a1.csv', a1) 464 | np.savetxt('d1.csv', d1) 465 | np.savetxt('s1.csv', s1) 466 | 467 | # %%bye, bye! 468 | -------------------------------------------------------------------------------- /Axisimetrico/funciones.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Código creado por: Diego Andrés Alvarez (github.com/diegoandresalvarez) 4 | 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import warnings 8 | 9 | # %% constantes que ayudaran en la lectura del codigo 10 | X, Y = 0, 1 11 | NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8 = range(8) 12 | 13 | # %% variables globales que se heredarán del programa principal 14 | xnod = None 15 | LaG = None 16 | 17 | # %% 18 | def compartir_variables(xnod_, LaG_): 19 | ''' 20 | Importa variables globales del programa principal a este módulo 21 | ''' 22 | global xnod, LaG 23 | xnod, LaG = xnod_, LaG_ 24 | 25 | # %% 26 | def t2ft_R89_axisimetrico(xnod, lado, carga): 27 | '''Función que convierte las fuerzas superficiales aplicadas a un elemento 28 | finito rectangular de 8 (serendípito) o 9 (lagrangiano) nodos a sus 29 | correspondientes cargas nodales equivalentes ft en el caso AXISIMETRICO 30 | 31 | Recibe: 32 | xnod: coordenadas nodales del elemento finito 33 | SERENDIPITO 8 LAGRANGIANO 9 34 | xnod = [ x1e y1e xnod = [ x1e y1e 35 | x2e y2e x2e y2e 36 | ... ... ... ... 37 | x8e y8e ] x9e y9e ] 38 | 39 | lado: arista en la que se aplica la carga, puede tomar los siguientes 40 | valores: 123, 345, 567, 781 41 | 42 | carga: fuerza distribuida en los nodos 43 | 44 | [ t1x t1y t2x t2y t3x t3y ]; % si carga se aplica sobre lado 123 45 | [ t3x t3y t4x t4y t5x t5y ]; % si carga se aplica sobre lado 345 46 | [ t5x t5y t6x t6y t7x t7y ]; % si carga se aplica sobre lado 567 47 | [ t7x t7y t8x t8y t1x t1y ]; % si carga se aplica sobre lado 781 48 | ''' 49 | 50 | # se definen los indices de los lados 51 | if lado == 123: idx = np.array([ 1, 2, 3 ]) - 1 52 | elif lado == 345: idx = np.array([ 3, 4, 5 ]) - 1 53 | elif lado == 567: idx = np.array([ 5, 6, 7 ]) - 1 54 | elif lado == 781: idx = np.array([ 7, 8, 1 ]) - 1 55 | else: 56 | raise Exception('Únicamente se permiten los lados 123, 345, 567 o 781') 57 | 58 | nno = xnod.shape[0] 59 | if nno not in (8, 9): 60 | raise Exception('Solo para elementos rectangulares de 8 o 9 nodos') 61 | 62 | # parámetros para mejorar la lectura del código 63 | X, Y = 0, 1 64 | 65 | # se define el número de puntos de la cuadratura y se obtienen los puntos 66 | # de evaluación y los pesos de la cuadratura 67 | n_gl = 2 68 | x_gl, w_gl = np.polynomial.legendre.leggauss(n_gl) 69 | 70 | # se definen las funciones de forma unidimensionales y sus derivadas 71 | NN = lambda xi: np.array([ xi*(xi-1)/2, (1+xi)*(1-xi), xi*(1+xi)/2 ]) 72 | dNN_dxi = lambda xi: np.array([ xi - 1/2 , -2*xi , xi + 1/2 ]) 73 | 74 | # se calcula el vector de fuerzas distribuidas en los nodos 75 | te = np.zeros(2*nno) 76 | te[np.c_[2*idx, 2*idx + 1].ravel()] = carga 77 | 78 | # cálculo de la integral: 79 | suma = np.zeros((2*nno, 2*nno)) 80 | N = np.zeros(nno) 81 | dN_dxi = np.zeros(nno) 82 | for p in range(n_gl): 83 | # se evalúan las funciones de forma 84 | N[idx] = NN(x_gl[p]) 85 | matN = np.empty((2,2*nno)) 86 | for i in range(nno): 87 | matN[:,[2*i, 2*i+1]] = np.array([[N[i], 0 ], 88 | [0, N[i]]]) 89 | 90 | # se calcula el jacobiano 91 | dN_dxi[idx] = dNN_dxi(x_gl[p]) 92 | r = np.dot(N, xnod[:,X]) 93 | dx_dxi = np.dot(dN_dxi, xnod[:,X]) 94 | dy_dxi = np.dot(dN_dxi, xnod[:,Y]) 95 | ds_dxi = np.hypot(dx_dxi, dy_dxi) 96 | 97 | # y se calcula la sumatoria 98 | suma += r * matN.T @ matN * ds_dxi*w_gl[p] 99 | 100 | # finalmente, se retorna el vector de fuerzas nodales equivalentes 101 | return suma @ te 102 | 103 | #%% 104 | def plot_esf_def(variable, titulo, angulo = None): 105 | ''' 106 | Grafica variable para la malla de EFs especificada. 107 | 108 | Uso: 109 | variable: es la variable que se quiere graficar 110 | titulo: título del gráfico 111 | angulo: opcional para los esfuerzos principales s1 y s2 y tmax 112 | ''' 113 | 114 | # Para propósitos de graficación el EF se divide en 6 triángulos así: 115 | # 116 | # 7 -------6--------5 117 | # | /|\ | 118 | # | EFT6 / | \ EFT3 | 119 | # | / | \ | 120 | # | / | \ | 121 | # | / | \ | 122 | # | / | \ | 123 | # | / | \ | 124 | # 8/ EFT5 | EFT4 \4 125 | # |\ | /| 126 | # | \ | / | 127 | # | \ | / | 128 | # | \ | / | 129 | # | \ | / | 130 | # | \ | / | 131 | # | EFT1 \ | / EFT2 | 132 | # | \|/ | 133 | # 1--------2--------3 134 | 135 | nef = LaG.shape[0] # número de elementos finitos en la malla original 136 | 137 | # se arma la matriz de correspondencia (LaG) de la nueva malla triangular 138 | LaG_t = np.zeros((6*nef, 3), dtype = int) 139 | for e in range(nef): 140 | LaG_t[6*e + 0, :] = LaG[e, [NL1, NL2, NL8]] 141 | LaG_t[6*e + 1, :] = LaG[e, [NL2, NL3, NL4]] 142 | LaG_t[6*e + 2, :] = LaG[e, [NL4, NL5, NL6]] 143 | LaG_t[6*e + 3, :] = LaG[e, [NL2, NL4, NL6]] 144 | LaG_t[6*e + 4, :] = LaG[e, [NL2, NL6, NL8]] 145 | LaG_t[6*e + 5, :] = LaG[e, [NL6, NL7, NL8]] 146 | 147 | # se inicializa el lienzo 148 | fig, ax = plt.subplots() 149 | 150 | # se encuentra el máximo en valor absoluto para ajustar el colorbar() 151 | val_max = np.max(np.abs(variable)) 152 | 153 | # se grafica la malla de EFS, los colores en cada triángulo y las curvas 154 | # de nivel 155 | for e in range(nef): 156 | # se dibujan las aristas 157 | nod_ef = LaG[e, [NL1, NL2, NL3, NL4, NL5, NL6, NL7, NL8, NL1]] 158 | plt.plot(xnod[nod_ef, X], xnod[nod_ef, Y], lw = 0.5, color = 'gray') 159 | 160 | im = ax.tripcolor(xnod[:, X], xnod[:, Y], LaG_t, variable, cmap = 'bwr', 161 | shading = 'gouraud', vmin = -val_max, vmax = val_max) 162 | ax.tricontour(xnod[:, X], xnod[:, Y], LaG_t, variable, 20) 163 | 164 | # a veces sale un warning, simplemente porque no existe la curva 0 165 | warnings.filterwarnings("ignore") 166 | ax.tricontour(xnod[:, X], xnod[:, Y], LaG_t, variable, levels=[0], linewidths=3) 167 | warnings.filterwarnings("default") 168 | 169 | fig.colorbar(im, ax = ax, format = '%6.3g') 170 | 171 | # para los esfuerzos principales se grafican las líneas que indiquen las 172 | # direcciones de los esfuerzos en cada nodo de la malla 173 | if angulo is not None: 174 | if type(angulo) is np.ndarray: 175 | angulo = [ angulo ] 176 | for ang in angulo: 177 | ax.quiver(xnod[:, X], xnod[:, Y], 178 | variable*np.cos(ang), variable*np.sin(ang), 179 | headwidth=0, headlength=0, headaxislength=0, pivot='middle') 180 | 181 | # se especifican los ejes y el título, y se colocan los ejes iguales 182 | ax.set_xlabel('$r$ [m]') 183 | ax.set_ylabel('$z$ [m]') 184 | ax.set_title(titulo, fontsize=20) 185 | 186 | ax.set_aspect('equal') 187 | ax.autoscale(tight=True) 188 | plt.tight_layout() 189 | plt.show() 190 | -------------------------------------------------------------------------------- /Axisimetrico/leer_GMSH.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Funciones para leer y graficar una malla creada en GMSH. 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | import gmsh # pip install --upgrade gmsh 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from mpl_toolkits.mplot3d import Axes3D 12 | from matplotlib.colors import BASE_COLORS, TABLEAU_COLORS 13 | 14 | # %% Funciones: 15 | 16 | def xnod_from_msh(archivo, dim=2): 17 | ''' Obtiene la matriz de coordenadas de los nodos que contiene la malla de 18 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 19 | ''' 20 | gmsh.initialize() 21 | gmsh.open(archivo) 22 | nod, xnod, pxnod = gmsh.model.mesh.getNodes() 23 | xnod = xnod.reshape((nod.size, -1)) 24 | gmsh.finalize() 25 | 26 | return xnod[:, :dim] 27 | 28 | 29 | def LaG_from_msh(archivo): 30 | ''' Obtiene la matriz de interconexión nodal (LaG) que contiene la malla de 31 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 32 | 33 | Retorna: Matriz LaG_mat, donde la primera columna representa la super- 34 | ficie a la que pertenece cada elemento finito (ordenadas de forma 35 | ascendente desde 0), de tal manera que diferencie elementos finitos con 36 | propiedades distintas 37 | ''' 38 | gmsh.initialize() 39 | gmsh.open(archivo) 40 | 41 | LaG = np.array([]) # Matriz de interconexión nodal 42 | elems = np.array([]) # vector de elementos finitos 43 | mats = np.array([]) # Vector de materiales al que pertenece cada EF 44 | material = 0 # Se inicializa el material 45 | 46 | for ent in gmsh.model.getEntities(2): 47 | dim, tag = ent 48 | tipo, efs, nodos = gmsh.model.mesh.getElements(dim, tag) 49 | if tipo.size == 1: 50 | nodos = nodos[0] 51 | elems = np.append(elems, efs[0]).astype(int) 52 | LaG = np.append(LaG, nodos).astype(int) 53 | mats = np.append(mats, np.tile(material, efs[0].size)).astype(int) 54 | material += 1 55 | else: 56 | raise ValueError('La malla tiene varios tipos de elementos ' 57 | 'finitos 2D.') 58 | 59 | nef = elems.size 60 | LaG = LaG.reshape((nef, -1)) - 1 61 | LaG_mat = np.c_[mats, LaG] 62 | gmsh.finalize() 63 | 64 | return LaG_mat 65 | 66 | 67 | def plot_msh(file, tipo, mostrar_nodos=False, mostrar_num_nodo=False, 68 | mostrar_num_elem=False): 69 | ''' Función para graficar la malla contenida en el archivo "file". 70 | Argumentos: 71 | - file: str. Debe ser un archivo de extensión .msh exportado por GMSH. 72 | - tipo: str. 'shell' o '2D' 73 | - mostrar_nodos: bool. Define si se muestran los nodos en el gráfico. 74 | - mostrar_num_nodo: bool. Define si se muestra el número de cada nodo 75 | en el gráfico. 76 | - mostrar_num_elem: bool. Define si se muestra el número de cada ele- 77 | mento finito en el gráfico. 78 | ''' 79 | 80 | LaG_mat = LaG_from_msh(file) 81 | mat = LaG_mat[:, 0] 82 | LaG = LaG_mat[:, 1:] 83 | nef = LaG.shape[0] 84 | 85 | # Se determina el tipo de elemento finito 86 | if LaG.shape[1] == 3: 87 | elem = 'T3' 88 | elif LaG.shape[1] == 4: 89 | elem = 'Q4' 90 | elif LaG.shape[1] == 6: 91 | elem = 'T6' 92 | elif LaG.shape[1] == 8: 93 | elem = 'Q8' 94 | elif LaG.shape[1] == 9: 95 | elem = 'Q9' 96 | elif LaG.shape[1] == 10: 97 | elem = 'T10' 98 | 99 | if tipo=='shell': 100 | xnod = xnod_from_msh(file, 3) 101 | nno = xnod.shape[0] 102 | 103 | cg = np.empty((nef,3)) # Centro de gravedad del EF 104 | colores = list(BASE_COLORS.values()) + list(TABLEAU_COLORS.values()) 105 | 106 | fig = plt.figure(figsize=(12, 12)) 107 | ax = plt.gca(projection='3d') 108 | for e in range(nef): 109 | if elem=='T3' or elem=='Q4': 110 | nodos = np.r_[LaG[e], LaG[e, 0]] 111 | elif elem =='T6': 112 | nodos = LaG[e, [0, 3, 1, 4, 2, 5, 0]] 113 | elif elem =='Q8' or elem == 'Q9': 114 | nodos = LaG[e, [0, 4, 1, 5, 2, 6, 3, 7, 0]] 115 | elif elem == 'T10': 116 | nodos = LaG[e, [0, 3, 4, 1, 5, 6, 2, 7, 8, 0]] 117 | color = colores[mat[e]] 118 | X = xnod[nodos,0] 119 | Y = xnod[nodos,1] 120 | Z = xnod[nodos,2] 121 | 122 | ax.plot3D(X, Y, Z, '-', lw=0.8, c=color) 123 | if mostrar_nodos: 124 | ax.plot3D(xnod[LaG[e],0], xnod[LaG[e],1], xnod[LaG[e],2], 125 | 'ko', ms=5, mfc='r') 126 | if mostrar_num_elem: 127 | # se calcula la posición del centro de gravedad del EF 128 | cg[e] = np.mean(xnod[LaG[e]], axis=0) 129 | 130 | # y se reporta el número del elemento actual 131 | ax.text(cg[e,0], cg[e,1], cg[e,2], f'{e+1}', 132 | horizontalalignment='center', 133 | verticalalignment='center', color=color) 134 | if mostrar_num_nodo: 135 | for i in range(nno): 136 | ax.text(xnod[i,0], xnod[i,1], xnod[i,2], f'{i+1}', color='r') 137 | fig.suptitle(f'Malla de EF Shell ({elem})', fontsize='x-large') 138 | ax.set_xlabel('x') 139 | ax.set_ylabel('y') 140 | ax.set_zlabel('z') 141 | ax.view_init(45, 45) 142 | 143 | elif tipo=='2D': 144 | xnod = xnod_from_msh(file, 2) 145 | nno = xnod.shape[0] 146 | 147 | cg = np.empty((nef,2)) 148 | colores = list(BASE_COLORS.values()) + list(TABLEAU_COLORS.values()) 149 | fig = plt.figure(figsize=(12, 12)) 150 | ax = plt.gca() 151 | for e in range(nef): 152 | if elem=='T3' or elem=='Q4': 153 | nodos = np.r_[LaG[e], LaG[e, 0]] 154 | elif elem =='T6': 155 | nodos = LaG[e, [0, 3, 1, 4, 2, 5, 0]] 156 | elif elem =='Q8' or elem == 'Q9': 157 | nodos = LaG[e, [0, 4, 1, 5, 2, 6, 3, 7, 0]] 158 | elif elem == 'T10': 159 | nodos = LaG[e, [0, 3, 4, 1, 5, 6, 2, 7, 8, 0]] 160 | color = colores[mat[e]] 161 | X = xnod[nodos,0] 162 | Y = xnod[nodos,1] 163 | ax.plot(X, Y, '-', lw=0.8, c=color) 164 | if mostrar_nodos: 165 | ax.plot(xnod[LaG[e],0], xnod[LaG[e],1], 'ko', ms=5, mfc='r') 166 | if mostrar_num_elem: 167 | # se calcula la posición del centro de gravedad del EF 168 | cg[e] = np.mean(xnod[LaG[e]], axis=0) 169 | 170 | # y se reporta el número del elemento actual 171 | ax.text(cg[e,0], cg[e,1], f'{e+1}', horizontalalignment='center', 172 | verticalalignment='center', color=color) 173 | if mostrar_num_nodo: 174 | for i in range(nno): 175 | ax.text(xnod[i,0], xnod[i,1], f'{i+1}', color='r') 176 | fig.suptitle(f'Malla de EF 2D ({elem})', fontsize='x-large') 177 | ax.set_xlabel('x') 178 | ax.set_ylabel('y') 179 | ax.set_aspect('equal', adjustable='box') 180 | else: 181 | raise ValueError('El argumento "tipo" introducido no es válido') -------------------------------------------------------------------------------- /Axisimetrico/malla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Axisimetrico/malla.png -------------------------------------------------------------------------------- /Axisimetrico/malla_boussinesq.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Malla de elementos finitos para el problema de Boussinesq. 4 | EF a utilizar: Cuadrilátero serendípito de 8 nodos 5 | 6 | Por: Alejandro Hincapié G. 7 | """ 8 | 9 | import gmsh 10 | 11 | gmsh.initialize() 12 | gmsh.option.setNumber("General.Terminal", 1) 13 | gmsh.model.add("modelo_ejm") 14 | 15 | 16 | geom = gmsh.model.geo 17 | h = 10 18 | L = 10 19 | n = 20 # No. de EF en el lado largo 20 | m = 10 # No. de EF en el lado cargado 21 | 22 | p1 = geom.addPoint(0, -h, 0, 1) 23 | p2 = geom.addPoint(L, -h, 0, 1) 24 | p3 = geom.addPoint(L, 0, 0, 1) 25 | p4 = geom.addPoint(1, 0, 0, 0.05) 26 | p5 = geom.addPoint(0, 0, 0, 0.05) 27 | 28 | l1 = geom.addLine(p1, p2) 29 | l2 = geom.addLine(p2, p3) 30 | l3 = geom.addLine(p3, p4) 31 | l4 = geom.addLine(p4, p5) 32 | l5 = geom.addLine(p5, p1) 33 | 34 | 35 | 36 | geom.addCurveLoop([l1, l2, l3, l4, l5], 1) 37 | s1 = geom.addPlaneSurface([1]) 38 | 39 | 40 | 41 | gmsh.model.addPhysicalGroup(1, [l4], 11) 42 | gmsh.model.setPhysicalName(1, 11, "carga_0_-4000") 43 | 44 | gmsh.model.addPhysicalGroup(1, [l1, l2], 12) 45 | gmsh.model.setPhysicalName(1, 12, "borde_res_xy") 46 | 47 | gmsh.model.addPhysicalGroup(1, [l5], 13) 48 | gmsh.model.setPhysicalName(1, 13, "borde_res_x") 49 | 50 | gmsh.model.addPhysicalGroup(2, [s1], 101) 51 | gmsh.model.setPhysicalName(2, 101, "mat_1e6_0.3_0") 52 | 53 | geom.synchronize() 54 | gmsh.option.setNumber("Mesh.RecombineAll", 1) 55 | gmsh.option.setNumber("Mesh.Algorithm", 1) 56 | gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 1) 57 | gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 3) 58 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) 59 | gmsh.option.setNumber('Mesh.Points', 1) 60 | 61 | gmsh.model.mesh.generate(2) 62 | gmsh.model.mesh.setOrder(2) 63 | 64 | 65 | gmsh.write("malla_boussinesq.msh") 66 | 67 | gmsh.fltk.run() 68 | 69 | gmsh.finalize() -------------------------------------------------------------------------------- /Axisimetrico/malla_grupos_fisicos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Axisimetrico/malla_grupos_fisicos.png -------------------------------------------------------------------------------- /Axisimetrico/readme.md: -------------------------------------------------------------------------------- 1 | # Modificación de un programa de análisis por MEF para el caso axisimétrico, usando elementos finitos serendípitos de 8 nodos. 2 | 3 | ## Objetivo: 4 | Modificar el programa dado [ejemplo_Q8_axisimetrico_original.py](ejemplo_Q8_axisimetrico_original.py) (por: Diego A. Álvarez) de tal manera los datos de entrada puedan ser leídos directamente desde una malla creada en GMSH, haciendo uso de los grupos físicos. 5 | 6 | El programa con todas las modificaciones respectivas es el siguiente: [ejemplo_Q8_axisimetrico_modificado.py](ejemplo_Q8_axisimetrico_modificado.py). 7 | 8 | ## Descripción de modificaciones realizadas: 9 | 10 | ### Lectura de propiedades de material 11 | Cada superficie distinta creada en GMSH corresponderá en esencia a un "material diferente", incluso si sus propiedades fuesen iguales, ya que GMSH reportará los elementos finitos como pertenecientes a una entidad distinta. 12 | Para cada superficie se debe asignar un grupo físico con la sintaxis `mat_E_nu_rho`, por ejemplo: Si se trata de un material con **E = 1×10⁸ Pa**, **ν = 0.3** y **ρ = 2400 kg/m³**, la superficie física asociada debería llamarse `mat_1e8_0.3_2400`, el programa leerá los números entre guiones bajos en ese orden, siempre y cuando estén escritos de tal modo que Python los pueda convertir a flotantes. 13 | 14 | **Importante:** Al crear la malla, las etiquetas grupos físicos asociados a cada superficie deben tener el mismo orden que las etiquetas de las superficies respectivas, pues GMSH reporta tanto grupos físicos como entidades en orden creciente de etiquetas. 15 | 16 | ### Lectura de condiciones de frontera: 17 | 18 | Las condiciones de frontera se pueden aplicar sobre puntos físicos o sobre curvas físicas. Para el caso de los puntos con ciertas condiciones de apoyo, el grupo físico se debe crear, según el tipo de restricción, así: 19 | 20 | - **Punto con desplazamiento restringido en x, y**: Sintaxis nombre del grupo físico: `punto_res_xy` 21 | - **Punto con desplazamiento restringido en x**: Sintaxis nombre del grupo físico: `punto_res_x` 22 | - **Punto con desplazamiento restringido en y**: Sintaxis nombre del grupo físico: `punto_res_y` 23 | 24 | Para el caso de bordes con condiciones de apoyo aplicadas, los grupos físicos se manejan de manera similar al caso anterior, en este caso: 25 | - **Borde con desplazamiento restringido en x, y (empotrado)**: Sintaxis nombre del grupo físico: `borde_res_xy` 26 | - **Borde con desplazamiento restringido en x**: Sintaxis nombre del grupo físico: `borde_res_x` 27 | - **Borde con desplazamiento restringido en y**: Sintaxis nombre del grupo físico: `borde_res_y` 28 | 29 | ### Lectura de cargas puntuales: 30 | 31 | Las cargas puntales se deben reportar en grupos físicos de dimensión 0, es decir puntos físicos. Estos deben llevar un nombre con la siguiente sintaxis: `puntual_Px_Py`, esto implica que en el punto (o puntos) pertenecientes a este grupo físico se quiere aplicar una fuerza puntual de componentes ortogonales **Px** y **Py** (con su respectivo signo). 32 | 33 | ### Lectura de cargas distribuidas sobre un borde: 34 | 35 | Las cargas distribuidas (fuerzas superficiales) se reportan sobre curvas físicas. En este caso se tiene una restricción y es que bajo estas condiciones el programa solo permite leer cargas distribuidas de magnitud y dirección uniformes sobre la curva en cuestión. En este caso, una carga distribuida de componentes **fx** y **fy** (lo cual implica que la carga puede tener alguna inclinación) corresponderá a la siguiente sintaxis en el nombre del grupo físico: `carga_fx_fy` (con su respectivo signo). 36 | 37 | 38 | ## Ejemplo del uso del programa: 39 | 40 | Se resolvió por el MEF el siguiente problema axisimétrico: 41 | 42 | ![Ejemplo](ejemplo.png) 43 | 44 | Para modelar este problema se creó la siguiente malla (nótese los nombres de los grupos físicos para indicar restricciones y/o cargas). Esta malla se creó utilizando la API de GMSH para Python por medio del código [malla_boussinesq.py](malla_boussinesq.py). 45 | 46 | ![Grupos](malla_grupos_fisicos.png) 47 | 48 | ![Malla](malla.png) 49 | 50 | Finalmente tras correr el análisis se obtuvo la siguiente deformada (escalada 1000 veces). Los demás resultados se pueden constatar al ejecutar el [código respectivo](ejemplo_Q8_axisimetrico_modificado.py). 51 | 52 | ![Deformada](deformada.png) 53 | -------------------------------------------------------------------------------- /Mallas_GMSH/1_Malla-simple_2d.geo: -------------------------------------------------------------------------------- 1 | // Ejm 1: Malla no estructurada 2 | 3 | tm = 2; 4 | tmr = 0.3; 5 | 6 | Point(1) = {0, 0, 0, tm}; 7 | Point(2) = {40, 0, 0, tm}; 8 | Point(3) = {40, 20, 0, tm}; 9 | Point(4) = {0, 20, 0, tm}; 10 | 11 | r = 3; // Radio del agujero circular 12 | 13 | Point(5) = {20, 10, 0}; 14 | Point(6) = {20+r, 10, 0, tmr}; 15 | Point(7) = {20-r, 10, 0, tmr}; 16 | 17 | Line(1) = {1, 2}; 18 | Line(2) = {2, 3}; 19 | Line(3) = {3, 4}; 20 | Line(4) = {4, 1}; 21 | 22 | Curve Loop(1) = {1, 2, 3, 4}; 23 | 24 | Circle(5) = {6, 5, 7}; 25 | Circle(6) = {7, 5, 6}; 26 | 27 | Curve Loop(2) = {5, 6}; 28 | 29 | Plane Surface(1) = {1, 2}; 30 | 31 | Physical Line("Borde inferior") = {1}; 32 | Physical Surface("Mi superficie") = {1}; 33 | 34 | Mesh 2; 35 | 36 | Mesh.SurfaceFaces = 1; 37 | Mesh.Points = 1; 38 | 39 | Save "ejm_1.msh"; -------------------------------------------------------------------------------- /Mallas_GMSH/2_Malla-estructurada_2d.geo: -------------------------------------------------------------------------------- 1 | // Ejm 2: Malla estructurada 2 | 3 | // Variables asociadas a la geometría 4 | L = 6.5/2; 5 | r = 1; 6 | 7 | // Puntos: 8 | pc = newp; Point(pc) = {0, 0, 0}; // Punto central del círculo 9 | p1 = newp; Point(p1) = {r, 0, 0}; 10 | p2 = newp; Point(p2) = {L, 0, 0}; 11 | p3 = newp; Point(p3) = {L, L, 0}; 12 | p4 = newp; Point(p4) = {0, L, 0}; 13 | p5 = newp; Point(p5) = {0, r, 0}; 14 | p6 = newp; Point(p6) = {r*Cos(Pi/4), r*Sin(Pi/4), 0}; 15 | 16 | // Líneas: 17 | l1 = newl; Line(l1) = {p1, p2}; 18 | l2 = newl; Line(l2) = {p2, p3}; 19 | l3 = newl; Line(l3) = {p3, p4}; 20 | l4 = newl; Line(l4) = {p4, p5}; 21 | l5 = newl; Line(l5) = {p3, p6}; 22 | c1 = newc; Circle(c1) = {p5, pc, p6}; 23 | c2 = newc; Circle(c2) = {p6, pc, p1}; 24 | 25 | // Curve Loops: 26 | cl1 = newll; Curve Loop(cl1) = {l1, l2, l5, c2}; 27 | cl2 = newll; Curve Loop(cl2) = {-l5, l3, l4, c1}; 28 | 29 | // Superficies: 30 | s1 = news; Plane Surface(s1) = {cl1}; 31 | s2 = news; Plane Surface(s2) = {cl2}; 32 | 33 | Physical Surface("Mi superficie") = {s1, s2}; 34 | 35 | // Queremos una malla estructurada con m elementos en las líneas perpendiculares al círculo 36 | // y n elementos en las otras 37 | m = 15; 38 | n = 10; 39 | 40 | Transfinite Curve{c1, l3, c2, l2} = n+1; // Definimos m+1 nodos en líneas perpendiculares al círculo 41 | 42 | Transfinite Curve{l1, l5, l4} = m+1; // Definimos n+1 nodos en las demás líneas 43 | 44 | Transfinite Surface{s1, s2}; // Usar malla 2D estructurada 45 | Recombine Surface{s1, s2}; // Para usar elementos cuadriláteros en vez de triángulos 46 | 47 | Mesh.SurfaceFaces = 1; 48 | Mesh.Points = 1; 49 | 50 | Mesh 2; 51 | 52 | Save "ejm_2.msh"; 53 | -------------------------------------------------------------------------------- /Mallas_GMSH/3_Malla-estructurada-extrude_2d.geo: -------------------------------------------------------------------------------- 1 | // Ejemplo 3: Programa para crear una malla estructurada sencilla usando la función 'extrude'. 2 | // Por: Alejandro Hincapie G. 3 | 4 | 5 | Point(1) = {0, 0, 0}; // Se crea un punto en origen de coordenadas 6 | 7 | // Al extruir el punto se genera una línea. El argumento Layers permite definir cuántos elementos 8 | // queremos a lo largo de esta línea 9 | 10 | l_ext1[] = Extrude {0, 20, 0} { 11 | Point{1}; Layers{10};}; 12 | 13 | l1 = l_ext1[1]; // Se extrae la etiqueta de la línea generada al extruir el punto 1 14 | n = #l_ext1[]; // Número de elementos de la lista generada 15 | 16 | Printf("Extrusión punto 1:"); 17 | For i In {0:n-1} 18 | Printf("l_ext1[%g] = %g", i, l_ext1[i]); 19 | EndFor 20 | 21 | // Ahora extruimos la línea anterior para generar una superficie. El argumento Recombine permite 22 | // que en la superficie generada la malla conste de elementos cuadriláteros en lugar de triángulos 23 | 24 | s_ext1[] = Extrude {40, 0, 0} { 25 | Curve{l1}; Layers{20}; Recombine;}; 26 | 27 | Printf(" "); 28 | Printf("Extrusión línea l1:"); 29 | 30 | For i In {0:#s_ext1[]-1} 31 | Printf("s_ext1[%g] = %g", i, s_ext1[i]); 32 | EndFor 33 | 34 | s1 = s_ext1[1]; // Se extrae la etiqueta de la superficie generada al extruir 35 | 36 | Physical Surface("Superficie 1") = {s1}; 37 | 38 | 39 | //------------------------------------------------------------------------------------------------- 40 | // Crear malla usando función Extrude alrededor de un eje de rotación 41 | //------------------------------------------------------------------------------------------------- 42 | 43 | p1 = newp; Point(p1) = {50, 0, 0}; 44 | l_ext2[] = Extrude {15, 0, 0} {Point{p1}; Layers{10};}; 45 | 46 | l2 = l_ext2[1]; 47 | 48 | // Ahora usamos la función 'Extrude' para generar la superficie 49 | 50 | s_ext2[] = Extrude {{0, 0, 1}, {45, 0, 0}, Pi/2} { 51 | Curve{l2}; Layers{15}; Recombine;}; 52 | 53 | s2 = s_ext2[1]; 54 | 55 | Physical Surface("Superficie 2") = {s2}; 56 | 57 | 58 | // Ajustes finales de la malla 59 | Mesh 2; // Generar la malla 2D 60 | SetOrder 2; // Se definen elementos finitos de orden 2 61 | Mesh.SecondOrderIncomplete = 1; // Usar elementos finitos serendípitos (incompletos) 62 | Mesh.SurfaceFaces = 1; // Ver las "caras" de los elementos finitos 2D 63 | Mesh.Points = 1; // Ver los nodos de la malla 64 | Save "extrude.msh"; 65 | -------------------------------------------------------------------------------- /Mallas_GMSH/4_Scordelis-lo-roof_shell.geo: -------------------------------------------------------------------------------- 1 | // Ejemplo: Malla shell en GMSH 2 | 3 | // Variables de la geometría 4 | 5 | L = 25; // Longitud del cascarón 6 | r = 25; // radio de la superficie 7 | t = 40*Pi/180; // Ángulo de apertura 8 | N = 8; // Tamaño de la malla (N x N) 9 | 10 | 11 | Point(0) = {0, 0, 0}; // Punto central del arco 12 | 13 | // Se crean dos puntos que definan el arco 14 | 15 | Point(1) = {r*Sin(t), 0, r*Cos(t)}; 16 | Point(2) = {0, 0, r}; 17 | 18 | // Se crea el arco y se define el núm. de nodos que tendrá 19 | 20 | Circle(1) = {1, 0, 2}; 21 | Transfinite Line{1} = N+1; 22 | 23 | 24 | // Al extruir este arco se genera la superficie 25 | 26 | superf[] = Extrude {0, L, 0} { 27 | Curve{1}; Layers{N}; 28 | }; 29 | 30 | // Se obtienen tags de: borde derecho, superficie, borde superior y borde inferior 31 | // (el borde izquierdo lógicamente es la curva 1) 32 | 33 | bd = superf[0]; // Borde derecho 34 | s = superf[1]; // Superficie generada 35 | bs = superf[2]; // Borde superior 36 | bi = -superf[3]; // Borde inferior 37 | 38 | Printf("bs = %g, s=%g, bs=%g, bi=%g", bd, s, bs, bi); 39 | 40 | Physical Line("AB") = {bi}; 41 | Physical Line("BC") = {bd}; 42 | Physical Line("CD") = {bs}; 43 | Physical Line("AD") = {1}; 44 | Physical Surface("ABCD") = {s}; 45 | 46 | Transfinite Surface{s}; // Malla 2D estructurada 47 | Recombine Surface{s}; // Usar EF cuadriláteros 48 | 49 | Mesh 2; 50 | Mesh.SurfaceFaces = 1; 51 | Mesh.Points = 1; 52 | General.Axes = 1; // Para que aparezcan los ejes en la interfaz 53 | 54 | Save "malla_shell.msh"; -------------------------------------------------------------------------------- /Mallas_GMSH/6_Paraboloide_hiperbolico.geo: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // Ejemplo 6: Malla de elementos Shell de un paraboloide hiperbólico 3 | 4 | // Por: Alejandro Hincapié G. 5 | //--------------------------------------------------------------------- 6 | N = 8; // Malla estructurada de 2N x N elementos 7 | 8 | //--------------------------------------------------------------------- 9 | // La superficie a generar es el paraboloide hiperbólico definido por 10 | // la función f(x, y) = y^2 - x^2 en los intervalos 0 <= x <= 1/2 y 11 | // -1/2 <= y <= 1/2. 12 | //--------------------------------------------------------------------- 13 | // Para generar la superficie se deben crear sus 4 bordes que están 14 | // definidos por ciertas parábolas de la forma f(x) = ax^2+bx+c: 15 | 16 | //--------------------------------------------------------------------- 17 | // Lado AB: Parábola z = y^2 - 1/4 18 | //--------------------------------------------------------------------- 19 | a = 1; b = 0; c = -1/4; 20 | x = 1/2; // Esta parábola está sobre el plano x=1/2 21 | 22 | y1 = -1/2; y2 = 1/2; // Coordenadas inicial y final en Y 23 | 24 | plano = 2; // La variable plano será: 1 para xz o 2 para yz 25 | 26 | // Se crean puntos inicial y final de esta parábola 27 | pA = newp; Point(pA) = {x, y1, y1^2-x^2}; 28 | pB = newp; Point(pB) = {x, y2, y2^2-x^2}; 29 | 30 | //--------------------------------------------------------------------- 31 | // Se crea una macro para convertir la parábola en una curva de Bezier: 32 | 33 | Macro obtener_pc_bezier 34 | // A diferencia de Python, en el lenguaje del GMSH no se pueden 35 | // crear funciones que tomen argumentos, en su lugar se deben crear 36 | // Macros que toman y crean variables globales en el programa 37 | //----------------------------------------------------------------- 38 | // Ésta usa los parámetros de una parábola f(x) = ax^2+bx+c o 39 | // f(y) = ay^2 + by + c y obtiene el punto de control que permite 40 | // dibujarla como una curva de Bezier de 3 puntos 41 | 42 | If (plano == 2) // Si la parábola está en plano YZ 43 | Cy = (y1+y2)/2; 44 | Cz = (y2-y1)/2 * (2*a*y1 + b) + (a*y1^2+b*y1+c); 45 | Else // Si la parábola está en plano XZ 46 | Cx = (x1+x2)/2; 47 | Cz = (x2-x1)/2 * (2*a*x1 + b) + (a*x1^2+b*x1+c); 48 | EndIf 49 | 50 | Return 51 | //--------------------------------------------------------------------- 52 | 53 | Call obtener_pc_bezier; // Llamando la Macro obtenemos punto de control 54 | 55 | pc1 = newp; Point(pc1) = {x, Cy, Cz}; 56 | Bezier(1) = {pA, pc1, pB}; 57 | 58 | //--------------------------------------------------------------------- 59 | // Lado BC (parábola z = 1/4 - x^2) 60 | //--------------------------------------------------------------------- 61 | a = -1; b = 0; c = 1/4; 62 | y = 1/2; // Esta parábola está sobre el plano y = 1/2 63 | 64 | x1 = 1/2; x2 = 0; 65 | plano = 1; 66 | 67 | pC = newp; Point(pC) = {x2, y, y^2-x2^2}; 68 | 69 | Call obtener_pc_bezier; 70 | 71 | pc2 = newp; Point(pc2) = {Cx, y, Cz}; 72 | Bezier(2) = {pB, pc2, pC}; 73 | 74 | //--------------------------------------------------------------------- 75 | // Lado CD (parábola z = y^2) 76 | //--------------------------------------------------------------------- 77 | a = 1; b = 0; c = 0; 78 | x = 0; // Esta parábola está sobre el plano x=0 79 | 80 | y1 = 1/2; y2 = -1/2; 81 | plano = 2; // Plano yz 82 | 83 | pD = newp; Point(pD) = {x, y2, y2^2-x^2}; 84 | 85 | Call obtener_pc_bezier; 86 | 87 | pc3 = newp; Point(pc3) = {x, Cy, Cz}; 88 | Bezier(3) = {pC, pc3, pD}; 89 | 90 | //--------------------------------------------------------------------- 91 | // Lado AD (parábola z = 1/4-x^2) 92 | //--------------------------------------------------------------------- 93 | a = -1; b = 0; c = 1/4; 94 | y = -1/2; // Esta parábola está sobre el plano y = 1/2 95 | 96 | x1 = 0; x2 = 1/2; 97 | plano = 1; // Plano xz 98 | 99 | Call obtener_pc_bezier; 100 | 101 | pc4 = newp; Point(pc4) = {Cx, y, Cz}; 102 | Bezier(4) = {pD, pc4, pA}; 103 | 104 | //--------------------------------------------------------------------- 105 | // Se crea la superficie 106 | //--------------------------------------------------------------------- 107 | Curve Loop(1) = {1, 2, 3, 4}; 108 | Surface(1) = {1}; 109 | Physical Surface("ABCD") = {1}; 110 | //--------------------------------------------------------------------- 111 | // Se define la malla como estructurada 112 | //--------------------------------------------------------------------- 113 | Transfinite Curve{1, 3} = 2*N + 1; 114 | Transfinite Curve{2, 4} = N + 1; 115 | 116 | Transfinite Surface{1}; 117 | 118 | //--------------------------------------------------------------------- 119 | // Se crea y guarda la malla 120 | //--------------------------------------------------------------------- 121 | Mesh 2; 122 | Save "paraboloide.msh"; 123 | 124 | General.Axes = 1; // Mostrar ejes de coordenadas simples 125 | Mesh.SurfaceFaces = 1; // Ver las "caras de los EF 2D 126 | -------------------------------------------------------------------------------- /Mallas_GMSH/7_Ejemplos_curvas_spline.geo: -------------------------------------------------------------------------------- 1 | // Curva del seno entre 0 y 2Pi: 2 | x = 0; 3 | 4 | For i In {1:61} 5 | Point(i) = {x, Sin(x), 0}; 6 | x += Pi/30; 7 | EndFor 8 | 9 | Spline(1) = {1:61}; 10 | 11 | //------------------------------------------------ 12 | // Curva polar rosa de 4 pétalos: 13 | y = 3; 14 | 15 | t = 0; 16 | 17 | For i In {100:161} 18 | r = Sin(2*t); 19 | Point(i) = {r*Cos(t), r*Sin(t) + y, 0}; 20 | t += Pi/30; 21 | EndFor 22 | 23 | Spline(2) = {100:161}; 24 | 25 | //------------------------------------------------- 26 | // Curva polar Nefroide de Freeth: 27 | x = 6; 28 | y = 5; 29 | 30 | t = 0; 31 | 32 | For i In {200:261} 33 | r = 1 + 2*Sin(t/2); 34 | Point(i) = {r*Cos(t) + x, r*Sin(t) + y, 0}; 35 | t += Pi/15; 36 | EndFor 37 | 38 | Spline(3) = {200:261}; 39 | -------------------------------------------------------------------------------- /Mallas_GMSH/7_Malla_con_splines.geo: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // Malla para el perfil de un ala (Airfoil) usando B-Splines en GMSH 3 | // 4 | // Por: Alejandro Hincapié G. 5 | //----------------------------------------------------------------------------------- 6 | 7 | L = 1; // Longitud desde el centro de la malla interior hasta los bordes 8 | 9 | //----------------------------------------------------------------------------------- 10 | // Se crean los puntos de control de la B-Spline: 11 | //----------------------------------------------------------------------------------- 12 | Point(1) = {1, 0, 0}; 13 | Point(2) = {0.8, 0.027, 0}; 14 | Point(3) = {0.55, 0.058, 0}; 15 | Point(4) = {0.3, 0.08, 0}; 16 | Point(5) = {0.15, 0.078, 0}; 17 | Point(6) = {0, 0.052, 0}; 18 | Point(7) = {0, 0, 0}; 19 | Point(8) = {0, -0.01, 0}; 20 | Point(9) = {0.15, -0.052, 0}; 21 | Point(10) = {0.3, -0.05, 0}; 22 | Point(11) = {0.55, -0.043, 0}; 23 | Point(12) = {0.8, -0.017, 0}; 24 | 25 | //----------------------------------------------------------------------------------- 26 | // Se crea la B-Spline tomando los puntos anteriores como puntos de control 27 | //----------------------------------------------------------------------------------- 28 | af = newl; BSpline(af) = {1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 1}; 29 | 30 | Transfinite Curve(af) = 301; // Refinamos malla en el contorno del airfoil para adaptar a la curvatura 31 | 32 | cint = newll; Curve Loop(cint) = {af}; // Curve loop interior 33 | 34 | 35 | //----------------------------------------------------------------------------------- 36 | // Se crea el contorno de la superficie exterior 37 | //----------------------------------------------------------------------------------- 38 | p1 = newp; Point(p1) = {0.5-L, -L, 0}; 39 | p2 = newp; Point(p2) = {0.5+L, -L, 0}; 40 | p3 = newp; Point(p3) = {0.5+L, L, 0}; 41 | p4 = newp; Point(p4) = {0.5-L, L, 0}; 42 | 43 | l1 = newl; Line(l1) = {p1, p2}; 44 | l2 = newl; Line(l2) = {p2, p3}; 45 | l3 = newl; Line(l3) = {p3, p4}; 46 | l4 = newl; Line(l4) = {p4, p1}; 47 | 48 | cext = newll; Curve Loop(cext) = {l1, l2, l3, l4}; // Curve loop exterior 49 | 50 | //----------------------------------------------------------------------------------- 51 | // Se genera la superficie 52 | //----------------------------------------------------------------------------------- 53 | s1 = news; Plane Surface(s1) = {cext, cint}; // Superficie externa 54 | 55 | Physical Surface("Aire") = {s1}; 56 | 57 | //----------------------------------------------------------------------------------- 58 | // Finalmente se crea la malla y se guarda: 59 | //----------------------------------------------------------------------------------- 60 | 61 | Mesh 2; 62 | 63 | SetOrder 2; // Creamos una malla con elementos de orden 2: 64 | 65 | Save "airfoil.msh"; 66 | 67 | Mesh.SurfaceFaces = 1; 68 | Mesh.Points = 1; 69 | -------------------------------------------------------------------------------- /Mallas_GMSH/malla_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_1.png -------------------------------------------------------------------------------- /Mallas_GMSH/malla_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_2.png -------------------------------------------------------------------------------- /Mallas_GMSH/malla_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_3.png -------------------------------------------------------------------------------- /Mallas_GMSH/malla_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_4.png -------------------------------------------------------------------------------- /Mallas_GMSH/malla_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_5.png -------------------------------------------------------------------------------- /Mallas_GMSH/malla_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_6.jpg -------------------------------------------------------------------------------- /Mallas_GMSH/malla_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_7.png -------------------------------------------------------------------------------- /Mallas_GMSH/malla_7_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_GMSH/malla_7_2.png -------------------------------------------------------------------------------- /Mallas_GMSH/readme.md: -------------------------------------------------------------------------------- 1 | # Mallas 2D creadas con GMSH utilizando el lenguaje nativo del programa (formato .geo) 2 | 3 | ### Ejemplo #1: Malla 2D no estructurada con un agujero en la mitad 4 | ![Malla 1](malla_1.png) 5 | 6 | ### Ejemplo #2: Malla 2D estructurada usando las funciones *Transfinite Curve* y *Transfinite Surface* 7 | ![Malla 2](malla_2.png) 8 | 9 | ### Ejemplo #3: Malla 2D estructurada usando la función *extrude* 10 | ![Malla 3](malla_3.png) 11 | 12 | ### Ejemplo #4: Malla de elementos Shell estructurada, para el problema *Scordelis lo roof* 13 | ![Malla 4](malla_4.png) 14 | 15 | ### Ejemplo #5: Malla de elementos Shell estructurada para el problema *Twisted beam* 16 | ![Malla 5](malla_5.png) 17 | 18 | ### Ejemplo #6: Malla de elementos Shell estructurada para una superficie de *Paraboloide hiperbólico* 19 | ![Malla 6](malla_6.jpg) 20 | 21 | ### Ejemplo #7: Malla 2D de un perfil alar (*airfoil*) utilizando la función B-Spline 22 | ![Malla 7](malla_7.png) 23 | ![Malla 72](malla_7_2.png) 24 | -------------------------------------------------------------------------------- /Mallas_python/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | 3 | -------------------------------------------------------------------------------- /Mallas_python/1_Malla-simple_2d.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Programa para crear una malla básica en GMSH usando la API de Python. 4 | 5 | Por: Alejandro Hincapié G. 6 | ''' 7 | 8 | import gmsh 9 | 10 | # %% Para comenzar a usar funciones de la API de GMSH se debe incializar: 11 | 12 | gmsh.initialize() 13 | 14 | # %% Con este comando se activa la opción de que GMSH imprima mensajes en la 15 | # consola, ya que por defecto no lo hace: 16 | 17 | gmsh.option.setNumber("General.Terminal", 1) 18 | 19 | # %% Podemos crear un nuevo modelo con la siguiente línea. Esta línea es opcio- 20 | # nal, si no se usa el programa genera un modelo "Unnamed" por defecto. 21 | 22 | gmsh.model.add("modelo_1") 23 | 24 | # %% Se crean las primeras entidades: Los puntos (dimensión 0): 25 | 26 | # En este programa se usará el kernel de geometría que trae por defecto el 27 | # GMSH, no es el único, pero para este caso funciona bastante bien. 28 | 29 | # Sintaxis: gmsh.model.geo.addPoint(x, y, z, tm, tag) 30 | # tm = Tamaño de malla en el punto 31 | 32 | tm = 2 # Tamaño de malla a utilizar en todos los puntos 33 | tmr = 0.3 # Tamaño de malla refinada (alrededor del agujero) 34 | 35 | gmsh.model.geo.addPoint(0, 0, 0, tm, 1) # Punto 1 coord (0,0,0) 36 | gmsh.model.geo.addPoint(40, 0, 0, tm, 2) # Punto 2 coord (40,0,0) ... 37 | gmsh.model.geo.addPoint(40, 20, 0, tm, 3) 38 | gmsh.model.geo.addPoint(0, 20, 0, tm, 4) 39 | 40 | # Se deben definir 3 puntos que permitan crear luego el círculo. El centro, un 41 | # punto a la izquierda y otro a la derecha: 42 | 43 | r = 3 # radio del círculo 44 | 45 | gmsh.model.geo.addPoint(20, 10, 0, tm, 5) # Punto central del círculo 46 | gmsh.model.geo.addPoint(20+r, 10, 0, tmr, 6) 47 | gmsh.model.geo.addPoint(20-r, 10, 0, tmr, 7) 48 | 49 | # %% Se crean las siguientes entidades: Las curvas (dimensión 1): 50 | 51 | # Primero las líneas rectas de los bordes: 52 | # Sintaxis: gmsh.model.geo.addLine(punto inicial, punto final, tag) 53 | 54 | gmsh.model.geo.addLine(1, 2, 1) # Éste sería el borde inferior 55 | gmsh.model.geo.addLine(2, 3, 2) # ... 56 | gmsh.model.geo.addLine(3, 4, 3) 57 | gmsh.model.geo.addLine(4, 1, 4) 58 | 59 | # Ahora los arcos de circunferencia para el agujero (se deben crear 2 arcos, ya 60 | # que el kernel built-in no permite crear arcos con un ángulo mayor a 180°) 61 | # Sintaxis: gmsh.model.geo.addCircle(p. inicial, p. centro, p. final, tag) 62 | 63 | gmsh.model.geo.addCircleArc(6, 5, 7, 5) # Arco superior 64 | gmsh.model.geo.addCircleArc(7, 5, 6, 6) # Arco inferior 65 | 66 | # %% Ahora se define la siguiente entidad: La superficie (dimensión 2): 67 | 68 | # Para definir superficies, primero se deben definir 'Curve Loops' que las 69 | # limiten. En este caso un Curve Loop será el borde exterior, y el otro será 70 | # el agujero. 71 | 72 | gmsh.model.geo.addCurveLoop([1, 2, 3, 4], 1) 73 | gmsh.model.geo.addCurveLoop([5, 6], 2) 74 | 75 | # Ahora sí se puede definir la superficie, así: 76 | #Sintaxis: gmsh.model.geo.addPlaneSurface([Lista de Curve Loops], tag), donde: 77 | # En la lista de Curve Loops el primer elemento es el loop que define 78 | # el contorno de la superficie, los demás son agujeros dentro de ella. 79 | 80 | gmsh.model.geo.addPlaneSurface([1, 2], 1) 81 | 82 | # %% Ahora se crean los grupos físicos que se requieran 83 | #Por defecto, si hay grupos físicos definidos, GMSH solo reporta elementos fini- 84 | #tos que pertenezcan a algún grupo físico. En este caso se crearán dos: 85 | # - Una superficie física que contenga nuestra superficie creada 86 | # - Una curva física que contenga el borde inferior 87 | 88 | # Sintaxis: gmsh.model.addPhysicalGroup(dimensión, lista de entidades, tag) 89 | 90 | s = gmsh.model.addPhysicalGroup(2, [1]) # En este caso no se especifica tag 91 | gmsh.model.setPhysicalName(2, s, "Mi superficie") # Se puede definir un nombre 92 | 93 | gmsh.model.addPhysicalGroup(1, [1], 101) # Puedo especificar tag manualmente 94 | gmsh.model.setPhysicalName(1, 101, "Borde inferior") 95 | 96 | # %% Antes de mallar, se debe sincronizar la representación CAD del GMSH con el 97 | # modelo actual: 98 | 99 | gmsh.model.geo.synchronize() 100 | 101 | # %% Ahora sí se procede a crear la malla: 102 | 103 | gmsh.model.mesh.generate(2) 104 | 105 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) # Ver las "caras" de los elementos finitos 2D 106 | gmsh.option.setNumber('Mesh.Points', 1) # Ver los nodos de la malla 107 | 108 | 109 | # Y finalmente guardar la malla 110 | filename = 'ejm_1.msh' 111 | gmsh.write(filename) 112 | 113 | # Podemos visualizar el resultado en la interfaz gráfica de GMSH 114 | gmsh.fltk.run() 115 | 116 | # %% Tras finalizar el proceso se recomienda usar este comando 117 | gmsh.finalize() 118 | 119 | # %% Podemos graficar la malla para ver el resultado: 120 | 121 | from leer_GMSH import plot_msh # Funciones para leer y graficar la malla 122 | 123 | #plot_msh(filename, '2D', True, True, True) 124 | -------------------------------------------------------------------------------- /Mallas_python/2_Malla-estructurada_2d.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Programa para crear una malla estructurada en GMSH usando la API de Python. 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | import gmsh 9 | from math import cos, sin, pi 10 | 11 | 12 | gmsh.initialize() 13 | gmsh.option.setNumber("General.Terminal", 1) 14 | 15 | gmsh.model.add("modelo_2") 16 | 17 | 18 | # %% Variables asociadas a la geometría: 19 | L = 6.5/2 20 | r = 1 21 | 22 | # %% Se crea la geometría: 23 | 24 | # La siguiente línea permite abreviar el código: 25 | geom = gmsh.model.geo 26 | 27 | # Puntos: 28 | pc = geom.addPoint(0, 0, 0) # Punto central del círculo 29 | p1 = geom.addPoint(r, 0, 0) 30 | p2 = geom.addPoint(L, 0, 0) 31 | p3 = geom.addPoint(L, L, 0) 32 | p4 = geom.addPoint(0, L, 0) 33 | p5 = geom.addPoint(0, r, 0) 34 | p6 = geom.addPoint(r*cos(pi/4), r*sin(pi/4), 0) 35 | 36 | 37 | # Líneas 38 | l1 = geom.addLine(p1, p2) 39 | l2 = geom.addLine(p2, p3) 40 | l3 = geom.addLine(p3, p4) 41 | l4 = geom.addLine(p4, p5) 42 | l5 = geom.addLine(p3, p6) 43 | c1 = geom.addCircleArc(p5, pc, p6) 44 | c2 = geom.addCircleArc(p6, pc, p1) 45 | 46 | # Curve loops: 47 | cl1 = geom.addCurveLoop([l1, l2, l5, c2]) 48 | cl2 = geom.addCurveLoop([-l5, l3, l4, c1]) 49 | 50 | 51 | # Dado que queremos una malla estructurada, definimos el número de elementos 52 | # que queremos en ciertas líneas: 53 | # Sintaxis: gmsh.model.geo.mesh.setTransfiniteCurve(tag, # nodos) 54 | 55 | m = 15 # Líneas perpendiculares al círculo 56 | n = 10 # Líneas en el sentido del círculo 57 | 58 | for tag in [c1, l3, c2, l2]: 59 | gmsh.model.geo.mesh.setTransfiniteCurve(tag, n+1) 60 | for tag in [l5, l4, l1]: 61 | gmsh.model.geo.mesh.setTransfiniteCurve(tag, m+1) 62 | 63 | # Finalmente creamos la superficie: 64 | s1 = geom.addPlaneSurface([cl1]) 65 | s2 = geom.addPlaneSurface([cl2]) 66 | # Definimos la superficie física: 67 | gmsh.model.addPhysicalGroup(2, [s1, s2], 101) 68 | gmsh.model.setPhysicalName(2, 101, 'mi superficie') 69 | 70 | 71 | # %% Definimos que la superficie creada tenga una malla estructurada: 72 | geom.mesh.setTransfiniteSurface(s1) 73 | geom.mesh.setTransfiniteSurface(s2) 74 | 75 | # Y recombinamos para que use elementos cuadriláteros en lugar de triángulos 76 | geom.mesh.setRecombine(2, s1) 77 | geom.mesh.setRecombine(2, s2) 78 | 79 | 80 | # %% Finalmente se crea la malla y se guarda: 81 | 82 | geom.synchronize() 83 | 84 | # Ver las "caras" de los elementos finitos 2D 85 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) 86 | 87 | # Ver los nodos de la malla 88 | gmsh.option.setNumber('Mesh.Points', 1) 89 | 90 | gmsh.model.mesh.generate(2) 91 | 92 | # Se guarda la malla 93 | filename = 'ejm_2.msh' 94 | gmsh.write(filename) 95 | 96 | # Podemos visualizar el resultado en la interfaz gráfica de GMSH 97 | gmsh.fltk.run() 98 | 99 | # Se finaliza el programa 100 | gmsh.finalize() 101 | 102 | # %% Podemos graficar la malla para ver el resultado: 103 | 104 | from leer_GMSH import plot_msh # Funciones para leer y graficar la malla 105 | 106 | #plot_msh(filename, '2D', True) 107 | -------------------------------------------------------------------------------- /Mallas_python/3_Malla-estructurada-extrude_2d.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Ejemplo 3: Programa para crear una malla estructurada sencilla usando las fun- 4 | ciones 'extrude' y 'revolve'. 5 | 6 | Por: Alejandro Hincapié G. 7 | """ 8 | 9 | import gmsh 10 | from math import pi 11 | 12 | 13 | # Se inicializa el modelo 14 | gmsh.initialize() 15 | gmsh.option.setNumber("General.Terminal", 1) 16 | gmsh.model.add("modelo_3") 17 | 18 | # Línea para abreviar el código 19 | geom = gmsh.model.geo 20 | 21 | # Se crean las entidades 22 | geom.addPoint(0, 0 ,0, tag=1) 23 | 24 | # Podemos generar una línea extruyendo el punto creado anteriormente 25 | l_ext1 = geom.extrude([(0, 1)], 0, 20, 0, numElements=[10]) 26 | 27 | print('\nExtrusión punto 1 = ', l_ext1) # Se muestra la lista creada al extruir 28 | 29 | # ============================================================================= 30 | # En la lista que se crea al extruir una entidad, el segundo elemento siempre 31 | # corresponde a la entidad de dimensión superior generada. En este caso, al ex- 32 | # truir un punto (dim=2), la entidad de orden superior que se genera es una linea 33 | # (dim=1). En la lista creada, cada elemento corresponde a una tupla (dim, tag) 34 | # ============================================================================= 35 | l1 = l_ext1[1] # Se obtiene la tupla (dim, tag) de la línea recién creada 36 | 37 | # Se crea la superficie extruyendo la línea anterior 38 | # ============================================================================= 39 | # Sintaxis: gmsh.model.geo.extrude([lista de entidades], u, v, w), donde: 40 | # - La lista de entidades consiste en pares (dim, tag) 41 | # - u, v, w son las componentes del vector de traslación 42 | # - El argumento numElements nos permite especificar el número de 43 | # "capas" creadas al extruir 44 | # - El argumento recombine fuerza que la malla estructurada que se 45 | # genera conste de cuadriláteros en lugar de triángulos 46 | # ============================================================================= 47 | 48 | s_ext1 = geom.extrude([l1], 40, 0, 0, numElements=[20], recombine=True) 49 | print('\nExtrusión curva l1 =', s_ext1, '\n') # Se muestra la lista generada al extruir la línea 50 | 51 | s1 = s_ext1[1] # Y nuevamente obtenemos su dupla (dim, tag) 52 | 53 | # Se crea la superficie física 54 | gmsh.model.addPhysicalGroup(2, [s1[1]], 21) 55 | gmsh.model.setPhysicalName(2, 21, 'superficie 1') 56 | 57 | # ============================================================================= 58 | # Creamos la segunda malla usando la función 'Revolve' 59 | # ============================================================================= 60 | p1 = geom.addPoint(50, 0, 0) 61 | 62 | # Extruimos el punto para generar una línea 63 | l_ext2 = geom.extrude([(0, p1)], 15, 0, 0, numElements=[10]) 64 | l2 = l_ext2[1] # Obtenemos tag de la línea generada 65 | 66 | # Extruimos esta línea alrededor de un eje de rotación usando la función 'Revolve' 67 | s_ext2 = geom.revolve([l2], 45, 0, 0, 0, 0, 1, pi/2, numElements=[15], recombine=True) 68 | s2 = s_ext2[1] 69 | 70 | gmsh.model.addPhysicalGroup(2, [s2[1]], 22) 71 | gmsh.model.setPhysicalName(2, 22, 'superficie 2') 72 | 73 | 74 | # Se sincroniza el modelo y se crea la malla 75 | geom.synchronize() 76 | 77 | gmsh.model.mesh.generate(2) 78 | 79 | # Creamos una malla con elementos de orden 2 80 | gmsh.model.mesh.setOrder(2) 81 | 82 | # Usar elementos serendípitos (incompletos) 83 | gmsh.option.setNumber('Mesh.SecondOrderIncomplete', 1) 84 | 85 | # Para mejorar la visualización 86 | gmsh.option.setNumber('Mesh.Points', 1) 87 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) 88 | 89 | gmsh.fltk.run() # Muestra el resultado en la interfaz gráfica 90 | 91 | gmsh.finalize() 92 | -------------------------------------------------------------------------------- /Mallas_python/4_Scordelis-lo-roof_shell.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Ejemplo: Malla shell en GMSH 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | import gmsh 9 | from math import sin, cos, radians 10 | 11 | # %% Se inicializa y se crea el modelo: 12 | 13 | gmsh.initialize() 14 | 15 | gmsh.option.setNumber("General.Terminal", 1) 16 | 17 | gmsh.model.add("modelo_shell") 18 | 19 | 20 | # %% Se crean algunas variables de la geometría: 21 | 22 | L = 25 # Longitud del cascarón 23 | r = 25 # radio de la superficie 24 | t = radians(40) # Ángulo de apertura 25 | N = 8 # Tamaño de la malla (N x N) 26 | 27 | # %% La siguiente línea permite abreviar el código: 28 | geom = gmsh.model.geo 29 | 30 | # %% Se crean los puntos necesarios 31 | geom.addPoint(0, 0, 0, tag=0) # Punto central del arco 32 | geom.addPoint(r*sin(t), 0, r*cos(t), tag=1) 33 | geom.addPoint(0, 0, r, tag=2) 34 | 35 | # %% Se crea el arco de circunferencia: 36 | 37 | geom.addCircleArc(1, 0, 2, tag=1) 38 | # Dado que queremos una malla estructurada, definimos el número de elementos 39 | # que queremos en la línea creada: 40 | # Sintaxis: gmsh.model.geo.mesh.setTransfiniteCurve(tags, # nodos) 41 | 42 | geom.mesh.setTransfiniteCurve(1, N+1) # El número de nodos será N+1 43 | 44 | 45 | # %% Para crear la superficie se debe extruir el arco anterior: 46 | # Sintaxis: gmsh.model.geo.extrude([lista de entidades], u, v, w), donde: 47 | # La lista de entidades consiste en pares (dim, tag) 48 | # u, v, w son las componentes del vector de traslación 49 | superf = geom.extrude([(1,1)], 0, L, 0, numElements=[N]) 50 | 51 | # el comando anterior retorna una lista, analicemos de qué se compone: 52 | 53 | for i in range(len(superf)): 54 | print(f'superf[{i}] = {superf[i]}') 55 | 56 | # La superficie generada es el segundo elemento de la lista anterior 57 | bd, s, bs, bi = superf 58 | 59 | 60 | # %% Creamos los grupos físicos: 61 | 62 | # Bordes físicos: AB, BC, CD y AD: 63 | nombres = ['AB', 'BC', 'CD', 'AD'] 64 | bordes = [bi[1], bd[1], bs[1], 1] 65 | for i in range(len(nombres)): 66 | gmsh.model.addPhysicalGroup(1, [bordes[i]], i+1) 67 | gmsh.model.setPhysicalName(1, i+1, nombres[i]) 68 | 69 | 70 | # Superficie física: 71 | dim, tag = s 72 | gmsh.model.addPhysicalGroup(dim, [tag], 101) 73 | gmsh.model.setPhysicalName(dim, 101, 'ABCD') 74 | 75 | # %% Definimos que la superficie creada tenga una malla estructurada: 76 | geom.mesh.setTransfiniteSurface(tag) 77 | 78 | # Y recombinamos para que use elementos cuadriláteros en lugar de triángulos 79 | geom.mesh.setRecombine(2, tag) 80 | 81 | # %% Finalmente se crea la malla y se guarda: 82 | 83 | geom.synchronize() 84 | gmsh.model.mesh.generate(2) 85 | 86 | filename = 'malla_shell.msh' 87 | gmsh.write(filename) 88 | 89 | # Si queremos que en la visualización se muestren los ejes de coordenadas, po- 90 | # demos modificar esta opción, tomado del manual: 91 | # ============================================================================= 92 | # General.Axes 93 | # Axes (0: none, 1: simple axes, 2: box, 3: full grid, 4: open grid, 5: ruler) 94 | # Default value: 0 95 | # ============================================================================= 96 | gmsh.option.setNumber('General.Axes', 1) # 1 para "ejes simples" 97 | 98 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) 99 | gmsh.option.setNumber('Mesh.Points', 1) 100 | 101 | # Visualizamos el resultado en la interfaz gráfica de GMSH 102 | gmsh.fltk.run() 103 | 104 | # Se finaliza el programa 105 | gmsh.finalize() 106 | -------------------------------------------------------------------------------- /Mallas_python/5_Twisted-beam_shell.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Ejemplo 5: Malla shell en GMSH ( Problema "Twisted beam") 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | # %% Se inicializa el módulo: 9 | 10 | import gmsh 11 | import numpy as np 12 | 13 | 14 | gmsh.initialize() 15 | gmsh.option.setNumber("General.Terminal", 1) 16 | gmsh.model.add("modelo_4") 17 | 18 | # %% Variables asociadas a la geometría: 19 | 20 | L = 12 21 | b = 1.1 22 | N = 2 # La malla será de 6N x N elementos 23 | 24 | # %% Se crea la geometría: 25 | geom = gmsh.model.geo 26 | 27 | # Puntos: 28 | p1 = geom.addPoint(0, 0, -b/2) 29 | p2 = geom.addPoint(0, 0, b/2) 30 | p3 = geom.addPoint(-b/2, -L, 0) 31 | p4 = geom.addPoint(b/2, -L, 0) 32 | 33 | # Líneas: 34 | l1 = geom.addLine(p1, p2) 35 | l2 = geom.addLine(p2, p3) 36 | l3 = geom.addLine(p3, p4) 37 | l4 = geom.addLine(p4, p1) 38 | 39 | # Superficie: 40 | cl = geom.addCurveLoop([l1, l2, l3, l4]) 41 | s = geom.addSurfaceFilling([cl]) 42 | 43 | 44 | # %% Se define la malla como estructurada: 45 | 46 | # Líneas en el sentido Z 47 | for tag in [l1, l3]: 48 | geom.mesh.setTransfiniteCurve(tag, N+1) 49 | 50 | # Líneas en el sentido Y 51 | for tag in [l2, l4]: 52 | geom.mesh.setTransfiniteCurve(tag, 6*N+1) 53 | 54 | geom.mesh.setTransfiniteSurface(s) 55 | geom.mesh.setRecombine(2, s) # Usar cuadriláteros en lugar de triángulos 56 | 57 | # %% Finalmente se crea la malla 58 | 59 | geom.synchronize() 60 | gmsh.model.mesh.generate(2) 61 | #gmsh.model.mesh.setOrder(1) 62 | 63 | # %% Se crean grupos físicos: 64 | 65 | gmsh.model.addPhysicalGroup(2, [s], 21) 66 | gmsh.model.setPhysicalName(2, 21, "Superficie") 67 | 68 | # %% Se guarda la malla 69 | filename = 'twisted_beam.msh' 70 | gmsh.write(filename) 71 | 72 | 73 | # Si queremos que en la visualización se muestren los ejes de coordenadas, po- 74 | # demos modificar esta opción, tomado del manual: 75 | # ============================================================================= 76 | # General.Axes 77 | # Axes (0: none, 1: simple axes, 2: box, 3: full grid, 4: open grid, 5: ruler) 78 | # Default value: 0 79 | # ============================================================================= 80 | gmsh.option.setNumber('General.Axes', 1) # 1 para "ejes simples" 81 | 82 | 83 | # Podemos visualizar el resultado en la interfaz gráfica de GMSH 84 | gmsh.fltk.run() 85 | 86 | # Se finaliza el programa 87 | gmsh.finalize() 88 | -------------------------------------------------------------------------------- /Mallas_python/6_Paraboloide-hiperbolico_shell.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Ejemplo 6: Malla de elementos Shell de un paraboloide hiperbólico 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | import gmsh 9 | 10 | def obtener_pc_bezier(a, b, c, p1, p2): 11 | ''' Obtiene el punto de control de la curva de Bezier que permite interpolar 12 | la parábola f(x) = ax^2 + bx + c o f(y) = ay^2 + by + c 13 | ''' 14 | x1, y1 = p1 15 | x2, y2 = p2 16 | f = lambda x: a*x**2 + b*x + c 17 | 18 | Cx = (x1+x2)/2 19 | Cy = (x2-x1)/2 * (2*a*x1 + b) + f(x1) 20 | 21 | return Cx, Cy 22 | 23 | # %% Se inicializa el módulo 24 | 25 | gmsh.initialize() 26 | gmsh.option.setNumber("General.Terminal", 1) 27 | gmsh.model.add("modelo_6") 28 | 29 | geom = gmsh.model.geo 30 | 31 | N = 8 # La malla será estructurada de 2N x N elementos 32 | 33 | f = lambda x, y: y**2 - x**2 # Función que define el paraboloide hiperb. 34 | 35 | 36 | # %% Lado AB (parábola z = y^2 - 1/4): 37 | a = 1; b = 0; c = -1/4 38 | x = 1/2 # Esta parábola está sobre el plano x=1/2 39 | 40 | pA = geom.addPoint(x, -1/2, f(x, -1/2)) 41 | pB = geom.addPoint(x, 1/2, f(x, 1/2)) 42 | 43 | # Obtenemos las coordenadas del pto de control de la Curva de Bezier que per- 44 | # mite trazar la parábola z = y^2 - 1/4: 45 | cy, cz = obtener_pc_bezier(a, b, c, [-1/2, f(x, -1/2)], [1/2, f(x, 1/2)]) 46 | pc1 = geom.addPoint(x, cy, cz) # Punto de control de la curva de Bezier 47 | 48 | b1 = geom.addBezier([pA, pc1, pB]) # Se crea la parábola como una curva de 49 | # Bezier con pto inicial, final y de control 50 | 51 | # %% Lado BC (parábola z = 1/4 - x^2) 52 | a = -1; b = 0; c = 1/4 53 | y = 1/2 # Esta parábola está sobre el plano y = 1/2 54 | 55 | pC = geom.addPoint(0, y, f(0, y)) 56 | 57 | cx, cz = obtener_pc_bezier(a, b, c, [1/2, f(1/2, y)], [0, f(0, y)]) 58 | pc2 = geom.addPoint(cx, y, cz) 59 | 60 | b2 = geom.addBezier([pB, pc2, pC]) 61 | 62 | 63 | # %% Lado CD (parábola z = y^2) 64 | a = 1; b = 0; c = 0 65 | x = 0 # Esta parábola está sobre el plano x=0 66 | 67 | pD = geom.addPoint(x, -1/2, f(x, -1/2)) 68 | cy, cz = obtener_pc_bezier(a, b, c, [-1/2, f(x, 1/2)], [1/2, f(x, -1/2)]) 69 | pc3 = geom.addPoint(x, cy, cz) 70 | 71 | b3 = geom.addBezier([pC, pc3, pD]) 72 | 73 | 74 | # %% Lado AD (parábola z = 1/4-x^2) 75 | a = -1; b = 0; c = 1/4 76 | y = -1/2 # Esta parábola está sobre el plano y = 1/2 77 | 78 | cx, cz = obtener_pc_bezier(a, b, c, [0, f(0, y)], [1/2, f(1/2, y)]) 79 | pc4 = geom.addPoint(cx, y, cz) 80 | 81 | b4 = geom.addBezier([pD, pc4, pA]) 82 | 83 | 84 | # %% Se define malla estructurada: 85 | 86 | for curva in [b1, b3]: 87 | geom.mesh.setTransfiniteCurve(curva, 2*N+1) 88 | for curva in [b2, b4]: 89 | geom.mesh.setTransfiniteCurve(curva, N+1) 90 | 91 | # %% Se define curve loop y superficie: 92 | 93 | geom.addCurveLoop([b1, b2, b3, b4], 1) 94 | s = geom.addSurfaceFilling([1]) 95 | 96 | geom.mesh.setTransfiniteSurface(s) # Se define malla estructurada en superf. 97 | 98 | gmsh.model.addPhysicalGroup(2, [s], 101) 99 | gmsh.model.setPhysicalName(2, 101, "ABCD") 100 | 101 | # %% Finalmente se crea la malla y se guarda: 102 | 103 | geom.synchronize() 104 | gmsh.model.mesh.generate(2) 105 | 106 | filename = 'paraboloide.msh' 107 | gmsh.write(filename) 108 | 109 | gmsh.option.setNumber('General.Axes', 1) # 1 para "ejes simples" 110 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) 111 | 112 | # Visualizamos el resultado en la interfaz gráfica de GMSH 113 | gmsh.fltk.run() 114 | 115 | # Se finaliza el programa 116 | gmsh.finalize() 117 | 118 | # %% Graficamos la malla: 119 | 120 | from leer_GMSH import plot_msh # Funciones para leer y graficar la malla 121 | 122 | #plot_msh(filename, 'shell', True) 123 | -------------------------------------------------------------------------------- /Mallas_python/7_Malla_con_splines.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Malla para el perfil de un ala (Airfoil) usando B-Splines en GMSH 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | import gmsh # pip install --upgrade gmsh 9 | 10 | gmsh.initialize() 11 | gmsh.option.setNumber("General.Terminal", 1) 12 | gmsh.model.add("modelo_6") 13 | 14 | geom = gmsh.model.geo # Línea para abreviar los comandos de geometría 15 | 16 | L = 1 # Longitud desde el centro de la malla interior hasta los bordes 17 | 18 | # %% Se crean los puntos de control de la Spline: 19 | 20 | geom.addPoint(1, 0, 0) 21 | geom.addPoint(0.8, 0.027, 0) 22 | geom.addPoint(0.55, 0.058, 0) 23 | geom.addPoint(0.3, 0.08, 0) 24 | geom.addPoint(0.15, 0.078, 0) 25 | geom.addPoint(0, 0.052, 0) 26 | geom.addPoint(0, 0, 0) 27 | geom.addPoint(0, -0.01, 0) 28 | geom.addPoint(0.15, -0.052, 0) 29 | geom.addPoint(0.3, -0.05, 0) 30 | geom.addPoint(0.55, -0.043, 0) 31 | geom.addPoint(0.8, -0.017, 0) 32 | 33 | 34 | # Se crea la B-Spline con tomando los puntos anteriores como puntos de control: 35 | 36 | af = geom.addBSpline([1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 1]) 37 | 38 | geom.mesh.setTransfiniteCurve(af, 301) 39 | 40 | cint = geom.addCurveLoop([af]) # Curve loop interior 41 | 42 | # %% Se crea el borde de la superficie exterior: 43 | p1 = geom.addPoint(0.5-L, -L, 0) 44 | p2 = geom.addPoint(0.5+L, -L, 0) 45 | p3 = geom.addPoint(0.5+L, L, 0) 46 | p4 = geom.addPoint(0.5-L, L, 0) 47 | 48 | l1 = geom.addLine(p1, p2) 49 | l2 = geom.addLine(p2, p3) 50 | l3 = geom.addLine(p3, p4) 51 | l4 = geom.addLine(p4, p1) 52 | 53 | cext = geom.addCurveLoop([l1, l2, l3, l4]) # Curve loop exterior 54 | 55 | s1 = geom.addPlaneSurface([cext, cint]) # Se crea la superficie exterior 56 | 57 | # Se crea superficie física 58 | gmsh.model.addPhysicalGroup(2, [s1], tag=101) 59 | gmsh.model.setPhysicalName(2, 101, "Aire") 60 | 61 | # %% Finalmente se crea la malla y se guarda: 62 | 63 | geom.synchronize() 64 | gmsh.model.mesh.generate(2) 65 | 66 | # Creamos una malla con elementos de orden 2: 67 | gmsh.model.mesh.setOrder(2) 68 | 69 | filename = 'airfoil.msh' 70 | gmsh.write(filename) 71 | 72 | gmsh.option.setNumber('Mesh.SurfaceFaces', 1) 73 | gmsh.option.setNumber('Mesh.Points', 1) 74 | 75 | # Visualizamos el resultado en la interfaz gráfica de GMSH 76 | gmsh.fltk.run() 77 | 78 | # Se finaliza el programa 79 | gmsh.finalize() 80 | -------------------------------------------------------------------------------- /Mallas_python/leer_GMSH.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Funciones para leer y graficar una malla creada en GMSH. 4 | 5 | Por: Alejandro Hincapié Giraldo 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | from mpl_toolkits.mplot3d import Axes3D 10 | from matplotlib.colors import get_named_colors_mapping 11 | 12 | # %% Funciones: 13 | 14 | def xnod_from_msh(archivo, dim=2): 15 | ''' Obtiene la matriz de coordenadas de los nodos que contiene la malla de 16 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 17 | ''' 18 | 19 | # Se lee el archivo y se toman cada una de sus líneas: 20 | m = open(archivo) 21 | malla = m.readlines() 22 | malla = [linea.rstrip('\n') for linea in malla] 23 | 24 | # Se determina en qué lineas comienza y termina el reporte de nodos del 25 | # archivo: 26 | inicio_nodos = malla.index('$Nodes') 27 | fin_nodos = malla.index('$EndNodes') 28 | 29 | # Se toma únicamente la parte del archivo que se requiere: 30 | malla = malla[inicio_nodos+1:fin_nodos] 31 | 32 | # Se leen los parámetros iniciales: 33 | nblocks, nnodos = [int(n) for n in malla[0].split()[0:2]] 34 | 35 | # Se inicializan las listas para cada una de las entidades que 36 | # reporta el archivo: 37 | nodos_puntos = []; xnod_puntos = [] 38 | nodos_bordes = []; xnod_bordes = [] 39 | nodos_superf = []; xnod_superf = [] 40 | 41 | 42 | for j in range(1, len(malla)): 43 | line = malla[j] 44 | if len(line.split()) == 4: # Se busca el reporte de cada bloque 45 | tipo_ent = int(line.split()[0]) # Punto, borde o superf. 46 | 47 | nno_bloque = int(line.split()[-1]) # No. de nodos del bloque 48 | nodos_bloque = malla[j+1:j+1+nno_bloque] # Lista de nodos del bloque 49 | nodos_bloque = [int(n) for n in nodos_bloque] # Se convierten a enteros 50 | 51 | xnod_b = malla[j+1+nno_bloque:j+1+2*nno_bloque] 52 | 53 | # Se reportan las coordenadas como una "matriz": 54 | xnod_bloque = [] 55 | for l in xnod_b: 56 | coord = [float(n) for n in l.split()] 57 | xnod_bloque.append(coord) 58 | 59 | # Finalmente se agregan los datos leídos a la lista corres- 60 | # pondiente según la entidad (punto, línea o superficie): 61 | if tipo_ent == 0: 62 | nodos_puntos.append(nodos_bloque) 63 | xnod_puntos.append(xnod_bloque) 64 | elif tipo_ent == 1: 65 | nodos_bordes.append(nodos_bloque) 66 | xnod_bordes.append(xnod_bloque) 67 | elif tipo_ent == 2: 68 | nodos_superf.append(nodos_bloque) 69 | xnod_superf.append(xnod_bloque) 70 | 71 | # Se ensambla finalmente la matriz xnod completa: 72 | 73 | xnod = np.empty((nnodos, 3)) 74 | 75 | # Primero se adicionan los nodos correspondientes a los puntos: 76 | for i in range(len(nodos_puntos)): 77 | idx = np.array(nodos_puntos[i]) - 1 78 | xnod[idx, :] = np.array(xnod_puntos[i]) 79 | 80 | # Luego se agregan los nodos correspondientes a los bordes: 81 | for i in range(len(nodos_bordes)): 82 | idx = np.array(nodos_bordes[i]) - 1 83 | xnod[idx, :] = np.array(xnod_bordes[i]) 84 | 85 | # Finalmente se agregan los nodos correspondientes a la superficie: 86 | for i in range(len(nodos_superf)): 87 | idx = np.array(nodos_superf[i]) - 1 88 | xnod[idx, :] = np.array(xnod_superf[i]) 89 | 90 | # Se toman las columnas necesarias según la dimensión 91 | xnod = xnod[:, :dim] 92 | 93 | return xnod 94 | 95 | def LaG_from_msh(archivo): 96 | ''' Obtiene la matriz de interconexión nodal (LaG) que contiene la malla de 97 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 98 | ''' 99 | # Se lee el archivo y se toman cada una de sus líneas: 100 | m = open(archivo) 101 | malla = m.readlines() 102 | malla = [linea.rstrip('\n') for linea in malla] 103 | 104 | # Se determina en qué lineas comienza y termina el reporte de nodos del 105 | # archivo: 106 | for i in range(len(malla)): 107 | inicio_elem = malla.index('$Elements') 108 | fin_elem = malla.index('$EndElements') 109 | 110 | # Se toman solo las líneas necesarias 111 | malla = malla[inicio_elem+1:fin_elem] 112 | 113 | nblocks, nelem = [int(n) for n in malla[0].split()[0:2]] 114 | matrices = [] # Guardará las matrices LaG asociadas a cada entidad 115 | tags = [] # Tag correspondiente a cada entidad 116 | fila = 1 # Contador de filas recorridas 117 | nmat = 1 118 | for i in range(nblocks): 119 | linea = malla[fila] 120 | 121 | dim, tag, tipo_ef, nef = [int(n) for n in linea.split()] 122 | if dim==2: # solo se toman nodos pertenecientes a superficies 123 | tags.extend([nmat]*nef) 124 | nmat += 1 125 | matrices.append(malla[fila+1:fila+1+nef]) 126 | fila += (1+nef) 127 | 128 | LaG = [] # Se inicializa la matriz LaG 129 | 130 | for matriz in matrices: 131 | for i in range(len(matriz)): 132 | linea = [int(n) for n in matriz[i].split()] 133 | LaG.append(linea) 134 | LaG = np.array(LaG) 135 | 136 | # Se reporta la superficie a la que pertenece cada elemento finito 137 | LaG[:, 0] = np.array(tags) 138 | 139 | return LaG - 1 140 | 141 | 142 | def plot_msh(file, tipo, mostrar_nodos=False, mostrar_num_nodo=False, 143 | mostrar_num_elem=False): 144 | ''' Función para graficar la malla contenida en el archivo "file". 145 | Argumentos: 146 | - file: str. Debe ser un archivo de extensión .msh exportado por GMSH. 147 | - tipo: str. 'shell' o '2D' 148 | - mostrar_nodos: bool. Define si se muestran los nodos en el gráfico. 149 | - mostrar_num_nodo: bool. Define si se muestra el número de cada nodo 150 | en el gráfico. 151 | - mostrar_num_elem: bool. Define si se muestra el número de cada ele- 152 | mento finito en el gráfico. 153 | ''' 154 | LaG_mat = LaG_from_msh(file) 155 | mat = LaG_mat[:, 0] 156 | LaG = LaG_mat[:, 1:] 157 | nef = LaG.shape[0] 158 | 159 | # Se determina el tipo de elemento finito 160 | if LaG.shape[1] == 3: 161 | elem = 'T3' 162 | elif LaG.shape[1] == 4: 163 | elem = 'Q4' 164 | elif LaG.shape[1] == 6: 165 | elem = 'T6' 166 | elif LaG.shape[1] == 8: 167 | elem = 'Q8' 168 | elif LaG.shape[1] == 9: 169 | elem = 'Q9' 170 | elif LaG.shape[1] == 10: 171 | elem = 'T10' 172 | 173 | if tipo=='shell': 174 | xnod = xnod_from_msh(file, 3) 175 | nno = xnod.shape[0] 176 | 177 | cg = np.empty((nef,3)) # Centro de gravedad del EF 178 | colores = ['k'] + list(get_named_colors_mapping().values()) 179 | fig = plt.figure(figsize=(12, 12)) 180 | ax = plt.gca(projection='3d') 181 | for e in range(nef): 182 | if elem=='T3' or elem=='Q4': 183 | nodos = np.r_[LaG[e], LaG[e, 0]] 184 | elif elem =='T6': 185 | nodos = LaG[e, [0, 3, 1, 4, 2, 5, 0]] 186 | elif elem =='Q8' or elem == 'Q9': 187 | nodos = LaG[e, [0, 4, 1, 5, 2, 6, 3, 7, 0]] 188 | elif elem == 'T10': 189 | nodos = LaG[e, [0, 3, 4, 1, 5, 6, 2, 7, 8, 0]] 190 | color = colores[mat[e]] 191 | X = xnod[nodos,0] 192 | Y = xnod[nodos,1] 193 | Z = xnod[nodos,2] 194 | 195 | ax.plot3D(X, Y, Z, '-', lw=0.8, c=color) 196 | if mostrar_nodos: 197 | ax.plot3D(xnod[LaG[e],0], xnod[LaG[e],1], xnod[LaG[e],2], 198 | 'ko', ms=5, mfc='r') 199 | if mostrar_num_elem: 200 | # se calcula la posición del centro de gravedad del EF 201 | cg[e] = np.mean(xnod[LaG[e]], axis=0) 202 | 203 | # y se reporta el número del elemento actual 204 | ax.text(cg[e,0], cg[e,1], cg[e,2], f'{e+1}', horizontalalignment='center', 205 | verticalalignment='center', color='b') 206 | if mostrar_num_nodo: 207 | for i in range(nno): 208 | ax.text(xnod[i,0], xnod[i,1], xnod[i,2], f'{i+1}', color='r') 209 | fig.suptitle(f'Malla de EF Shell ({elem})', fontsize='x-large') 210 | ax.set_xlabel('x') 211 | ax.set_ylabel('y') 212 | ax.set_zlabel('z') 213 | ax.view_init(45, 45) 214 | 215 | elif tipo=='2D': 216 | xnod = xnod_from_msh(file, 2) 217 | nno = xnod.shape[0] 218 | 219 | cg = np.empty((nef,2)) 220 | colores = ['k'] + list(get_named_colors_mapping().values()) 221 | 222 | fig = plt.figure(figsize=(12, 12)) 223 | ax = plt.gca() 224 | for e in range(nef): 225 | if elem=='T3' or elem=='Q4': 226 | nodos = np.r_[LaG[e], LaG[e, 0]] 227 | elif elem =='T6': 228 | nodos = LaG[e, [0, 3, 1, 4, 2, 5, 0]] 229 | elif elem =='Q8' or elem == 'Q9': 230 | nodos = LaG[e, [0, 4, 1, 5, 2, 6, 3, 7, 0]] 231 | elif elem == 'T10': 232 | nodos = LaG[e, [0, 3, 4, 1, 5, 6, 2, 7, 8, 0]] 233 | color = colores[mat[e]] 234 | X = xnod[nodos,0] 235 | Y = xnod[nodos,1] 236 | ax.plot(X, Y, '-', lw=0.8, c=color) 237 | if mostrar_nodos: 238 | ax.plot(xnod[LaG[e],0], xnod[LaG[e],1], 'ko', ms=5, mfc='r') 239 | if mostrar_num_elem: 240 | # se calcula la posición del centro de gravedad del EF 241 | cg[e] = np.mean(xnod[LaG[e]], axis=0) 242 | 243 | # y se reporta el número del elemento actual 244 | ax.text(cg[e,0], cg[e,1], f'{e+1}', horizontalalignment='center', 245 | verticalalignment='center', color='b') 246 | if mostrar_num_nodo: 247 | for i in range(nno): 248 | ax.text(xnod[i,0], xnod[i,1], f'{i+1}', color='r') 249 | fig.suptitle(f'Malla de EF 2D ({elem})', fontsize='x-large') 250 | ax.set_xlabel('x') 251 | ax.set_ylabel('y') 252 | ax.set_aspect('equal', adjustable='box') 253 | else: 254 | raise ValueError('El argumento "tipo" introducido no es válido') 255 | -------------------------------------------------------------------------------- /Mallas_python/malla_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_1.png -------------------------------------------------------------------------------- /Mallas_python/malla_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_2.png -------------------------------------------------------------------------------- /Mallas_python/malla_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_3.png -------------------------------------------------------------------------------- /Mallas_python/malla_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_4.png -------------------------------------------------------------------------------- /Mallas_python/malla_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_5.png -------------------------------------------------------------------------------- /Mallas_python/malla_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_6.jpg -------------------------------------------------------------------------------- /Mallas_python/malla_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_7.png -------------------------------------------------------------------------------- /Mallas_python/malla_7_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/Mallas_python/malla_7_2.png -------------------------------------------------------------------------------- /Mallas_python/readme.md: -------------------------------------------------------------------------------- 1 | # Mallas 2D creadas con la API de Python para GMSH (formato .py) 2 | 3 | ### Ejemplo #1: Malla 2D no estructurada con un agujero en la mitad 4 | ![Malla 1](malla_1.png) 5 | 6 | ### Ejemplo #2: Malla 2D estructurada usando las funciones *Transfinite Curve* y *Transfinite Surface* 7 | ![Malla 2](malla_2.png) 8 | 9 | ### Ejemplo #3: Malla 2D estructurada usando la función *extrude* 10 | ![Malla 3](malla_3.png) 11 | 12 | ### Ejemplo #4: Malla de elementos Shell estructurada, para el problema *Scordelis lo roof* 13 | ![Malla 4](malla_4.png) 14 | 15 | ### Ejemplo #5: Malla de elementos Shell estructurada para el problema *Twisted beam* 16 | ![Malla 5](malla_5.png) 17 | 18 | ### Ejemplo #6: Malla de elementos Shell estructurada para una superficie de *Paraboloide hiperbólico* 19 | ![Malla 6](malla_6.jpg) 20 | 21 | ### Ejemplo #7: Malla 2D de un perfil alar (*airfoil*) utilizando la función B-Spline 22 | ![Malla 7](malla_7.png) 23 | ![Malla 72](malla_7_2.png) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutoriales de GMSH 2 | 3 |

4 | 5 | 6 |

7 | 8 | El propósito de este repositorio es albergar los códigos utilizados en los [tutoriales de GMSH](https://www.youtube.com/playlist?list=PLu42Gwp4NwSutpf8S_B6mX_vNgETAXtMs) que subí a Youtube. 9 | 10 | En estos tutoriales se busca explicar el uso del software libre [GMSH](https://gmsh.info) para la creación de mallas de elementos finitos en 2D, desde los conceptos más básicos hasta algunos aspectos un poco más avanzados que permitan adaptar el uso del programa a las necesidades del usuario, según la malla que desee crear o en función del problema específico que desee modelar. 11 | 12 | Los temas cubiertos en estos tutoriales son: 13 | - Conceptos básicos sobre creación de mallas 2D en GMSH 14 | - Mallas estructuradas 2D en GMSH 15 | - Uso del lenguaje del GMSH para crear mallas 16 | - Uso de la API de GMSH en Python para crear mallas 17 | - Mallas de elementos Shell (cascarón) estructuradas y no estructuradas 18 | - Curvas especiales: B-Splines y Bezier 19 | 20 | ## Videotutoriales en Youtube 21 | 22 | Cada uno de los tutoriales se basa en crear una cierta malla de elementos finitos en la cual se apliquen conceptos básicos o avanzados del software. Los archivos correspondientes a estas mallas se encuentran disponibles tanto en formato Python `.py` como en formato GMSH `.geo` 23 | 24 | ### Tutorial 1: Conceptos básicos sobre creación de mallas 25 | 26 | Video GMSH: [Tutorial 1 en Youtube](https://youtu.be/Jn4QbNt-lfU) 27 | 28 | - Malla formato `.geo`: [Malla_1.geo](/Mallas_GMSH/1_Malla-simple_2d.geo) 29 | 30 | Video API de Python: [Tutorial 1 (Python) en Youtube](https://youtu.be/az4OATXyA9E) 31 | 32 | - Malla formato `.py`: [Malla_1.py](/Mallas_python/1_Malla-simple_2d.py) 33 | 34 | ### Tutorial 2: Mallas estructuradas (Transfinite meshes) 35 | 36 | Video GMSH + Python: [Tutorial malla estructurada en Youtube](https://youtu.be/8IrGJzqJ9SE) 37 | 38 | - Malla formato `.geo`: [Malla_2.geo](/Mallas_GMSH/2_Malla-estructurada_2d.geo) 39 | - Malla formato `.py`: [Malla_2.py](/Mallas_python/2_Malla-estructurada_2d.py) 40 | 41 | ### Tutorial 3: Mallas estructuradas extruidas (Extruded meshes) 42 | 43 | Video GMSH + Python: [Tutorial malla extruida en Youtube](https://youtu.be/lppSadXC_T0) 44 | 45 | - Malla formato `.geo`: [Malla_3.geo](/Mallas_GMSH/3_Malla-estructurada-extrude_2d.geo) 46 | - Malla formato `.py`: [Malla_3.py](/Mallas_python/3_Malla-estructurada-extrude_2d.py) 47 | 48 | ### Tutorial 4: Mallas 2D utilizando Splines 49 | 50 | Video GMSH + Python: [Tutorial malla con Splines en Youtube](https://youtu.be/ggdNZoTUanc) 51 | 52 | - Ejemplos de uso Splines: [Ejemplos_splines.geo](/Mallas_GMSH/7_Ejemplos_curvas_spline.geo) 53 | - Malla formato `.geo`: [Malla_4.geo](/Mallas_GMSH/7_Malla_con_splines.geo) 54 | - Malla formato `.py`: [Malla_4.py](/Mallas_python/7_Malla_con_splines.py) 55 | 56 | ### Tutorial 5: Malla de elementos Shell/Cascarón en GMSH 1 57 | 58 | Video GMSH + Python: [Tutorial 1 malla de elementos Shell en Youtube](https://youtu.be/eFFwv9CFDCo) 59 | 60 | - Malla formato `.geo`: [Malla_5.geo](/Mallas_GMSH/4_Scordelis-lo-roof_shell.geo) 61 | - Malla formato `.py`: [Malla_5.py](/Mallas_python/4_Scordelis-lo-roof_shell.py) 62 | 63 | 64 | ### Tutorial 6: Malla de elementos Shell/Cascarón en GMSH 2 65 | 66 | Video GMSH + Python: [Tutorial 2 malla de elementos Shell en Youtube](http://52.68.96.58) 67 | 68 | - Malla formato `.geo`: [Malla_6.geo](/Mallas_GMSH/5_Twisted-beam_shell.py) [**Pendiente**] 69 | - Malla formato `.py`: [Malla_6.py](/Mallas_python/5_Twisted-beam_shell.py) 70 | 71 | ## Recursos disponibles: 72 | 73 | 74 | ### Funciones para leer la malla en Python: 75 | 76 | El código [leer_GMSH.py](/leer_GMSH.py) contiene funciones útiles para procesar en Python las mallas creadas con el GMSH. Estas funciones permiten obtener la matriz de coordenadas nodales y la matriz de interconexión nodal a partir del archivo **.msh** que exporta GMSH con los datos de la malla. Adicionalmente, incluye una función para graficar las mallas a partir del archivo leído, con múltiples opciones de visualización. *(por ahora, solo permite leer y graficar mallas de elementos 2D y de tipo Shell, no mallas 3D)*. 77 | 78 | **Ejemplo de uso:** Con el siguiente código se puede observar como utilizar las funciones dadas en [leer_GMSH.py](/leer_GMSH.py) para obtener los parámetros más importantes de la malla: 79 | 80 | ```python 81 | from leer_GMSH import xnod_from_msh, LaG_from_msh, plot_msh 82 | 83 | malla = 'ejm_2.msh' 84 | 85 | # Matriz de coordenadas nodales 86 | xnod = xnod_from_msh(malla, dim=2) 87 | 88 | # Se imprimen los primeros 10 nodos 89 | for i in range(10): 90 | x, y = xnod[i] 91 | print(f'Nodo {i+1:2.0f}: x = {x:.4f}, y = {y:.4f}') 92 | 93 | # Matriz de interconexión nodal 94 | LaG = LaG_from_msh(malla) 95 | nef = LaG.shape[0] 96 | 97 | # Se imprimen los primeros 5 elementos y los 5 últimos: 98 | print() 99 | for e in list(range(5)) + list(range(nef-5, nef)): 100 | print(f'Elemento {e+1:3.0f}: Superficie = {LaG[e, 0]+1}, ' 101 | f'Nodos = {LaG[e, 1:]+1}') 102 | 103 | 104 | # Se grafica la malla: 105 | plot_msh(malla, '2D', mostrar_nodos=True, mostrar_num_nodo=False, 106 | mostrar_num_elem=True) 107 | ``` 108 | 109 | Esto da como resultado: 110 | ``` 111 | Nodo 1: x = 1.0000, y = 0.0000 112 | Nodo 2: x = 3.2500, y = 0.0000 113 | Nodo 3: x = 3.2500, y = 3.2500 114 | Nodo 4: x = 0.0000, y = 3.2500 115 | Nodo 5: x = 0.0000, y = 1.0000 116 | Nodo 6: x = 0.7071, y = 0.7071 117 | Nodo 7: x = 1.1500, y = 0.0000 118 | Nodo 8: x = 1.3000, y = 0.0000 119 | Nodo 9: x = 1.4500, y = 0.0000 120 | Nodo 10: x = 1.6000, y = 0.0000 121 | 122 | Elemento 1: Superficie = 1, Nodos = [ 1 7 85 84] 123 | Elemento 2: Superficie = 1, Nodos = [84 85 86 83] 124 | Elemento 3: Superficie = 1, Nodos = [83 86 87 82] 125 | Elemento 4: Superficie = 1, Nodos = [82 87 88 81] 126 | Elemento 5: Superficie = 1, Nodos = [81 88 89 80] 127 | Elemento 296: Superficie = 2, Nodos = [332 333 70 71] 128 | Elemento 297: Superficie = 2, Nodos = [333 334 69 70] 129 | Elemento 298: Superficie = 2, Nodos = [334 335 68 69] 130 | Elemento 299: Superficie = 2, Nodos = [335 336 67 68] 131 | Elemento 300: Superficie = 2, Nodos = [336 52 5 67] 132 | ``` 133 | ![grafico](/grafico_malla2.png) 134 | 135 | ### Funciones para leer grupos físicos en la malla 136 | 137 | El código [obtener_grupos_fisicos.py](/obtener_grupos_fisicos.py) contiene una función que utiliza las funcionalidades de GMSH en Python para leer una malla a partir del archivo **.msh** exportado por GMSH y obtener de él los grupos físicos creados, con sus respectivos nombres (si los hay) y los nodos asociados a cada grupo físico. Muy útil para identificar nodos a los cuales se debe imponer una condición particular asociada al análisis por MEF, por ejemplo, condiciones de frontera del sólido o cargas puntuales/distribuidas aplicadas. Con este propósito, contiene otra función que permite directamente obtener los nodos asociados a un grupo físico específico conociendo el nombre. *(Por ahora, solo permite leer mallas de elementos 2D y de tipo Shell, no mallas 3D)*. 138 | 139 | **Ejemplo de uso:** En el siguiente código se puede observar cómo utilizar estas funciones para obtener todos los parámetros de los grupos físicos que se tienen en una malla 140 | 141 | ```python 142 | from obtener_grupos_fisicos import grupos_fisicos, obtener_nodos 143 | 144 | malla = 'scordelis.msh' 145 | 146 | # Obtener todos los grupos físicos de la malla: 147 | dict_nombres, dict_nodos = grupos_fisicos(malla) 148 | print('Grupos físicos reportados:\n') 149 | for tag in dict_nombres.keys(): 150 | dim = dict_nombres[tag][0] 151 | nombre = dict_nombres[tag][1] 152 | nodos = dict_nodos[tag] 153 | print(f'Grupo físico: {tag}\nDimensión: {dim}\n' 154 | f'Nombre: {nombre}\nContiene nodos: {nodos}.\n') 155 | 156 | # Obtener nodos únicamente del grupo físico llamado "AB": 157 | nod_AB = obtener_nodos(malla, 'AB') 158 | print('~'*50) 159 | print(f'Grupo físico "AB" contiene nodos: {nod_AB}') 160 | ``` 161 | 162 | Esto da como resultado: 163 | 164 | ``` 165 | Grupos físicos reportados: 166 | 167 | Grupo físico: 1 168 | Dimensión: 1 169 | Nombre: AB 170 | Contiene nodos: [23 24 20 19 21 3 1 22 25]. 171 | 172 | Grupo físico: 2 173 | Dimensión: 1 174 | Nombre: BC 175 | Contiene nodos: [12 16 4 15 13 14 17 18 3]. 176 | 177 | Grupo físico: 3 178 | Dimensión: 1 179 | Nombre: CD 180 | Contiene nodos: [28 4 30 26 29 31 2 27 32]. 181 | 182 | Grupo físico: 4 183 | Dimensión: 1 184 | Nombre: AD 185 | Contiene nodos: [ 5 8 9 7 10 11 2 1 6]. 186 | 187 | Grupo físico: 101 188 | Dimensión: 2 189 | Nombre: mi superficie 190 | Contiene nodos: [78 74 70 77 71 66 72 76 63 64 69 79 80 60 67 75 65 73 61 62 81 68 28 39 191 | 49 5 8 12 16 23 50 37 4 41 24 30 51 52 54 9 56 57 15 58 7 20 19 26 192 | 29 40 43 13 55 10 31 53 59 14 17 11 18 2 21 27 34 35 32 36 42 38 47 3 193 | 33 44 1 48 45 46 6 22 25]. 194 | 195 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 196 | Grupo físico "AB" contiene nodos: [22 1 24 3 23 20 25 19 21] 197 | ``` 198 | -------------------------------------------------------------------------------- /area_malla.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Programa para calcular el área de una malla creada en GMSH usando la API de 4 | GMSH en Python. 5 | 6 | Por: Alejandro Hincapié G. 7 | ''' 8 | 9 | import gmsh # pip install --upgrade gmsh 10 | import numpy as np 11 | 12 | def xnod_from_msh(archivo): 13 | ''' Obtiene la matriz de coordenadas de los nodos que contiene la malla de 14 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 15 | Argumentos: 16 | - archivo: Nombre del archivo que contiene la malla 17 | - dim: 2 si la malla es 2D (valor por defecto), 3 si la malla es 3D. 18 | ''' 19 | gmsh.initialize() 20 | gmsh.open(archivo) 21 | nod, xnod, pxnod = gmsh.model.mesh.getNodes() 22 | xnod = xnod.reshape((nod.size, -1)) 23 | gmsh.finalize() 24 | 25 | return xnod 26 | 27 | def LaG_from_msh(archivo): 28 | ''' Obtiene la matriz de interconexión nodal (LaG) que contiene la malla de 29 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 30 | 31 | Retorna: Matriz LaG_mat, donde la primera columna representa la super- 32 | ficie a la que pertenece cada elemento finito (ordenadas de forma 33 | ascendente desde 0), de tal manera que diferencie elementos finitos con 34 | propiedades distintas 35 | ''' 36 | gmsh.initialize() 37 | gmsh.open(archivo) 38 | 39 | LaG = np.array([]) # Matriz de interconexión nodal 40 | elems = np.array([]) # vector de elementos finitos 41 | mats = np.array([]) # Vector de materiales al que pertenece cada EF 42 | material = 0 # Se inicializa el material 43 | 44 | for ent in gmsh.model.getEntities(2): 45 | dim, tag = ent 46 | tipo, efs, nodos = gmsh.model.mesh.getElements(dim, tag) 47 | if tipo.size == 1: 48 | nodos = nodos[0] 49 | elems = np.append(elems, efs[0]).astype(int) 50 | LaG = np.append(LaG, nodos).astype(int) 51 | mats = np.append(mats, np.tile(material, efs[0].size)).astype(int) 52 | material += 1 53 | else: 54 | raise ValueError('La malla tiene varios tipos de elementos ' 55 | 'finitos 2D.') 56 | 57 | nef = elems.size 58 | LaG = LaG.reshape((nef, -1)) - 1 59 | LaG_mat = np.c_[mats, LaG] 60 | gmsh.finalize() 61 | 62 | return LaG_mat 63 | 64 | def area_triangulo(coord): 65 | ''' Calcula el área de un elemento finito triangular dadas sus coordenadas. 66 | Argumentos: 67 | coord: Es un vector de coordenadas de la forma: [XY1, XY2, XY3], donde: 68 | XY1, XY2 y XY3 son los vectores de coordenadas de cada nodo, así: 69 | XY1 = [x1, y1, z1] son las coordenadas del nodo 1, y así para los otros 70 | nodos. 71 | ''' 72 | c1, c2, c3 = coord 73 | u = c2 - c1 74 | v = c3 - c1 75 | 76 | return np.linalg.norm(np.cross(u, v))/2 77 | 78 | 79 | def area_malla(coord): 80 | ''' Calcula el área de la malla dado el array de coordenadas de todos los 81 | elementos finitos que la componen. 82 | ''' 83 | return np.array(list(map(area_triangulo, coord))).sum() 84 | 85 | 86 | malla = './malla.msh' # Nombre del archivo .msh que contiene la malla 87 | 88 | # Se obtienen las coordenadas de los nodos y la matriz de nodos: 89 | 90 | coord = xnod_from_msh(malla) # Coordenadas de cada nodo 91 | nodos = LaG_from_msh(malla)[:, 1:] # Nodos correspondientes a cada elemento finito 92 | nef = nodos.shape[0] # Número de elementos finitos de la malla 93 | dim = coord.shape[1] # Dimensión de los elementos finitos usados 94 | 95 | coord_triangulos = np.empty((nef, 3, dim)) 96 | for i in range(nef): 97 | coord_triangulos[i] = coord[nodos[i]] # Obtiene las coordenadas de los 3 98 | # vértices de cada triángulo 99 | 100 | A = area_malla(coord_triangulos) 101 | 102 | print(f"El area de la malla es: {A}") 103 | -------------------------------------------------------------------------------- /grafico_malla2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejohg/tutoriales-gmsh/e43d58e7e0fd045e263bdc21dae17685332f735d/grafico_malla2.png -------------------------------------------------------------------------------- /leer_GMSH.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Funciones para leer y graficar una malla creada en GMSH. 4 | 5 | Por: Alejandro Hincapié G. 6 | """ 7 | 8 | import gmsh # pip install --upgrade gmsh 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from mpl_toolkits.mplot3d import Axes3D 12 | from matplotlib.colors import BASE_COLORS, TABLEAU_COLORS 13 | 14 | # %% Funciones: 15 | 16 | def xnod_from_msh(archivo, dim=2): 17 | ''' Obtiene la matriz de coordenadas de los nodos que contiene la malla de 18 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 19 | ''' 20 | gmsh.initialize() 21 | gmsh.open(archivo) 22 | nod, xnod, pxnod = gmsh.model.mesh.getNodes() 23 | xnod = xnod.reshape((nod.size, -1)) 24 | gmsh.finalize() 25 | 26 | return xnod[:, :dim] 27 | 28 | 29 | def LaG_from_msh(archivo): 30 | ''' Obtiene la matriz de interconexión nodal (LaG) que contiene la malla de 31 | EF a trabajar, a partir del archivo .msh exportado por el programa GMSH. 32 | 33 | Retorna: Matriz LaG_mat, donde la primera columna representa la super- 34 | ficie a la que pertenece cada elemento finito (ordenadas de forma 35 | ascendente desde 0), de tal manera que diferencie elementos finitos con 36 | propiedades distintas 37 | ''' 38 | gmsh.initialize() 39 | gmsh.open(archivo) 40 | 41 | LaG = np.array([]) # Matriz de interconexión nodal 42 | elems = np.array([]) # vector de elementos finitos 43 | mats = np.array([]) # Vector de materiales al que pertenece cada EF 44 | material = 0 # Se inicializa el material 45 | 46 | for ent in gmsh.model.getEntities(2): 47 | dim, tag = ent 48 | tipo, efs, nodos = gmsh.model.mesh.getElements(dim, tag) 49 | if tipo.size == 1: 50 | nodos = nodos[0] 51 | elems = np.append(elems, efs[0]).astype(int) 52 | LaG = np.append(LaG, nodos).astype(int) 53 | mats = np.append(mats, np.tile(material, efs[0].size)).astype(int) 54 | material += 1 55 | else: 56 | raise ValueError('La malla tiene varios tipos de elementos ' 57 | 'finitos 2D.') 58 | 59 | nef = elems.size 60 | LaG = LaG.reshape((nef, -1)) - 1 61 | LaG_mat = np.c_[mats, LaG] 62 | gmsh.finalize() 63 | 64 | return LaG_mat 65 | 66 | 67 | def plot_msh(file, tipo, mostrar_nodos=False, mostrar_num_nodo=False, 68 | mostrar_num_elem=False): 69 | ''' Función para graficar la malla contenida en el archivo "file". 70 | Argumentos: 71 | - file: str. Debe ser un archivo de extensión .msh exportado por GMSH. 72 | - tipo: str. 'shell' o '2D' 73 | - mostrar_nodos: bool. Define si se muestran los nodos en el gráfico. 74 | - mostrar_num_nodo: bool. Define si se muestra el número de cada nodo 75 | en el gráfico. 76 | - mostrar_num_elem: bool. Define si se muestra el número de cada ele- 77 | mento finito en el gráfico. 78 | ''' 79 | 80 | LaG_mat = LaG_from_msh(file) 81 | mat = LaG_mat[:, 0] 82 | LaG = LaG_mat[:, 1:] 83 | nef = LaG.shape[0] 84 | 85 | # Se determina el tipo de elemento finito 86 | if LaG.shape[1] == 3: 87 | elem = 'T3' 88 | elif LaG.shape[1] == 4: 89 | elem = 'Q4' 90 | elif LaG.shape[1] == 6: 91 | elem = 'T6' 92 | elif LaG.shape[1] == 8: 93 | elem = 'Q8' 94 | elif LaG.shape[1] == 9: 95 | elem = 'Q9' 96 | elif LaG.shape[1] == 10: 97 | elem = 'T10' 98 | 99 | if tipo=='shell': 100 | xnod = xnod_from_msh(file, 3) 101 | nno = xnod.shape[0] 102 | 103 | cg = np.empty((nef,3)) # Centro de gravedad del EF 104 | colores = list(BASE_COLORS.values()) + list(TABLEAU_COLORS.values()) 105 | 106 | fig = plt.figure(figsize=(12, 12)) 107 | ax = plt.gca(projection='3d') 108 | for e in range(nef): 109 | if elem=='T3' or elem=='Q4': 110 | nodos = np.r_[LaG[e], LaG[e, 0]] 111 | elif elem =='T6': 112 | nodos = LaG[e, [0, 3, 1, 4, 2, 5, 0]] 113 | elif elem =='Q8' or elem == 'Q9': 114 | nodos = LaG[e, [0, 4, 1, 5, 2, 6, 3, 7, 0]] 115 | elif elem == 'T10': 116 | nodos = LaG[e, [0, 3, 4, 1, 5, 6, 2, 7, 8, 0]] 117 | color = colores[mat[e]] 118 | X = xnod[nodos,0] 119 | Y = xnod[nodos,1] 120 | Z = xnod[nodos,2] 121 | 122 | ax.plot3D(X, Y, Z, '-', lw=0.8, c=color) 123 | if mostrar_nodos: 124 | ax.plot3D(xnod[LaG[e],0], xnod[LaG[e],1], xnod[LaG[e],2], 125 | 'ko', ms=5, mfc='r') 126 | if mostrar_num_elem: 127 | # se calcula la posición del centro de gravedad del EF 128 | cg[e] = np.mean(xnod[LaG[e]], axis=0) 129 | 130 | # y se reporta el número del elemento actual 131 | ax.text(cg[e,0], cg[e,1], cg[e,2], f'{e+1}', 132 | horizontalalignment='center', 133 | verticalalignment='center', color=color) 134 | if mostrar_num_nodo: 135 | for i in range(nno): 136 | ax.text(xnod[i,0], xnod[i,1], xnod[i,2], f'{i+1}', color='r') 137 | fig.suptitle(f'Malla de EF Shell ({elem})', fontsize='x-large') 138 | ax.set_xlabel('x') 139 | ax.set_ylabel('y') 140 | ax.set_zlabel('z') 141 | ax.view_init(45, 45) 142 | 143 | elif tipo=='2D': 144 | xnod = xnod_from_msh(file, 2) 145 | nno = xnod.shape[0] 146 | 147 | cg = np.empty((nef,2)) 148 | colores = list(BASE_COLORS.values()) + list(TABLEAU_COLORS.values()) 149 | fig = plt.figure(figsize=(12, 12)) 150 | ax = plt.gca() 151 | for e in range(nef): 152 | if elem=='T3' or elem=='Q4': 153 | nodos = np.r_[LaG[e], LaG[e, 0]] 154 | elif elem =='T6': 155 | nodos = LaG[e, [0, 3, 1, 4, 2, 5, 0]] 156 | elif elem =='Q8' or elem == 'Q9': 157 | nodos = LaG[e, [0, 4, 1, 5, 2, 6, 3, 7, 0]] 158 | elif elem == 'T10': 159 | nodos = LaG[e, [0, 3, 4, 1, 5, 6, 2, 7, 8, 0]] 160 | color = colores[mat[e]] 161 | X = xnod[nodos,0] 162 | Y = xnod[nodos,1] 163 | ax.plot(X, Y, '-', lw=0.8, c=color) 164 | if mostrar_nodos: 165 | ax.plot(xnod[LaG[e],0], xnod[LaG[e],1], 'ko', ms=5, mfc='r') 166 | if mostrar_num_elem: 167 | # se calcula la posición del centro de gravedad del EF 168 | cg[e] = np.mean(xnod[LaG[e]], axis=0) 169 | 170 | # y se reporta el número del elemento actual 171 | ax.text(cg[e,0], cg[e,1], f'{e+1}', horizontalalignment='center', 172 | verticalalignment='center', color=color) 173 | if mostrar_num_nodo: 174 | for i in range(nno): 175 | ax.text(xnod[i,0], xnod[i,1], f'{i+1}', color='r') 176 | fig.suptitle(f'Malla de EF 2D ({elem})', fontsize='x-large') 177 | ax.set_xlabel('x') 178 | ax.set_ylabel('y') 179 | ax.set_aspect('equal', adjustable='box') 180 | else: 181 | raise ValueError('El argumento "tipo" introducido no es válido') -------------------------------------------------------------------------------- /obtener_grupos_fisicos.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Funciones para obtener todos los grupos físicos definidos en una malla, su dimen- 4 | sión y los nodos asociados a cada uno, a partir de un archivo .msh exportado 5 | por GMSH. 6 | 7 | Para correr este programa y usar las funciones se requiere tener instalada la API 8 | de GMSH en Python, usar el comando pip install --upgrade gmsh 9 | 10 | Por: Alejandro Hincapié G. 11 | """ 12 | 13 | import gmsh # pip install --upgrade gmsh 14 | 15 | def grupos_fisicos(archivo, dim=-1): 16 | ''' Lee un archivo de texto con extensión .msh que contiene los datos de 17 | una malla generada en GMSH. 18 | Si se especifica el argumento dim, solo se reportan los grupos físicos 19 | de tal dimensión, si no, se reportan todos. 20 | Retorna: 21 | - Un diccionario en el cual las claves son las ETIQUETAS de los 22 | grupos físicos y los valores son tuplas con la DIMENSIÓN y el 23 | NOMBRE asociados a cada etiqueta. 24 | - Un diccionario en el cual las claves son las etiquetas de los 25 | grupos físicos y los valores son listas con los nodos asociados 26 | a cada grupo físico. 27 | ''' 28 | if archivo[-4:] != '.msh': 29 | raise ValueError('Solo se admite un archivo de extensión .msh') 30 | 31 | gmsh.initialize() 32 | gmsh.open(archivo) 33 | 34 | grupos = gmsh.model.getPhysicalGroups(dim) 35 | dims = [d[0] for d in grupos] 36 | tags = [d[1] for d in grupos] 37 | nombres = [(dim, gmsh.model.getPhysicalName(dim, tag)) 38 | for dim, tag in grupos] 39 | 40 | dict1 = dict(zip(tags, nombres)) # Primer diccionario a reportar 41 | 42 | nodos = [gmsh.model.mesh.getNodesForPhysicalGroup(dims[i], tags[i])[0] 43 | for i in range(len(dims))] 44 | dict2 = dict(zip(tags, nodos)) # Segundo diccionario a reportar 45 | 46 | gmsh.finalize() 47 | 48 | return dict1, dict2 49 | 50 | 51 | def obtener_nodos(archivo, nombre_grupo): 52 | ''' Lee un archivo de extensión .msh y obtiene los nodos asociados al grupo 53 | físico de nombre "nombre_grupo". 54 | ''' 55 | d1, d2 = grupos_fisicos(archivo) 56 | nombres = [t[1] for t in d1.values()] 57 | if nombre_grupo in nombres: 58 | nodos = d2[list(d2.keys())[nombres.index(nombre_grupo)]] 59 | return nodos 60 | else: 61 | raise ValueError('El nombre de grupo físico ingresado no existe.') 62 | -------------------------------------------------------------------------------- /scordelis.msh: -------------------------------------------------------------------------------- 1 | $MeshFormat 2 | 4.1 0 8 3 | $EndMeshFormat 4 | $PhysicalNames 5 | 5 6 | 1 3 "AD" 7 | 1 4 "BC" 8 | 1 5 "AB" 9 | 1 6 "CD" 10 | 2 101 "mi superficie" 11 | $EndPhysicalNames 12 | $Entities 13 | 6 4 1 0 14 | 0 0 0 0 0 15 | 1 16.06969024216348 0 19.15111107797445 0 16 | 2 0 0 25 0 17 | 3 16.06969024216348 25 19.15111107797445 0 18 | 4 0 25 0 0 19 | 5 0 25 25 0 20 | 3 0 0 19.15111107797445 16.06969024216348 0 25 1 3 2 1 -2 21 | 4 0 25 19.15111107797445 16.06969024216348 25 25 1 4 2 3 -5 22 | 5 16.06969024216348 0 19.15111107797445 16.06969024216348 25 19.15111107797445 1 -5 2 1 -3 23 | 6 0 0 25 0 25 25 1 6 2 2 -5 24 | 7 0 0 19.15111107797445 16.06969024216348 25 25 1 101 4 3 6 -4 -5 25 | $EndEntities 26 | $Nodes 27 | 9 25 1 25 28 | 0 1 0 1 29 | 1 30 | 16.06969024216348 0 19.15111107797445 31 | 0 2 0 1 32 | 2 33 | 0 0 25 34 | 0 3 0 1 35 | 3 36 | 16.06969024216348 25 19.15111107797445 37 | 0 5 0 1 38 | 4 39 | 0 25 25 40 | 1 3 0 3 41 | 5 42 | 6 43 | 7 44 | 12.4999999912267 0 21.65063509967623 45 | 8.550503556475576 0 23.49231552935339 46 | 4.341204430722733 0 24.62019382723607 47 | 1 4 0 3 48 | 8 49 | 9 50 | 10 51 | 12.4999999912267 25 21.65063509967623 52 | 8.550503556475576 25 23.49231552935339 53 | 4.341204430722733 25 24.62019382723607 54 | 1 5 0 3 55 | 11 56 | 12 57 | 13 58 | 16.06969024216348 6.25 19.15111107797445 59 | 16.06969024216348 12.5 19.15111107797445 60 | 16.06969024216348 18.75 19.15111107797445 61 | 1 6 0 3 62 | 14 63 | 15 64 | 16 65 | 0 6.25 25 66 | 0 12.5 25 67 | 0 18.75 25 68 | 2 7 0 9 69 | 17 70 | 18 71 | 19 72 | 20 73 | 21 74 | 22 75 | 23 76 | 24 77 | 25 78 | 12.4999999912267 6.25 21.65063509967623 79 | 12.4999999912267 12.5 21.65063509967623 80 | 12.4999999912267 18.75 21.65063509967623 81 | 8.550503556475576 6.25 23.49231552935339 82 | 8.550503556475576 12.5 23.49231552935339 83 | 8.550503556475576 18.75 23.49231552935339 84 | 4.341204430722732 6.25 24.62019382723608 85 | 4.341204430722732 12.5 24.62019382723608 86 | 4.341204430722732 18.75 24.62019382723608 87 | $EndNodes 88 | $Elements 89 | 5 32 1 32 90 | 1 3 1 4 91 | 1 1 5 92 | 2 5 6 93 | 3 6 7 94 | 4 7 2 95 | 1 4 1 4 96 | 5 3 8 97 | 6 8 9 98 | 7 9 10 99 | 8 10 4 100 | 1 5 1 4 101 | 9 1 11 102 | 10 11 12 103 | 11 12 13 104 | 12 13 3 105 | 1 6 1 4 106 | 13 2 14 107 | 14 14 15 108 | 15 15 16 109 | 16 16 4 110 | 2 7 3 16 111 | 17 1 5 17 11 112 | 18 11 17 18 12 113 | 19 12 18 19 13 114 | 20 13 19 8 3 115 | 21 5 6 20 17 116 | 22 17 20 21 18 117 | 23 18 21 22 19 118 | 24 19 22 9 8 119 | 25 6 7 23 20 120 | 26 20 23 24 21 121 | 27 21 24 25 22 122 | 28 22 25 10 9 123 | 29 7 2 14 23 124 | 30 23 14 15 24 125 | 31 24 15 16 25 126 | 32 25 16 4 10 127 | $EndElements 128 | --------------------------------------------------------------------------------