Init
This commit is contained in:
		
						commit
						93c9e7b91b
					
				
							
								
								
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -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"] | ||||||
							
								
								
									
										
											BIN
										
									
								
								__pycache__/geometry_viewer.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/geometry_viewer.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								__pycache__/tab_drag.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/tab_drag.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								__pycache__/tab_search.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/tab_search.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								__pycache__/tab_simulator.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/tab_simulator.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										71
									
								
								geometry_viewer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								geometry_viewer.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||||
							
								
								
									
										1
									
								
								launcher.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								launcher.bat
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | docker run -it --rm -e DISPLAY=host.docker.internal:0 launchsim | ||||||
							
								
								
									
										32
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||||
							
								
								
									
										1
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | matplotlib | ||||||
							
								
								
									
										200
									
								
								tab_drag.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								tab_drag.py
									
									
									
									
									
										Normal file
									
								
							| @ -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("<<ComboboxSelected>>", 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) | ||||||
							
								
								
									
										219
									
								
								tab_search.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								tab_search.py
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
							
								
								
									
										471
									
								
								tab_simulator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										471
									
								
								tab_simulator.py
									
									
									
									
									
										Normal file
									
								
							| @ -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("<Configure>", 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_alcanzado<X_final: | ||||||
|  |                         v0_min=guess | ||||||
|  |                     else: | ||||||
|  |                         v0_max=guess | ||||||
|  |                     v0=guess | ||||||
|  |             else: | ||||||
|  |                 v0_min=0.0 | ||||||
|  |                 v0_max=1000.0 | ||||||
|  |                 for _ in range(100): | ||||||
|  |                     guess=0.5*(v0_min+v0_max) | ||||||
|  |                     dist_alcanzado = self.simular_dist_con_rozamiento(guess, alpha_rad, self.h0) | ||||||
|  |                     if abs(dist_alcanzado - X_final)<0.1: | ||||||
|  |                         v0=guess | ||||||
|  |                         break | ||||||
|  |                     if dist_alcanzado<X_final: | ||||||
|  |                         v0_min=guess | ||||||
|  |                     else: | ||||||
|  |                         v0_max=guess | ||||||
|  |                     v0=guess | ||||||
|  | 
 | ||||||
|  |         if v0<=0: | ||||||
|  |             self.set_log_message("No se pudo determinar v0>0", "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<len(self.trayectoria)-1 and tiempos[idx+1]<t_slider: | ||||||
|  |                 idx+=1 | ||||||
|  |             x_real=self.trayectoria[idx][1] | ||||||
|  |             y_real=self.trayectoria[idx][2] | ||||||
|  |             vx_real=self.trayectoria[idx][3] | ||||||
|  |             vy_real=self.trayectoria[idx][4] | ||||||
|  | 
 | ||||||
|  |         # Pos en canvas | ||||||
|  |         w = self.current_canvas_width | ||||||
|  |         h = self.current_canvas_height | ||||||
|  |         sx = self.margen + (x_real - self.min_x)*self.scale | ||||||
|  |         sy = (h - self.margen) - (y_real - self.min_y)*self.scale | ||||||
|  | 
 | ||||||
|  |         coords_act = self.canvas.coords(self.proyectil) | ||||||
|  |         x_c=(coords_act[0]+coords_act[2])/2 | ||||||
|  |         y_c=(coords_act[1]+coords_act[3])/2 | ||||||
|  |         dx=sx-x_c | ||||||
|  |         dy=sy-y_c | ||||||
|  |         self.canvas.move(self.proyectil, dx, dy) | ||||||
|  | 
 | ||||||
|  |         vel = math.sqrt(vx_real**2 + vy_real**2) | ||||||
|  |         if self.vel_text: | ||||||
|  |             coords_text=self.canvas.coords(self.vel_text) | ||||||
|  |             xt=coords_text[0] | ||||||
|  |             yt=coords_text[1] | ||||||
|  |             dx_t=sx-xt | ||||||
|  |             dy_t=(sy+15)-yt | ||||||
|  |             self.canvas.move(self.vel_text, dx_t, dy_t) | ||||||
|  |             self.canvas.itemconfig(self.vel_text, text=f"v={vel:.2f} m/s") | ||||||
|  | 
 | ||||||
|  |         # Pos X e Y (en metros, no en pixeles) | ||||||
|  |         self.entry_pos_x.delete(0, tk.END) | ||||||
|  |         self.entry_pos_x.insert(0, f"{x_real:.2f}") | ||||||
|  | 
 | ||||||
|  |         self.entry_pos_y.delete(0, tk.END) | ||||||
|  |         self.entry_pos_y.insert(0, f"{y_real:.2f}") | ||||||
|  | 
 | ||||||
|  |         self.actualizar_energia(t_slider) | ||||||
|  | 
 | ||||||
|  |     def actualizar_energia(self, t_slider): | ||||||
|  |         if not self.trayectoria: | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         if t_slider<=0: | ||||||
|  |             vx_=self.trayectoria[0][3] | ||||||
|  |             vy_=self.trayectoria[0][4] | ||||||
|  |             x_=self.trayectoria[0][1] | ||||||
|  |             y_=self.trayectoria[0][2] | ||||||
|  |         elif t_slider>=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 idx<len(self.trayectoria)-1 and tiempos[idx+1]<t_slider: | ||||||
|  |                 idx+=1 | ||||||
|  |             vx_=self.trayectoria[idx][3] | ||||||
|  |             vy_=self.trayectoria[idx][4] | ||||||
|  |             x_=self.trayectoria[idx][1] | ||||||
|  |             y_=self.trayectoria[idx][2] | ||||||
|  | 
 | ||||||
|  |         Ec = 0.5*self.m*(vx_**2 + vy_**2) | ||||||
|  |         Ep=0.0 | ||||||
|  |         if y_>0: | ||||||
|  |             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") | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Pedro Jose Romero Gombau
						Pedro Jose Romero Gombau