Files
engineering-skills/vdi2230/scripts/auto_size.py
T
davide 4a2e3f8389 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>
2026-03-23 10:37:40 +01:00

150 lines
5.1 KiB
Python

#!/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]}")