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:
@@ -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 (M12–M24). 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,25–0,40 e la vite può rompersi. Usare sempre pasta anti-grippante (Molykote 1000, Copaslip, Electrolube) e classe µ C/D (µ = 0,18–0,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 |
|
||||
| 10–30 kN | M12 | M10 | M8 |
|
||||
| 30–60 kN | M16 | M12 | M10 |
|
||||
| 60–120 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 R7–R12 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]
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
@@ -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}}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
# VDI 2230:2003 — Formulario rapido (per spiegazioni al progettista)
|
||||
|
||||
## Passi R0–R13 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) /
|
||||
((dW–dh)(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,08–0,10 | A |
|
||||
| Vite in acciaio, stato di consegna | 0,10–0,14 | B |
|
||||
| Zincato a caldo, senza lubrificante | 0,14–0,20 | C/D |
|
||||
| Acciaio inox senza lubrificante | 0,20–0,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,35–0,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,13–0,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) | 2–4 |
|
||||
| Testa/dado su piastra grezza (Rz > 10) | 4–7 |
|
||||
| Piano su piano rettificato (Rz ≤ 10) | 1,5–2,5 |
|
||||
| Piano su piano grezzo (Rz > 10) | 2,5–6,5 |
|
||||
| Contributo filettatura | 1–2 |
|
||||
|
||||
**Regola pratica:** per giunzione standard (2 giunture + testa + filetto, acciaio): fZ_totale ≈ 10–15 µm
|
||||
|
||||
---
|
||||
|
||||
## Fattori di serraggio αA tipici
|
||||
|
||||
| Metodo | αA tipico | Note |
|
||||
| --- | --- | --- |
|
||||
| Chiave a stella manuale | 2,5–4,0 | Grande dispersione |
|
||||
| Chiave dinamometrica (classe B) | 1,6–2,0 | Standard industriale |
|
||||
| Chiave dinamometrica (classe A) | 1,2–1,6 | Calibrata, accurata |
|
||||
| Avvitatore controllato | 1,4–1,6 | Produzione in serie |
|
||||
| Serraggio angolare / snervamento | 1,0 | Nessuna dispersione |
|
||||
@@ -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.000–170.000 |
|
||||
| `pG_N_mm2` | N/mm² | `900` | Pressione superficiale limite sotto testa/dado. Acciaio = 900–1.000; alluminio = 300–450; ghisa = 400–600 |
|
||||
| `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 ≈ 10–15 µm |
|
||||
|
||||
## Fatica
|
||||
|
||||
| Parametro | Unità | Default | Descrizione |
|
||||
|-----------|-------|---------|-------------|
|
||||
| `thread_treatment` | — | `"SV"` | Trattamento filetto: `"SV"` = rullato pre-tempra; `"SG"` = rullato post-tempra (+20–30% σ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
|
||||
}
|
||||
```
|
||||
@@ -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]}")
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 R0–R13 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]}")
|
||||
Reference in New Issue
Block a user