115 lines
4.3 KiB
Python
115 lines
4.3 KiB
Python
# 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"
|
|||
|
|
)
|