From 93c9e7b91be86ce80499e9c0071edb5870a83ff0 Mon Sep 17 00:00:00 2001 From: Pedro Jose Romero Gombau Date: Thu, 16 Jan 2025 16:28:45 +0100 Subject: [PATCH] Init --- Dockerfile | 13 + __pycache__/geometry_viewer.cpython-311.pyc | Bin 0 -> 4602 bytes __pycache__/tab_drag.cpython-311.pyc | Bin 0 -> 12406 bytes __pycache__/tab_search.cpython-311.pyc | Bin 0 -> 10364 bytes __pycache__/tab_simulator.cpython-311.pyc | Bin 0 -> 25328 bytes geometry_viewer.py | 71 +++ launcher.bat | 1 + main.py | 32 ++ requirements.txt | 1 + tab_drag.py | 200 +++++++++ tab_search.py | 219 +++++++++ tab_simulator.py | 471 ++++++++++++++++++++ 12 files changed, 1008 insertions(+) create mode 100644 Dockerfile create mode 100644 __pycache__/geometry_viewer.cpython-311.pyc create mode 100644 __pycache__/tab_drag.cpython-311.pyc create mode 100644 __pycache__/tab_search.cpython-311.pyc create mode 100644 __pycache__/tab_simulator.cpython-311.pyc create mode 100644 geometry_viewer.py create mode 100644 launcher.bat create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 tab_drag.py create mode 100644 tab_search.py create mode 100644 tab_simulator.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bec5f46 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.9-slim + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && \ + apt-get install -y python3-tk python3-pil python3-pil.imagetk && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY . /app + +RUN pip install --no-cache-dir -r requirements.txt + +CMD ["python", "main.py"] \ No newline at end of file diff --git a/__pycache__/geometry_viewer.cpython-311.pyc b/__pycache__/geometry_viewer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d87062024652434287929ad9457e6a6a87bdaba GIT binary patch literal 4602 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 literal 0 HcmV?d00001 diff --git a/__pycache__/tab_search.cpython-311.pyc b/__pycache__/tab_search.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70765db775f80947705822f35de1528dc5f1dffa GIT binary patch literal 10364 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 ^u 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 W+nGEw^)~nIw`$(+o>GMM;Qs(ErUa(| literal 0 HcmV?d00001 diff --git a/geometry_viewer.py b/geometry_viewer.py new file mode 100644 index 0000000..336ddfb --- /dev/null +++ b/geometry_viewer.py @@ -0,0 +1,71 @@ +import matplotlib +matplotlib.use("TkAgg") # Para asegurarnos de usar Tkinter como backend +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D # necesario para 3D +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import numpy as np + +def plot_geometry_in_frame(parent_frame, geom, p1, p2): + """ + Dibuja la geometría (Prisma/Cilindro/Esfera) en un Axes3D dentro + de 'parent_frame' (un Frame de Tkinter). No abre ventana nueva. + (Ejemplo de integración en tu TabDrag) + """ + + fig = plt.Figure(figsize=(4, 3), dpi=100) + ax = fig.add_subplot(111, projection='3d') + ax.set_title(f"{geom}", fontsize=10) + + if geom == "Prisma cuadrado": + lado = p1 + largo = p2 + Xs = [0, lado, lado, 0, 0, lado, lado, 0] + Ys = [0, 0, lado, lado, 0, 0, lado, lado] + Zs = [0, 0, 0, 0, largo, largo, largo, largo] + 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: + ax.plot([Xs[i],Xs[j]], [Ys[i],Ys[j]], [Zs[i],Zs[j]], color='g') + ax.set_xlim(0, max(lado,1)) + ax.set_ylim(0, max(lado,1)) + ax.set_zlim(0, max(largo,1)) + + elif geom == "Cilindro": + r = p1 + h = p2 + theta = np.linspace(0, 2*np.pi, 30) + z = np.linspace(0, h, 30) + theta_grid, z_grid = np.meshgrid(theta, z) + X = r * np.cos(theta_grid) + Y = r * np.sin(theta_grid) + Z = z_grid + ax.plot_surface(X, Y, Z, color='cyan', alpha=0.5) + ax.set_xlim(-r, r) + ax.set_ylim(-r, r) + ax.set_zlim(0, h) + + elif geom == "Esfera": + r = p1 + phi = np.linspace(0, np.pi, 30) + theta = np.linspace(0, 2*np.pi, 30) + phi_grid, theta_grid = np.meshgrid(phi, theta) + X = r*np.sin(phi_grid)*np.cos(theta_grid) + Y = r*np.sin(phi_grid)*np.sin(theta_grid) + Z = r*np.cos(phi_grid) + ax.plot_surface(X, Y, Z, color='yellow', alpha=0.6) + ax.set_xlim(-r, r) + ax.set_ylim(-r, r) + ax.set_zlim(-r, r) + + else: + ax.text2D(0.2, 0.5, "Geometría desconocida", transform=ax.transAxes) + + # Borramos lo anterior en parent_frame y embebemos el nuevo canvas + for child in parent_frame.winfo_children(): + child.destroy() + + canvas = FigureCanvasTkAgg(fig, master=parent_frame) + canvas_widget = canvas.get_tk_widget() + canvas_widget.pack(fill="both", expand=True) + canvas.draw() diff --git a/launcher.bat b/launcher.bat new file mode 100644 index 0000000..7c85d2b --- /dev/null +++ b/launcher.bat @@ -0,0 +1 @@ +docker run -it --rm -e DISPLAY=host.docker.internal:0 launchsim \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..241ab4a --- /dev/null +++ b/main.py @@ -0,0 +1,32 @@ +import tkinter as tk +from tkinter import ttk + +from tab_simulator import TabSimulator +from tab_drag import TabDrag +from tab_search import TabSearch + +class MainApp: + def __init__(self, master): + self.master = master + self.master.title("Obtención de trayectoria y energía") + + self.notebook = ttk.Notebook(master) + self.notebook.pack(fill="both", expand=True) + + # Pestaña 1: Simulador Trayectoria + self.tab_sim = TabSimulator(self.notebook) + self.notebook.add(self.tab_sim.frame, text="Simulador") + + # Pestaña 2: Cálculo Coef. Rozamiento + self.tab_drag = TabDrag(self.notebook, self.tab_sim) + self.notebook.add(self.tab_drag.frame, text="Rozamiento") + + # Pestaña 3: Búsqueda (ángulo que minimiza la velocidad) + self.tab_search = TabSearch(self.notebook, self.tab_sim) + self.notebook.add(self.tab_search.frame, text="Optimización") + + +if __name__ == "__main__": + root = tk.Tk() + app = MainApp(root) + root.mainloop() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4b43f7e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +matplotlib \ No newline at end of file diff --git a/tab_drag.py b/tab_drag.py new file mode 100644 index 0000000..ca99062 --- /dev/null +++ b/tab_drag.py @@ -0,0 +1,200 @@ +import tkinter as tk +from tkinter import ttk +import math + +import matplotlib +matplotlib.use("TkAgg") +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +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. + """ + self.notebook = notebook + self.tab_simulator = tab_simulator + + self.frame = tk.Frame(notebook) + self.frame.pack(fill="both", expand=True) + + # Layout principal + 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) + + self.frame_3d = tk.Frame(frame_right) + self.frame_3d.pack(fill="both", expand=True) + + # Sección Izquierda: geometría + parámetros + tk.Label(frame_left, text="Selecciona geometría:").pack(anchor="w") + self.geometria_var = tk.StringVar(value="Prisma cuadrado") + self.combo_geometrias = ttk.Combobox( + frame_left, textvariable=self.geometria_var, + values=["Prisma cuadrado", "Cilindro", "Esfera"], + state="readonly" + ) + self.combo_geometrias.pack(anchor="w", pady=5) + + self.frame_parametros = tk.Frame(frame_left, bd=1, relief="sunken") + self.frame_parametros.pack(fill="x", pady=5) + + self.label_param1 = tk.Label(self.frame_parametros, text="Parámetro 1:") + self.label_param1.grid(row=0, column=0, sticky="w", padx=5, pady=2) + self.entry_param1 = tk.Entry(self.frame_parametros, width=8) + self.entry_param1.grid(row=0, column=1, padx=5, pady=2) + self.entry_param1.insert(0, "1") + + self.label_param2 = tk.Label(self.frame_parametros, text="Parámetro 2:") + self.label_param2.grid(row=1, column=0, sticky="w", padx=5, pady=2) + self.entry_param2 = tk.Entry(self.frame_parametros, width=8) + self.entry_param2.grid(row=1, column=1, padx=5, pady=2) + self.entry_param2.insert(0, "7") + + self.label_result_b = tk.Label(frame_left, text="Coef (b): N/A", fg="blue") + self.label_result_b.pack() + + btn_calc_b = tk.Button( + frame_left, text="Calcular Coef. Rozamiento", + command=self.calcular_coef_rozamiento + ) + btn_calc_b.pack(pady=5) + + tk.Button( + frame_left, text="Refrescar Vista 3D", + command=self.refrescar_3d + ).pack(pady=10) + + # Creamos la figura 3D + self.fig = plt.Figure(figsize=(4,3), dpi=100) + self.ax = self.fig.add_subplot(111, projection='3d') + self.canvas_3d = FigureCanvasTkAgg(self.fig, master=self.frame_3d) + self.canvas_widget = self.canvas_3d.get_tk_widget() + self.canvas_widget.pack(fill="both", expand=True) + + self.combo_geometrias.bind("<>", self.on_change_geometria) + self.update_param_labels() + self.refrescar_3d() + + def on_change_geometria(self, event=None): + self.update_param_labels() + self.refrescar_3d() + + def update_param_labels(self): + geom = self.geometria_var.get() + self.entry_param2.config(state="normal", fg="black") + if geom=="Prisma cuadrado": + self.label_param1.config(text="Lado base (m):") + self.label_param2.config(text="Longitud (m):") + elif geom=="Cilindro": + self.label_param1.config(text="Radio (m):") + self.label_param2.config(text="Altura (m):") + elif geom=="Esfera": + self.label_param1.config(text="Radio (m):") + self.label_param2.config(text="(no aplica):") + self.entry_param2.delete(0, tk.END) + self.entry_param2.config(state="disabled", fg="gray") + else: + self.label_param1.config(text="Parámetro 1:") + self.label_param2.config(text="Parámetro 2:") + + def refrescar_3d(self): + # Dibuja la geometría en self.ax + geom = self.geometria_var.get() + try: + p1 = float(self.entry_param1.get()) + except ValueError: + p1=1.0 + try: + if self.entry_param2.cget("state")!="disabled": + p2 = float(self.entry_param2.get()) + else: + p2=1.0 + except ValueError: + 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)] + 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": + 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) + 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": + 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) + self.ax.plot_surface(X,Y,Z,color='orange',alpha=0.8) + self.ax.set_box_aspect((2*r,2*r,2*r)) + else: + self.ax.text2D(0.3,0.5,"Geom. desconocida", transform=self.ax.transAxes) + + self.ax.set_title(geom) + self.canvas_3d.draw() + + def calcular_coef_rozamiento(self): + geom = self.geometria_var.get() + try: + p1 = float(self.entry_param1.get()) + except: + self.label_result_b.config(text="Error param1", fg="red") + return + + Cd=1.0 + A=1.0 + if geom=="Prisma cuadrado": + try: + p2=float(self.entry_param2.get()) + except: + self.label_result_b.config(text="Error param2", fg="red") + return + Cd=1.15 + A=p1*p1 + elif geom=="Cilindro": + try: + p2=float(self.entry_param2.get()) + except: + 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 + b_calc=0.5*rho*Cd*A + + self.label_result_b.config(text=f"Coef (b) ~ {b_calc:.4f}", fg="blue") + self.tab_simulator.set_b_value(b_calc) diff --git a/tab_search.py b/tab_search.py new file mode 100644 index 0000000..20d3b9d --- /dev/null +++ b/tab_search.py @@ -0,0 +1,219 @@ +# tab_search.py + +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 + + +class TabSearch: + def __init__(self, notebook, tab_simulator): + """ + Tercera pestaña: 'Búsqueda de ángulo'. + Recibe 'tab_simulator' para leer la config en la pestaña 1: + - masa (m) + - coef de rozamiento (b) + - altura h0 + - si hay drag o no + - alcance (si el modo es "Alcance (m)") + """ + self.notebook = notebook + self.tab_sim = tab_simulator # referencia a la pestaña de simulación + + self.frame = tk.Frame(notebook, width=280) + self.frame.pack(side="left", fill="both", expand=True) + # Fijamos ancho y desactivamos propagación para la columna + self.frame.pack_propagate(False) + + # -- Layout principal: izquierda (botones + info), derecha (gráfico) -- + self.frame_left = tk.Frame(self.frame, width=240) + self.frame_left.pack(side="left", fill="y", padx=5, pady=5) + self.frame_left.pack_propagate(False) + + self.frame_right = tk.Frame(self.frame) + self.frame_right.pack(side="right", fill="both", expand=True, padx=5, pady=5) + + # 1) Botones e info en la parte izquierda + tk.Button(self.frame_left, text="Importar config", command=self.import_config).pack(pady=5) + tk.Button(self.frame_left, text="Ejecutar búsqueda", command=self.run_search).pack(pady=5) + + # Etiquetas para mostrar la config importada + self.label_m = tk.Label(self.frame_left, text="Masa: ??? kg", bg="white") + self.label_m.pack(pady=2, anchor="w") + + self.label_b = tk.Label(self.frame_left, text="b: ???", bg="white") + self.label_b.pack(pady=2, anchor="w") + + self.label_h0 = tk.Label(self.frame_left, text="h0: ??? m", bg="white") + self.label_h0.pack(pady=2, anchor="w") + + self.label_drag = tk.Label(self.frame_left, text="Drag: ???", bg="white") + self.label_drag.pack(pady=2, anchor="w") + + self.label_x_target = tk.Label(self.frame_left, text="X_target: ???", bg="white") + self.label_x_target.pack(pady=2, anchor="w") + + # Label para mostrar el ángulo/vel. hallados tras la búsqueda + self.label_result = tk.Label(self.frame_left, text="Resultado:\n...", fg="green", bg="white") + self.label_result.pack(pady=10, fill="x") + + # 2) Creamos figure + canvas a la derecha (para dibujar la curva) + self.fig = plt.Figure(figsize=(4, 3), dpi=100) + self.ax = self.fig.add_subplot(111) + self.ax.set_xlabel("Ángulo (grados)") + self.ax.set_ylabel("Vel. requerida (m/s)") + + self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame_right) + self.canvas_widget = self.canvas.get_tk_widget() + self.canvas_widget.pack(fill="both", expand=True) + + # Variables donde guardaremos la config importada + self.m = 1.0 # masa + self.b = 0.0 # coef rozamiento + self.h0 = 0.0 # altura + self.has_drag = False + self.x_target = None # Alcance importado, si está en modo "Alcance (m)" + + def import_config(self): + """ + Lee la config actual de la pestaña 1 (simulador): + - masa + - b (si hay rozamiento) + - h0 + - check_rozamiento => has_drag + - y si param es "Alcance (m)", tomamos ese v0 como x_target + """ + try: + # Masa + self.m = float(self.tab_sim.entry_masa.get()) + + # Rozamiento + if self.tab_sim.check_rozamiento.get(): + self.has_drag = True + self.b = float(self.tab_sim.entry_b.get()) + else: + self.has_drag = False + self.b = 0.0 + + # Altura + self.h0 = float(self.tab_sim.entry_h0.get()) + + # Alcance si param== "Alcance (m)" + if self.tab_sim.parametro_var.get() == "Alcance (m)": + self.x_target = float(self.tab_sim.entry_v0.get()) + else: + self.x_target = None + + # Actualizamos en pantalla + self.label_m.config(text=f"Masa: {self.m:.2f} kg") + self.label_b.config(text=f"b: {self.b:.4f}") + self.label_h0.config(text=f"h0: {self.h0:.2f} m") + self.label_drag.config(text=f"Drag: {self.has_drag}") + + if self.x_target is not None: + self.label_x_target.config(text=f"X_target: {self.x_target:.2f} m") + else: + self.label_x_target.config(text="X_target: (No hay)") + + self.label_result.config(text="Config importada OK", bg="lightgreen", fg="black") + + except ValueError: + self.label_result.config(text="Error al leer config", bg="red", fg="white") + self.x_target = None + + def run_search(self): + """ + Barre ángulos de 0..90 y busca la velocidad mínima + para alcanzar la distancia x_target (si existe). + Si no hay x_target (porque el modo era Vel(m/s)), + mostramos error. + """ + if self.x_target is None: + # Si el usuario está en modo "Velocidad (m/s)" en la pestaña 1, + # no tenemos 'x_target' válido. Lógica: + self.label_result.config( + text="Error: en la pestaña 1 no se eligió 'Alcance (m)'", + bg="red", fg="white" + ) + return + + X_target = self.x_target # usar la que importamos + angulos = [] + velocidades = [] + best_angle = None + best_v = 1e9 + + # Recorremos ángulos de 0..90 + for angle_deg in range(0, 91): + v0_min = 0.0 + v0_max = 300.0 + final_v = 0.0 + + # Bisección + for _ in range(100): + guess = 0.5*(v0_min + v0_max) + dist = self.simular_dist(guess, angle_deg, self.has_drag) + if abs(dist - X_target) < 0.1: + final_v = guess + break + if dist < X_target: + v0_min = guess + else: + v0_max = guess + final_v = guess + + angulos.append(angle_deg) + velocidades.append(final_v) + + if final_v < best_v: + best_v = final_v + best_angle = angle_deg + + # Dibujamos + self.ax.clear() + self.ax.set_xlabel("Ángulo (grados)") + self.ax.set_ylabel("Vel. requerida (m/s)") + self.ax.plot(angulos, velocidades, color="red") + + # Marcamos el punto mínimo + self.ax.plot([best_angle], [best_v], marker="o", markersize=8, color="green") + self.canvas.draw() + + self.label_result.config( + text=f"Mín. en {best_angle}°, v0={best_v:.2f} m/s", + bg="white", fg="green" + ) + + def simular_dist(self, v0_guess, angle_deg, drag): + """ + Integra la distancia horizontal partiendo de (0,h0) + hasta y<=0. Devuelve x final. Usa self.m, self.b, self.h0, drag (bool). + """ + dt = 0.01 + x = 0.0 + y = self.h0 + alpha = math.radians(angle_deg) + vx = v0_guess * math.cos(alpha) + vy = v0_guess * math.sin(alpha) + + while True: + if drag: + ax = -(self.b / self.m) * vx + ay = -9.8 - (self.b / self.m) * vy + else: + ax = 0.0 + ay = -9.8 + + vx += ax*dt + vy += ay*dt + x += vx*dt + y += vy*dt + + if y <= 0: + break + + return x diff --git a/tab_simulator.py b/tab_simulator.py new file mode 100644 index 0000000..0002cbd --- /dev/null +++ b/tab_simulator.py @@ -0,0 +1,471 @@ +import tkinter as tk +from tkinter import ttk +import math + +class TabSimulator: + def __init__(self, notebook): + self.frame = tk.Frame(notebook) + + self.m = 0.0 # Masa + self.h0 = 0.0 # Altura inicial (0..2m) + self.trayectoria = [] + self.t_final = 0 + self.proyectil = None + self.vel_text = None + self.current_canvas_width = 1 + self.current_canvas_height = 1 + + # ============ Estructura general ============ + # TOP + self.frame_top = tk.Frame(self.frame) + self.frame_top.pack(side="top", fill="x", padx=5, pady=5) + + # MIDDLE (canvas) + self.frame_middle = tk.Frame(self.frame) + self.frame_middle.pack(side="top", fill="both", expand=True) + + # BOTTOM (slider + energía + log) + self.frame_bottom = tk.Frame(self.frame) + self.frame_bottom.pack(side="bottom", fill="both", expand=False) + + # - Izquierda: slider + energía + self.frame_slider_and_energy = tk.Frame(self.frame_bottom) + self.frame_slider_and_energy.pack(side="left", fill="y", padx=5, pady=5) + + self.frame_slider = tk.Frame(self.frame_slider_and_energy) + self.frame_slider.pack(side="top", fill="x", padx=5, pady=5) + + self.frame_energy = tk.Frame(self.frame_slider_and_energy, bd=2, relief="groove") + self.frame_energy.pack(side="bottom", fill="both", expand=False, padx=5, pady=5) + + # - Derecha: log + self.frame_log = tk.Frame(self.frame_bottom) + self.frame_log.pack(side="right", fill="both", expand=True, padx=5, pady=5) + + # ============ Fila superior de widgets ============ + tk.Label(self.frame_top, text="Parámetro:").grid(row=0, column=0, sticky="w") + self.parametro_var = tk.StringVar() + self.combo_param = ttk.Combobox( + self.frame_top, textvariable=self.parametro_var, + values=["Velocidad (m/s)", "Alcance (m)"], width=15 + ) + self.combo_param.grid(row=0, column=1, padx=5) + self.combo_param.current(0) + + self.entry_v0 = tk.Entry(self.frame_top, width=8) + self.entry_v0.grid(row=0, column=2, padx=5) + + tk.Label(self.frame_top, text="Ángulo (grados):").grid(row=0, column=3, sticky="w") + self.entry_alpha = tk.Entry(self.frame_top, width=8) + self.entry_alpha.grid(row=0, column=4, padx=5) + + tk.Label(self.frame_top, text="Masa (kg):").grid(row=0, column=5, sticky="w") + self.entry_masa = tk.Entry(self.frame_top, width=8) + self.entry_masa.grid(row=0, column=6, padx=5) + + self.check_rozamiento = tk.BooleanVar() + self.check_rozamiento.set(False) + self.chk = tk.Checkbutton( + self.frame_top, text="Incluir rozamiento", + variable=self.check_rozamiento, + command=self.on_toggle_rozamiento + ) + self.chk.grid(row=0, column=7, padx=15) + + tk.Label(self.frame_top, text="Coef. (b):").grid(row=0, column=8, sticky="e") + self.entry_b = tk.Entry(self.frame_top, width=8, state="disabled") + self.entry_b.grid(row=0, column=9, padx=5) + + # Altura inicial (0..2) + tk.Label(self.frame_top, text="Altura (m):").grid(row=0, column=10, sticky="e") + self.entry_h0 = tk.Entry(self.frame_top, width=8) + self.entry_h0.grid(row=0, column=11, padx=5) + self.entry_h0.insert(0, "0.0") # por defecto + + # Botón Calcular + self.button_calcular = tk.Button( + self.frame_top, text="Calcular", + command=self.calcular_trayectoria + ) + self.button_calcular.grid(row=0, column=12, padx=10) + + # Cajitas para pos X e Y + tk.Label(self.frame_top, text="Pos X (m):").grid(row=0, column=13, padx=5) + self.entry_pos_x = tk.Entry(self.frame_top, width=8) + self.entry_pos_x.grid(row=0, column=14, padx=5) + + tk.Label(self.frame_top, text="Pos Y (m):").grid(row=0, column=15, padx=5) + self.entry_pos_y = tk.Entry(self.frame_top, width=8) + self.entry_pos_y.grid(row=0, column=16, padx=5) + + # ============ Canvas ============ + self.canvas = tk.Canvas(self.frame_middle, bg="white") + self.canvas.pack(fill="both", expand=True) + self.canvas.bind("", self.on_resize) + + # ============ Slider tiempo ============ + self.slider_time = tk.Scale( + self.frame_slider, from_=0, to=1, resolution=0.01, + orient=tk.HORIZONTAL, label="Tiempo (s):", + command=self.actualizar_posicion + ) + self.slider_time.pack(fill="x") + + # ============ Cuadro energía ============ + tk.Label(self.frame_energy, text="Energía mecánica", font=("Arial", 10, "bold")).pack() + + self.label_Ec = tk.Label(self.frame_energy, text="Ec: 0.0 J") + self.label_Ec.pack(anchor="w", padx=5) + + self.label_Ep = tk.Label(self.frame_energy, text="Ep: 0.0 J") + self.label_Ep.pack(anchor="w", padx=5) + + self.label_Etot = tk.Label(self.frame_energy, text="E_total: 0.0 J") + self.label_Etot.pack(anchor="w", padx=5) + + self.label_Esobredim = tk.Label(self.frame_energy, text="E_total x1.15: 0.0 J") + self.label_Esobredim.pack(anchor="w", padx=5) + + # Logger + self.text_log = tk.Text(self.frame_log, height=2, state="normal") + self.text_log.pack(fill="both", expand=True) + + def set_log_message(self, mensaje, bg_color="white", fg_color="black"): + self.text_log.config(state="normal", bg=bg_color, fg=fg_color) + self.text_log.delete("1.0", tk.END) + self.text_log.insert(tk.END, mensaje + "\n") + self.text_log.config(state="disabled") + + def set_b_value(self, new_b): + self.entry_b.config(state="normal") + self.entry_b.delete(0, tk.END) + self.entry_b.insert(0, f"{new_b:.4f}") + self.entry_b.config(state="disabled") + + def on_toggle_rozamiento(self): + if self.check_rozamiento.get(): + self.entry_b.config(state="normal") + else: + self.entry_b.config(state="disabled") + + def on_resize(self, event): + self.current_canvas_width = event.width + self.current_canvas_height = event.height + if self.trayectoria: + self.dibujar_trayectoria() + + def calcular_trayectoria(self): + # 1) Lee alpha, masa + try: + alpha_deg = float(self.entry_alpha.get()) + self.m = float(self.entry_masa.get()) + except ValueError: + self.set_log_message("Error: revisa (ángulo, masa).", "red", "white") + return + + # 2) Altura + try: + h0_val = float(self.entry_h0.get()) + except ValueError: + h0_val = 0.0 + if h0_val < 0: h0_val = 0.0 + if h0_val > 2: h0_val = 2.0 + self.h0 = h0_val + + # 3) Validar ángulo + if alpha_deg < 0 or alpha_deg > 90: + self.set_log_message("Introduce un ángulo entre 0 y 90", "red","white") + return + + alpha_rad = math.radians(alpha_deg) + + # 4) Leer param (Vel/Alcance) + modo = self.parametro_var.get() + try: + valor = float(self.entry_v0.get()) + except ValueError: + self.set_log_message("Error en el valor (vel/alcance).", "red","white") + return + + v0 = 0.0 + if modo == "Velocidad (m/s)": + v0 = valor + else: + # Modo alcance + X_final = valor + if not self.check_rozamiento.get(): + v0_min=0.0 + v0_max=1000.0 + for _ in range(100): + guess=0.5*(v0_min+v0_max) + dist_alcanzado = self.simular_dist_sin_rozamiento(guess, alpha_rad, self.h0) + if abs(dist_alcanzado - X_final)<0.1: + v0=guess + break + if dist_alcanzado0", "red","white") + return + + b=0.0 + if self.check_rozamiento.get(): + try: + b = float(self.entry_b.get()) + except: + self.set_log_message("Coef rozamiento no válido", "red","white") + return + + # 5) Integramos la trayectoria + self.canvas.delete("all") + self.trayectoria.clear() + self.proyectil=None + self.vel_text=None + + dt=0.01 + t=0.0 + x=0.0 + y=self.h0 + vx = v0*math.cos(alpha_rad) + vy = v0*math.sin(alpha_rad) + + if not self.check_rozamiento.get(): + # sin rozamiento + while True: + self.trayectoria.append((t,x,y,vx,vy)) + vy_new = vy-9.8*dt + x_new = x+vx*dt + y_new = y+vy*dt + t+=dt + vx= vx + vy= vy_new + x= x_new + y= y_new + if y<=0 and t>0.01: + break + else: + # con rozamiento lineal + while True: + self.trayectoria.append((t,x,y,vx,vy)) + ax=-(b/self.m)*vx + ay=-9.8-(b/self.m)*vy + vx_new = vx+ax*dt + vy_new = vy+ay*dt + x_new = x+vx_new*dt + y_new = y+vy_new*dt + t+=dt + vx= vx_new + vy= vy_new + x= x_new + y= y_new + if y<=0 and t>0.01: + break + + self.t_final = t + self.slider_time.config(from_=0, to=self.t_final) + self.slider_time.set(0) + + self.dibujar_trayectoria() + self.set_log_message(f"Cálculo OK. v0={v0:.2f} m/s", "green","white") + + def simular_dist_sin_rozamiento(self, v0_guess, alpha_rad, h0): + dt=0.01 + x=0.0 + y=h0 + vx=v0_guess*math.cos(alpha_rad) + vy=v0_guess*math.sin(alpha_rad) + t=0.0 + while True: + vy_new= vy-9.8*dt + x_new= x+ vx*dt + y_new= y+ vy*dt + t+=dt + vx= vx + vy= vy_new + x= x_new + y= y_new + if y<=0 and t>0.01: + break + return x + + def simular_dist_con_rozamiento(self, v0_guess, alpha_rad, h0): + dt=0.01 + x=0.0 + y=h0 + vx=v0_guess*math.cos(alpha_rad) + vy=v0_guess*math.sin(alpha_rad) + t=0.0 + try: + b=float(self.entry_b.get()) + except: + b=0.1 + while True: + ax=-(b/self.m)*vx + ay=-9.8-(b/self.m)*vy + vx_new= vx+ax*dt + vy_new= vy+ay*dt + x_new= x+vx_new*dt + y_new= y+vy_new*dt + t+=dt + vx= vx_new + vy= vy_new + x= x_new + y= y_new + if y<=0 and t>0.01: + break + return x + + def dibujar_trayectoria(self): + if not self.trayectoria: + return + + min_x = min(pt[1] for pt in self.trayectoria) + max_x = max(pt[1] for pt in self.trayectoria) + min_y = min(pt[2] for pt in self.trayectoria) + max_y = max(pt[2] for pt in self.trayectoria) + + rx = max_x - min_x + ry = max_y - min_y + if rx<1e-9: rx=1.0 + if ry<1e-9: ry=1.0 + + w = self.current_canvas_width + h = self.current_canvas_height + margen=20 + scale = min((w-2*margen)/rx, (h-2*margen)/ry) + + self.canvas.delete("all") + pts=[] + for (tt,xx,yy,vx_,vy_) in self.trayectoria: + sx = margen + (xx - min_x)*scale + sy = (h - margen) - (yy - min_y)*scale + pts.append((sx, sy)) + + for i in range(len(pts)-1): + x1,y1=pts[i] + x2,y2=pts[i+1] + self.canvas.create_line(x1,y1,x2,y2,fill="blue") + + if pts: + x0,y0 = pts[0] + r=5 + self.proyectil = self.canvas.create_oval(x0-r,y0-r, x0+r,y0+r, fill="red") + self.vel_text = self.canvas.create_text(x0+15,y0, text="v=0.00 m/s", fill="black") + + self.scale=scale + self.margen=margen + self.min_x=min_x + self.min_y=min_y + + self.actualizar_energia(0) + + def actualizar_posicion(self, val): + if not self.trayectoria or not self.proyectil: + return + + t_slider = float(val) + if t_slider<=0: + x_real=self.trayectoria[0][1] + y_real=self.trayectoria[0][2] + vx_real=self.trayectoria[0][3] + vy_real=self.trayectoria[0][4] + elif t_slider>=self.t_final: + x_real=self.trayectoria[-1][1] + y_real=self.trayectoria[-1][2] + vx_real=self.trayectoria[-1][3] + vy_real=self.trayectoria[-1][4] + else: + tiempos=[p[0] for p in self.trayectoria] + idx=0 + while idx=self.t_final: + vx_=self.trayectoria[-1][3] + vy_=self.trayectoria[-1][4] + x_=self.trayectoria[-1][1] + y_=self.trayectoria[-1][2] + else: + tiempos=[p[0] for p in self.trayectoria] + idx=0 + while idx0: + Ep=self.m*9.8*y_ + E_tot=Ec+Ep + E_sobredim=1.15*E_tot + + self.label_Ec.config(text=f"Ec: {Ec:.2f} J") + self.label_Ep.config(text=f"Ep: {Ep:.2f} J") + self.label_Etot.config(text=f"E_total: {E_tot:.2f} J") + self.label_Esobredim.config(text=f"E_total x1.15: {E_sobredim:.2f} J")