4a2e3f8389
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>
150 lines
5.1 KiB
Python
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]}")
|