v1.1 upload

This commit is contained in:
Pedro Jose Romero Gombau 2025-01-21 17:18:51 +01:00
parent d5f522c838
commit 656c26ef2c
20 changed files with 633 additions and 216 deletions

8
.gitignore vendored
View File

@ -1 +1,7 @@
SECRETS
SECRETS
venv/
__pycache__/
*.spec
build/
dist/
*.log

View File

@ -1,35 +1,77 @@
# Bienvenido a LaunchSim
## Bienvenido a LaunchSim
### Simulador de trayectorias para lanzaderas
## Descripción.
---
El objetivo de este programa es a partir de unos parámetros de entrada, conseguir una estimación mecánica y energética de las necesidades de la lanzadera para cumplir los objetivos de entrada. La aplicación cuenta con tres pestañas:
### Descripción
- **Simulación**: Pestaña en la que se introducen los datos y se visualiza la trayectoria en tiempo real.
- **Coef. Rozamiento**: Si se quiere tener en cuenta el efecto del rozamiento del aire, se podrá elegir en esta pantalla una geometría para el proyectil y calculará automáticamente el coeficiente de rozamiento.
- **Optimización**: Una vez generada una primera simulación, se puede optimizar para conseguir la el ángulo de lanzamiento que requiere la menor velocidad, minimizando así la energía. La optimización solo se puede realizar si el modo de simulación es *Alcance*.
LaunchSim es una aplicación diseñada para calcular trayectorias de proyectiles en función de parámetros físicos y mecánicos, proporcionando estimaciones energéticas y visualización interactiva. Además, incluye herramientas de optimización para minimizar la velocidad inicial necesaria.
## Instrucciones de instalación
### Estructura de la aplicación
Para poner en marcha la aplicación, hay dos opciones:
1. **Simulador:**
- Introduce los parámetros iniciales como ángulo, velocidad, alcance y altura inicial.
- Visualiza la trayectoria y calcula energías mecánicas y dinámicas en tiempo real.
- Directamente desde la consola de tu equipo
1. Tener *python* instalado.
2. Ejecutar `pip install -r requirements.txt`
3. Ejecutar `python main.py` desde el directorio del proyecto
2. **Coef. Rozamiento:**
- Selecciona una geometría (prisma, cilindro o esfera) para calcular el coeficiente de rozamiento (*b*) en función de la resistencia al aire.
- Desde docker:
1. Tener *Docker Desktop* instalado.
2. Ejecutar el archivo *buildimage.bat*
3. Ejecutar el archivo *launcher.bat*
3. **Optimización:**
- Calcula el ángulo óptimo para minimizar la velocidad inicial necesaria y optimiza el consumo energético.
## Para desarrolladores del programa
4. **Simulación Geométrica de Bobinas:**
- Diseña la geometría de una bobina seleccionando parámetros como el radio, el número de vueltas y la altura.
- Calcula automáticamente la sección transversal resultante de la bobina, utilizada para evaluar la resistencia eléctrica.
Una vez descargado el código desde el repositorio, es necesario crear un archivo *SECRETS* para subir nuevas versiones al registro, con el siguiente formato:
```
REG_USER=usuario
REG_PASSWORD=contraseña
```
5. **Simulación Eléctrica:**
- Utiliza los datos generados en la pestaña de bobinas para calcular parámetros eléctricos como resistencia, voltaje y corriente requeridos para alimentar el sistema.
- Proporciona una visualización de los valores eléctricos en tiempo real.
---
### Instalación
1. **Clonar el repositorio:**
```bash
git clone https://github.com/spark-ops/LaunchSim.git
cd LaunchSim
```
2. **Crear un entorno virtual:**
```bash
python -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
```
3. **Instalar dependencias:**
```bash
pip install -r requirements.txt
```
4. **Ejecutar la aplicación:**
```bash
python src/main.py
```
5. **Compilar como ejecutable (opcional):**
Si deseas distribuir la aplicación como un ejecutable independiente:
```bash
pyinstaller --onefile --noconsole --icon=src/static/icon.ico src/main.py
```
El archivo resultante estará en la carpeta `dist/`.
---
### Estructura del Proyecto
- **src/**: Contiene los archivos principales del código fuente.
- **static/**: Almacena los recursos estáticos como el icono de la aplicación.
- **VERSION:** Archivo que contiene la versión actual del software.
- **SECRETS:** Archivo para credenciales usadas en integración con Docker.
- **Dockerfile:** Archivo para crear una imagen Docker del proyecto.
---
### Contacto
## Contacto
E-mail: pedrojrg@spark-ops.com

46
README_APP.md Normal file
View File

@ -0,0 +1,46 @@
## Bienvenido a LaunchSim
### Simulador de trayectorias para lanzaderas
---
### Descripción
LaunchSim es una aplicación que permite calcular trayectorias de proyectiles y realizar estimaciones energéticas basadas en los parámetros de entrada. Además, incluye herramientas de optimización para encontrar el ángulo de lanzamiento más eficiente.
---
### Funcionalidades
1. **Simulación:**
- Introduce datos como ángulo, velocidad, masa y alcance.
- Visualiza la trayectoria en tiempo real y observa cálculos de energía.
2. **Cálculo del Coef. de Rozamiento:**
- Selecciona una geometría y calcula el coeficiente de rozamiento para proyectiles en movimiento.
3. **Optimización:**
- Encuentra el ángulo que minimiza la velocidad inicial y optimiza el consumo energético.
4. **Simulación Geométrica de Bobinas:**
- Permite diseñar una bobina eligiendo parámetros geométricos como radio, altura y número de vueltas.
- Calcula la sección transversal de la bobina, esencial para evaluar su resistencia eléctrica.
5. **Simulación Eléctrica:**
- Utiliza los datos de la geometría de la bobina para calcular resistencia, voltaje y corriente necesarios.
- Proporciona una simulación interactiva de los valores eléctricos.
---
### Requisitos del Sistema
- Sistema operativo: Windows 10 o superior.
- No requiere instalación previa de Python.
- Tamaño del archivo: ~50 MB.
---
### Contacto
Si tienes preguntas o necesitas soporte técnico, puedes ponerte en contacto con nosotros:
E-mail: pedrojrg@spark-ops.com

View File

@ -1 +1 @@
1.0
1.1

View File

@ -1 +0,0 @@
docker build -t launchsim .

View File

@ -1,85 +0,0 @@
REM @echo off
setlocal enabledelayedexpansion
REM Leer usuario y contraseña del archivo SECRETS
REM (deben ser líneas con formato REG_USER=xxx y REG_PASSWORD=xxx, sin espacios)
for /f "usebackq tokens=1,2 delims==" %%A in ("SECRETS") do (
if /I "%%A"=="REG_USER" set "REG_USER=%%B"
if /I "%%A"=="REG_PASSWORD" set "REG_PASSWORD=%%B"
)
REM Leer la versión del archivo VERSION
REM (asumimos que la primera línea tiene la versión, p.ej. 1.0)
for /f "usebackq tokens=* delims=" %%V in ("VERSION") do (
set "VERSION=%%V"
goto :GotVersion
)
:GotVersion
REM Verificar que los valores estén cargados
if not defined REG_USER (
echo Error: REG_USER no encontrado en SECRETS.
exit /b 1
)
if not defined REG_PASSWORD (
echo Error: REG_PASSWORD no encontrado en SECRETS.
exit /b 1
)
if not defined VERSION (
echo Error: VERSION no encontrado o vacío en VERSION.
exit /b 1
)
REM Login en el registro Docker
echo Realizando login en dockyard.spark-ops.com...
echo !REG_PASSWORD! | docker login dockyard.spark-ops.com --username !REG_USER! --password-stdin
if %ERRORLEVEL% neq 0 (
echo Error: Falló el login en el registro Docker.
exit /b 1
)
REM Construir la imagen Docker
echo Construyendo la imagen Docker launchsim:!VERSION!...
docker build -t launchsim:!VERSION! .
if %ERRORLEVEL% neq 0 (
echo Error: Falló la construcción de la imagen Docker.
exit /b 1
)
REM Etiquetar la imagen con la versión
echo Etiquetando la imagen para el registro...
docker tag launchsim:!VERSION! dockyard.spark-ops.com/launchsim/launchsim:!VERSION!
if %ERRORLEVEL% neq 0 (
echo Error: Falló al etiquetar la imagen Docker.
exit /b 1
)
REM Etiquetar la imagen como latest
echo Etiquetando la imagen como latest...
docker tag launchsim:!VERSION! dockyard.spark-ops.com/launchsim/launchsim:latest
if %ERRORLEVEL% neq 0 (
echo Error: Falló al etiquetar la imagen como latest.
exit /b 1
)
REM Subir la imagen con la versión
echo Subiendo la imagen al registro con la versión !VERSION!...
docker push dockyard.spark-ops.com/launchsim/launchsim:!VERSION!
if %ERRORLEVEL% neq 0 (
echo Error: Falló al subir la imagen con la versión.
exit /b 1
)
REM Subir la imagen como latest
echo Subiendo la imagen como latest al registro...
docker push dockyard.spark-ops.com/launchsim/launchsim:latest
if %ERRORLEVEL% neq 0 (
echo Error: Falló al subir la imagen como latest.
exit /b 1
)
echo Proceso completado exitosamente.
endlocal

View File

@ -1,71 +0,0 @@
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()

View File

@ -1 +0,0 @@
docker run -it --rm -e DISPLAY=host.docker.internal:0 launchsim

32
main.py
View File

@ -1,32 +0,0 @@
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()

View File

@ -1,2 +1,3 @@
matplotlib
numpy
numpy
pyinstaller

0
src/__init__.py Normal file
View File

52
src/main.py Normal file
View File

@ -0,0 +1,52 @@
# main.py
import os
import tkinter as tk
from tkinter import ttk
# Importamos las distintas pestañas
from src.tabs.tab_simulator import TabSimulator
from src.tabs.tab_search import TabSearch
from src.tabs.tab_drag import TabDrag
from src.tabs.tab_coil import TabCoil
from src.tabs.tab_electrical import TabElectrical
class MainApp:
def __init__(self, master):
self.master = master
self.master.title("LaunchSim")
# Intento de cargar icono (opcional)
icon_path = os.path.join(os.path.dirname(__file__), "static", "icon.ico")
if os.path.exists(icon_path):
self.master.iconbitmap(icon_path)
else:
print(f"Advertencia: Icono no encontrado en {icon_path}")
self.notebook = ttk.Notebook(master)
self.notebook.pack(fill="both", expand=True)
# Pestaña 1: Simulador
self.tab_sim = TabSimulator(self.notebook)
self.notebook.add(self.tab_sim.frame, text="Simulador Trayectoria")
# Pestaña 2: Rozamiento
self.tab_drag = TabDrag(self.notebook, self.tab_sim)
self.notebook.add(self.tab_drag.frame, text="Rozamiento")
# Pestaña 3: Optimización
self.tab_search = TabSearch(self.notebook, self.tab_sim)
self.notebook.add(self.tab_search.frame, text="Optimización")
# 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")
if __name__ == "__main__":
root = tk.Tk()
app = MainApp(root)
root.mainloop()

BIN
src/static/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

0
src/tabs/__init__.py Normal file
View File

175
src/tabs/geometry_viewer.py Normal file
View File

@ -0,0 +1,175 @@
# src/geometry_viewer.py
import matplotlib
matplotlib.use("TkAgg") # Para 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:
# Geometría no reconocida
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()
def plot_coil_in_frame(parent_frame,
N=10,
e_pvc=1.0,
r_int=5.0,
r_ext=8.0,
h_c=10.0,
d_cu=0.5):
"""
Dibuja un esquema 2D aproximado de la bobina en un Frame de Tkinter,
basado en los parámetros:
N : número total de vueltas
e_pvc : espesor del PVC
r_int : radio interior de la bobina
r_ext : radio exterior total de la bobina
h_c : altura total de la bobina
d_cu : diámetro del conductor
Se calcula:
r_capas = r_ext - (r_int + e_pvc)
Luego se distribuyen N vueltas en 'num_capas' radiales, etc.
"""
# 1) Cálculo de la anchura de bobinado
r_capas = r_ext - (r_int + e_pvc)
if r_capas < 0:
# Si ocurre un valor absurdo, forzamos a 0
r_capas = 0.0
# 2) Creamos la figura y eje 2D
fig = plt.Figure(figsize=(4, 3), dpi=100)
ax = fig.add_subplot(111)
ax.set_aspect("equal", adjustable="box")
ax.set_title("Bobina (vista 2D)", fontsize=10)
# 3) Dibujar PVC como dos rectángulos "verticales"
# Eje Y: 0 -> h_c
# Izquierda: x ~ -(r_int + e_pvc) hasta -r_int
# Derecha : x ~ (r_int) hasta (r_int + e_pvc)
ax.fill_between(
x=[-(r_int + e_pvc), -r_int],
y1=0,
y2=h_c,
color="black",
alpha=0.3
)
ax.fill_between(
x=[r_int, r_int + e_pvc],
y1=0,
y2=h_c,
color="black",
alpha=0.3
)
# 4) Cálculo de capas radiales y vueltas por capa
num_capas = int(np.floor(r_capas / d_cu))
if num_capas < 1:
num_capas = 1
vueltas_por_capa = int(np.ceil(N / num_capas))
# 5) Distancia vertical entre vueltas en cada capa
delta_h = h_c / (vueltas_por_capa + 1)
# Función auxiliar para dibujar un conductor (círculo)
def dibuja_conductor(xc, yc, radio):
circle = plt.Circle((xc, yc), radio, color="orange", fill=True)
ax.add_patch(circle)
# 6) Bucle para colocar cada capa (izq/dcha)
for i in range(num_capas):
# X de la capa i, en lado izquierdo
x_left = -(r_int + e_pvc + (2*i + 1) * (d_cu / 2))
# ... y lado derecho
x_right = +(r_int + e_pvc + (2*i + 1) * (d_cu / 2))
# Añadimos vueltas en altura
for j in range(vueltas_por_capa):
y = (j + 1) * delta_h
if y > h_c:
break # no dibujamos si excede la altura
dibuja_conductor(x_left, y, d_cu/2)
dibuja_conductor(x_right, y, d_cu/2)
# 7) Ajustar los límites del dibujo en X e Y
ax.set_xlim(-r_ext - d_cu, r_ext + d_cu)
ax.set_ylim(0, h_c + d_cu)
ax.set_xlabel("Radio (X)")
ax.set_ylabel("Altura (Y)")
ax.grid(True)
# 8) Borramos lo anterior en parent_frame y embebemos esta figura
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()

123
src/tabs/tab_coil.py Normal file
View File

@ -0,0 +1,123 @@
# src/tab_coil.py
import tkinter as tk
from tkinter import ttk
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
# Importamos la función para dibujar la bobina 2D
from src.tabs.geometry_viewer import plot_coil_in_frame
class TabCoil:
def __init__(self, notebook, tab_simulator):
"""
Pestaña para representar la bobina en 2D y ajustar sus parámetros.
Recibe:
- notebook: el ttk.Notebook donde se añadirá
- tab_simulator: referencia a TabSimulator, si se necesita comunicar
"""
self.notebook = notebook
self.tab_simulator = tab_simulator
# Frame principal dentro del notebook
self.frame = tk.Frame(notebook)
self.frame.pack(fill="both", expand=True)
# (Este add lo hace habitualmente main.py, pero se podría hacer aquí)
# notebook.add(self.frame, text="Bobinas")
# Layout principal: lado izq (parámetros) y lado der (dibujo)
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)
# Este frame contendrá el canvas/figura para la bobina en 2D
self.frame_2d = tk.Frame(frame_right)
self.frame_2d.pack(fill="both", expand=True)
#
# Sección Izquierda: parámetros de la bobina
#
tk.Label(frame_left, text="Parámetros de la Bobina:").pack(anchor="w")
# Variables
self.var_N = tk.IntVar(value=500)
self.var_e_pvc = tk.DoubleVar(value=4.04)
self.var_r_int = tk.DoubleVar(value=12.07)
self.var_r_ext = tk.DoubleVar(value=21.27)
self.var_h_c = tk.DoubleVar(value=53.12)
self.var_d_cu = tk.DoubleVar(value=0.8)
# Frame para agrupar los Entries
self.frame_params = tk.Frame(frame_left, bd=1, relief="sunken")
self.frame_params.pack(fill="x", pady=5)
# Función auxiliar para añadir filas (etiqueta + entry)
def add_param_row(label_text, var, row, width=8):
lbl = tk.Label(self.frame_params, text=label_text)
lbl.grid(row=row, column=0, sticky="w", padx=5, pady=2)
ent = tk.Entry(self.frame_params, textvariable=var, width=width)
ent.grid(row=row, column=1, padx=5, pady=2)
# Creamos las filas
rowcount = 0
add_param_row("N (vueltas):", self.var_N, rowcount); rowcount += 1
add_param_row("e_pvc (mm):", self.var_e_pvc, rowcount); rowcount += 1
add_param_row("r_int (mm):", self.var_r_int, rowcount); rowcount += 1
add_param_row("r_ext (mm):", self.var_r_ext, rowcount); rowcount += 1
add_param_row("h_c (mm):", self.var_h_c, rowcount); rowcount += 1
add_param_row("d_cu (mm):", self.var_d_cu, rowcount); rowcount += 1
# Botón para actualizar la vista 2D
btn_refrescar = tk.Button(
frame_left, text="Refrescar Vista 2D",
command=self.refrescar_2d
)
btn_refrescar.pack(pady=10)
#
# Creamos la figura y canvas para la bobina en 2D (opcional,
# pues 'plot_coil_in_frame' creará su propia Figure).
#
self.fig = plt.Figure(figsize=(4,3), dpi=100)
self.ax = self.fig.add_subplot(111)
self.canvas_2d = FigureCanvasTkAgg(self.fig, master=self.frame_2d)
self.canvas_2d_widget = self.canvas_2d.get_tk_widget()
self.canvas_2d_widget.pack(fill="both", expand=True)
# Dibujamos la bobina inicial
self.refrescar_2d()
def refrescar_2d(self):
"""
Lee los valores de la bobina y llama a 'plot_coil_in_frame'
para actualizar el dibujo en 2D.
"""
# Obtenemos valores
N_val = self.var_N.get()
e_pvc_val = self.var_e_pvc.get()
r_int_val = self.var_r_int.get()
r_ext_val = self.var_r_ext.get()
h_c_val = self.var_h_c.get()
d_cu_val = self.var_d_cu.get()
# Limpiamos el Axes actual (si lo usáramos)
self.ax.clear()
# Llamada a la función que inserta la figura en self.frame_2d
plot_coil_in_frame(
parent_frame=self.frame_2d,
N=N_val,
e_pvc=e_pvc_val,
r_int=r_int_val,
r_ext=r_ext_val,
h_c=h_c_val,
d_cu=d_cu_val
)

155
src/tabs/tab_electrical.py Normal file
View File

@ -0,0 +1,155 @@
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()

View File

@ -125,6 +125,8 @@ class TabSimulator:
self.label_Esobredim = tk.Label(self.frame_energy, text="E_total x1.15: 0.0 J")
self.label_Esobredim.pack(anchor="w", padx=5)
self.energy_required = 0.0
# Logger
self.text_log = tk.Text(self.frame_log, height=2, state="normal")
@ -463,9 +465,14 @@ class TabSimulator:
if y_>0:
Ep=self.m*9.8*y_
E_tot=Ec+Ep
self.energy_required = E_tot
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")
def get_energy_required(self):
return self.energy_required