"""Classical mechanical-engineering calculators. Closed-form solutions for the design problems engineers solve daily: - Beam deflection (cantilever / simply-supported) - Shaft torsion + critical speed - Bolt preload (torque-tension) - Buckling (Euler + Johnson) - Hertz contact stress - Fatigue (modified Goodman) - Pressure vessel (thin- and thick-wall) - 1D heat conduction + fin efficiency Every function returns a `dict` of named results so the parser can pretty- print and the chat UI can render a table. Units are SI internally: m, N, Pa, kg, K, W. Inputs accept mm + N + MPa where natural. """ from __future__ import annotations import math # ---------------- materials database ---------------- # # Real properties from MMPDS / ASM / manufacturer datasheets. # E, nu, density, yield, ultimate, fatigue_endurance (all SI: Pa, kg/m^3) MATERIALS = { "Al-6061-T6": {"E": 68.9e9, "nu": 0.33, "rho": 2700, "Sy": 276e6, "Su": 310e6, "Sf": 96.5e6, "k_th": 167, "alpha": 23.6e-6, "cost_kg": 4.0}, "Al-7075-T6": {"E": 71.7e9, "nu": 0.33, "rho": 2810, "Sy": 503e6, "Su": 572e6, "Sf": 159e6, "k_th": 130, "alpha": 23.4e-6, "cost_kg": 7.0}, "Steel-A36": {"E": 200e9, "nu": 0.30, "rho": 7850, "Sy": 250e6, "Su": 400e6, "Sf": 200e6, "k_th": 51, "alpha": 11.7e-6, "cost_kg": 0.9}, "Steel-4140Q&T": {"E": 205e9, "nu": 0.29, "rho": 7850, "Sy": 655e6, "Su": 950e6, "Sf": 475e6, "k_th": 42, "alpha": 12.3e-6, "cost_kg": 1.8}, "Stainless-304": {"E": 193e9, "nu": 0.29, "rho": 8000, "Sy": 215e6, "Su": 505e6, "Sf": 240e6, "k_th": 16, "alpha": 17.3e-6, "cost_kg": 4.5}, "Stainless-316": {"E": 193e9, "nu": 0.30, "rho": 8000, "Sy": 290e6, "Su": 580e6, "Sf": 257e6, "k_th": 16, "alpha": 16.0e-6, "cost_kg": 5.5}, "Ti-6Al-4V": {"E": 113.8e9, "nu": 0.34, "rho": 4430, "Sy": 880e6, "Su": 950e6, "Sf": 510e6, "k_th": 6.7, "alpha": 8.6e-6, "cost_kg": 55.0}, "Inconel-718": {"E": 211e9, "nu": 0.29, "rho": 8190, "Sy": 1170e6, "Su": 1380e6, "Sf": 620e6, "k_th": 11.4,"alpha": 13.0e-6, "cost_kg": 60.0}, "Brass-360": {"E": 97e9, "nu": 0.34, "rho": 8500, "Sy": 124e6, "Su": 338e6, "Sf": 110e6, "k_th": 115, "alpha": 20.5e-6, "cost_kg": 5.5}, "ABS-plastic": {"E": 2.3e9, "nu": 0.35, "rho": 1050, "Sy": 40e6, "Su": 44e6, "Sf": 14e6, "k_th": 0.17,"alpha": 90e-6, "cost_kg": 2.5}, "PLA": {"E": 3.5e9, "nu": 0.36, "rho": 1240, "Sy": 50e6, "Su": 65e6, "Sf": 16e6, "k_th": 0.13,"alpha": 68e-6, "cost_kg": 18.0}, "CFRP-UD": {"E": 135e9, "nu": 0.27, "rho": 1600, "Sy": 1500e6, "Su": 2000e6, "Sf": 800e6, "k_th": 5.0, "alpha": -0.4e-6, "cost_kg": 70.0}, } def material(spec: str) -> dict: if spec not in MATERIALS: raise KeyError(f"unknown material '{spec}'. Available: " f"{list(MATERIALS.keys())}") return MATERIALS[spec] def list_materials() -> str: """Format the materials database as a readable table.""" lines = [ "MATERIAL DATABASE (E=GPa, Sy/Su/Sf=MPa, rho=kg/m3, k_th=W/m·K)", f"{'name':18s} {'E':>6s} {'Sy':>6s} {'Su':>6s} {'Sf':>6s} " f"{'rho':>5s} {'k_th':>6s} {'$/kg':>6s}", "-" * 72, ] for n, m in MATERIALS.items(): lines.append( f"{n:18s} {m['E']/1e9:6.1f} {m['Sy']/1e6:6.0f} " f"{m['Su']/1e6:6.0f} {m['Sf']/1e6:6.0f} {m['rho']:5.0f} " f"{m['k_th']:6.1f} {m['cost_kg']:6.1f}") return "\n".join(lines) # ---------------- beam deflection ---------------- # def cantilever_beam(L_mm: float, P_N: float, E_GPa: float, width_mm: float, height_mm: float) -> dict: """Cantilever beam with point load P at the free end. Returns max deflection, max bending stress, and safety check inputs. Rectangular cross-section b x h, b horizontal, h vertical. """ L = L_mm * 1e-3; P = float(P_N); E = E_GPa * 1e9 b = width_mm * 1e-3; h = height_mm * 1e-3 I = b * h**3 / 12.0 c = h / 2 # tip deflection: delta = P*L^3 / (3 E I) delta = P * L**3 / (3 * E * I) M_max = P * L sigma_max = M_max * c / I return { "type": "cantilever beam (point load at tip)", "tip_deflection_mm": delta * 1e3, "max_bending_moment_Nm": M_max, "max_bending_stress_MPa": sigma_max / 1e6, "second_moment_I_mm4": I * 1e12, "section_modulus_S_mm3": I / c * 1e9, } def simply_supported_beam(L_mm: float, P_N: float, E_GPa: float, width_mm: float, height_mm: float, a_mm: float | None = None) -> dict: """Simply-supported beam with point load P at position `a` from left support (default centred).""" L = L_mm * 1e-3; P = float(P_N); E = E_GPa * 1e9 b = width_mm * 1e-3; h = height_mm * 1e-3 a = (a_mm * 1e-3) if a_mm else L / 2 if not 0 < a < L: raise ValueError("'a' must be between 0 and L") bb = L - a I = b * h**3 / 12.0 c = h / 2 # max deflection at x = sqrt((L^2 - bb^2)/3) (for a >= bb) if a == bb: delta = P * L**3 / (48 * E * I) else: delta = P * bb * (L**2 - bb**2)**1.5 / (9 * math.sqrt(3) * E * I * L) M_max = P * a * bb / L sigma_max = M_max * c / I return { "type": "simply-supported beam, point load", "max_deflection_mm": delta * 1e3, "max_bending_moment_Nm": M_max, "max_bending_stress_MPa": sigma_max / 1e6, "load_position_a_mm": a * 1e3, "second_moment_I_mm4": I * 1e12, } def distributed_load_beam(L_mm: float, w_N_per_mm: float, E_GPa: float, width_mm: float, height_mm: float, support: str = "simple") -> dict: """Beam under uniformly-distributed load w (N/mm). Support: simple|fixed|cantilever.""" L = L_mm * 1e-3; w = w_N_per_mm * 1e3; E = E_GPa * 1e9 b = width_mm * 1e-3; h = height_mm * 1e-3 I = b * h**3 / 12.0 c = h / 2 if support == "simple": delta = 5 * w * L**4 / (384 * E * I) M_max = w * L**2 / 8 elif support == "fixed": delta = w * L**4 / (384 * E * I) M_max = w * L**2 / 12 elif support == "cantilever": delta = w * L**4 / (8 * E * I) M_max = w * L**2 / 2 else: raise ValueError("support must be simple|fixed|cantilever") return { "type": f"{support} beam, UDL w={w_N_per_mm} N/mm", "max_deflection_mm": delta * 1e3, "max_bending_moment_Nm": M_max, "max_bending_stress_MPa": M_max * c / I / 1e6, } # ---------------- shaft torsion ---------------- # def shaft_torsion(T_Nm: float, L_mm: float, d_mm: float, G_GPa: float, d_inner_mm: float = 0) -> dict: """Solid or hollow shaft under torque T.""" T = float(T_Nm); L = L_mm * 1e-3; G = G_GPa * 1e9 d_o = d_mm * 1e-3; d_i = d_inner_mm * 1e-3 J = math.pi / 32 * (d_o**4 - d_i**4) # polar moment tau_max = T * (d_o / 2) / J # surface shear theta = T * L / (G * J) # twist angle [rad] return { "type": "shaft torsion", "max_shear_stress_MPa": tau_max / 1e6, "twist_angle_deg": math.degrees(theta), "polar_moment_J_mm4": J * 1e12, } def shaft_critical_speed(L_mm: float, d_mm: float, m_kg: float, E_GPa: float = 200, support: str = "simple") -> dict: """First critical (whirling) speed of a simply-supported or fixed shaft carrying a concentrated mass m at its centre.""" L = L_mm * 1e-3; d = d_mm * 1e-3; E = E_GPa * 1e9 I = math.pi * d**4 / 64 # stiffness at center for simply-supported = 48EI/L^3, fixed-fixed = 192EI/L^3 k = (48 if support == "simple" else 192) * E * I / L**3 omega_n = math.sqrt(k / m_kg) return { "type": f"{support} shaft, mass-at-center", "first_critical_rpm": omega_n * 60 / (2 * math.pi), "natural_frequency_Hz": omega_n / (2 * math.pi), "stiffness_at_center_N_per_mm": k / 1e3, } # ---------------- bolted joint ---------------- # def bolt_torque(M_size: str, grade: str = "8.8", friction_coef: float = 0.16, preload_pct: float = 75) -> dict: """Required tightening torque for a given bolt size + grade to reach a target preload (default 75% of proof load). Uses the K-factor approximation T = K * d * F_preload. """ # ISO 898-1 proof stresses (MPa) proof_stress = {"4.6": 225, "4.8": 310, "5.6": 280, "5.8": 380, "8.8": 580, "10.9": 830, "12.9": 970} # ISO 898 stress area (mm^2) stress_area = {"M3": 5.03, "M4": 8.78, "M5": 14.2, "M6": 20.1, "M8": 36.6, "M10": 58.0, "M12": 84.3, "M14": 115, "M16": 157, "M18": 192, "M20": 245, "M22": 303, "M24": 353, "M30": 561, "M36": 817} if grade not in proof_stress: raise ValueError(f"unknown grade '{grade}'. Try 8.8 / 10.9 / 12.9") if M_size not in stress_area: raise ValueError(f"unknown bolt '{M_size}'.") d_nom_mm = float(M_size[1:]) F_proof = proof_stress[grade] * 1e6 * stress_area[M_size] * 1e-6 # N F_preload = F_proof * preload_pct / 100 K = friction_coef # rough engineering K-factor ≈ µ T = K * d_nom_mm * 1e-3 * F_preload # N·m return { "bolt": f"{M_size} grade {grade}", "proof_load_N": F_proof, "target_preload_N": F_preload, "tightening_torque_Nm": T, "K_factor_used": K, } # ---------------- column buckling ---------------- # def euler_buckling(L_mm: float, d_mm: float, E_GPa: float, Sy_MPa: float, end_cond: str = "pinned-pinned") -> dict: """Critical buckling load for a round-section column. Uses Euler when slenderness > transition; Johnson formula below transition.""" L = L_mm * 1e-3; d = d_mm * 1e-3; E = E_GPa * 1e9; Sy = Sy_MPa * 1e6 A = math.pi * d**2 / 4 I = math.pi * d**4 / 64 r = math.sqrt(I / A) K_table = {"pinned-pinned": 1.0, "fixed-fixed": 0.5, "fixed-free": 2.0, "fixed-pinned": 0.7} K = K_table.get(end_cond, 1.0) Le = K * L slenderness = Le / r Cc = math.sqrt(2 * math.pi**2 * E / Sy) if slenderness > Cc: # Euler regime Pcr = math.pi**2 * E * I / Le**2 regime = "Euler (long column)" else: # Johnson regime Pcr = A * Sy * (1 - (Sy * slenderness**2) / (4 * math.pi**2 * E)) regime = "Johnson (intermediate column)" return { "type": f"column buckling, {end_cond}", "regime": regime, "slenderness_ratio": slenderness, "transition_Cc": Cc, "critical_load_N": Pcr, "critical_load_kN": Pcr / 1e3, "effective_length_mm": Le * 1e3, } # ---------------- pressure vessel ---------------- # def pressure_vessel(P_MPa: float, D_mm: float, t_mm: float, Sy_MPa: float, joint_eff: float = 0.85) -> dict: """Thin-wall pressure vessel (hoop & longitudinal stress) + thick-wall (Lame) check. Reports safety factor against yield.""" P = P_MPa * 1e6; D = D_mm * 1e-3; t = t_mm * 1e-3; Sy = Sy_MPa * 1e6 R = D / 2 # thin-wall (valid when t/R < 0.1) sigma_hoop_thin = P * R / t sigma_long_thin = P * R / (2 * t) # thick-wall (Lame, inside surface) Ro = R + t; Ri = R sigma_hoop_thick = P * (Ri**2 + Ro**2) / (Ro**2 - Ri**2) sigma_long_thick = P * Ri**2 / (Ro**2 - Ri**2) sigma_VM = sigma_hoop_thick # dominant stress on inside surface SF = Sy * joint_eff / sigma_VM return { "type": "cylindrical pressure vessel", "hoop_stress_thin_MPa": sigma_hoop_thin / 1e6, "hoop_stress_thick_MPa": sigma_hoop_thick / 1e6, "longitudinal_stress_MPa": sigma_long_thick / 1e6, "thickness_to_radius": t / R, "regime": "thin-wall" if t / R < 0.1 else "thick-wall", "safety_factor_vs_yield": SF, } # ---------------- Hertz contact ---------------- # def hertz_contact_sphere_plane(F_N: float, R_mm: float, E_GPa: float = 200, nu: float = 0.3, E2_GPa: float | None = None, nu2: float | None = None) -> dict: """Hertz contact between a sphere of radius R pressing on a flat plane. Both materials default to steel.""" F = float(F_N); R = R_mm * 1e-3 E1 = E_GPa * 1e9; nu1 = nu E2 = (E2_GPa if E2_GPa is not None else E_GPa) * 1e9 nu2 = nu2 if nu2 is not None else nu E_star = 1 / ((1 - nu1**2) / E1 + (1 - nu2**2) / E2) a = (3 * F * R / (4 * E_star)) ** (1/3) p_max = 3 * F / (2 * math.pi * a**2) delta = a**2 / R return { "type": "Hertz contact: sphere on plane", "contact_radius_a_mm": a * 1e3, "max_contact_pressure_MPa": p_max / 1e6, "indentation_delta_um": delta * 1e6, "effective_modulus_E_star_GPa": E_star / 1e9, } # ---------------- fatigue ---------------- # def fatigue_goodman(sigma_a_MPa: float, sigma_m_MPa: float, Sf_MPa: float, Su_MPa: float) -> dict: """Modified Goodman criterion for safety against fatigue.""" sa = sigma_a_MPa * 1e6; sm = sigma_m_MPa * 1e6 Sf = Sf_MPa * 1e6; Su = Su_MPa * 1e6 # n_f = 1 / (sa/Sf + sm/Su) if sa / Sf + sm / Su <= 0: n_f = float('inf') else: n_f = 1 / (sa / Sf + sm / Su) return { "type": "modified Goodman fatigue check", "alternating_stress_MPa": sigma_a_MPa, "mean_stress_MPa": sigma_m_MPa, "endurance_limit_MPa": Sf_MPa, "ultimate_strength_MPa": Su_MPa, "safety_factor_n_f": n_f, "verdict": ("INFINITE life" if n_f >= 1.0 else "FINITE life — design unsafe"), } # ---------------- thermal: 1D conduction + fin ---------------- # def fin_efficiency(L_mm: float, t_mm: float, k_W_mK: float, h_W_m2K: float) -> dict: """Straight rectangular fin: efficiency + heat transfer per unit area of fin base. L is fin length (mm), t is thickness (mm).""" L = L_mm * 1e-3; t = t_mm * 1e-3 Lc = L + t / 2 # corrected length m = math.sqrt(2 * h_W_m2K / (k_W_mK * t)) eta_f = math.tanh(m * Lc) / (m * Lc) # q per unit width of fin base (for delta_T = 1 K) q_per_K = h_W_m2K * Lc * eta_f * 1 # per unit width, per K return { "type": "rectangular fin efficiency", "fin_parameter_mL": m * Lc, "fin_efficiency": eta_f, "q_per_width_per_K_W_per_m_per_K": q_per_K, } def conduction_1d(L_mm: float, A_mm2: float, k_W_mK: float, T_hot_C: float, T_cold_C: float) -> dict: """Steady-state 1-D heat conduction through a wall.""" L = L_mm * 1e-3; A = A_mm2 * 1e-6 dT = T_hot_C - T_cold_C R = L / (k_W_mK * A) Q = dT / R return { "type": "1-D conduction (Fourier)", "heat_flow_W": Q, "thermal_resistance_K_per_W": R, "dT_K": dT, "flux_W_per_m2": Q / A, } # ---------------- master dispatcher (parser-friendly) ---------------- # def quick_analyse_part(name: str, bbox_xyz_mm: tuple, material_spec: str = "Al-6061-T6", load_N: float = 100, case: str = "cantilever") -> dict: """Auto-analyse a part by its bbox. `case`: 'cantilever' / 'simply' / 'column' / 'pressure'. Length = bbox longest dim; cross-section = the other two dims. """ L, W, H = sorted(bbox_xyz_mm, reverse=True) m = material(material_spec) E_GPa = m["E"] / 1e9 Sy_MPa = m["Sy"] / 1e6 out = {"part": name, "material": material_spec, "bbox_mm": (L, W, H), "applied_load_N": load_N, "case": case} if case == "cantilever": r = cantilever_beam(L, load_N, E_GPa, W, H) out.update(r) out["safety_factor"] = Sy_MPa / r["max_bending_stress_MPa"] elif case == "simply": r = simply_supported_beam(L, load_N, E_GPa, W, H) out.update(r) out["safety_factor"] = Sy_MPa / r["max_bending_stress_MPa"] elif case == "column": d = (W + H) / 2 # use average of W,H as round-equivalent r = euler_buckling(L, d, E_GPa, Sy_MPa) out.update(r) out["safety_factor"] = r["critical_load_N"] / load_N elif case == "pressure": # treat as cylindrical vessel: D = (W+H)/2, t = min/10 as wall guess D = (W + H) / 2; t = max(0.5, min(W, H) / 10) r = pressure_vessel(load_N / 1e6, D, t, Sy_MPa) # load_N reinterpreted as MPa here out["applied_pressure_MPa"] = load_N / 1e6 out.update(r) else: raise ValueError("case must be cantilever | simply | column | pressure") return out def format_result(d: dict) -> str: """Pretty-print an engineering result dict for the chat panel.""" if not isinstance(d, dict): return str(d) lines = [] title = d.get("type") or d.get("part") or "result" lines.append(f" 📐 {title}") for k, v in d.items(): if k == "type": continue if isinstance(v, float): if abs(v) > 1e6 or (abs(v) < 1e-2 and v != 0): lines.append(f" {k:36s} {v:.3e}") else: lines.append(f" {k:36s} {v:.4g}") elif isinstance(v, (list, tuple)): lines.append(f" {k:36s} {v}") else: lines.append(f" {k:36s} {v}") return "\n".join(lines)