# Generazione della mesh a elementi shell quadrangolari della fascetta. # # La fascetta è modellata come la superficie media di un cilindro: gli # elementi sono quadrilateri a 4 nodi disposti su una griglia strutturata # n_elementi_x × n_elementi_circonferenza. Lo spessore della parete non è # discretizzato, è un attributo degli elementi shell. # # Sistema di coordinate globale (coerente con plot_animazione_3d.py): # - x = asse del cilindro, da 0 a lunghezza # - y, z = piano della sezione circolare, con y = R·sin(theta), z = R·cos(theta) # - theta = 0 corrisponde al piano del sensore (y = 0), cresce in verso # antiorario nel piano y-z # # La mesh è chiusa lungo la circonferenza: l'ultima colonna di elementi # richiude sulla prima riga di nodi, senza nodi duplicati sulla cucitura. import numpy as np from config import FASCETTA, MESH def raggio_medio_m(fascetta: dict = FASCETTA) -> float: # Raggio della superficie media della parete: il diametro configurato è # quello esterno, la shell sta a metà dello spessore. diametro_m = fascetta["diametro_mm"] / 1000.0 spessore_m = fascetta["spessore_mm"] / 1000.0 return (diametro_m - spessore_m) / 2.0 def genera_mesh(fascetta: dict = FASCETTA, mesh: dict = MESH) -> dict: """Costruisce la mesh shell quadrangolare della fascetta. Restituisce un dizionario con: - nodi: array (n_nodi, 3) con le coordinate globali [m] - elementi: array (n_elementi, 4) con gli indici dei nodi, in verso antiorario visto dall'esterno del cilindro - indice_nodo: array (n_x + 1, n_theta) che mappa (i, j) sull'indice globale del nodo - x_nodi_m, theta_nodi_rad: coordinate parametriche della griglia - spessore_m, raggio_m, lunghezza_m: geometria della shell """ n_x = int(mesh["n_elementi_x"]) n_theta = int(mesh["n_elementi_circonferenza"]) if n_x < 1 or n_theta < 3: raise ValueError( "Servono almeno 1 elemento lungo x e 3 lungo la circonferenza." ) lunghezza_m = fascetta["lunghezza_mm"] / 1000.0 spessore_m = fascetta["spessore_mm"] / 1000.0 raggio_m = raggio_medio_m(fascetta) # Lungo x la griglia è aperta (n_x + 1 file di nodi), lungo theta è # chiusa (n_theta file, l'ultima si ricongiunge alla prima). x_nodi_m = np.linspace(0.0, lunghezza_m, n_x + 1) theta_nodi_rad = np.linspace(0.0, 2.0 * np.pi, n_theta, endpoint=False) X, Theta = np.meshgrid(x_nodi_m, theta_nodi_rad, indexing="ij") nodi = np.column_stack( ( X.ravel(), (raggio_m * np.sin(Theta)).ravel(), (raggio_m * np.cos(Theta)).ravel(), ) ) indice_nodo = np.arange((n_x + 1) * n_theta).reshape(n_x + 1, n_theta) i = np.arange(n_x)[:, None] j = np.arange(n_theta)[None, :] j_succ = (j + 1) % n_theta elementi = np.stack( ( np.broadcast_to(indice_nodo[i, j], (n_x, n_theta)), np.broadcast_to(indice_nodo[i + 1, j], (n_x, n_theta)), np.broadcast_to(indice_nodo[i + 1, j_succ], (n_x, n_theta)), np.broadcast_to(indice_nodo[i, j_succ], (n_x, n_theta)), ), axis=-1, ).reshape(-1, 4) return { "nodi": nodi, "elementi": elementi, "indice_nodo": indice_nodo, "x_nodi_m": x_nodi_m, "theta_nodi_rad": theta_nodi_rad, "n_elementi_x": n_x, "n_elementi_circonferenza": n_theta, "spessore_m": spessore_m, "raggio_m": raggio_m, "lunghezza_m": lunghezza_m, } def riepilogo_mesh(mesh_dati: dict) -> str: n_nodi = len(mesh_dati["nodi"]) n_elementi = len(mesh_dati["elementi"]) passo_x_mm = 1000.0 * mesh_dati["lunghezza_m"] / mesh_dati["n_elementi_x"] passo_circ_mm = ( 1000.0 * 2.0 * np.pi * mesh_dati["raggio_m"] / mesh_dati["n_elementi_circonferenza"] ) return ( f"Mesh shell: {n_elementi} elementi quadrangolari, {n_nodi} nodi\n" f" griglia: {mesh_dati['n_elementi_x']} (x) × " f"{mesh_dati['n_elementi_circonferenza']} (circonferenza)\n" f" passo elemento: {passo_x_mm:.2f} mm (x), {passo_circ_mm:.2f} mm (circonferenza)\n" f" raggio medio: {1000.0 * mesh_dati['raggio_m']:.2f} mm, " f"spessore shell: {1000.0 * mesh_dati['spessore_m']:.3f} mm" )