v1.1 upload
This commit is contained in:
parent
d5f522c838
commit
656c26ef2c
8
.gitignore
vendored
8
.gitignore
vendored
@ -1 +1,7 @@
|
||||
SECRETS
|
||||
SECRETS
|
||||
venv/
|
||||
__pycache__/
|
||||
*.spec
|
||||
build/
|
||||
dist/
|
||||
*.log
|
88
README.md
88
README.md
@ -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
46
README_APP.md
Normal 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
|
@ -1 +0,0 @@
|
||||
docker build -t launchsim .
|
85
dockyard.bat
85
dockyard.bat
@ -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
|
@ -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()
|
@ -1 +0,0 @@
|
||||
docker run -it --rm -e DISPLAY=host.docker.internal:0 launchsim
|
32
main.py
32
main.py
@ -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()
|
@ -1,2 +1,3 @@
|
||||
matplotlib
|
||||
numpy
|
||||
numpy
|
||||
pyinstaller
|
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
52
src/main.py
Normal file
52
src/main.py
Normal 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
BIN
src/static/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
0
src/tabs/__init__.py
Normal file
0
src/tabs/__init__.py
Normal file
175
src/tabs/geometry_viewer.py
Normal file
175
src/tabs/geometry_viewer.py
Normal 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
123
src/tabs/tab_coil.py
Normal 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
155
src/tabs/tab_electrical.py
Normal 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()
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user