add vdi2230 skill: VDI 2230 bolt joint dimensioning engine

Full R0-R13 calculation engine with:
- Guided data collection (N bolts, per-bolt load, DA_prime, thermal dT)
- Auto-sizing script (M4->M39 iteration across strength classes)
- fm_table A1 lookup for fast MA/FM queries without full R0-R13
- Warnings for stainless steel galling, ESV insert threads (Helicoil/Ensat)
- Bolt circle load distribution formula (FA + MB + MT)
- 7 evals covering static, fatigue, ESV aluminium, pressure seal,
  thermal steel-aluminium, and combined FA+FQ cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 10:37:40 +01:00
parent b3bffa2315
commit 4a2e3f8389
9 changed files with 1977 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
---
name: vdi2230
description: >
Calcolo sistematico di giunzioni bullonate secondo VDI 2230 Blatt 1:2003 — lo standard
tedesco per il dimensionamento di viti ad alta sollecitazione. Usare questa skill ogni
volta che un progettista o ingegnere chiede di scegliere o verificare una vite, calcolare
la coppia di serraggio MA, determinare il precarico FM, verificare tenuta o resistenza a
fatica di un bullone, controllare pressione sotto testa o lunghezza di avvitamento,
dimensionare flangie bullonate o giunzioni strutturali. Trigger anche su frasi come
"che vite uso per...", "regge questa M12?", "coppia di serraggio per classe 10.9",
"verifica VDI 2230", "bullone su alluminio", "quanto serrare questa flangia".
La skill esegue i calcoli tramite script Python precisi — mai a mano — e guida il
progettista nella raccolta dei dati con domande mirate e default ingegneristici sensati.
---
# VDI 2230:2003 — Dimensionamento Giunzioni Bullonate
Esegui sempre il calcolo tramite script — mai a mano. I 13 passi VDI 2230 sono interdipendenti: errori numerici in R3 si propagano fino a R12.
Leggi `references/input_params.md` per parametri non-standard (eccentricità, termico, tenuta, taglio) o se il progettista chiede il significato di un campo.
Leggi `references/formulario_rapido.md` se il progettista chiede spiegazioni su formule o termini VDI.
---
## Raccolta dati
Chiedi solo l'essenziale. Dichiara i default assunti e procedi.
**Chiedi sempre se non forniti:**
1. **FA_max [N]** — forza assiale massima sulla giunzione *totale*
- *Come misurarla/stimarla:* peso sospeso, pressione × area, forza da FEM/analitica, fattore dinamico × forza statica. Chiedere se è statica o ciclica (serve FA_min per la verifica fatica R9).
2. **N — numero di viti** nella giunzione
- *Calcolo per vite:* FA_per_bolt = FA_totale / N (solo se le viti sono in posizione simmetrica rispetto al carico).
- Se c'è un momento flettente sul giunto (MB), la vite più caricata vale: FA_bolt = FA_tot/N + MB/(N·r_bc) dove r_bc è il raggio del cerchio viti. Chiedere se esiste MB.
3. **lK [mm]** — somma degli spessori di tutte le parti serrate (senza dado)
- *Come ricavarla:* sommare i valori dal disegno. Se non disponibile, stimare dalla profondità dell'incastro visibile + spessore flange.
4. **DA [mm]** — diametro esterno della zona di contatto (flangia, piastra)
- *Come ricavarla:* è il diametro esterno della flangia o della piastra che viene compressa dalla testa vite. Se la piastra è grande rispetto alla vite, usare il "diametro efficace" ≈ 3·dW come limite pratico.
5. **DA_prime_mm [mm]** — diametro esterno del corpo base che sorregge il cono di deformazione
- *Default consigliato:* DA × 1,5 se il pezzo è massiccio; DA se è una flangia sottile. In assenza di disegno, usare DA (conservativo).
6. **dh [mm]** — diametro del foro passante vite
- *Come ricavarla:* da tabella DIN EN 20273: foro medio ≈ d + 1 mm (M≤10), d + 2 mm (M12M24). Se il foro non è ancora definito, usare d + 1,5 mm come stima.
7. **DSV** (vite passante con dado) **o ESV** (vite avvitata direttamente nel materiale)?
- *Come riconoscerlo:* ESV = filetto nel corpo, nessun dado. Chiedere anche il materiale del filetto interno (acciaio, ghisa, alluminio).
8. **Materiale delle parti serrate** — acciaio, alluminio, ghisa?
- *Impatto diretto su:* EP (cedevolezza), pG (pressione limite sotto testa), meff_min (lunghezza avvitamento minima ESV).
**Assumi senza chiedere** (dichiarandolo):
- Acciaio: EP = 210.000 N/mm², pG = 900 N/mm²
- µG = µK = 0,12 (classe B, acciaio leggermente oliato), αA = 1,7 (chiave dinamometrica)
- fZ = 12 µm, filettatura regolare, trattamento SV, meff = 1,5·d
- DA_prime_mm = DA × 1,5, hmin_mm = lK / 2 (salvo diversa indicazione)
**Chiedi solo se il caso lo richiede:**
- FQ [N]: forza trasversale → chiedi se il carico ha una componente perpendicolare all'asse vite (es. giunti flangiati soggetti a momento torcente attorno all'asse del pacco, supporti a sbalzo)
- pi_max [N/mm²]: pressione interna → chiedi se la giunzione deve tenere un fluido in pressione (specificare anche l'area guarnizione AD)
- FA_min [N]: per carichi ciclici/vibranti → chiedi "il carico oscilla o è sempre fisso?"
- ΔT [K]: variazione termica → chiedi se i materiali della vite e delle parti serrate sono diversi (es. vite acciaio in alluminio) o se c'è un ciclo termico (es. motori, scambiatori)
- Materiale inox: se l'utente menziona A2, A4, acciaio inossidabile → avvisare subito del rischio grippaggio (vedi nota sotto)
**⚠ Acciaio inossidabile — rischio grippaggio (Fressen)**
Inox A2/A4 senza lubrificante è soggetto a grippaggio durante il serraggio: µ può raggiungere 0,250,40 e la vite può rompersi. Usare sempre pasta anti-grippante (Molykote 1000, Copaslip, Electrolube) e classe µ C/D (µ = 0,180,25). Specificare esplicitamente al progettista anche se non chiesto.
---
**Stima iniziale diametro per vite** (poi verifica con lo script):
| FA_max *per vite* | 8.8 | 10.9 | 12.9 |
|-------------------|-----|------|------|
| ≤ 10 kN | M8 | M6 | M5 |
| 1030 kN | M12 | M10 | M8 |
| 3060 kN | M16 | M12 | M10 |
| 60120 kN | M22 | M18 | M14 |
| > 120 kN | M30+ | M24+ | M20+ |
Se il diametro non è fissato, usa direttamente `auto_size.py`.
**⚠ Nota auto_size con diametro variabile:** quando auto_size.py itera da M4 a M39, i parametri DA_mm, DA_prime_mm e lK_mm restano fissi. Questo è corretto se la geometria della flangia non cambia (es. foro predefinito). Se la geometria scala col diametro, lanciare auto_size con DA e dh aggiornati manualmente per le taglie candidate.
---
## Calcolo
**Risposta rapida (MA o FM senza verifica completa)**
Se il progettista chiede *solo* la coppia di serraggio o il precarico per una combinazione nota (size + classe + µ), usa direttamente la tabella `fm_table.json` tramite `get_fm_table()` — non serve eseguire lo script completo né raccogliere lK/DA/dh. Esempio: "MA per M16 10.9 µ=0,12" → risposta diretta dal dizionario A1. Specifica che il valore è per gambo cilindrico standard DIN EN ISO 4014.
**Verifica diametro scelto:**
```bash
python3 scripts/vdi2230_calc.py /tmp/input.json /tmp/result.json
```
**Trova diametro minimo** (ometti `"size"` dal JSON):
```bash
python3 scripts/auto_size.py /tmp/input.json /tmp/result.json
```
JSON minimo (caso standard DSV, acciaio, carico statico centrico):
```json
{
"size": "M12", "strength_class": "10.9", "joint_type": "DSV",
"lK_mm": 40, "DA_mm": 36, "dh_mm": 13.5, "DA_prime_mm": 54, "hmin_mm": 20,
"FA_max_N": 20000, "FA_min_N": 20000,
"mu_G": 0.12, "alpha_A": 1.7, "EP_N_mm2": 210000, "pG_N_mm2": 900,
"fZ_total_um": 12, "meff_actual_mm": 18
}
```
---
## Report al progettista
Presenta sempre: vite (size + classe), MA [Nm], FM min/max/zul [kN], Φ, e tabella verifiche R7R12 con ✅/❌ e valore sicurezza. Per le verifiche fallite usa il campo `result["steps"]["RX"]["remedies"]` per le azioni correttive — traducilo in linguaggio da progettista (es. "aumentare a M14" non "incrementare d").
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
VDI 2230 · [SIZE] [CLASSE] · [DSV/ESV]
MA = [X] Nm (µ=[µ]) · FM min=[X] kN · FM zul=[X] kN (×[X])
Φ = [X]
R7 Montaggio [✅/❌] ratio=[X] · R8 Esercizio [✅/❌] SF=[X]
R9 Fatica [✅/❌] SD=[X] · R10 Pressione [✅/❌] SP=[X]
R11 Avvit. [✅/❌] ratio=[X] · R12 Scorrimento [✅/❌] SG=[X]
[✅ APPROVATO / ❌ CORREZIONI — vedi sotto]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
+71
View File
@@ -0,0 +1,71 @@
{
"_comment": "VDI 2230:2003 Tabelle A1 (Schaftschrauben, Regelgewinde) e A3 (Feingewinde) — FM_Tab [kN] e MA [Nm] per n=0.9, Sechskant DIN EN ISO 4014-4018 / Zylinder ISO 4762",
"_usage": "Chiavi: size → classe → mu_value (come stringa '0.08','0.10',etc.) → {FM_kN, MA_Nm}",
"A1_regular_shank": {
"M4": {
"8.8": {"0.08":{"FM":4.6,"MA":2.3},"0.10":{"FM":4.5,"MA":2.6},"0.12":{"FM":4.4,"MA":3.0},"0.14":{"FM":4.3,"MA":3.3},"0.16":{"FM":4.2,"MA":3.6},"0.20":{"FM":3.9,"MA":4.1},"0.24":{"FM":3.7,"MA":4.5}},
"10.9": {"0.08":{"FM":6.8,"MA":3.3},"0.10":{"FM":6.7,"MA":3.9},"0.12":{"FM":6.5,"MA":4.6},"0.14":{"FM":6.3,"MA":4.8},"0.16":{"FM":6.1,"MA":5.3},"0.20":{"FM":5.7,"MA":6.0},"0.24":{"FM":5.4,"MA":6.6}},
"12.9": {"0.08":{"FM":8.0,"MA":3.9},"0.10":{"FM":7.8,"MA":4.5},"0.12":{"FM":7.6,"MA":5.1},"0.14":{"FM":7.4,"MA":5.6},"0.16":{"FM":7.1,"MA":6.2},"0.20":{"FM":6.7,"MA":7.0},"0.24":{"FM":6.3,"MA":7.8}}
},
"M5": {
"8.8": {"0.08":{"FM":7.6,"MA":4.4},"0.10":{"FM":7.4,"MA":5.2},"0.12":{"FM":7.2,"MA":5.9},"0.14":{"FM":7.0,"MA":6.5},"0.16":{"FM":6.8,"MA":7.1},"0.20":{"FM":6.4,"MA":8.1},"0.24":{"FM":6.0,"MA":9.0}},
"10.9": {"0.08":{"FM":11.1,"MA":6.5},"0.10":{"FM":10.8,"MA":7.6},"0.12":{"FM":10.6,"MA":8.6},"0.14":{"FM":10.3,"MA":9.5},"0.16":{"FM":10.0,"MA":10.4},"0.20":{"FM":9.4,"MA":11.9},"0.24":{"FM":8.8,"MA":13.2}},
"12.9": {"0.08":{"FM":13.0,"MA":7.6},"0.10":{"FM":12.7,"MA":8.9},"0.12":{"FM":12.4,"MA":10.0},"0.14":{"FM":12.0,"MA":11.2},"0.16":{"FM":11.7,"MA":12.2},"0.20":{"FM":11.0,"MA":14.0},"0.24":{"FM":10.3,"MA":15.5}}
},
"M6": {
"8.8": {"0.08":{"FM":10.7,"MA":7.7},"0.10":{"FM":10.4,"MA":9.0},"0.12":{"FM":10.2,"MA":10.1},"0.14":{"FM":9.9,"MA":11.3},"0.16":{"FM":9.6,"MA":12.3},"0.20":{"FM":9.0,"MA":14.1},"0.24":{"FM":8.4,"MA":15.6}},
"10.9": {"0.08":{"FM":15.7,"MA":11.3},"0.10":{"FM":15.3,"MA":13.2},"0.12":{"FM":14.9,"MA":14.9},"0.14":{"FM":14.5,"MA":16.5},"0.16":{"FM":14.1,"MA":18.0},"0.20":{"FM":13.2,"MA":20.7},"0.24":{"FM":12.4,"MA":22.9}},
"12.9": {"0.08":{"FM":18.4,"MA":13.2},"0.10":{"FM":17.9,"MA":15.4},"0.12":{"FM":17.5,"MA":17.4},"0.14":{"FM":17.0,"MA":19.3},"0.16":{"FM":16.5,"MA":21.1},"0.20":{"FM":15.5,"MA":24.2},"0.24":{"FM":14.5,"MA":26.8}}
},
"M8": {
"8.8": {"0.08":{"FM":19.5,"MA":18.5},"0.10":{"FM":19.1,"MA":21.6},"0.12":{"FM":18.6,"MA":24.6},"0.14":{"FM":18.1,"MA":27.3},"0.16":{"FM":17.6,"MA":29.8},"0.20":{"FM":16.5,"MA":34.3},"0.24":{"FM":15.5,"MA":38.0}},
"10.9": {"0.08":{"FM":28.7,"MA":27.2},"0.10":{"FM":28.0,"MA":31.8},"0.12":{"FM":27.3,"MA":36.1},"0.14":{"FM":26.6,"MA":40.1},"0.16":{"FM":25.8,"MA":43.8},"0.20":{"FM":24.3,"MA":50.3},"0.24":{"FM":22.7,"MA":55.8}},
"12.9": {"0.08":{"FM":33.6,"MA":31.8},"0.10":{"FM":32.8,"MA":37.2},"0.12":{"FM":32.0,"MA":42.2},"0.14":{"FM":31.1,"MA":46.9},"0.16":{"FM":30.2,"MA":51.2},"0.20":{"FM":28.4,"MA":58.9},"0.24":{"FM":26.6,"MA":65.3}}
},
"M10": {
"8.8": {"0.08":{"FM":31.0,"MA":36},"0.10":{"FM":30.3,"MA":43},"0.12":{"FM":29.6,"MA":48},"0.14":{"FM":28.8,"MA":54},"0.16":{"FM":27.9,"MA":59},"0.20":{"FM":26.3,"MA":68},"0.24":{"FM":24.7,"MA":75}},
"10.9": {"0.08":{"FM":45.6,"MA":53},"0.10":{"FM":44.5,"MA":63},"0.12":{"FM":43.4,"MA":71},"0.14":{"FM":42.2,"MA":79},"0.16":{"FM":41.0,"MA":87},"0.20":{"FM":38.6,"MA":100},"0.24":{"FM":36.2,"MA":110}},
"12.9": {"0.08":{"FM":53.3,"MA":62},"0.10":{"FM":52.1,"MA":73},"0.12":{"FM":50.8,"MA":83},"0.14":{"FM":49.4,"MA":93},"0.16":{"FM":48.0,"MA":101},"0.20":{"FM":45.2,"MA":116},"0.24":{"FM":42.4,"MA":129}}
},
"M12": {
"8.8": {"0.08":{"FM":45.2,"MA":63},"0.10":{"FM":44.1,"MA":73},"0.12":{"FM":43.0,"MA":84},"0.14":{"FM":41.9,"MA":93},"0.16":{"FM":40.7,"MA":102},"0.20":{"FM":38.3,"MA":117},"0.24":{"FM":35.9,"MA":130}},
"10.9": {"0.08":{"FM":66.3,"MA":92},"0.10":{"FM":64.8,"MA":108},"0.12":{"FM":63.2,"MA":123},"0.14":{"FM":61.5,"MA":137},"0.16":{"FM":59.8,"MA":149},"0.20":{"FM":56.3,"MA":172},"0.24":{"FM":52.8,"MA":191}},
"12.9": {"0.08":{"FM":77.6,"MA":108},"0.10":{"FM":75.9,"MA":126},"0.12":{"FM":74.0,"MA":144},"0.14":{"FM":72.0,"MA":160},"0.16":{"FM":70.0,"MA":175},"0.20":{"FM":65.8,"MA":201},"0.24":{"FM":61.8,"MA":223}}
},
"M14": {
"8.8": {"0.08":{"FM":62.0,"MA":100},"0.10":{"FM":60.6,"MA":117},"0.12":{"FM":59.1,"MA":133},"0.14":{"FM":57.5,"MA":148},"0.16":{"FM":55.9,"MA":162},"0.20":{"FM":52.6,"MA":187},"0.24":{"FM":49.3,"MA":207}},
"10.9": {"0.08":{"FM":91.0,"MA":146},"0.10":{"FM":88.9,"MA":172},"0.12":{"FM":86.7,"MA":195},"0.14":{"FM":84.4,"MA":218},"0.16":{"FM":82.1,"MA":238},"0.20":{"FM":77.2,"MA":274},"0.24":{"FM":72.5,"MA":304}},
"12.9": {"0.08":{"FM":106.5,"MA":171},"0.10":{"FM":104.1,"MA":201},"0.12":{"FM":101.5,"MA":229},"0.14":{"FM":98.8,"MA":255},"0.16":{"FM":96.0,"MA":279},"0.20":{"FM":90.4,"MA":321},"0.24":{"FM":84.8,"MA":356}}
},
"M16": {
"8.8": {"0.08":{"FM":84.7,"MA":153},"0.10":{"FM":82.9,"MA":180},"0.12":{"FM":80.9,"MA":206},"0.14":{"FM":78.8,"MA":230},"0.16":{"FM":76.6,"MA":252},"0.20":{"FM":72.2,"MA":291},"0.24":{"FM":67.8,"MA":325}},
"10.9": {"0.08":{"FM":124.4,"MA":224},"0.10":{"FM":121.7,"MA":264},"0.12":{"FM":118.8,"MA":302},"0.14":{"FM":115.7,"MA":338},"0.16":{"FM":112.6,"MA":370},"0.20":{"FM":106.1,"MA":428},"0.24":{"FM":99.6,"MA":477}},
"12.9": {"0.08":{"FM":145.5,"MA":262},"0.10":{"FM":142.4,"MA":309},"0.12":{"FM":139.0,"MA":354},"0.14":{"FM":135.4,"MA":395},"0.16":{"FM":131.7,"MA":433},"0.20":{"FM":124.1,"MA":501},"0.24":{"FM":116.6,"MA":558}}
},
"M20": {
"8.8": {"0.08":{"FM":136,"MA":308},"0.10":{"FM":134,"MA":363},"0.12":{"FM":130,"MA":415},"0.14":{"FM":127,"MA":464},"0.16":{"FM":123,"MA":509},"0.20":{"FM":116,"MA":588},"0.24":{"FM":109,"MA":655}},
"10.9": {"0.08":{"FM":194,"MA":438},"0.10":{"FM":190,"MA":517},"0.12":{"FM":186,"MA":592},"0.14":{"FM":181,"MA":661},"0.16":{"FM":176,"MA":725},"0.20":{"FM":166,"MA":838},"0.24":{"FM":156,"MA":933}},
"12.9": {"0.08":{"FM":227,"MA":513},"0.10":{"FM":223,"MA":605},"0.12":{"FM":217,"MA":692},"0.14":{"FM":212,"MA":773},"0.16":{"FM":206,"MA":848},"0.20":{"FM":194,"MA":980},"0.24":{"FM":182,"MA":1092}}
},
"M24": {
"8.8": {"0.08":{"FM":196,"MA":529},"0.10":{"FM":192,"MA":625},"0.12":{"FM":188,"MA":714},"0.14":{"FM":183,"MA":798},"0.16":{"FM":178,"MA":875},"0.20":{"FM":168,"MA":1011},"0.24":{"FM":157,"MA":1126}},
"10.9": {"0.08":{"FM":280,"MA":754},"0.10":{"FM":274,"MA":890},"0.12":{"FM":267,"MA":1017},"0.14":{"FM":260,"MA":1136},"0.16":{"FM":253,"MA":1246},"0.20":{"FM":239,"MA":1440},"0.24":{"FM":224,"MA":1604}},
"12.9": {"0.08":{"FM":327,"MA":882},"0.10":{"FM":320,"MA":1041},"0.12":{"FM":313,"MA":1190},"0.14":{"FM":305,"MA":1329},"0.16":{"FM":296,"MA":1458},"0.20":{"FM":279,"MA":1685},"0.24":{"FM":262,"MA":1877}}
},
"M30": {
"8.8": {"0.08":{"FM":313,"MA":1053},"0.10":{"FM":307,"MA":1246},"0.12":{"FM":300,"MA":1428},"0.14":{"FM":292,"MA":1597},"0.16":{"FM":284,"MA":1754},"0.20":{"FM":268,"MA":2931},"0.24":{"FM":252,"MA":2265}},
"10.9": {"0.08":{"FM":446,"MA":1500},"0.10":{"FM":437,"MA":1775},"0.12":{"FM":427,"MA":2033},"0.14":{"FM":416,"MA":2274},"0.16":{"FM":405,"MA":2498},"0.20":{"FM":382,"MA":2893},"0.24":{"FM":359,"MA":3226}},
"12.9": {"0.08":{"FM":522,"MA":1755},"0.10":{"FM":511,"MA":2077},"0.12":{"FM":499,"MA":2380},"0.14":{"FM":487,"MA":2662},"0.16":{"FM":474,"MA":2923},"0.20":{"FM":447,"MA":3386},"0.24":{"FM":420,"MA":3775}}
},
"M36": {
"8.8": {"0.08":{"FM":458,"MA":1825},"0.10":{"FM":448,"MA":2164},"0.12":{"FM":438,"MA":2482},"0.14":{"FM":427,"MA":2778},"0.16":{"FM":415,"MA":3054},"0.20":{"FM":392,"MA":3541},"0.24":{"FM":368,"MA":3951}},
"10.9": {"0.08":{"FM":652,"MA":2600},"0.10":{"FM":638,"MA":3082},"0.12":{"FM":623,"MA":3535},"0.14":{"FM":608,"MA":3957},"0.16":{"FM":591,"MA":4349},"0.20":{"FM":558,"MA":5043},"0.24":{"FM":524,"MA":5627}},
"12.9": {"0.08":{"FM":763,"MA":3042},"0.10":{"FM":747,"MA":3607},"0.12":{"FM":729,"MA":4136},"0.14":{"FM":711,"MA":4631},"0.16":{"FM":692,"MA":5089},"0.20":{"FM":653,"MA":5902},"0.24":{"FM":614,"MA":6585}}
},
"M39": {
"8.8": {"0.08":{"FM":548,"MA":2348},"0.10":{"FM":537,"MA":2791},"0.12":{"FM":525,"MA":3208},"0.14":{"FM":512,"MA":3597},"0.16":{"FM":498,"MA":3958},"0.20":{"FM":470,"MA":4598},"0.24":{"FM":443,"MA":5137}},
"10.9": {"0.08":{"FM":781,"MA":3345},"0.10":{"FM":765,"MA":3975},"0.12":{"FM":748,"MA":4569},"0.14":{"FM":729,"MA":5123},"0.16":{"FM":710,"MA":5637},"0.20":{"FM":670,"MA":6549},"0.24":{"FM":630,"MA":7317}},
"12.9": {"0.08":{"FM":914,"MA":3914},"0.10":{"FM":895,"MA":4652},"0.12":{"FM":875,"MA":5346},"0.14":{"FM":853,"MA":5994},"0.16":{"FM":831,"MA":6596},"0.20":{"FM":784,"MA":7664},"0.24":{"FM":738,"MA":8562}}
}
}
}
+96
View File
@@ -0,0 +1,96 @@
{
"_comment": "VDI 2230:2003 — Dati filettatura metrica ISO (DIN 13/DIN ISO 262) e proprietà viti",
"thread_metric_coarse": {
"_comment": "d=nominale, P=passo, d2=fianco, d3=nocciolo, AS=sez.sforzo, Ad3=sez.nocciolo, AN=sez.nominale — tutti in mm e mm²",
"M4": {"d":4, "P":0.7, "d2":3.545, "d3":3.242, "AS":8.78, "Ad3":8.26, "AN":12.57, "dW_hex":6.9, "dW_cyl":7.66},
"M5": {"d":5, "P":0.8, "d2":4.480, "d3":4.134, "AS":14.2, "Ad3":13.4, "AN":19.63, "dW_hex":8.9, "dW_cyl":8.87},
"M6": {"d":6, "P":1.0, "d2":5.350, "d3":4.917, "AS":20.1, "Ad3":19.0, "AN":28.27, "dW_hex":10.9, "dW_cyl":11.3},
"M7": {"d":7, "P":1.0, "d2":6.350, "d3":5.917, "AS":28.9, "Ad3":27.5, "AN":38.48, "dW_hex":12.9, "dW_cyl":13.3},
"M8": {"d":8, "P":1.25, "d2":7.188, "d3":6.647, "AS":36.6, "Ad3":34.7, "AN":50.27, "dW_hex":13.9, "dW_cyl":14.2},
"M10": {"d":10, "P":1.5, "d2":9.026, "d3":8.376, "AS":58.0, "Ad3":55.1, "AN":78.54, "dW_hex":17.0, "dW_cyl":17.6},
"M12": {"d":12, "P":1.75, "d2":10.863,"d3":10.106,"AS":84.3, "Ad3":80.1, "AN":113.1, "dW_hex":19.9, "dW_cyl":20.5},
"M14": {"d":14, "P":2.0, "d2":12.701,"d3":11.835,"AS":115.4, "Ad3":110.0, "AN":153.9, "dW_hex":23.8, "dW_cyl":24.9},
"M16": {"d":16, "P":2.0, "d2":14.701,"d3":13.835,"AS":156.7, "Ad3":150.3, "AN":201.1, "dW_hex":27.0, "dW_cyl":27.7},
"M18": {"d":18, "P":2.5, "d2":16.376,"d3":15.294,"AS":192.5, "Ad3":183.6, "AN":254.5, "dW_hex":30.0, "dW_cyl":31.3},
"M20": {"d":20, "P":2.5, "d2":18.376,"d3":17.294,"AS":245.0, "Ad3":234.9, "AN":314.2, "dW_hex":32.9, "dW_cyl":34.6},
"M22": {"d":22, "P":2.5, "d2":20.376,"d3":19.294,"AS":303.4, "Ad3":292.4, "AN":380.1, "dW_hex":37.0, "dW_cyl":38.0},
"M24": {"d":24, "P":3.0, "d2":22.051,"d3":20.752,"AS":352.5, "Ad3":338.2, "AN":452.4, "dW_hex":40.9, "dW_cyl":41.6},
"M27": {"d":27, "P":3.0, "d2":25.051,"d3":23.752,"AS":459.4, "Ad3":443.2, "AN":572.6, "dW_hex":46.0, "dW_cyl":47.3},
"M30": {"d":30, "P":3.5, "d2":27.727,"d3":26.211,"AS":560.6, "Ad3":539.2, "AN":706.9, "dW_hex":51.0, "dW_cyl":52.7},
"M33": {"d":33, "P":3.5, "d2":30.727,"d3":29.211,"AS":694.0, "Ad3":669.9, "AN":855.3, "dW_hex":57.0, "dW_cyl":58.8},
"M36": {"d":36, "P":4.0, "d2":33.402,"d3":31.670,"AS":817.0, "Ad3":787.4, "AN":1017.9,"dW_hex":63.5, "dW_cyl":65.1},
"M39": {"d":39, "P":4.0, "d2":36.402,"d3":34.670,"AS":976.0, "Ad3":943.5, "AN":1194.6,"dW_hex":69.5, "dW_cyl":71.3}
},
"thread_metric_fine": {
"M8x1": {"d":8, "P":1.0, "d2":7.350, "d3":6.917, "AS":39.2, "Ad3":37.6},
"M10x1": {"d":10, "P":1.0, "d2":9.350, "d3":8.917, "AS":64.5, "Ad3":62.5},
"M10x1.25":{"d":10, "P":1.25, "d2":9.188, "d3":8.647, "AS":61.2, "Ad3":58.7},
"M12x1.25":{"d":12, "P":1.25, "d2":11.188,"d3":10.647,"AS":92.1, "Ad3":89.0},
"M12x1.5": {"d":12, "P":1.5, "d2":11.026,"d3":10.376,"AS":88.1, "Ad3":84.5},
"M14x1.5": {"d":14, "P":1.5, "d2":13.026,"d3":12.376,"AS":125.3, "Ad3":120.2},
"M16x1.5": {"d":16, "P":1.5, "d2":15.026,"d3":14.376,"AS":167.3, "Ad3":162.1},
"M18x1.5": {"d":18, "P":1.5, "d2":17.026,"d3":16.376,"AS":216.2, "Ad3":210.6},
"M20x1.5": {"d":20, "P":1.5, "d2":19.026,"d3":18.376,"AS":272.3, "Ad3":265.1},
"M24x1.5": {"d":24, "P":1.5, "d2":23.026,"d3":22.376,"AS":401.3, "Ad3":392.8},
"M24x2": {"d":24, "P":2.0, "d2":22.701,"d3":21.835,"AS":384.4, "Ad3":374.2}
},
"strength_classes": {
"_comment": "Rp0_2min e Rm_min in N/mm², ES in N/mm²",
"8.8": {"Rp0_2min": 640, "Rm_min": 800, "ES": 206000},
"10.9": {"Rp0_2min": 940, "Rm_min": 1040, "ES": 206000},
"12.9": {"Rp0_2min": 1100, "Rm_min": 1220, "ES": 206000}
},
"materials_clamped": {
"_comment": "EP in N/mm², alpha in 1/K, pG in N/mm²",
"steel_S235": {"EP": 210000, "alpha": 11.5e-6, "pG_min": 800, "pG_max": 900, "shear_ratio": 0.60},
"steel_S355": {"EP": 210000, "alpha": 11.5e-6, "pG_min": 900, "pG_max": 1000, "shear_ratio": 0.60},
"steel_hardened": {"EP": 206000, "alpha": 11.5e-6, "pG_min": 1100, "pG_max": 1250, "shear_ratio": 0.62},
"cast_iron_GJL": {"EP": 120000, "alpha": 10.5e-6, "pG_min": 400, "pG_max": 600, "shear_ratio": 0.64},
"cast_iron_GJS": {"EP": 170000, "alpha": 11.0e-6, "pG_min": 700, "pG_max": 900, "shear_ratio": 0.64},
"aluminum_6082": {"EP": 70000, "alpha": 23.0e-6, "pG_min": 300, "pG_max": 450, "shear_ratio": 0.60},
"aluminum_cast": {"EP": 70000, "alpha": 21.0e-6, "pG_min": 150, "pG_max": 250, "shear_ratio": 0.58}
},
"friction_classes": {
"_comment": "µ_min e µ_max per filettatura (µG) e appoggio (µK)",
"A": {"mu_min": 0.04, "mu_max": 0.10, "description": "MoS2/PTFE/grafite in vernice o pasta; cera fusa"},
"B": {"mu_min": 0.08, "mu_max": 0.16, "description": "Stato di consegna (legg. oliato); grassi; Zn galv."},
"C": {"mu_min": 0.14, "mu_max": 0.24, "description": "Senza lubrificante; fosfatato; Zn-Fe secco"},
"D": {"mu_min": 0.20, "mu_max": 0.35, "description": "Zincato a caldo senza lubrificante"},
"E": {"mu_min": 0.30, "mu_max": 0.50, "description": "Austenitici / Al-Mg senza lubrificante"}
},
"friction_interface_muT": {
"steel_steel_dry": {"mu_min": 0.10, "mu_max": 0.23},
"steel_steel_oiled": {"mu_min": 0.07, "mu_max": 0.12},
"steel_castiron_dry": {"mu_min": 0.12, "mu_max": 0.24},
"steel_castiron_oiled":{"mu_min": 0.06, "mu_max": 0.10},
"steel_aluminum_dry": {"mu_min": 0.10, "mu_max": 0.28},
"steel_aluminum_oiled":{"mu_min": 0.05, "mu_max": 0.18}
},
"tightening_factors_alphaA": {
"torque_manual_classC": {"alphaA_min": 2.5, "alphaA_max": 4.0, "note": "Chiave a forchetta/stella manuale"},
"torque_wrench_classB": {"alphaA_min": 1.6, "alphaA_max": 2.0, "note": "Chiave dinamometrica manuale, classe B"},
"torque_wrench_classA": {"alphaA_min": 1.2, "alphaA_max": 1.6, "note": "Chiave dinamometrica calibrata, classe A"},
"power_tool_controlled": {"alphaA_min": 1.4, "alphaA_max": 1.6, "note": "Avvitatore controllato in coppia"},
"yield_or_angle_controlled":{"alphaA_min": 1.0, "alphaA_max": 1.0, "note": "Serraggio angolare o al limite di snervamento"}
},
"embedding_fZ_micron": {
"_comment": "fZ in µm per interfaccia — funzione della rugosità Rz",
"head_bearing_Rz_le10": {"fZ_min": 2.0, "fZ_max": 4.0},
"head_bearing_Rz_gt10": {"fZ_min": 4.0, "fZ_max": 7.0},
"flat_joint_Rz_le10": {"fZ_min": 1.5, "fZ_max": 2.5},
"flat_joint_Rz_gt10": {"fZ_min": 2.5, "fZ_max": 6.5},
"thread_engagement": {"fZ_min": 1.0, "fZ_max": 2.0}
},
"min_engagement_length_ratio": {
"_comment": "meff_min / d — rapporto lunghezza avvitamento / diametro nominale",
"steel_8.8": 0.85,
"steel_10.9": 0.90,
"steel_12.9": 0.95,
"cast_iron_8.8": 1.30,
"cast_iron_10.9": 1.50,
"cast_iron_12.9": 1.70,
"aluminum_8.8": 2.00,
"aluminum_10.9": 2.50,
"aluminum_12.9": 3.00
}
}
+217
View File
@@ -0,0 +1,217 @@
{
"skill_name": "vdi2230-dimensionamento-viti",
"evals": [
{
"id": 1,
"prompt": "Ho una flangia in acciaio S355 con due metà che devo serrare insieme. La flangia è spessa 40mm in totale, il diametro flangia è 80mm e i fori sono da 17mm. Il carico assiale che arriva sulla connessione è circa 50kN statico. Che vite uso e con che coppia la servo?",
"expected_output": "La skill deve: (1) raccogliere i dati mancanti con domande mirate o assumere default sensati, (2) eseguire lo script vdi2230_calc.py o auto_size.py, (3) restituire almeno: la dimensione vite consigliata (verosimilmente M16 8.8 o M14 10.9), la coppia di serraggio MA in Nm, e il precarico FM. Le verifiche R7-R12 devono essere presentate tutte.",
"assertions": [
{
"text": "Il risultato include una dimensione vite specifica (es. M14, M16) e una classe di resistenza",
"type": "output_contains_pattern",
"pattern": "M\\d{1,2}.*(?:8\\.8|10\\.9|12\\.9)"
},
{
"text": "Il risultato include la coppia di serraggio MA in Nm",
"type": "output_contains_keyword",
"keyword": "Nm"
},
{
"text": "Il risultato include almeno un valore di precarico FM in kN",
"type": "output_contains_keyword",
"keyword": "kN"
},
{
"text": "Sono presenti le verifiche R7-R12 con esito ✅ o ❌",
"type": "output_contains_keyword",
"keyword": "R7"
},
{
"text": "Lo script Python è stato eseguito (non calcolo manuale)",
"type": "output_contains_keyword",
"keyword": "vdi2230_calc"
}
]
},
{
"id": 2,
"prompt": "Devo verificare se una vite M10 classe 8.8 regge su un supporto in alluminio EN-AW 6082. La vite è avvitata direttamente nell'alluminio (no dado), lunghezza avvitamento 16mm. Carico: FA max = 8000N, FA min = 1000N (carico a fatica), nessuna forza trasversale. lK = 20mm, DA = 30mm, foro da 11mm.",
"expected_output": "La skill deve: (1) riconoscere il caso ESV (vite in foro filettato) e alluminio come materiale critico per pG e meff, (2) eseguire il calcolo, (3) segnalare se la lunghezza di avvitamento 16mm è sufficiente (meff_min per alluminio ≈ 2·d = 20mm → probabile fallimento R11), (4) fornire azione correttiva concreta.",
"assertions": [
{
"text": "Il tipo giunzione ESV è correttamente identificato",
"type": "output_contains_keyword",
"keyword": "ESV"
},
{
"text": "Il materiale alluminio è considerato nel calcolo",
"type": "output_contains_keyword",
"keyword": "alluminio"
},
{
"text": "La verifica R11 (lunghezza avvitamento) appare nel report",
"type": "output_contains_keyword",
"keyword": "R11"
},
{
"text": "Se R11 fallisce, è presente un'azione correttiva (aumentare meff o inserto)",
"type": "output_contains_keyword",
"keyword": "avvitamento"
},
{
"text": "La verifica a fatica R9 è presente dato il carico variabile",
"type": "output_contains_keyword",
"keyword": "R9"
}
]
},
{
"id": 3,
"prompt": "Sizing automatico: ho un coperchio da pressione con tenuta verso 8 bar interni. Area guarnizione circa 1200mm². Il coperchio è in ghisa GJL-250, spessore 35mm, flangia da 60mm. I bulloni saranno in posizione simmetrica, con foro da 14mm. Carico assiale sui bulloni trascurabile (è solo pressione). Dimmi qual è il bullone minimo e con che coppia.",
"expected_output": "La skill deve: (1) convertire 8 bar in N/mm² (0.8 N/mm²) e calcolare FKP = AD·pi = 1200·0.8 = 960N, (2) usare auto_size.py per trovare il diametro minimo, (3) usare pG adeguato per ghisa (400-600 N/mm²), (4) restituire la soluzione minima con coppia MA.",
"assertions": [
{
"text": "La pressione 8 bar è convertita in N/mm² (0.8) per il calcolo",
"type": "output_contains_keyword",
"keyword": "0.8"
},
{
"text": "Il materiale ghisa è considerato con pG appropriato (≤ 600 N/mm²)",
"type": "output_contains_keyword",
"keyword": "ghisa"
},
{
"text": "Auto-sizing è stato usato o il diametro minimo è stato trovato iterativamente",
"type": "output_contains_keyword",
"keyword": "auto_size"
},
{
"text": "Il risultato include la coppia di serraggio MA",
"type": "output_contains_keyword",
"keyword": "MA"
}
]
},
{
"id": 4,
"prompt": "Qual è la coppia di serraggio per una M20 classe 10.9 con coefficiente d'attrito 0.14? È una vite passante con dado, condizione di consegna senza lubrificante.",
"expected_output": "Domanda rapida — la skill deve rispondere leggendo MA dalla tabella A1 fm_table.json senza eseguire il calcolo completo R0-R13. Il valore atteso è MA ≈ 661 Nm (tabella A1, M20 10.9, µ=0.14). Non deve chiedere lK, DA, dh o altri parametri geometrici non necessari per questa risposta.",
"assertions": [
{
"text": "Il valore MA restituito è nell'intervallo corretto 600-800 Nm per M20 10.9 µ=0.14",
"type": "numeric_range",
"min": 600,
"max": 800,
"unit": "Nm"
},
{
"text": "Il coefficiente di attrito µ=0.14 è usato nel calcolo",
"type": "output_contains_keyword",
"keyword": "0.14"
},
{
"text": "Il precarico FM è riportato (da tabella A1: ≈ 181 kN per M20 10.9 µ=0.14)",
"type": "output_contains_keyword",
"keyword": "kN"
},
{
"text": "La risposta NON chiede lK, DA, dh o altri parametri geometrici — sono inutili per una semplice lettura di MA/FM da tabella",
"type": "behavior",
"description": "Per domande pure MA/FM con size+classe+µ noti, la skill usa fm_table lookup diretto senza script completo"
}
]
},
{
"id": 5,
"prompt": "Ho già fatto il calcolo VDI 2230 e mi è venuto fuori che R9 (fatica) non passa: σa = 38 MPa ma σAS = 31 MPa. SD = 0.82. Cosa posso fare per farlo passare senza cambiare il diametro?",
"expected_output": "La skill deve fornire azioni correttive specifiche per R9 senza ridimensionamento: (1) passare a filetto rullato post-tempra SG (+20-30% su σAS), (2) aumentare il precarico FM che riduce l'ampiezza relativa, (3) progetto a vita finita se NZ è basso. Deve spiegare il meccanismo fisico dietro ogni azione.",
"assertions": [
{
"text": "È menzionato il trattamento SG (rullato post-tempra) come alternativa a SV",
"type": "output_contains_keyword",
"keyword": "SG"
},
{
"text": "È spiegato come aumentare il precarico riduce σa",
"type": "output_contains_keyword",
"keyword": "precarico"
},
{
"text": "La risposta non suggerisce inutilmente di aumentare il diametro",
"type": "behavior",
"description": "Il progettista ha esplicitamente chiesto soluzioni senza cambio diametro"
},
{
"text": "Il meccanismo fisico è spiegato (σAS dipende da FSm/F0.2min per SG)",
"type": "output_contains_keyword",
"keyword": "σAS"
}
]
}
,
{
"id": 6,
"prompt": "Ho un coperchio in alluminio EN-AW 6082 su un carter in acciaio. Le viti M10 classe 8.8 vengono serrate a freddo (20°C) ma in esercizio il carter raggiunge 120°C. lK=30mm, DA=28mm, dh=11mm. FA_max=5000N statico. Che succede al precarico in esercizio e la vite regge?",
"expected_output": "La skill deve: (1) riconoscere il caso termico critico acciaio-alluminio (alpha_P alluminio ≈ 2,3e-5 vs alpha_S acciaio ≈ 1,15e-5), (2) calcolare la variazione ΔFVth con dT_P_K=100K, (3) verificare che il precarico residuo in esercizio FM_min - ΔFVth sia ancora sufficiente (FKerf). Se il precarico si azzera la giunzione apre. Deve anche verificare pG per alluminio (pG=300-450 N/mm²).",
"assertions": [
{
"text": "La variazione termica ΔFVth è calcolata e presentata",
"type": "output_contains_keyword",
"keyword": "termico"
},
{
"text": "Il coefficiente di dilatazione diverso tra alluminio e acciaio è menzionato",
"type": "output_contains_keyword",
"keyword": "dilatazione"
},
{
"text": "La verifica R10 usa pG corretto per alluminio (≤ 450 N/mm²)",
"type": "output_contains_keyword",
"keyword": "alluminio"
},
{
"text": "È valutato se il precarico residuo in esercizio è ancora sufficiente",
"type": "output_contains_keyword",
"keyword": "FM_min"
},
{
"text": "Se il precarico si riduce pericolosamente, la skill suggerisce contromisure (es. ridurre fZ, aumentare FM_min, usare rondella dura, serrare a caldo)",
"type": "behavior",
"description": "La skill deve proporre azioni correttive concrete al progettista se la verifica termica fallisce"
}
]
},
{
"id": 7,
"prompt": "Ho 4 viti M14 classe 10.9 su una flangia in acciaio che deve reggere contemporaneamente una forza assiale FA_tot = 60 kN e un momento torcente MT = 800 Nm attorno all'asse del giunto. Il raggio del cerchio viti è 50mm. lK=45mm, DA=42mm, dh=15mm. Le viti reggono?",
"expected_output": "La skill deve: (1) dividere FA_tot per N=4 viti → FA_bolt=15000N, (2) calcolare FQ_bolt = MT/(N·r_bc) = 800000/(4·50) = 4000N per vite, (3) eseguire il calcolo con FA_max_N=15000 e FQ_max_N=4000, (4) verificare R12 anti-scorrimento (critico per FQ). Deve dichiarare esplicitamente il calcolo per-vite.",
"assertions": [
{
"text": "FA_bolt = FA_tot/N = 15000 N è dichiarato esplicitamente",
"type": "output_contains_keyword",
"keyword": "15000"
},
{
"text": "FQ_bolt = 4000 N è calcolato dal momento torcente",
"type": "output_contains_keyword",
"keyword": "4000"
},
{
"text": "La verifica R12 anti-scorrimento è presente nel report",
"type": "output_contains_keyword",
"keyword": "R12"
},
{
"text": "Il numero di viti N=4 è usato nel calcolo per-vite",
"type": "output_contains_keyword",
"keyword": "4"
},
{
"text": "Lo script è eseguito con FQ_max_N e FA_max_N entrambi forniti",
"type": "output_contains_keyword",
"keyword": "FQ"
}
]
}
]
}
+173
View File
@@ -0,0 +1,173 @@
# VDI 2230:2003 — Formulario rapido (per spiegazioni al progettista)
## Passi R0R13 in sintesi
| Passo | Cosa calcola | Formula chiave | Output |
| --- | --- | --- | --- |
| R0 | Verifica limite geometrico | G = hmin+dW (DSV); G'≈1,75·dW (ESV) | Validità modello |
| R1 | Fattore di serraggio | αA = FM_max/FM_min | αA (da tab. A8) |
| R2 | Forza min. serraggio | FKerf = max(FKQ; FKP+FKA) | FKerf [N] |
| R3 | Cedevolezze e Φ | δS, δP; Φ = n·δP/(δS+δP) | FSA = Φ·FA |
| R4 | Perdite precarico | FZ = fZ/(δS+δP); ΔF'Vth | FZ, ΔFVth [N] |
| R5 | Precarico minimo | FM_min = FKerf+(1-Φ)·FA+FZ+ΔFVth | FM_min [N] |
| R6 | Precarico massimo | FM_max = αA · FM_min | FM_max [N] |
| R7 | Verifica montaggio | FM_zul ≥ FM_max | OK/FAIL |
| R8 | Verifica esercizio | σred,B = √(σz²+3(kt·τ)²) < Rp0,2 | SF ≥ 1,0 |
| R9 | Verifica fatica | σa ≤ σAS; SD = σAS/σa ≥ 1,2 | SD |
| R10 | Pressione superficiale | pmax = FM_zul/Ap ≤ pG | SP ≥ 1,0 |
| R11 | Lunghezza avvitamento | meff ≥ meff_min | OK/FAIL |
| R12 | Anti-scorrimento | FKR_min > FKQ_erf; SG ≥ 1,2 | SG |
| R13 | Coppia di serraggio | MA = FM_zul·(0,16P+0,58d2·µG+DKm/2·µK) | MA [Nm] |
---
## Logica del verspannungsschaubild (diagramma di serraggio)
```text
Forza
│ FM_max ─────────────── (limite superiore montaggio)
│ FM_zul ─────────────── (capacità della vite)
│ FM_min ─────────────── (limite inferiore montaggio)
│ │ FSA = Φ·FA (quota a carico vite)
│ │ FPA = (1-Φ)·FA (scarica le parti)
│ FKR ─────── (forza residua in giuntura in esercizio)
└──────────────────────────────► Deformazione
```
- **Φ piccolo** (0,05÷0,20 tipico): la vite è molto più cedevole delle parti → assorbe poca variazione di carico → migliore fatica
- **Φ grande** (>0,5): vite rigida → amplifica il carico variabile → fatica critica
---
## Cedevolezza vite δS — Schema elementi in serie
```text
δS = δSK + δ_gambo + δ_Gew + δG + δM [mm/N]
δSK = 0,5d / (ES·π/4·d²) (testa esagonale)
δi = li / (ES·Ai) (elemento generico)
δGew= lGew / (ES·π/4·d3²) (filetto libero)
δG = 0,5d / (ES·π/4·d3²) (filetto avvitato, zona vite)
δM = 0,4d / (ES·π/4·d²) (zona dado, DSV)
= 0,33d/ (EM·π/4·d²) (foro filettato, ESV)
```
## Cedevolezza parti δP — Modello cono
```text
tan φ ≈ 0,6 (approssimazione valida per βL=0,5÷4, ψ=4÷6)
Se DA ≥ DA,Gr: δP = 2·ln[(dW+dh)(dW+w·lK·tanφ–dh) /
((dWdh)(dW+w·lK·tanφ+dh))] /
(w·EP·π·dh·tanφ)
```
dove w=1 (DSV), w=2 (ESV); DA,Gr = dW + w·lK·tanφ
---
## Limiti di fatica viti ad alta resistenza
```text
SV (rullato pre-tempra): σASV = 0,85·(150/d + 45) [MPa, d in mm]
SG (rullato post-tempra): σASG = (2 FSm/F0,2min)·σASV
Effetto diametro (M10→σASV=57 MPa; M24→σASV=51 MPa)
SG ≈ 20-30% migliore di SV — preferibile per carichi a fatica critici
```
---
## Pressioni limite pG orientative [N/mm²]
| Materiale | pG min | pG max | Note |
| --- | --- | --- | --- |
| Acciaio strutturale | 800 | 1000 | +25% con svasatura |
| Acciaio bonificato | 1100 | 1250 | |
| Ghisa grigia GJL | 400 | 600 | |
| Ghisa sferoidale GJS | 700 | 900 | |
| Alluminio en. (6082) | 300 | 450 | Ridurre ad alta T |
| Alluminio pressofuso | 150 | 250 | |
---
## Distribuzione carico su cerchio viti (flangia con N bulloni)
Per giunti flangiati soggetti a forza assiale FA_tot e/o momento flettente MB:
```text
Carico per vite più sollecitata:
FA_bolt = FA_tot / N + MB / (N · r_bc)
dove:
N = numero di viti
r_bc = raggio del cerchio viti [mm]
MB = momento flettente alla giuntura [N·mm]
Ipotesi: viti equidistanti, regime elastico, asse di flessione passa per il centro.
```
**Nota pratica:** se c'è anche un momento torcente MT attorno all'asse del giunto,
la forza trasversale per vite vale FQ_bolt = MT / (N · r_bc) e va inserita come
FQ_max_N nel calcolo anti-scorrimento R12.
Per flangie cilindriche con pressione interna pi [N/mm²] e area guarnizione AD [mm²]:
```text
FKP_tot = pi · AD → FKP_bolt = FKP_tot / N
Inserire come carico aggiuntivo in R2 (già gestito da pi_max_N_mm2 + AD_mm2 divisi per N)
```
---
## Classi di attrito — guida rapida selezione
| Situazione tipica | µ consigliato | Classe |
| --- | --- | --- |
| Vite fosfatata + grasso MoS2 | 0,080,10 | A |
| Vite in acciaio, stato di consegna | 0,100,14 | B |
| Zincato a caldo, senza lubrificante | 0,140,20 | C/D |
| Acciaio inox senza lubrificante | 0,200,30 | D/E |
**Per il calcolo:** usare sempre µ_min (caso peggiore per FM_zul e MA).
### ⚠ Acciaio inossidabile — grippaggio (Fressen)
Inox A2/A4 a secco è soggetto a grippaggio per saldatura a freddo durante il serraggio:
µ reale può salire a 0,350,50 e la vite si rompe prima di raggiungere il precarico.
Precauzioni obbligatorie:
- Usare **pasta anti-grippante** (Molykote 1000, Copaslip, Electrolube AGB): riduce µ a 0,130,18 e previene il grippaggio.
- Calcolare con µ classe C (0,18) per non sottostimare MA.
- Serrare **lentamente** (chiave dinamometrica, non avvitatore a impulsi) e in più passate.
- Per filettature piccole (≤ M8 inox) valutare dadi autobloccanti DIN 985 solo se non-smontaggio frequente (il dente di bloccaggio aumenta il rischio di rottura).
Per il calcolo: impostare `mu_G = 0.18`, `alpha_A = 1.7` (chiave dinamometrica calibrata obbligatoria).
---
## Settaggio fZ — valori pratici
| Condizione | fZ per interfaccia [µm] |
| --- | --- |
| Testa/dado su piastra rettificata (Rz ≤ 10) | 24 |
| Testa/dado su piastra grezza (Rz > 10) | 47 |
| Piano su piano rettificato (Rz ≤ 10) | 1,52,5 |
| Piano su piano grezzo (Rz > 10) | 2,56,5 |
| Contributo filettatura | 12 |
**Regola pratica:** per giunzione standard (2 giunture + testa + filetto, acciaio): fZ_totale ≈ 1015 µm
---
## Fattori di serraggio αA tipici
| Metodo | αA tipico | Note |
| --- | --- | --- |
| Chiave a stella manuale | 2,54,0 | Grande dispersione |
| Chiave dinamometrica (classe B) | 1,62,0 | Standard industriale |
| Chiave dinamometrica (classe A) | 1,21,6 | Calibrata, accurata |
| Avvitatore controllato | 1,41,6 | Produzione in serie |
| Serraggio angolare / snervamento | 1,0 | Nessuna dispersione |
+144
View File
@@ -0,0 +1,144 @@
# Parametri JSON di input — Riferimento completo
Tutti i parametri accettati da `vdi2230_calc.py` e `auto_size.py`, con unità, default e note d'uso.
## Identificazione vite
| Parametro | Tipo | Default | Descrizione |
|-----------|------|---------|-------------|
| `size` | string | — | Dimensione nominale: `"M6"`, `"M12"`, `"M24"`, `"M12x1.5"` (fine) |
| `strength_class` | string | — | Classe di resistenza: `"8.8"`, `"10.9"`, `"12.9"` |
| `thread_type` | string | `"coarse"` | `"coarse"` = filettatura regolare; `"fine"` = filettatura fine |
| `joint_type` | string | `"DSV"` | `"DSV"` = passante+dado; `"ESV"` = vite in foro filettato |
## Geometria giunzione
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `lK_mm` | mm | **obbligatorio** | Lunghezza di serraggio — somma degli spessori di tutte le parti serrate |
| `DA_mm` | mm | **obbligatorio** | Diametro esterno della zona di contatto (flangia, piastra) |
| `dh_mm` | mm | **obbligatorio** | Diametro del foro di passaggio vite |
| `DA_prime_mm` | mm | `DA × 1,5` | Diametro esterno del corpo base (supporto al cono di deformazione); in genere ≥ DA |
| `hmin_mm` | mm | `lK / 2` | Spessore minimo della piastra più sottile tra quelle serrate |
| `ssym_mm` | mm | `0` | Eccentricità di serraggio: distanza asse vite asse simmetrico del corpo di deformazione. Zero = caso centrico |
| `a_mm` | mm | `0` | Eccentricità del carico: distanza linea d'azione FA asse simmetrico. Sempre ≥ 0 |
| `n_factor` | — | `1.0` | Fattore di introduzione carico n: 1.0 = carico introdotto lontano dalla giuntura (caso standard); 0 = carico direttamente sul gambo |
| `IBers_mm4` | mm⁴ | auto | Momento d'inerzia sostitutivo del corpo di deformazione. Se 0, calcolato come π/64·(DA⁴-dh⁴) |
| `lGew_mm` | mm | `0` | Lunghezza del filetto libero non avvitato (fuori dalla zona di serraggio) |
## Carichi di esercizio
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `FA_max_N` | N | **obbligatorio** | Forza assiale massima. Positiva = separa le superfici di giuntura |
| `FA_min_N` | N | `= FA_max_N` | Forza assiale minima — usare per carichi variabili (verifica fatica R9) |
| `FQ_max_N` | N | `0` | Forza trasversale massima (perpendicolare all'asse vite) |
| `MY_max_Nmm` | N·mm | `0` | Momento torcente attorno all'asse vite |
| `MB_max_Nmm` | N·mm | `0` | Momento flettente esterno alla giuntura (caso raro) |
## Tenuta (solo se necessaria)
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `pi_max_N_mm2` | N/mm² | `0` | Pressione interna massima da sigillare |
| `AD_mm2` | mm² | `0` | Area efficace di tenuta della guarnizione |
| `IBT_mm4` | mm⁴ | `0` | Momento d'inerzia della sezione di giuntura (per verifica anti-apertura) |
| `u_mm` | mm | `0` | Distanza del punto di inizio apertura dal centro del corpo di deformazione |
| `qF` | — | `1` | Numero di interfacce che trasmettono FQ |
| `qM` | — | `1` | Numero di interfacce che trasmettono MY |
| `ra_mm` | mm | `0` | Raggio di attrito per la trasmissione di MY |
## Materiali e attrito
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `EP_N_mm2` | N/mm² | `210000` | Modulo elastico delle parti serrate. Acciaio = 210.000; alluminio = 70.000; ghisa = 120.000170.000 |
| `pG_N_mm2` | N/mm² | `900` | Pressione superficiale limite sotto testa/dado. Acciaio = 9001.000; alluminio = 300450; ghisa = 400600 |
| `mu_G` | — | `0.12` | Coefficiente di attrito nella filettatura. Classe A=0,06; B=0,12; C=0,18; D=0,25 |
| `mu_K` | — | `= mu_G` | Coefficiente di attrito nell'appoggio testa/dado |
| `mu_T` | — | `0.12` | Coefficiente di attrito nelle superfici di giuntura (per calcolo anti-scorrimento) |
| `internal_material` | — | `"steel"` | Materiale del corpo filettato per ESV: `"steel"`, `"cast_iron"`, `"aluminum"` |
## Metodo di serraggio
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `alpha_A` | — | `1.7` | Fattore di serraggio αA = FM_max/FM_min. Chiave manuale ≈ 3,0; chiave dinamometrica ≈ 1,7; angolare = 1,0 |
| `fZ_total_um` | µm | `12` | Settaggio totale (somma di tutte le interfacce). 2 giunture piane acciaio ≈ 1015 µm |
## Fatica
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `thread_treatment` | — | `"SV"` | Trattamento filetto: `"SV"` = rullato pre-tempra; `"SG"` = rullato post-tempra (+2030% σAS) |
| `meff_actual_mm` | mm | `1,5·d` | Lunghezza di avvitamento effettiva disponibile |
| `At_shear_mm2` | mm² | auto | Area di taglio per verifica R12 (0 = usa Ad3) |
| `tau_B_MPa` | N/mm² | auto | Resistenza a taglio (0 = usa 0,6·Rm) |
## Temperatura (solo se T ≠ ambiente)
| Parametro | Unità | Default | Descrizione |
|-----------|-------|---------|-------------|
| `alpha_S` | 1/K | `1.15e-5` | Coefficiente di dilatazione termica della vite (acciaio) |
| `alpha_P` | 1/K | `1.15e-5` | Coefficiente di dilatazione termica delle parti serrate. Alluminio ≈ 2,3e-5 |
| `dT_S_K` | K | `0` | Variazione di temperatura della vite rispetto al montaggio |
| `dT_P_K` | K | `0` | Variazione di temperatura delle parti serrate |
---
## Esempi JSON per casi tipici
### Caso 1: Flangia acciaio-acciaio, carico statico, DSV
```json
{
"size": "M16", "strength_class": "10.9", "joint_type": "DSV",
"lK_mm": 50, "DA_mm": 45, "dh_mm": 17.5, "DA_prime_mm": 80, "hmin_mm": 25,
"FA_max_N": 60000, "FA_min_N": 60000,
"mu_G": 0.12, "alpha_A": 1.7,
"EP_N_mm2": 210000, "pG_N_mm2": 900,
"fZ_total_um": 12, "meff_actual_mm": 24
}
```
### Caso 2: Vite in alluminio (ESV), carico variabile
```json
{
"size": "M10", "strength_class": "8.8", "joint_type": "ESV",
"lK_mm": 25, "DA_mm": 28, "dh_mm": 11, "DA_prime_mm": 45, "hmin_mm": 25,
"FA_max_N": 12000, "FA_min_N": 2000,
"mu_G": 0.14, "alpha_A": 1.7,
"EP_N_mm2": 70000, "pG_N_mm2": 400,
"fZ_total_um": 15, "meff_actual_mm": 20,
"internal_material": "aluminum", "thread_treatment": "SV"
}
```
### Caso 3: Giuntura con tenuta (pressione interna)
```json
{
"size": "M12", "strength_class": "10.9", "joint_type": "DSV",
"lK_mm": 35, "DA_mm": 40, "dh_mm": 13.5, "DA_prime_mm": 65, "hmin_mm": 17,
"FA_max_N": 15000, "FA_min_N": 15000,
"pi_max_N_mm2": 5.0, "AD_mm2": 800,
"mu_G": 0.12, "alpha_A": 1.7,
"EP_N_mm2": 210000, "pG_N_mm2": 900,
"fZ_total_um": 12, "meff_actual_mm": 18
}
```
### Caso 4: Giuntura con forza trasversale (anti-scorrimento)
```json
{
"size": "M14", "strength_class": "10.9", "joint_type": "DSV",
"lK_mm": 45, "DA_mm": 42, "dh_mm": 15, "DA_prime_mm": 75, "hmin_mm": 22,
"FA_max_N": 25000, "FA_min_N": 25000,
"FQ_max_N": 20000, "mu_T": 0.12, "qF": 1,
"mu_G": 0.12, "alpha_A": 1.7,
"EP_N_mm2": 210000, "pG_N_mm2": 900,
"fZ_total_um": 12, "meff_actual_mm": 21
}
```
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
VDI 2230:2003 — Auto-sizing: trova il diametro minimo che soddisfa tutte le verifiche.
Uso: python auto_size.py <input_base.json> [output.json]
L'input NON deve contenere "size" — viene cercato automaticamente.
Specifica "strength_class", "joint_type" e tutti gli altri parametri.
"""
import json
import sys
import os
# Aggiungi la cartella scripts al path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPT_DIR)
from vdi2230_calc import run_full_calculation
# Sequenza di ricerca: dal più piccolo al più grande
SIZES_COARSE = ["M4","M5","M6","M7","M8","M10","M12","M14","M16","M18",
"M20","M22","M24","M27","M30","M33","M36","M39"]
SIZES_FINE = ["M8x1","M10x1","M10x1.25","M12x1.25","M12x1.5",
"M14x1.5","M16x1.5","M18x1.5","M20x1.5","M24x1.5","M24x2"]
def auto_size(base_input: dict,
strength_classes: list = None,
max_iterations: int = 20,
verbose: bool = True) -> dict:
"""
Prova dimensioni crescenti finché tutte le verifiche sono OK.
Se strength_classes è una lista, prova per ogni classe.
Restituisce il primo dimensionamento OK o il migliore trovato.
"""
if strength_classes is None:
sc_list = [base_input.get("strength_class", "8.8")]
else:
sc_list = strength_classes
thread_type = base_input.get("thread_type", "coarse")
sizes = SIZES_COARSE if thread_type == "coarse" else SIZES_FINE
best = None
all_attempts = []
for sc in sc_list:
for size in sizes:
inp = dict(base_input)
inp["size"] = size
inp["strength_class"] = sc
# Aggiorna geometria dipendente dalla dimensione (se non fornita)
if "dh_mm" not in base_input:
# Foro medio DIN EN 20273 ≈ d + 1 mm (approssimazione)
d = float(size.replace("M","").split("x")[0])
inp["dh_mm"] = d + 1.0
if "meff_actual_mm" not in base_input:
d = float(size.replace("M","").split("x")[0])
inp["meff_actual_mm"] = 1.5 * d
result = run_full_calculation(inp)
summary = result.get("summary", {})
ok = summary.get("all_ok", False)
errors = result.get("error")
attempt = {
"size": size,
"strength_class": sc,
"ok": ok,
"summary": summary,
"error": errors
}
all_attempts.append(attempt)
if verbose:
vf = summary.get("verifications", {})
fails = [k for k, v in vf.items() if not v]
status = "" if ok else f"❌ ({', '.join(fails)})"
print(f" {size:6s} {sc:5s}{status}")
if ok:
best = attempt
break # trovato il minimo per questa classe
if best:
break
return {
"best": best,
"all_attempts": all_attempts,
"found": best is not None,
"message": f"✅ Soluzione minima: {best['size']} {best['strength_class']}" if best else
"❌ Nessuna soluzione trovata entro M39"
}
def print_sizing_report(result: dict):
print("\n" + "=" * 60)
print(" VDI 2230 — AUTO-SIZING REPORT")
print("=" * 60)
print(f"\n {result['message']}\n")
if result["found"]:
b = result["best"]
s = b["summary"]
print(f" Vite selezionata: {s.get('size')} {s.get('strength_class')} "
f"({s.get('thread_type','')}, {s.get('joint_type','')})")
print(f" FM min = {s.get('FM_min_kN',0):.2f} kN")
print(f" FM max = {s.get('FM_max_kN',0):.2f} kN")
print(f" FM zul = {s.get('FM_zul_kN',0):.2f} kN "
f"(margine = {s.get('FM_zul_kN',0)/s.get('FM_max_kN',1):.2f})")
print(f" Phi = {s.get('Phi',0):.4f}")
print(f" MA = {s.get('MA_Nm',0):.1f} Nm")
print()
print(" Verifiche:")
for k, v in s.get("verifications", {}).items():
print(f" {'' if v else ''} {k}")
print("=" * 60)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Uso: python auto_size.py <base_input.json> [output.json]")
print("L'input NON deve contenere 'size' — viene ricercato automaticamente.")
sys.exit(1)
with open(sys.argv[1], "r", encoding="utf-8") as f:
base = json.load(f)
# Rimuovi 'size' se presente (auto-sizing)
base.pop("size", None)
# Cerca su tutte le classi se non specificata, altrimenti solo quella indicata
sc_search = None
if "strength_class" not in base:
sc_search = ["8.8", "10.9", "12.9"]
print(f"\nRicerca dimensionamento ottimale VDI 2230...")
print(f"Classe(i): {sc_search or base.get('strength_class')}")
print()
result = auto_size(base, strength_classes=sc_search, verbose=True)
print_sizing_report(result)
if len(sys.argv) >= 3:
with open(sys.argv[2], "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\nRisultati salvati in: {sys.argv[2]}")
+55
View File
@@ -0,0 +1,55 @@
{
"_comment": "Esempio input VDI 2230 — Giunzione flangia flangia, M12 10.9, DSV, carico alternato",
"_units": "Forze in N, lunghezze in mm, pressioni in N/mm², temperature in K",
"size": "M12",
"strength_class": "10.9",
"thread_type": "coarse",
"joint_type": "DSV",
"lK_mm": 40,
"DA_mm": 36,
"dh_mm": 13.5,
"DA_prime_mm": 60,
"hmin_mm": 20,
"ssym_mm": 0,
"a_mm": 0,
"n_factor": 1.0,
"IBers_mm4": 0,
"FA_max_N": 20000,
"FA_min_N": 5000,
"FQ_max_N": 0,
"MY_max_Nmm": 0,
"mu_G": 0.12,
"mu_K": 0.12,
"mu_T": 0.12,
"qF": 1,
"qM": 1,
"ra_mm": 0,
"pi_max_N_mm2": 0,
"AD_mm2": 0,
"IBT_mm4": 0,
"u_mm": 0,
"MB_max_Nmm": 0,
"alpha_A": 1.7,
"EP_N_mm2": 210000,
"pG_N_mm2": 900,
"fZ_total_um": 12,
"alpha_S": 1.15e-5,
"alpha_P": 1.15e-5,
"dT_S_K": 0,
"dT_P_K": 0,
"meff_actual_mm": 20,
"internal_material": "steel",
"thread_treatment": "SV",
"lGew_mm": 5
}
+940
View File
@@ -0,0 +1,940 @@
#!/usr/bin/env python3
from typing import Optional
"""
VDI 2230:2003 — Motore di calcolo completo R0-R13
Uso: python vdi2230_calc.py <input.json>
Output: JSON con tutti i risultati e il report testuale
"""
import json
import math
import sys
import os
# ─── Caricamento dati ────────────────────────────────────────────────────────
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(SCRIPT_DIR, "..", "data")
def load_json(filename):
path = os.path.join(DATA_DIR, filename)
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
THREAD = load_json("thread_data.json")
FM_TAB = load_json("fm_table.json")
# ─── Costanti ────────────────────────────────────────────────────────────────
ES_BOLT = 206000.0 # N/mm² modulo elastico acciaio vite
PI = math.pi
ALPHA = PI / 6 # 30° semi-angolo filettatura metrica
# ─── Funzioni ausiliarie ─────────────────────────────────────────────────────
def get_thread_data(size: str, thread_type: str = "coarse") -> dict:
"""Recupera dati filettatura. size es: 'M12', 'M12x1.5'"""
if thread_type == "coarse":
data = THREAD["thread_metric_coarse"]
key = size.upper()
else:
data = THREAD["thread_metric_fine"]
key = size.upper()
if key not in data:
raise ValueError(f"Filettatura '{size}' non trovata nei dati ({thread_type}). "
f"Disponibili: {list(data.keys())}")
return data[key]
def get_strength(fk: str) -> dict:
"""Proprietà meccaniche per classe di resistenza '8.8', '10.9', '12.9'"""
sc = THREAD["strength_classes"]
if fk not in sc:
raise ValueError(f"Classe '{fk}' non trovata. Disponibili: {list(sc.keys())}")
return sc[fk]
def get_fm_table(size: str, strength_class: str, mu: float) -> Optional[dict]:
"""
Legge FM_Tab e MA da tabella A1 per la µ più vicina disponibile.
Restituisce {'FM': kN, 'MA': Nm} o None se fuori tabella.
"""
tab = FM_TAB.get("A1_regular_shank", {})
size_upper = size.upper()
if size_upper not in tab:
return None
mu_keys = sorted([float(k) for k in tab[size_upper][strength_class].keys()])
# Trova µ più vicina
closest = min(mu_keys, key=lambda x: abs(x - mu))
entry = tab[size_upper][strength_class][f"{closest:.2f}"]
return {"FM_kN": entry["FM"], "MA_Nm": entry["MA"], "mu_used": closest}
# ─── R0: stima diametro e verifica limite G ──────────────────────────────────
def r0_check_geometry(d: float, lK: float, dW: float, dh: float,
DA: float, hmin: float,
joint_type: str = "DSV") -> dict:
"""
joint_type: 'DSV' (vite passante) o 'ESV' (vite in foro filettato)
Restituisce dizionario con G, cT_max, e flag di validità.
"""
if joint_type == "DSV":
G = hmin + dW
G_type = "DSV"
else:
G = 1.75 * dW # valore medio dell'intervallo (1.5...2)·dW
G_type = "ESV"
valid = DA <= G
return {
"G": G,
"G_type": G_type,
"DA": DA,
"valid": valid,
"warning": f"DA={DA:.1f} > G={G:.1f}: calcolo eccentrico potrebbe avere errori significativi!" if not valid else ""
}
# ─── R1: fattore di serraggio ────────────────────────────────────────────────
def r1_tightening_factor(method: str, mu_class: str = "B") -> float:
"""
method: 'torque_wrench_classB', 'torque_manual_classC',
'yield_or_angle_controlled', 'torque_wrench_classA',
'power_tool_controlled'
Restituisce αA tipico (valore medio dell'intervallo).
"""
tf = THREAD["tightening_factors_alphaA"]
if method not in tf:
raise ValueError(f"Metodo '{method}' non trovato. Disponibili: {list(tf.keys())}")
entry = tf[method]
return (entry["alphaA_min"] + entry["alphaA_max"]) / 2.0
# ─── R2: forza di serraggio minima FKerf ────────────────────────────────────
def r2_min_clamp_force(FQ_max: float = 0.0, MY_max: float = 0.0,
mu_T: float = 0.12, qF: int = 1, qM: int = 1,
ra: float = 0.0,
pi_max: float = 0.0, AD: float = 0.0,
FA_max: float = 0.0, ssym: float = 0.0,
a: float = 0.0, u: float = 0.0,
IBT: float = 0.0, MB_max: float = 0.0) -> dict:
"""
Calcola FKerf per tre requisiti:
- FKQ: anti-scorrimento
- FKP: tenuta
- FKA: anti-apertura (klaffen)
Tutti i valori in N (non kN) e N·mm.
"""
# a) Anti-scorrimento
FKQ = 0.0
if FQ_max > 0:
FKQ += FQ_max / (qF * mu_T)
if MY_max > 0 and ra > 0:
FKQ += MY_max / (qM * ra * mu_T)
# b) Tenuta
FKP = AD * pi_max if (AD > 0 and pi_max > 0) else 0.0
# c) Anti-apertura
FKA = 0.0
if IBT > 0 and u > 0 and FA_max > 0:
denom = IBT + ssym * u * AD
FKA = FA_max * AD * (a * u - ssym * u) / denom
FKA += MB_max * u * AD / denom if MB_max > 0 else 0.0
FKA = max(0.0, FKA)
FKerf = max(FKQ, FKP + FKA)
return {
"FKQ_N": FKQ,
"FKP_N": FKP,
"FKA_N": FKA,
"FKerf_N": FKerf,
"governing": "FKQ" if FKQ >= FKP + FKA else "FKP+FKA"
}
# ─── R3: cedevolezze e fattore di carico Φ ──────────────────────────────────
def r3_bolt_resilience(thread_data: dict, lK: float,
lGew: float = 0.0, lGew_sections: list = None,
joint_type: str = "DSV",
E_internal: float = None) -> float:
"""
Calcola δS [mm/N] per vite con gambo pieno standard.
lGew_sections: lista di (li, di) per elementi non-standard nel gambo.
E_internal: modulo elastico del materiale madrevite (ESV). Default = ES_BOLT.
"""
d = thread_data["d"]
d3 = thread_data["d3"]
P = thread_data["P"]
d2 = thread_data["d2"]
AN = PI / 4 * d**2
Ad3= PI / 4 * d3**2
if E_internal is None:
E_internal = ES_BOLT
# Testa (esagonale standard: lSK = 0.5d)
lSK = 0.5 * d
dSK = lSK / (ES_BOLT * AN)
# Gambo (elementi standard — tutto il gambo come sezione piena se non specificato)
d_shaft = 0.0
if lGew_sections:
d_shaft = sum(li / (ES_BOLT * (PI / 4 * di**2)) for li, di in lGew_sections)
else:
# Se non ci sono sezioni diverse, usiamo tutto lK come gambo pieno d
# (approssimazione; nella realtà parte è gambo, parte filetto)
pass
# Filetto libero non avvitato
d_Gew = lGew / (ES_BOLT * Ad3) if lGew > 0 else 0.0
# Filetto avvitato + zona madrevite
lG = 0.5 * d
dG = lG / (ES_BOLT * Ad3)
lM = 0.4 * d if joint_type == "DSV" else 0.33 * d
dM = lM / (E_internal * AN)
dGM = dG + dM
# Approssimazione: per lK senza dettagli sezioni → tutto il gambo libero in sede piena
if not lGew_sections:
l_shaft = lK - lGew # porzione gambo pieno (cilindrico)
d_shaft = l_shaft / (ES_BOLT * AN)
dS = dSK + d_shaft + d_Gew + dGM
return dS
def r3_plate_resilience(dW: float, dh: float, lK: float, DA: float,
EP: float, DA_prime: float = None,
joint_type: str = "DSV") -> dict:
"""
Calcola δP [mm/N] con il modello a cono VDI 2230 §5.1.2.
Restituisce anche il cono angle φ e il tipo di modello usato.
"""
w = 1 if joint_type == "DSV" else 2
if DA_prime is None:
DA_prime = DA # approssimazione conservativa
# Angolo cono
bL = lK / dW
psi = DA_prime / dW
if joint_type == "DSV":
tan_phi = 0.362 + 0.032 * math.log(bL / 2 + 1e-9) + 0.153 * math.log(psi)
else:
tan_phi = 0.348 + 0.013 * math.log(bL + 1e-9) + 0.193 * math.log(psi)
# Clamp al range fisico [0.3, 1.0]
tan_phi = max(0.3, min(tan_phi, 1.0))
phi_deg = math.degrees(math.atan(tan_phi))
# Diametro limite
DA_Gr = dW + w * lK * tan_phi
if DA >= DA_Gr:
# Solo coni (no manicotto)
model = "coni_puri"
num = 2 * math.log(
((dW + dh) * (dW + w * lK * tan_phi - dh)) /
((dW - dh) * (dW + w * lK * tan_phi + dh) + 1e-15)
)
den = w * EP * PI * dh * tan_phi
dP = num / den if den != 0 else 1e-12
elif dW < DA < DA_Gr:
# Coni + manicotto
model = "coni_e_manicotto"
lV = (DA - dW) / (2 * tan_phi)
lH = lK - 2 * lV / w
num_cone = math.log(
((dW + dh) * (dW + 2 * lV * tan_phi - dh)) /
((dW - dh) * (dW + 2 * lV * tan_phi + dh) + 1e-15)
)
dP_cone = num_cone / (EP * PI * dh * tan_phi)
dP_sleeve = 4 * lH / (EP * PI * (DA**2 - dh**2))
dP = (2 / w) * dP_cone + dP_sleeve
else:
# Solo manicotto (dW >= DA)
model = "solo_manicotto"
dP = 4 * lK / (EP * PI * (DA**2 - dh**2))
return {
"dP_mm_per_N": abs(dP),
"tan_phi": tan_phi,
"phi_deg": phi_deg,
"DA_Gr": DA_Gr,
"model": model
}
def r3_load_factor(dS: float, dP: float, n: float = 1.0,
ssym: float = 0.0, a: float = 0.0,
lK: float = 0.0, EP: float = 210000.0,
IBers: float = None) -> dict:
"""
Calcola Φ (fattore di carico) e forze addizionali.
Per caso centrico: ssym=0, a=0.
Per caso eccentrico: fornire ssym, a, lK, EP, IBers.
"""
if ssym == 0 and a == 0:
# Caso centrico
Phi_n = n * dP / (dS + dP)
return {"Phi": Phi_n, "type": "centrico",
"dP_star": dP, "dP_doublestar": dP}
# Caso eccentrico
if IBers is None or IBers == 0:
raise ValueError("IBers richiesto per caso eccentrico (ssym ≠ 0 o a ≠ 0)")
bP_lK = lK / (EP * IBers) # flessibilità a flessione (= lK/(EP·IBers))
dP_star = dP + ssym**2 * bP_lK
dP_dstar = dP_star + ssym * a * bP_lK # può diventare negativo → caso di apertura anticipata
Phi_star = n * dP_dstar / (dS + dP_star)
return {
"Phi": Phi_star,
"type": "eccentrico",
"ssym": ssym,
"a": a,
"dP_star": dP_star,
"dP_doublestar": dP_dstar,
"bP_term": bP_lK
}
# ─── R4: variazioni di precarico ────────────────────────────────────────────
def r4_preload_changes(fZ_total: float, dS: float, dP: float,
lK: float = 0.0,
alpha_S: float = 11.5e-6, dT_S: float = 0.0,
alpha_P: float = 11.5e-6, dT_P: float = 0.0,
E_S_RT: float = 206000.0, E_P_RT: float = 210000.0,
E_S_T: float = None, E_P_T: float = None) -> dict:
"""
fZ_total: settaggio totale [µm]
Restituisce FZ [N] e delta_F_Vth [N] (semplificato).
"""
# Settaggio
FZ = (fZ_total * 1e-3) / (dS + dP) # fZ in µm → mm / cedevolezze in mm/N → N
# Variazione termica semplificata (formula R4/2)
delta_F_Vth = 0.0
if lK > 0 and (dT_S != 0 or dT_P != 0):
E_S_T = E_S_T or E_S_RT
E_P_T = E_P_T or E_P_RT
num = lK * (alpha_S * dT_S - alpha_P * dT_P)
den = dS * (E_S_RT / E_S_T) + dP * (E_P_RT / E_P_T)
delta_F_Vth = num / den if den != 0 else 0.0
return {
"FZ_N": FZ,
"fZ_total_um": fZ_total,
"deltaF_Vth_N": delta_F_Vth,
"note_FM_min": "Usare deltaF_Vth=0 se negativo e non si garantisce sequenza temp→carico" if delta_F_Vth < 0 else ""
}
# ─── R5/R6: precarico minimo e massimo ──────────────────────────────────────
def r5_r6_assembly_preload(FKerf_N: float, Phi: float, FA_max_N: float,
FZ_N: float, dF_Vth_N: float,
alpha_A: float) -> dict:
"""
Calcola FM_min e FM_max. Tutti i valori in N.
"""
dF_for_min = max(0.0, dF_Vth_N) if dF_Vth_N < 0 else dF_Vth_N
# Nota: se dF_Vth < 0 e sequenza non garantita → usa 0
dF_for_min = dF_Vth_N # l'utente deve gestire il segno prima di chiamare
FM_min = FKerf_N + (1.0 - Phi) * FA_max_N + FZ_N + dF_for_min
FM_max = alpha_A * FM_min
return {
"FM_min_N": FM_min,
"FM_min_kN": FM_min / 1000,
"FM_max_N": FM_max,
"FM_max_kN": FM_max / 1000,
"alpha_A": alpha_A
}
# ─── R7: verifica montaggio e scelta diametro ────────────────────────────────
def r7_assembly_stress(thread_data: dict, strength: dict,
FM_max_N: float, mu_G: float,
n_util: float = 0.9,
d0: float = None) -> dict:
"""
Verifica che FMzul >= FM_max.
Calcola anche σred,M e FMzul esatto.
"""
d = thread_data["d"]
d2 = thread_data["d2"]
d3 = thread_data["d3"]
P = thread_data["P"]
AS = thread_data["AS"]
AN = PI / 4 * d**2
Rp02 = strength["Rp0_2min"]
if d0 is None:
d0 = (d2 + d3) / 2 # dS per sezione di sforzo standard
A0 = PI / 4 * d0**2
# Termine torsionale (formula 5.5/7)
torsion_ratio = (3 / 2) * (d2 / d0) * (P / (PI * d2) + 1.155 * mu_G)
FM_zul = A0 * n_util * Rp02 / math.sqrt(1 + 3 * torsion_ratio**2)
# Lettura tabella (se disponibile)
# FM_Tab viene letta in run_full_calculation con la classe corretta
ok = FM_zul >= FM_max_N
return {
"FM_zul_N": FM_zul,
"FM_zul_kN": FM_zul / 1000,
"FM_max_N": FM_max_N,
"FM_max_kN": FM_max_N / 1000,
"ratio": FM_zul / FM_max_N,
"ok": ok,
"status": "✅ OK" if ok else "❌ FALLISCE",
"remedies": [] if ok else [
"Aumentare il diametro nominale (es. M12→M14)",
"Passare a una classe di resistenza superiore (8.8→10.9→12.9)",
"Ridurre µG con lubrificante adeguato (classe A o B)",
"Usare serraggio angolare o al limite di snervamento (αA=1,0)",
],
"d0_used": d0,
"torsion_ratio": torsion_ratio
}
# ─── R8: verifica esercizio ──────────────────────────────────────────────────
def r8_working_stress(thread_data: dict, strength: dict,
FM_zul_N: float, Phi: float, FA_max_N: float,
mu_G: float, dF_Vth_N: float = 0.0,
kt: float = 0.5, d0: float = None) -> dict:
"""
Calcola σred,B e sicurezza SF. dF_Vth_N: se > 0 usare 0 (salvo garanzia).
"""
d = thread_data["d"]
d2 = thread_data["d2"]
d3 = thread_data["d3"]
P = thread_data["P"]
Rp02 = strength["Rp0_2min"]
if d0 is None:
d0 = (d2 + d3) / 2
A0 = PI / 4 * d0**2
WP = PI / 16 * d0**3
dF_use = 0.0 if dF_Vth_N > 0 else dF_Vth_N
FS_max = FM_zul_N + Phi * FA_max_N - dF_use
# Tensioni
sz_max = FS_max / A0
# Momento nel filetto e tensione torsionale
MG = FM_zul_N * (d2 / 2) * (P / (PI * d2) + 1.155 * mu_G)
tau_max = MG / WP
# Tensione equivalente
sigma_red_B = math.sqrt(sz_max**2 + 3 * (kt * tau_max)**2)
SF = Rp02 / sigma_red_B
ok = sigma_red_B < Rp02
return {
"FS_max_N": FS_max,
"FS_max_kN": FS_max / 1000,
"sz_max_MPa": sz_max,
"tau_max_MPa": tau_max,
"sigma_red_B": sigma_red_B,
"Rp02_MPa": Rp02,
"SF": SF,
"ok": ok,
"status": "✅ OK" if ok else "❌ FALLISCE",
"remedies": [] if ok else [
"Aumentare il diametro (riduce σz per maggiore A0)",
"Aumentare la classe di resistenza (aumenta Rp0.2)",
"Verificare se il serraggio senza torsione è possibile (kt=0 → σred,B = σz)",
],
"kt": kt
}
# ─── R9: fatica ─────────────────────────────────────────────────────────────
def r9_fatigue(thread_data: dict, strength: dict,
FM_zul_N: float, Phi: float,
FA_max_N: float, FA_min_N: float,
thread_treatment: str = "SV") -> dict:
"""
thread_treatment: 'SV' (rullato prima della tempra) o 'SG' (rullato dopo tempra).
"""
d = thread_data["d"]
AS = thread_data["AS"]
Rp02= strength["Rp0_2min"]
FSA_o = Phi * FA_max_N
FSA_u = Phi * FA_min_N
sigma_a = (FSA_o - FSA_u) / (2 * AS)
# Tensione media
FSm = (FSA_o + FSA_u) / 2 + FM_zul_N
F02_min = Rp02 * AS
ratio = FSm / F02_min
ratio = max(0.3, min(ratio, 0.999)) # clamp al range di validità
# Limite di fatica
sigma_ASV = 0.85 * (150 / d + 45)
if thread_treatment == "SG":
sigma_AS = (2 - ratio) * sigma_ASV
else:
sigma_AS = sigma_ASV
SD = sigma_AS / sigma_a if sigma_a > 0 else float("inf")
ok = sigma_a <= sigma_AS
return {
"FSA_o_N": FSA_o,
"FSA_u_N": FSA_u,
"sigma_a_MPa": sigma_a,
"sigma_AS_MPa": sigma_AS,
"sigma_ASV_MPa": sigma_ASV,
"SD": SD,
"FSm_ratio": ratio,
"treatment": thread_treatment,
"ok": ok,
"status": "✅ OK" if ok else "❌ FALLISCE",
"remedies": [] if ok else [
"Aumentare il diametro (AS maggiore → σa minore)",
"Usare filetto rullato post-tempra SG (+20-30% su σAS)",
"Aumentare il precarico FM (riduce l'ampiezza relativa FSA)",
"Verificare se il numero di cicli consente progetto a vita finita",
],
"SD_recommended": "SD ≥ 1,2 raccomandato VDI"
}
# ─── R10: pressione superficiale ────────────────────────────────────────────
def r10_surface_pressure(FM_zul_N: float, dW: float, dh: float,
FA_max_N: float, Phi: float,
pG_N_mm2: float,
dF_Vth_N: float = 0.0,
FV_max_N: float = None,
alpha_A: float = None,
FM_tab_N: float = None) -> dict:
"""
Verifica pressione superficiale sotto testa/dado.
"""
# Area appoggio (anello circolare con svasatura al foro)
DKi = dh # diametro interno appoggio ≈ diametro foro
# dwa = diametro esterno appoggio testa vite ≈ dW (senza rondella)
Ap_min = PI / 4 * (dW**2 - DKi**2)
if Ap_min <= 0:
return {"error": f"Area appoggio negativa: dW={dW}, dh={dh}"}
# Pressione montaggio
pM_max = FM_zul_N / Ap_min
ok_M = pM_max <= pG_N_mm2
# Pressione esercizio
dF_use = 0.0 if dF_Vth_N > 0 else dF_Vth_N
if FV_max_N is None:
FV_max_N = FM_zul_N # conservativo
FSA_max = Phi * FA_max_N
pB_max = (FV_max_N + FSA_max - dF_use) / Ap_min
ok_B = pB_max <= pG_N_mm2
# Per serraggio angolare/snervamento: pmax = FM_Tab/Ap * 1.4
p_yield = None
if FM_tab_N is not None:
p_yield = FM_tab_N / Ap_min * 1.4
return {
"Ap_min_mm2": Ap_min,
"pG_MPa": pG_N_mm2,
"pM_max_MPa": pM_max,
"ok_montaggio":ok_M,
"pB_max_MPa": pB_max,
"ok_esercizio":ok_B,
"SP_montaggio":pG_N_mm2 / pM_max if pM_max > 0 else 999,
"SP_esercizio": pG_N_mm2 / pB_max if pB_max > 0 else 999,
"p_yield_MPa": p_yield,
"status": "✅ OK" if (ok_M and ok_B) else "❌ FALLISCE",
"remedies": [] if (ok_M and ok_B) else [
"Aggiungere rondella (aumenta Ap: dWa = dW + 1,6·hS)",
"Usare materiale più resistente sotto testa (pG maggiore)",
"Ridurre FM_zul (solo se R7 rimane verificato)",
]
}
# ─── R11: lunghezza avvitamento ──────────────────────────────────────────────
def r11_engagement_length(d: float, meff_actual: float,
strength_class: str,
internal_material: str = "steel") -> dict:
"""
Verifica meff_actual >= meff_min.
internal_material: 'steel', 'cast_iron', 'aluminum'
"""
el = THREAD["min_engagement_length_ratio"]
key = f"{internal_material}_{strength_class}"
if key not in el:
# fallback
ratio = el.get(f"steel_{strength_class}", 1.0)
else:
ratio = el[key]
meff_min = ratio * d
ok = meff_actual >= meff_min
return {
"meff_min_mm": meff_min,
"meff_actual_mm": meff_actual,
"ratio": meff_actual / meff_min,
"ok": ok,
"status": "✅ OK" if ok else f"❌ FALLISCE — meff attuale {meff_actual:.1f} mm < minimo {meff_min:.1f} mm",
"remedies": [] if ok else [
f"Aumentare la lunghezza di avvitamento a ≥ {meff_min:.1f} mm (approfondire il foro o usare dado più alto)",
"Usare inserto filettato (Helicoil/Böllhoff Ensat/Kerb-Konus): aumenta meff disponibile, riduce la pressione sul filetto e ripristina la resistenza dell'acciaio anche in alluminio o ghisa",
"Passare a classe di resistenza inferiore (riduce meff_min richiesta)",
"Passare a filettatura fine (stesso diametro esterno, passo ridotto → meff_min leggermente minore)",
]
}
# ─── R12: anti-scorrimento e taglio ─────────────────────────────────────────
def r12_slip_shear(FM_zul_N: float, alpha_A: float, Phi: float,
FA_max_N: float, FZ_N: float, dF_Vth_N: float,
FKQ_erf_N: float,
FQ_max_N: float = 0.0, At_mm2: float = None,
tau_B_MPa: float = None, d3: float = None) -> dict:
"""
Calcola FKR_min e verifica SG e SA.
"""
dF_use = 0.0 if dF_Vth_N < 0 else dF_Vth_N # caso conservativo
FKR_min = FM_zul_N / alpha_A - (1 - Phi) * FA_max_N - FZ_N - dF_use
SG = FKR_min / FKQ_erf_N if FKQ_erf_N > 0 else float("inf")
ok_slip = FKR_min > FKQ_erf_N
# Taglio
ok_shear = True
SA = None
tau_Q_max = None
if FQ_max_N > 0 and At_mm2 and tau_B_MPa:
tau_Q_max = FQ_max_N / At_mm2
SA = tau_B_MPa * At_mm2 / FQ_max_N
ok_shear = SA >= 1.1
return {
"FKR_min_N": FKR_min,
"FKR_min_kN": FKR_min / 1000,
"FKQ_erf_N": FKQ_erf_N,
"SG": SG,
"ok_slip": ok_slip,
"status_slip": "✅ OK" if ok_slip else "❌ FALLISCE",
"remedies_slip": [] if ok_slip else [
"Aumentare il precarico FM (cambio metodo di serraggio o diametro)",
"Aumentare µT con trattamento superficiale (sabbiatura, ecc.)",
"Aggiungere spine di posizionamento per trasmettere FQ in forma",
],
"SG_limit": "SG ≥ 1,2 (statico) | ≥ 1,8 (alternato)",
"tau_Q_max": tau_Q_max,
"SA": SA,
"ok_shear": ok_shear,
"status_shear":"✅ OK" if ok_shear else "❌ FALLISCE (taglio)"
}
# ─── R13: coppia di serraggio ────────────────────────────────────────────────
def r13_tightening_torque(thread_data: dict, FM_zul_N: float,
mu_G: float, mu_K: float,
dW: float, dh: float) -> dict:
"""
Calcola MA e le componenti di coppia.
"""
d = thread_data["d"]
d2 = thread_data["d2"]
P = thread_data["P"]
DKi = dh # diametro interno appoggio
DKm = (dW + DKi) / 2 # diametro medio appoggio
# Momento filetto + appoggio (FM[N]*dist[mm] = N·mm → /1000 = N·m)
MA = FM_zul_N * (0.16 * P + 0.58 * d2 * mu_G + DKm / 2 * mu_K) / 1000
# Componenti
M_thread = FM_zul_N * (0.16 * P + 0.58 * d2 * mu_G) / 1000
M_bearing = FM_zul_N * DKm / 2 * mu_K / 1000
return {
"MA_Nm": MA,
"M_thread_Nm": M_thread,
"M_bearing_Nm": M_bearing,
"DKm_mm": DKm,
"FM_zul_kN": FM_zul_N / 1000
}
# ─── Calcolo completo orchestrato ───────────────────────────────────────────
def run_full_calculation(inp: dict) -> dict:
"""
Esegue R0R13 dato un dizionario di input.
"""
results = {"input": inp, "steps": {}, "summary": {}}
errors = []
try:
# === Dati base ===
size = inp["size"] # es "M12"
fk = inp["strength_class"] # "8.8","10.9","12.9"
thread_type = inp.get("thread_type", "coarse")
td = get_thread_data(size, thread_type)
sc = get_strength(fk)
lK = inp["lK_mm"]
DA = inp["DA_mm"]
dh = inp["dh_mm"]
dW = td["dW_hex"]
joint = inp.get("joint_type", "DSV") # DSV o ESV
DA_prime = inp.get("DA_prime_mm", DA * 1.5)
hmin = inp.get("hmin_mm", lK / 2) # approssimazione conservativa
ssym = inp.get("ssym_mm", 0.0)
a_dist = inp.get("a_mm", 0.0)
n_factor = inp.get("n_factor", 1.0)
FA_max = inp["FA_max_N"]
FA_min = inp.get("FA_min_N", FA_max) # statico di default
mu_G = inp.get("mu_G", 0.12)
mu_K = inp.get("mu_K", mu_G)
alpha_A = inp.get("alpha_A", 1.7) # torque_wrench_classB tipico
EP = inp.get("EP_N_mm2", 210000.0)
pG = inp.get("pG_N_mm2", 900.0)
lGew = inp.get("lGew_mm", 0.0)
meff = inp.get("meff_actual_mm", td["d"] * 1.5)
FQ_max = inp.get("FQ_max_N", 0.0)
MY_max = inp.get("MY_max_Nmm", 0.0)
mu_T = inp.get("mu_T", 0.12)
qF = inp.get("qF", 1)
qM = inp.get("qM", 1)
ra = inp.get("ra_mm", 0.0)
pi_max = inp.get("pi_max_N_mm2", 0.0)
AD = inp.get("AD_mm2", 0.0)
IBT = inp.get("IBT_mm4", 0.0)
u_dist = inp.get("u_mm", 0.0)
MB_max = inp.get("MB_max_Nmm", 0.0)
# Variazioni termiche
fZ_um = inp.get("fZ_total_um", 10.0) # settaggio default conservativo
alpha_S = inp.get("alpha_S", 11.5e-6)
alpha_P = inp.get("alpha_P", 11.5e-6)
dT_S = inp.get("dT_S_K", 0.0)
dT_P = inp.get("dT_P_K", 0.0)
int_mat = inp.get("internal_material", "steel")
thread_treat = inp.get("thread_treatment", "SV")
at_shear = inp.get("At_shear_mm2", td["Ad3"])
tau_B = inp.get("tau_B_MPa", 0.6 * sc["Rm_min"])
# Calcolo IBers se non fornito (approssimazione cilindrica)
IBers_def = PI / 64 * (DA**4 - dh**4)
IBers = inp.get("IBers_mm4", IBers_def)
# R0
r = r0_check_geometry(td["d"], lK, dW, dh, DA, hmin, joint)
results["steps"]["R0"] = r
# R2
r = r2_min_clamp_force(FQ_max, MY_max, mu_T, qF, qM, ra,
pi_max, AD, FA_max, ssym, a_dist,
u_dist, IBT, MB_max)
results["steps"]["R2"] = r
FKerf = r["FKerf_N"]
# R3 - cedevolezze
dS = r3_bolt_resilience(td, lK, lGew, joint_type=joint)
rP = r3_plate_resilience(dW, dh, lK, DA, EP, DA_prime, joint)
dP = rP["dP_mm_per_N"]
results["steps"]["R3_dS"] = {"dS_mm_per_N": dS}
results["steps"]["R3_dP"] = rP
# R3 - fattore di carico
rPhi = r3_load_factor(dS, dP, n_factor, ssym, a_dist, lK, EP, IBers)
results["steps"]["R3_Phi"] = rPhi
Phi = rPhi["Phi"]
# R4
rChg = r4_preload_changes(fZ_um, dS, dP, lK, alpha_S, dT_S, alpha_P, dT_P)
results["steps"]["R4"] = rChg
FZ = rChg["FZ_N"]
dFVth= rChg["deltaF_Vth_N"]
# Gestione segno dFVth per FM_min
dFVth_for_min = 0.0 if dFVth < 0 else dFVth
# R5/R6
rFM = r5_r6_assembly_preload(FKerf, Phi, FA_max, FZ, dFVth_for_min, alpha_A)
results["steps"]["R5_R6"] = rFM
FM_min = rFM["FM_min_N"]
FM_max = rFM["FM_max_N"]
# R7
rR7 = r7_assembly_stress(td, sc, FM_max, mu_G)
# Leggi FM_Tab
fm_tab = get_fm_table(size, fk, mu_G)
if fm_tab:
FM_tab_N = fm_tab["FM_kN"] * 1000
MA_tab = fm_tab["MA_Nm"]
rR7["FM_Tab_kN"] = fm_tab["FM_kN"]
rR7["mu_used"] = fm_tab["mu_used"]
rR7["ok_tab"] = FM_tab_N >= FM_max
rR7["status"] = ("✅ OK" if FM_tab_N >= FM_max else
"❌ FALLISCE — aumentare diametro o classe") + " (tabella)"
results["steps"]["R7"] = rR7
FM_zul = rR7["FM_zul_N"]
# R8
rR8 = r8_working_stress(td, sc, FM_zul, Phi, FA_max, mu_G, dFVth)
results["steps"]["R8"] = rR8
# R9
rR9 = r9_fatigue(td, sc, FM_zul, Phi, FA_max, FA_min, thread_treat)
results["steps"]["R9"] = rR9
# R10
rR10 = r10_surface_pressure(FM_zul, dW, dh, FA_max, Phi, pG, dFVth,
FM_tab_N=FM_tab_N if fm_tab else None)
results["steps"]["R10"] = rR10
# R11
rR11 = r11_engagement_length(td["d"], meff, fk, int_mat)
results["steps"]["R11"] = rR11
# R12
rR12 = r12_slip_shear(FM_zul, alpha_A, Phi, FA_max, FZ, dFVth,
FKerf if FQ_max > 0 else 0.0,
FQ_max, at_shear, tau_B, td["d3"])
results["steps"]["R12"] = rR12
# R13
rR13 = r13_tightening_torque(td, FM_zul, mu_G, mu_K, dW, dh)
results["steps"]["R13"] = rR13
# Sommario verifiche
verifications = {
"R7_montaggio": rR7["ok"],
"R8_esercizio": rR8["ok"],
"R9_fatica": rR9["ok"],
"R10_pressione": rR10["ok_montaggio"] and rR10["ok_esercizio"],
"R11_avvitamento": rR11["ok"],
"R12_scorrimento": rR12["ok_slip"]
}
if FQ_max > 0:
verifications["R12_taglio"] = rR12["ok_shear"]
all_ok = all(verifications.values())
results["summary"] = {
"size": size,
"strength_class": fk,
"thread_type": thread_type,
"joint_type": joint,
"FM_min_kN": FM_min / 1000,
"FM_max_kN": FM_max / 1000,
"FM_zul_kN": FM_zul / 1000,
"Phi": Phi,
"MA_Nm": rR13["MA_Nm"],
"MA_tab_Nm": MA_tab if fm_tab else None,
"verifications": verifications,
"all_ok": all_ok,
"global_status": "✅ DIMENSIONAMENTO OK" if all_ok else "❌ VERIFICHE FALLITE — vedere dettaglio"
}
except Exception as e:
results["error"] = str(e)
import traceback
results["traceback"] = traceback.format_exc()
return results
# ─── Stampa report leggibile ─────────────────────────────────────────────────
def print_report(res: dict):
s = res.get("summary", {})
steps = res.get("steps", {})
print("=" * 70)
print(" VDI 2230:2003 — REPORT CALCOLO GIUNZIONE BULLONATA")
print("=" * 70)
if "error" in res:
print(f"\n❌ ERRORE: {res['error']}")
return
print(f"\n Vite: {s.get('size','')} — Classe {s.get('strength_class','')}{s.get('thread_type','')}{s.get('joint_type','')}")
print()
print(" ── RISULTATI CHIAVE ──────────────────────────────────────")
print(f" FM min = {s.get('FM_min_kN',0):.2f} kN")
print(f" FM max = {s.get('FM_max_kN',0):.2f} kN")
print(f" FM zul (calc)= {s.get('FM_zul_kN',0):.2f} kN")
print(f" Phi (fattore carico) = {s.get('Phi',0):.4f}")
print(f" MA (calc) = {s.get('MA_Nm',0):.1f} Nm")
if s.get('MA_tab_Nm'):
print(f" MA (tabella) = {s.get('MA_tab_Nm'):.1f} Nm")
print()
print(" ── VERIFICHE ─────────────────────────────────────────────")
for k, v in s.get("verifications", {}).items():
symbol = "" if v else ""
print(f" {symbol} {k}")
print()
print(f" ══ {s.get('global_status','')}")
# Dettaglio cedevolezze
if "R3_dS" in steps and "R3_dP" in steps:
dS = steps["R3_dS"].get("dS_mm_per_N", 0)
dP = steps["R3_dP"].get("dP_mm_per_N", 0)
print(f"\n Cedevolezze: δS = {dS:.3e} mm/N | δP = {dP:.3e} mm/N")
print(f" Modello cono: {steps['R3_dP'].get('model','')} | tan φ = {steps['R3_dP'].get('tan_phi',0):.3f}")
# Dettaglio R8
if "R8" in steps:
r = steps["R8"]
print(f"\n R8 Esercizio: σz = {r.get('sz_max_MPa',0):.1f} MPa | τ = {r.get('tau_max_MPa',0):.1f} MPa | σred,B = {r.get('sigma_red_B',0):.1f} MPa | SF = {r.get('SF',0):.2f}")
# Dettaglio R9
if "R9" in steps:
r = steps["R9"]
print(f" R9 Fatica: σa = {r.get('sigma_a_MPa',0):.1f} MPa | σAS = {r.get('sigma_AS_MPa',0):.1f} MPa | SD = {r.get('SD',0):.2f}")
# Dettaglio R10
if "R10" in steps:
r = steps["R10"]
print(f" R10 Pressione: pM = {r.get('pM_max_MPa',0):.0f} MPa | pB = {r.get('pB_max_MPa',0):.0f} MPa | pG = {r.get('pG_MPa',0):.0f} MPa")
print("=" * 70)
# ─── Entry point ─────────────────────────────────────────────────────────────
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Uso: python vdi2230_calc.py <input.json> [output.json]")
sys.exit(1)
with open(sys.argv[1], "r", encoding="utf-8") as f:
input_data = json.load(f)
result = run_full_calculation(input_data)
print_report(result)
if len(sys.argv) >= 3:
with open(sys.argv[2], "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\nRisultati salvati in: {sys.argv[2]}")