# ============================= # app/simulation/ui/gearbox.py # ============================= from __future__ import annotations import tkinter as tk from tkinter import ttk from typing import Dict, Any from app.simulation.ui import UITab from app.simulation.modules.gearbox import GEARBOX_DEFAULTS class GearboxTab(UITab): NAME = "gearbox" TITLE = "Getriebe & Antrieb" PRIO = 12 def __init__(self, parent, sim): self.sim = sim self.frame = ttk.Frame(parent, padding=8) for c in (0,1,2,3): self.frame.columnconfigure(c, weight=1) # ---------- Linke Spalte ---------- rowL = 0 def L(lbl, var=None, w=12, kind="entry", values=None): nonlocal rowL ttk.Label(self.frame, text=lbl).grid(row=rowL, column=0, sticky="w") if kind == "entry": ttk.Entry(self.frame, textvariable=var, width=w).grid(row=rowL, column=1, sticky="w") elif kind == "label": ttk.Label(self.frame, textvariable=var).grid(row=rowL, column=1, sticky="w") elif kind == "combo": ttk.Combobox(self.frame, textvariable=var, state="readonly", values=values or [], width=w).grid(row=rowL, column=1, sticky="w") elif kind == "buttons": f = ttk.Frame(self.frame); f.grid(row=rowL, column=1, sticky="w") ttk.Button(f, text="▼", width=3, command=self.shift_down).pack(side="left", padx=(0,4)) ttk.Button(f, text="N", width=3, command=self.set_neutral).pack(side="left", padx=(0,4)) ttk.Button(f, text="▲", width=3, command=self.shift_up).pack(side="left") rowL += 1 # Live/Controls (Labels → werden im _tick() live aktualisiert) self.gear_var = tk.StringVar(); L("Gang", self.gear_var, kind="label") L("Schalten", kind="buttons") self.speed_var = tk.StringVar(); L("Geschwindigkeit [km/h]", self.speed_var, kind="label") self.clutch_v = tk.StringVar(); L("Kupplung [%]", self.clutch_v, kind="label") self.slip_v = tk.StringVar(); L("Reifenschlupf [%]", self.slip_v, kind="label") ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1 # Kupplung/Automation self.cl_Tmax = tk.DoubleVar(); L("Kupplung Tmax [Nm]", self.cl_Tmax) self.cl_agr = tk.DoubleVar(); L("Aggressivität [0..1]", self.cl_agr) self.cl_curve= tk.StringVar(); L("Kupplungs-Kurve", self.cl_curve, kind="combo", values=["linear","progressive","soft"]) self.cl_drag = tk.DoubleVar(); L("Kupplungs-Schlepp [Nm]", self.cl_drag) self.sh_time = tk.DoubleVar(); L("Schaltzeit [s]", self.sh_time) self.sync_rb = tk.DoubleVar(); L("Sync-Band [RPM]", self.sync_rb) # ---------- Rechte Spalte ---------- rowR = 0 def R(lbl, var=None, w=12, kind="entry"): nonlocal rowR ttk.Label(self.frame, text=lbl).grid(row=rowR, column=2, sticky="w") if kind == "entry": ttk.Entry(self.frame, textvariable=var, width=w).grid(row=rowR, column=3, sticky="w") elif kind == "label": ttk.Label(self.frame, textvariable=var).grid(row=rowR, column=3, sticky="w") rowR += 1 # Übersetzungen / Rad self.primary = tk.DoubleVar(); R("Primärübersetzung [-]", self.primary) self.zf = tk.IntVar(); R("Ritzel vorn [Z]", self.zf) self.zr = tk.IntVar(); R("Ritzel hinten [Z]", self.zr) self.rwheel = tk.DoubleVar(); R("Radradius [m]", self.rwheel) self.eta = tk.DoubleVar(); R("Wirkungsgrad [-]", self.eta) self.couple = tk.DoubleVar(); R("RPM-Kopplung [0..1]", self.couple) ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1 # Gangübersetzungen 1..6 self.g1 = tk.DoubleVar(); R("Gang 1 Ratio", self.g1) self.g2 = tk.DoubleVar(); R("Gang 2 Ratio", self.g2) self.g3 = tk.DoubleVar(); R("Gang 3 Ratio", self.g3) self.g4 = tk.DoubleVar(); R("Gang 4 Ratio", self.g4) self.g5 = tk.DoubleVar(); R("Gang 5 Ratio", self.g5) self.g6 = tk.DoubleVar(); R("Gang 6 Ratio", self.g6) ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1 # Widerstände / Reifen self.c_rr = tk.DoubleVar(); R("Rollkoeff. c_rr", self.c_rr) self.rho = tk.DoubleVar(); R("Luftdichte [kg/m³]", self.rho) self.cd = tk.DoubleVar(); R("c_d [-]", self.cd) self.A = tk.DoubleVar(); R("Stirnfläche [m²]", self.A) self.mu_p = tk.DoubleVar(); R("Reifen μ_peak", self.mu_p) self.mu_s = tk.DoubleVar(); R("Reifen μ_slide", self.mu_s) self.w_rear = tk.DoubleVar(); R("Gewichtsanteil hinten [-]", self.w_rear) # ---------- Buttons ---------- rowBtns = max(rowL, rowR) + 1 btn = ttk.Frame(self.frame); btn.grid(row=rowBtns, column=0, columnspan=4, sticky="w", pady=(8,0)) ttk.Button(btn, text="Aktualisieren", command=self.refresh).pack(side="left") ttk.Button(btn, text="Anwenden", command=self.apply).pack(side="left", padx=(8,0)) self.refresh() self._tick() # --- Live-Update nur für Labels --- def _tick(self): snap = self.sim.snapshot() gear = int(snap.get("gear", 0)) self.gear_var.set("N" if gear == 0 else str(gear)) self.speed_var.set(f"{float(snap.get('speed_kmh', 0.0)):.1f}") self.clutch_v.set(f"{float(snap.get('clutch_pct', 0.0)):.0f}") self.slip_v.set(f"{float(snap.get('wheel_slip_pct', 0.0)):.0f}") try: self.frame.after(200, self._tick) except tk.TclError: pass # --- Actions (Buttons) --- def shift_up(self): self.sim.v.set("gear_shift_up", True) def shift_down(self): self.sim.v.set("gear_shift_down", True) def set_neutral(self): self.sim.v.set("gear_set_neutral", True) # --- Data flow --- def refresh(self): # Live-Felder werden vom _tick() versorgt; hier nur Config mergen g = dict(GEARBOX_DEFAULTS) g.update(self.sim.v.config.get("gearbox", {})) self.cl_Tmax.set(g["clutch_max_torque_nm"]) self.cl_agr.set(g["clutch_aggressiveness"]) self.cl_curve.set(g.get("clutch_curve", "linear")) self.cl_drag.set(g["clutch_drag_nm"]) self.sh_time.set(g["shift_time_s"]) self.sync_rb.set(g["sync_rpm_band"]) self.primary.set(g["primary_ratio"]) self.zf.set(g["front_sprocket_teeth"]) self.zr.set(g["rear_sprocket_teeth"]) self.rwheel.set(g["wheel_radius_m"]) self.eta.set(g["drivetrain_efficiency"]) self.couple.set(g["rpm_couple_gain"]) ratios = list(g["gear_ratios"]) + [0.0]*7 self.g1.set(ratios[1]); self.g2.set(ratios[2]); self.g3.set(ratios[3]) self.g4.set(ratios[4]); self.g5.set(ratios[5]); self.g6.set(ratios[6]) self.c_rr.set(g["rolling_c"]) self.rho.set(g["air_density"]) self.cd.set(g["aero_cd"]) self.A.set(g["frontal_area_m2"]) self.mu_p.set(g["tire_mu_peak"]) self.mu_s.set(g["tire_mu_slide"]) self.w_rear.set(g["rear_static_weight_frac"]) def apply(self): cfg = {"gearbox": { "clutch_max_torque_nm": float(self.cl_Tmax.get()), "clutch_aggressiveness": float(self.cl_agr.get()), "clutch_curve": self.cl_curve.get(), "clutch_drag_nm": float(self.cl_drag.get()), "shift_time_s": float(self.sh_time.get()), "sync_rpm_band": float(self.sync_rb.get()), "primary_ratio": float(self.primary.get()), "front_sprocket_teeth": int(self.zf.get()), "rear_sprocket_teeth": int(self.zr.get()), "wheel_radius_m": float(self.rwheel.get()), "drivetrain_efficiency": float(self.eta.get()), "rpm_couple_gain": float(self.couple.get()), "gear_ratios": [ 0.0, float(self.g1.get()), float(self.g2.get()), float(self.g3.get()), float(self.g4.get()), float(self.g5.get()), float(self.g6.get()) ], "rolling_c": float(self.c_rr.get()), "air_density": float(self.rho.get()), "aero_cd": float(self.cd.get()), "frontal_area_m2": float(self.A.get()), "tire_mu_peak": float(self.mu_p.get()), "tire_mu_slide": float(self.mu_s.get()), "rear_static_weight_frac": float(self.w_rear.get()), }} self.sim.load_config(cfg) def save_into_config(self, out: Dict[str, Any]) -> None: out.setdefault("gearbox", {}).update({ "clutch_max_torque_nm": float(self.cl_Tmax.get()), "clutch_aggressiveness": float(self.cl_agr.get()), "clutch_curve": self.cl_curve.get(), "clutch_drag_nm": float(self.cl_drag.get()), "shift_time_s": float(self.sh_time.get()), "sync_rpm_band": float(self.sync_rb.get()), "primary_ratio": float(self.primary.get()), "front_sprocket_teeth": int(self.zf.get()), "rear_sprocket_teeth": int(self.zr.get()), "wheel_radius_m": float(self.rwheel.get()), "drivetrain_efficiency": float(self.eta.get()), "rpm_couple_gain": float(self.couple.get()), "gear_ratios": [0.0, float(self.g1.get()), float(self.g2.get()), float(self.g3.get()), float(self.g4.get()), float(self.g5.get()), float(self.g6.get())], "rolling_c": float(self.c_rr.get()), "air_density": float(self.rho.get()), "aero_cd": float(self.cd.get()), "frontal_area_m2": float(self.A.get()), "tire_mu_peak": float(self.mu_p.get()), "tire_mu_slide": float(self.mu_s.get()), "rear_static_weight_frac": float(self.w_rear.get()), }) def load_from_config(self, cfg: Dict[str, Any]) -> None: g = dict(GEARBOX_DEFAULTS); g.update(cfg.get("gearbox", {})) self.cl_Tmax.set(g["clutch_max_torque_nm"]) self.cl_agr.set(g["clutch_aggressiveness"]) self.cl_curve.set(g.get("clutch_curve","linear")) self.cl_drag.set(g["clutch_drag_nm"]) self.sh_time.set(g["shift_time_s"]) self.sync_rb.set(g["sync_rpm_band"]) self.primary.set(g["primary_ratio"]) self.zf.set(g["front_sprocket_teeth"]) self.zr.set(g["rear_sprocket_teeth"]) self.rwheel.set(g["wheel_radius_m"]) self.eta.set(g["drivetrain_efficiency"]) self.couple.set(g["rpm_couple_gain"]) ratios = list(g["gear_ratios"]) + [0.0]*7 self.g1.set(ratios[1]); self.g2.set(ratios[2]); self.g3.set(ratios[3]) self.g4.set(ratios[4]); self.g5.set(ratios[5]); self.g6.set(ratios[6]) self.c_rr.set(g["rolling_c"]) self.rho.set(g["air_density"]) self.cd.set(g["aero_cd"]) self.A.set(g["frontal_area_m2"]) self.mu_p.set(g["tire_mu_peak"]) self.mu_s.set(g["tire_mu_slide"]) self.w_rear.set(g["rear_static_weight_frac"])