From 4c4230e4cc8c2f5d8d091b73147a64eb9971c7a9 Mon Sep 17 00:00:00 2001 From: Pedro Jose Romero Gombau Date: Mon, 27 Jan 2025 16:27:19 +0100 Subject: [PATCH] Electric Update --- .drone.yml | 2 +- README.md | 2 +- VERSION | 2 +- src/LaunchSim.py | 10 +- src/tabs/tab_coil.py | 2 +- src/tabs/tab_drag.py | 144 ++++++++++++----- src/tabs/tab_dynamic.py | 307 +++++++++++++++++++++++++++++++++++++ src/tabs/tab_electrical.py | 155 ------------------- 8 files changed, 424 insertions(+), 200 deletions(-) delete mode 100644 src/tabs/tab_electrical.py diff --git a/.drone.yml b/.drone.yml index 7b1a5d0..e2dde4d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,7 +7,7 @@ steps: image: python:3.9 commands: - python -m venv venv - - ./venv/Scripts/activate + - source venv/bin/activate - pip install -r requirements.txt - python -m nuitka --standalone --onefile --enable-plugin=tk-inter src/LaunchSim.py diff --git a/README.md b/README.md index 28383b2..31ea4cd 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ LaunchSim es una aplicación diseñada para calcular trayectorias de proyectiles Si deseas distribuir la aplicación como un ejecutable independiente: - En Windows: ```bash - python -m nuitka --standalone --windows-disable-console=disable --enable-plugin=tk-inter --windows-icon-from-ico=src/static/icon.ico src/LaunchSim.py + python -m nuitka --standalone --windows-disable-console --enable-plugin=tk-inter --windows-icon-from-ico=src/static/icon.ico src/LaunchSim.py ``` - En Linux/Mac: ```bash diff --git a/VERSION b/VERSION index b123147..a58941b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1 \ No newline at end of file +1.3 \ No newline at end of file diff --git a/src/LaunchSim.py b/src/LaunchSim.py index 1196b6b..5883f26 100644 --- a/src/LaunchSim.py +++ b/src/LaunchSim.py @@ -12,7 +12,7 @@ from tabs.tab_simulator import TabSimulator from tabs.tab_search import TabSearch from tabs.tab_drag import TabDrag from tabs.tab_coil import TabCoil -from tabs.tab_electrical import TabElectrical +from tabs.tab_dynamic import TabDynamic class MainApp: def __init__(self, master): @@ -44,10 +44,10 @@ class MainApp: # Pestaña 4: Bobinas self.tab_coil = TabCoil(self.notebook, self.tab_sim) self.notebook.add(self.tab_coil.frame, text="Bobinas") - - # Pestaña 5: Eléctrico - self.tab_elec = TabElectrical(self.notebook, self.tab_sim, self.tab_coil) - self.notebook.add(self.tab_elec.frame, text="Eléctrico") + + # Pestaña 5: Cálculos dinámicos + tab_dyn = TabDynamic(self.notebook, self.tab_coil, self.tab_sim, self.tab_drag) + self.notebook.add(tab_dyn.frame, text="Cálculos Dinámicos") if __name__ == "__main__": root = tk.Tk() diff --git a/src/tabs/tab_coil.py b/src/tabs/tab_coil.py index 36fa397..11b2561 100644 --- a/src/tabs/tab_coil.py +++ b/src/tabs/tab_coil.py @@ -129,7 +129,7 @@ def inductance_from_spiralist(espiras, wire_radius): M_ij = mutual_inductance_coaxial(r_i, r_j, dist_z) L_total += 2.0 * M_ij - return L_total + return abs(L_total) # ------------------------------------------------------------------- # Importamos la función para dibujar la bobina 2D (tal y como estaba). diff --git a/src/tabs/tab_drag.py b/src/tabs/tab_drag.py index ca99062..2c0abd7 100644 --- a/src/tabs/tab_drag.py +++ b/src/tabs/tab_drag.py @@ -12,8 +12,8 @@ import numpy as np class TabDrag: def __init__(self, notebook, tab_simulator): """ - Pestaña para calcular un coef. 'b' a partir de la geometría. - Luego se pasa a tab_simulator.set_b_value(...) para usarlo en la simulación. + Pestaña para calcular un coef. 'b' a partir de la geometría + y exponer métodos para la reluctancia (l_fe y S_disp). """ self.notebook = notebook self.tab_simulator = tab_simulator @@ -88,13 +88,13 @@ class TabDrag: def update_param_labels(self): geom = self.geometria_var.get() self.entry_param2.config(state="normal", fg="black") - if geom=="Prisma cuadrado": + if geom == "Prisma cuadrado": self.label_param1.config(text="Lado base (m):") self.label_param2.config(text="Longitud (m):") - elif geom=="Cilindro": + elif geom == "Cilindro": self.label_param1.config(text="Radio (m):") self.label_param2.config(text="Altura (m):") - elif geom=="Esfera": + elif geom == "Esfera": self.label_param1.config(text="Radio (m):") self.label_param2.config(text="(no aplica):") self.entry_param2.delete(0, tk.END) @@ -109,50 +109,48 @@ class TabDrag: try: p1 = float(self.entry_param1.get()) except ValueError: - p1=1.0 + p1 = 1.0 try: - if self.entry_param2.cget("state")!="disabled": + if self.entry_param2.cget("state") != "disabled": p2 = float(self.entry_param2.get()) else: - p2=1.0 + p2 = 1.0 except ValueError: - p2=1.0 + p2 = 1.0 self.ax.clear() self.ax.set_axis_off() - if geom=="Prisma cuadrado": - Xs=[0,p1,p1,0,0,p1,p1,0] - Ys=[0,0,p1,p1,0,0,p1,p1] - Zs=[0,0,0,0,p2,p2,p2,p2] - edges=[(0,1),(1,2),(2,3),(3,0), - (4,5),(5,6),(6,7),(7,4), - (0,4),(1,5),(2,6),(3,7)] + if geom == "Prisma cuadrado": + Xs = [0,p1,p1,0,0,p1,p1,0] + Ys = [0,0,p1,p1,0,0,p1,p1] + Zs = [0,0,0,0,p2,p2,p2,p2] + edges = [(0,1),(1,2),(2,3),(3,0), + (4,5),(5,6),(6,7),(7,4), + (0,4),(1,5),(2,6),(3,7)] for (i,j) in edges: self.ax.plot([Xs[i],Xs[j]], [Ys[i],Ys[j]], [Zs[i],Zs[j]], color='orange') self.ax.set_box_aspect((p1,p1,p2)) - elif geom=="Cilindro": + elif geom == "Cilindro": import numpy as np - import math - r=p1 - h=p2 - theta=np.linspace(0,2*math.pi,30) - z=np.linspace(0,h,30) - T,Z=np.meshgrid(theta,z) - X=r*np.cos(T) - Y=r*np.sin(T) + r = p1 + h = p2 + theta = np.linspace(0,2*math.pi,30) + z = np.linspace(0,h,30) + T,Z = np.meshgrid(theta,z) + X = r*np.cos(T) + Y = r*np.sin(T) self.ax.plot_surface(X, Y, Z, color='orange', alpha=0.8) self.ax.set_box_aspect((2*r,2*r,h)) - elif geom=="Esfera": + elif geom == "Esfera": import numpy as np - import math - r=p1 - phi=np.linspace(0,math.pi,30) - t=np.linspace(0,2*math.pi,30) - phi_grid,t_grid=np.meshgrid(phi,t) - X=r*np.sin(phi_grid)*np.cos(t_grid) - Y=r*np.sin(phi_grid)*np.sin(t_grid) - Z=r*np.cos(phi_grid) + r = p1 + phi = np.linspace(0,math.pi,30) + t = np.linspace(0,2*math.pi,30) + phi_grid,t_grid = np.meshgrid(phi,t) + X = r*np.sin(phi_grid)*np.cos(t_grid) + Y = r*np.sin(phi_grid)*np.sin(t_grid) + Z = r*np.cos(phi_grid) self.ax.plot_surface(X,Y,Z,color='orange',alpha=0.8) self.ax.set_box_aspect((2*r,2*r,2*r)) else: @@ -162,6 +160,9 @@ class TabDrag: self.canvas_3d.draw() def calcular_coef_rozamiento(self): + """ + Calcula un coeficiente b ~ 0.5*rho*Cd*A + """ geom = self.geometria_var.get() try: p1 = float(self.entry_param1.get()) @@ -186,11 +187,9 @@ class TabDrag: self.label_result_b.config(text="Error param2", fg="red") return Cd=0.82 - import math A=math.pi*(p1**2) elif geom=="Esfera": Cd=0.47 - import math A=math.pi*(p1**2) rho=1.225 @@ -198,3 +197,76 @@ class TabDrag: self.label_result_b.config(text=f"Coef (b) ~ {b_calc:.4f}", fg="blue") self.tab_simulator.set_b_value(b_calc) + + def get_l_fe(self): + """ + Devuelve la 'altura' l_fe (m) según la geometría: + - Prisma cuadrado: param2 (Longitud) + - Cilindro: param2 (Altura) + - Esfera: 2*radio (diámetro) + """ + geom = self.geometria_var.get() + + try: + p1 = float(self.entry_param1.get()) + except ValueError: + p1 = 1.0 + + l_fe_val = 1.0 + + if geom == "Prisma cuadrado": + try: + l_fe_val = float(self.entry_param2.get()) + except ValueError: + l_fe_val = 1.0 + + elif geom == "Cilindro": + try: + l_fe_val = float(self.entry_param2.get()) + except ValueError: + l_fe_val = 1.0 + + elif geom == "Esfera": + # Usamos 2*r (diámetro). Ajusta si prefieres 0 + l_fe_val = 2.0 * p1 + + return l_fe_val + + def get_projectile_section_for_reluctance(self, tab_coil): + """ + Devuelve S_disp (m^2) para la fórmula de reluctancia: + - Si la geometría es "Esfera", => S_disp = 2 * S_bob + con S_bob = pi * (r_ext^2) + (r_ext lo lees de tab_coil en mm, conv a m) + - En otros casos => área frontal real (p1^2 si prisma, pi*r^2 si cilindro). + """ + geom = self.geometria_var.get() + + # 1) Leer r_ext de tab_coil (en mm) => m + try: + r_ext_mm = float(tab_coil.var_r_ext.get()) + except: + r_ext_mm = 10.0 + r_ext_m = r_ext_mm / 1000.0 + + # S_bob = pi*(r_ext_m^2) + s_bob = math.pi*(r_ext_m**2) + + # 2) Leer p1 + try: + p1 = float(self.entry_param1.get()) + except ValueError: + p1 = 1.0 + + if geom == "Esfera": + # S_disp = 2*S_bob + return 2.0 * s_bob + elif geom == "Prisma cuadrado": + # Sección frontal ~ p1 x p1 + return (p1**2) + elif geom == "Cilindro": + # Sección frontal ~ pi*(radio^2) = pi*(p1^2) + return math.pi*(p1**2) + else: + # Por defecto, algo por si la geo no es conocida + return 1.0 diff --git a/src/tabs/tab_dynamic.py b/src/tabs/tab_dynamic.py index e69de29..84ce8f9 100644 --- a/src/tabs/tab_dynamic.py +++ b/src/tabs/tab_dynamic.py @@ -0,0 +1,307 @@ +# src/tab_dynamic.py + +import tkinter as tk +from tkinter import ttk +from tkinter.scrolledtext import ScrolledText +import math +import numpy as np + +MU0 = 4.0e-7 * math.pi + +class TabDynamic: + def __init__(self, notebook, tab_coil=None, tab_simulator=None, tab_drag=None): + """ + Pestaña que: + - Obtiene el trabajo W desde tab_simulator. + - Calcula la reluctancia media con la integración en x in [0, h_c]. + - Usa el modelo W = ((N I)^2 * h_c)/(4 mu0 S_fe (sumR_avg)^2) para hallar I. + - Ofrece dimensionamiento de fuente (condensador / DC). + """ + self.notebook = notebook + self.tab_coil = tab_coil + self.tab_simulator = tab_simulator + self.tab_drag = tab_drag + + self.frame = tk.Frame(notebook) + self.frame.pack(fill="both", expand=True) + + # Panel Izq: Botones / parámetros + left_frame = tk.Frame(self.frame) + left_frame.pack(side="left", fill="y", padx=5, pady=5) + + # Panel Der: Logs + right_frame = tk.Frame(self.frame, bd=2, relief="groove") + right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5) + + # Sección 1: Importar Config + import_frame = tk.LabelFrame(left_frame, text="1) Importar Config") + import_frame.pack(fill="x", pady=5) + + btn_import = tk.Button(import_frame, text="Importar Config", command=self.importar_config) + btn_import.pack(pady=5) + + self.var_W_mech = tk.StringVar(value="") + tk.Label(import_frame, text="W (J):").pack(anchor="w") + tk.Entry(import_frame, textvariable=self.var_W_mech, width=12, state="readonly").pack(pady=2) + + self.W_mech = 0.0 # energía mecánica importada + + # Sección 2: Calcular Reluctancia Media + rel_frame = tk.LabelFrame(left_frame, text="2) Reluctancia Media") + rel_frame.pack(fill="x", pady=5) + + self.var_sumR_avg = tk.StringVar(value="") + tk.Button(rel_frame, text="Calcular Rel. Media", command=self.calcular_reluctancia_media).pack(pady=5) + tk.Label(rel_frame, text="sum(R)_avg:").pack(anchor="w") + tk.Entry(rel_frame, textvariable=self.var_sumR_avg, width=15, state="readonly").pack(pady=2) + + self.sumR_avg = 0.0 # valor en el interior + + # Sección 3: Calcular Corriente + work_frame = tk.LabelFrame(left_frame, text="3) Corriente Necesaria") + work_frame.pack(fill="x", pady=5) + + self.var_S_fe = tk.DoubleVar(value=1e-4) # Sección en la zona férrea + tk.Label(work_frame, text="S_fe (m²):").grid(row=0, column=0, sticky="w", padx=5, pady=2) + tk.Entry(work_frame, textvariable=self.var_S_fe, width=10).grid(row=0, column=1, padx=5, pady=2) + + tk.Button(work_frame, text="Calcular Corriente", command=self.calcular_corriente_trabajo).grid(row=1, column=0, columnspan=2, pady=5) + + self.var_I_req = tk.StringVar(value="") + tk.Label(work_frame, text="I_req (A):").grid(row=2, column=0, sticky="w", padx=5, pady=2) + tk.Entry(work_frame, textvariable=self.var_I_req, width=10, state="readonly").grid(row=2, column=1, padx=5, pady=2) + + # Sección 4: Dimensionar Fuente + fuente_frame = tk.LabelFrame(left_frame, text="4) Dimensionar Fuente") + fuente_frame.pack(fill="x", pady=5) + + tk.Button(fuente_frame, text="Dimensionar Capacitor", command=self.dimensionar_condensador).pack(pady=5) + tk.Button(fuente_frame, text="Fuente DC minima", command=self.dimensionar_fuente_dc).pack(pady=5) + + self.var_C_sug = tk.StringVar(value="") + self.var_V_sug = tk.StringVar(value="") + self.var_Vdc_min = tk.StringVar(value="") + + tk.Label(fuente_frame, text="C (F) ~").pack(anchor="w") + tk.Entry(fuente_frame, textvariable=self.var_C_sug, width=15, state="readonly").pack(pady=2) + + tk.Label(fuente_frame, text="Vcap (V) ~").pack(anchor="w") + tk.Entry(fuente_frame, textvariable=self.var_V_sug, width=15, state="readonly").pack(pady=2) + + tk.Label(fuente_frame, text="V_DC min (V) ~").pack(anchor="w") + tk.Entry(fuente_frame, textvariable=self.var_Vdc_min, width=15, state="readonly").pack(pady=2) + + # LOGS + tk.Label(right_frame, text="Logs:").pack(anchor="w") + self.log_area = ScrolledText(right_frame, wrap="word", height=25, width=50) + self.log_area.pack(fill="both", expand=True) + self.log_area.configure(state="disabled") + self.log_area.tag_config("info", foreground="green") + self.log_area.tag_config("error", foreground="red") + + # -------------------------------------------------------- + # Log helper + # -------------------------------------------------------- + def log_message(self, msg, mode="info"): + self.log_area.configure(state="normal") + self.log_area.insert("end", msg + "\n", mode) + self.log_area.see("end") + self.log_area.configure(state="disabled") + + # -------------------------------------------------------- + # 1) Importar Config (energía W) + # -------------------------------------------------------- + def importar_config(self): + """ + Lee la energía mecánica W de tab_simulator y la guarda en self.W_mech + """ + try: + if not self.tab_simulator: + raise ValueError("No hay tab_simulator para leer W.") + w_val = self.tab_simulator.get_energy_required() + if w_val<=0: + raise ValueError("Energía <= 0.") + self.W_mech = w_val + self.var_W_mech.set(f"{w_val:.3f}") + self.log_message(f"Importado W={w_val:.3f} J", "info") + + except Exception as e: + self.log_message(f"Error importar_config: {e}", "error") + + # -------------------------------------------------------- + # 2) Calcular Reluctancia Media (sum(R)_avg) + # => integrando la ecuación sum_R(x)= ... + # -------------------------------------------------------- + def calcular_reluctancia_media(self): + try: + if not self.tab_coil: + raise ValueError("Falta referencia a tab_coil.") + if not self.tab_drag: + raise ValueError("Falta referencia a tab_drag.") + + # 1) Leer h_c, N, r_int (en mm->m) + h_c_m = float(self.tab_coil.var_h_c.get())/1000.0 + if h_c_m<=0: + raise ValueError("h_c_m <=0.") + + r_int_m = float(self.tab_coil.var_r_int.get())/1000.0 + if r_int_m<=0: + raise ValueError("r_int_m <=0.") + + # 2) l_fe => de tab_drag + l_fe_m = self.tab_drag.get_l_fe() # en metros + if l_fe_m<0: + raise ValueError("l_fe<0?") + + # 3) s_disp => get_projectile_section_for_reluctance + s_disp = self.tab_drag.get_projectile_section_for_reluctance(self.tab_coil) + if s_disp<=0: + raise ValueError("s_disp <=0.") + + # 4) s_c= pi*r_int^2 + s_c = math.pi*(r_int_m**2) + if s_c<=0: + raise ValueError("s_c<=0.") + + # 5) Definir sumR(x) + def sumR_of_x(x): + # sum_R = h_c/(mu0*s_disp) + # + (h_c + l_fe - x)/(mu0*s_disp) + # + (h_c - x)/(mu0*s_c) + return ( + (h_c_m/(MU0*s_disp)) + + ((h_c_m + l_fe_m - x)/(MU0*s_disp)) + + ((h_c_m - x)/(MU0*s_c)) + ) + + # 6) Integración en [0..h_c_m] + n_steps=200 + dx = h_c_m/n_steps + area_sum=0.0 + x_curr=0.0 + for _ in range(n_steps): + x_mid= x_curr+0.5*dx + val_mid= sumR_of_x(x_mid) + area_sum+= val_mid + x_curr+=dx + area_sum*= dx + + sumR_avg= area_sum/h_c_m + if sumR_avg<=1e-30: + raise ValueError("sumR_avg nulo o negativo.") + + self.sumR_avg= sumR_avg + self.var_sumR_avg.set(f"{sumR_avg:.3e}") + self.log_message(f"Reluctancia media= {sumR_avg:.3e} H^-1", "info") + + except ValueError as ve: + self.sumR_avg=0.0 + self.var_sumR_avg.set("") + self.log_message(f"Error calc Reluctancia media: {ve}", "error") + except Exception as ex: + self.sumR_avg=0.0 + self.var_sumR_avg.set("") + self.log_message(f"Excepción calc Reluct. media: {ex}", "error") + + # -------------------------------------------------------- + # 3) Calcular Corriente, con W = ((N I)^2 * h_c)/(4 mu0 S_fe (sumR_avg)^2) + # -------------------------------------------------------- + def calcular_corriente_trabajo(self): + try: + if self.W_mech<=0: + raise ValueError("No hay W_mech positivo. Importa config primero.") + if self.sumR_avg<=0: + raise ValueError("sumR_avg<=0. Calcula la Reluctancia media antes.") + + # 1) Leer h_c (mm->m), N, etc. de tab_coil + h_c_m = float(self.tab_coil.var_h_c.get())/1000.0 + N_val = float(self.tab_coil.var_N.get()) + if h_c_m<=0 or N_val<=0: + raise ValueError("Parámetros de bobina inválidos.") + + # 2) Leer S_fe + S_fe_val= self.var_S_fe.get() + if S_fe_val<=0: + raise ValueError("S_fe <=0") + + # 3) Tomamos sumR_avg, W + sumR_avg= self.sumR_avg + W= self.W_mech + + # 4) W= ((N I)^2 * h_c)/(4 mu0 S_fe (sumR_avg)^2) + # => (N I)^2= [4 mu0 S_fe (sumR_avg)^2 W]/h_c + top= 4.0*MU0*S_fe_val*(sumR_avg**2)* W + bottom= h_c_m + if bottom<=1e-30: + raise ValueError("h_c_m=0?") + + NI_sq= top/bottom + if NI_sq<=0: + raise ValueError("Resultado (N I)^2 <=0, no válido.") + + NI= math.sqrt(NI_sq) + I_req= NI/ N_val + + self.var_I_req.set(f"{I_req:.3f}") + self.log_message(f"I necesaria= {I_req:.3f} A", "info") + + except ValueError as ve: + self.var_I_req.set("") + self.log_message(f"Error calc I trabajo: {ve}", "error") + except Exception as ex: + self.var_I_req.set("") + self.log_message(f"Excepción calc I trabajo: {ex}", "error") + + # -------------------------------------------------------- + # 4) Dimensionar la fuente (ejemplo condensador y DC) + # -------------------------------------------------------- + def dimensionar_condensador(self): + """ + Ejemplo: suponer que la energía total W -> (1/2)C V^2 + W_marg = W_mech * factor... etc. + """ + try: + W= self.W_mech + if W<=0: + raise ValueError("Primero importa config. W<=0?") + + # suponer un factor "pérdidas" + eff=0.7 + # E capacitor= W/ eff + E_cap= W/eff + # asumes C= 1mF => V= sqrt(2 E_cap/C) + # o V= 200 => C= 2E_cap/V^2, etc. + # Aquí arbitrariamente p.ej C=1e-3 y calculamos V + C_val= 1e-3 + Vcalc= math.sqrt(2.0*E_cap/C_val) + + self.var_C_sug.set(f"{C_val:.3g}") + self.var_V_sug.set(f"{Vcalc:.1f}") + self.log_message(f"Caps: C=1mF => V~{Vcalc:.1f} V (descarga)", "info") + + except ValueError as ve: + self.log_message(f"Error dimensionar_condensador: {ve}", "error") + except Exception as ex: + self.log_message(f"Excepción dimensionar_condensador: {ex}", "error") + + def dimensionar_fuente_dc(self): + """ + Ejemplo simple: V_dc >= R_coil * I_req + R_coil en ohms => la obtendrías o la metes manual. + """ + try: + Ireq_str= self.var_I_req.get() + Ireq= float(Ireq_str) + if Ireq<=0: + raise ValueError("I_req <=0? Calcula la corriente primero.") + + # supongamos R_coil= 2 ohms (ejemplo) + R_coil= 2.0 + V_dc= R_coil* Ireq + self.var_Vdc_min.set(f"{V_dc:.1f}") + self.log_message(f"Fuente DC~ {V_dc:.1f} V (min. ohmico)", "info") + + except ValueError as ve: + self.log_message(f"Error dimensionar_fuente_dc: {ve}", "error") + except Exception as ex: + self.log_message(f"Excepción dimensionar_fuente_dc: {ex}", "error") diff --git a/src/tabs/tab_electrical.py b/src/tabs/tab_electrical.py deleted file mode 100644 index f30cee0..0000000 --- a/src/tabs/tab_electrical.py +++ /dev/null @@ -1,155 +0,0 @@ -import tkinter as tk -from tkinter import ttk -import math - -import matplotlib -matplotlib.use("TkAgg") -import matplotlib.pyplot as plt -from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg - -import numpy as np - -class TabElectrical: - def __init__(self, notebook, tab_simulator, tab_coil): - """ - Pestaña para estimar la potencia y corriente necesarias - para suministrar la energía 'E_mec' en diferentes tiempos, - con un voltaje fijo V. - - 'tab_simulator': donde se calcula la energía mecánica requerida. - - 'tab_coil': por si quieres leer datos de la bobina (en este ejemplo, - no lo usamos ya, pero lo mantenemos para coherencia). - """ - self.notebook = notebook - self.tab_sim = tab_simulator - self.tab_coil = tab_coil - - # Creamos el frame principal de la pestaña - self.frame = tk.Frame(notebook) - self.frame.pack(fill="both", expand=True) - # Nota: si en main.py ya se hace 'notebook.add(self.frame, text="Eléctrico")', - # no repitas esa línea aquí. - - # Layout principal (izquierda: controles, derecha: gráfica) - frame_left = tk.Frame(self.frame) - frame_left.pack(side="left", fill="y", padx=5, pady=5) - - frame_right = tk.Frame(self.frame, bd=2, relief="groove") - frame_right.pack(side="right", fill="both", expand=True, padx=5, pady=5) - - # - # Sección Izquierda - # - tk.Label(frame_left, text="Simulación Eléctrica (Potencia & Corriente)").pack(anchor="w", pady=3) - - btn_import = tk.Button( - frame_left, text="Importar Config", - command=self.import_config - ) - btn_import.pack(pady=5, anchor="w") - - # Variables - self.var_emech = tk.DoubleVar(value=0.0) # Energía mecánica importada - self.var_V = tk.DoubleVar(value=12.0) # Voltaje (valor por defecto 12 V) - self.var_tmax = tk.DoubleVar(value=2.0) # Tiempo máximo de estudio (s) - - # Muestra la energía mecánica importada - tk.Label(frame_left, text="E_mec (J):").pack(anchor="w") - self.lbl_emech = tk.Label(frame_left, text="N/A") - self.lbl_emech.pack(anchor="w", pady=2) - - # Frame para pedir V y T_max - param_frame = tk.Frame(frame_left, bd=1, relief="sunken") - param_frame.pack(fill="x", pady=10) - - tk.Label(param_frame, text="Voltaje (V):").grid(row=0, column=0, sticky="w", padx=4, pady=2) - self.entry_V = tk.Entry(param_frame, width=8, textvariable=self.var_V) - self.entry_V.grid(row=0, column=1, padx=5, pady=2) - - tk.Label(param_frame, text="T_max (s):").grid(row=1, column=0, sticky="w", padx=4, pady=2) - self.entry_Tmax = tk.Entry(param_frame, width=8, textvariable=self.var_tmax) - self.entry_Tmax.grid(row=1, column=1, padx=5, pady=2) - - # Botón de simulación - btn_simular = tk.Button(param_frame, text="Simular", command=self.simular) - btn_simular.grid(row=2, column=0, columnspan=2, pady=5) - - # - # Sección Derecha: gráficas - # - self.fig = plt.Figure(figsize=(5, 3), dpi=100) - - # Subplot 1: Potencia vs tiempo - self.ax_p = self.fig.add_subplot(211) - # Subplot 2: Corriente vs tiempo - self.ax_i = self.fig.add_subplot(212) - - self.canvas = FigureCanvasTkAgg(self.fig, master=frame_right) - self.canvas_widget = self.canvas.get_tk_widget() - self.canvas_widget.pack(fill="both", expand=True) - - def import_config(self): - """ - Importa la energía mecánica desde la pestaña de simulación. - Ajusta la variable self.var_emech. - """ - # Supongamos que TabSimulator tiene un método 'get_energy_required()' - # que devuelve la energía calculada. - try: - E_mech_val = self.tab_sim.get_energy_required() - except: - E_mech_val = 0.0 - - self.var_emech.set(E_mech_val) - self.lbl_emech.config(text=f"{E_mech_val:.3f} J") - - def simular(self): - """ - Barrido de tiempos (0..T_max): - P(t) = E / t - I(t) = P(t) / V - Se grafican P(t) e I(t). - """ - E = self.var_emech.get() # Joules - V = self.var_V.get() # Voltios - T_max = self.var_tmax.get() # s - - # Evitar tiempos <= 0 - if T_max <= 0.0: - T_max = 2.0 - self.var_tmax.set(T_max) - - # Creamos un vector de tiempos discretos, evitando t=0 - # por ejemplo de 0.01 a T_max en 50 pasos - t_vals = np.linspace(0.01, T_max, 50) - p_vals = [] # Potencia en cada t - i_vals = [] # Corriente en cada t - - for t in t_vals: - # P(t) = E / t - P_t = 0.0 - I_t = 0.0 - if t > 0: - P_t = E / t - # I(t) = P(t)/V = E/(V*t) - if V > 1e-12: - I_t = P_t / V - - p_vals.append(P_t) - i_vals.append(I_t) - - # Graficamos - self.ax_p.clear() - self.ax_p.plot(t_vals, p_vals, label="P(t) = E / t", color="blue") - self.ax_p.set_xlabel("Tiempo (s)") - self.ax_p.set_ylabel("Potencia (W)") - self.ax_p.grid(True) - self.ax_p.legend() - - self.ax_i.clear() - self.ax_i.plot(t_vals, i_vals, label="I(t) = P(t)/V", color="red") - self.ax_i.set_xlabel("Tiempo (s)") - self.ax_i.set_ylabel("Corriente (A)") - self.ax_i.grid(True) - self.ax_i.legend() - - self.canvas.draw()