Files

115 lines
4.3 KiB
Python
Raw Permalink Normal View History

# 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"
)