Introduce mesh.py, che costruisce una griglia strutturata di quadrilateri a 4 nodi sulla superficie media del cilindro, chiusa lungo la circonferenza, come base per la futura analisi FEM. plot_mesh.py la visualizza in vista isometrica e main.py raccoglie tutte le azioni del progetto dietro un unico punto di ingresso. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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"
|
||
)
|