Spaces:
Running
Running
| """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) | |