parent
96d6cbd125
commit
d4ac1c9b02
@ -1,3 +1,4 @@
|
||||
matplotlib
|
||||
numpy
|
||||
pyinstaller
|
||||
pyinstaller
|
||||
scipy
|
@ -2,6 +2,7 @@
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter.scrolledtext import ScrolledText
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("TkAgg")
|
||||
@ -10,11 +11,135 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
|
||||
import numpy as np
|
||||
import math as m
|
||||
import decimal as dec
|
||||
|
||||
# Importamos la función para dibujar la bobina 2D
|
||||
# Se importan las integrales elípticas de SciPy para el cálculo preciso de la inductancia
|
||||
from scipy.special import ellipk, ellipe
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Constantes y funciones para el cálculo de inductancia
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
MU0 = 4.0e-7 * m.pi # Permeabilidad magnética del vacío (H/m)
|
||||
|
||||
def self_inductance_loop(r, a_eff=1e-4):
|
||||
"""
|
||||
Autoinductancia aproximada de un anillo fino de radio r.
|
||||
Emplea la fórmula: L ~ mu0*r * [ln(8r/a_eff) - 2].
|
||||
"""
|
||||
if r <= 0:
|
||||
return 0.0
|
||||
return MU0 * r * (m.log(8.0 * r / a_eff) - 2.0)
|
||||
|
||||
def mutual_inductance_coaxial(r1, r2, z_dist):
|
||||
"""
|
||||
Inductancia mutua entre dos anillos coaxiales de radios r1 y r2,
|
||||
separados una distancia axial z_dist.
|
||||
Emplea las integrales elípticas de primera y segunda especie.
|
||||
"""
|
||||
if r1 < 1e-12 or r2 < 1e-12:
|
||||
return 0.0
|
||||
|
||||
k_sq = 4.0 * r1 * r2 / ((r1 + r2)**2 + z_dist**2)
|
||||
if k_sq > 1.0:
|
||||
# Caso no físico (puede ocurrir por redondeos), retornamos 0
|
||||
return 0.0
|
||||
|
||||
k = np.sqrt(k_sq)
|
||||
|
||||
# Caso casi degenerado (r1 ~ r2, z_dist ~ 0)
|
||||
if (1.0 - k_sq) < 1e-12:
|
||||
# Aproximación cuando r1 ≈ r2 y z ≈ 0
|
||||
return MU0 * min(r1, r2) * m.pi
|
||||
else:
|
||||
Kk = ellipk(k_sq)
|
||||
Ek = ellipe(k_sq)
|
||||
factor = MU0 * np.sqrt(r1 * r2)
|
||||
# Expresión: M = mu0 * sqrt(r1*r2)*((2-k)*K - 2E) / k
|
||||
return factor * ((2.0 - k) * Kk - 2.0 * Ek) / k
|
||||
|
||||
def compute_coil_geometry(N_total, R_int, R_ext, espesor_plast, H, wire_d):
|
||||
"""
|
||||
Genera la lista de (r_i, z_i) para las N_total espiras, asumiendo
|
||||
que se llenan capas radiales y, en cada capa, vueltas a lo largo de la altura.
|
||||
Todos los parámetros deben estar en metros.
|
||||
"""
|
||||
radial_thickness = R_ext - (R_int + 2 * espesor_plast)
|
||||
if radial_thickness <= 0:
|
||||
raise ValueError("No hay espacio radial para el bobinado.")
|
||||
|
||||
max_layers = int(np.floor(radial_thickness / wire_d))
|
||||
if max_layers < 1:
|
||||
raise ValueError("No cabe ni una capa con el grosor de hilo dado.")
|
||||
|
||||
max_turns_per_layer = int(np.floor(H / wire_d))
|
||||
if max_turns_per_layer < 1:
|
||||
raise ValueError("No cabe ni una vuelta en la altura dada.")
|
||||
|
||||
max_turns_possible = max_layers * max_turns_per_layer
|
||||
if max_turns_possible < N_total:
|
||||
raise ValueError(
|
||||
f"Con {max_layers} capas y {max_turns_per_layer} vueltas/capa "
|
||||
f"sólo se pueden alojar {max_turns_possible} espiras, "
|
||||
f"pero se solicitan {N_total}."
|
||||
)
|
||||
|
||||
espiras = []
|
||||
vueltas_restantes = N_total
|
||||
layer_index = 0
|
||||
|
||||
while vueltas_restantes > 0 and layer_index < max_layers:
|
||||
r_layer = R_int + espesor_plast + layer_index * wire_d + wire_d / 2.0
|
||||
|
||||
if vueltas_restantes >= max_turns_per_layer:
|
||||
n_vueltas = max_turns_per_layer
|
||||
else:
|
||||
n_vueltas = vueltas_restantes
|
||||
|
||||
for turn in range(n_vueltas):
|
||||
z_i = (turn + 0.5) * wire_d
|
||||
espiras.append((r_layer, z_i))
|
||||
|
||||
vueltas_restantes -= n_vueltas
|
||||
layer_index += 1
|
||||
|
||||
if vueltas_restantes > 0:
|
||||
raise ValueError(
|
||||
f"No se pudo colocar toda la bobina. "
|
||||
f"Faltan {vueltas_restantes} espiras por ubicar."
|
||||
)
|
||||
|
||||
return espiras
|
||||
|
||||
def inductance_from_spiralist(espiras, wire_radius):
|
||||
"""
|
||||
Calcula la inductancia total (en henrios) a partir de la lista (r_i, z_i),
|
||||
sumando la autoinductancia de cada espira y la mutua entre pares (O(N^2)).
|
||||
"""
|
||||
N = len(espiras)
|
||||
L_total = 0.0
|
||||
|
||||
for i in range(N):
|
||||
r_i, z_i = espiras[i]
|
||||
# Autoinductancia
|
||||
L_total += self_inductance_loop(r_i, wire_radius)
|
||||
# Mutua con las espiras j > i
|
||||
for j in range(i + 1, N):
|
||||
r_j, z_j = espiras[j]
|
||||
dist_z = abs(z_j - z_i)
|
||||
M_ij = mutual_inductance_coaxial(r_i, r_j, dist_z)
|
||||
L_total += 2.0 * M_ij
|
||||
|
||||
return abs(L_total)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Importamos la función para dibujar la bobina 2D (tal y como estaba).
|
||||
# -------------------------------------------------------------------
|
||||
from tabs.geometry_viewer import plot_coil_in_frame
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Definición de la clase TabCoil, añadiendo la ventana de logs y
|
||||
# el botón de cálculo de inductancia con mensajes de estado.
|
||||
# -------------------------------------------------------------------
|
||||
class TabCoil:
|
||||
def __init__(self, notebook, tab_simulator):
|
||||
"""
|
||||
@ -30,9 +155,6 @@ class TabCoil:
|
||||
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)
|
||||
@ -50,18 +172,15 @@ class TabCoil:
|
||||
tk.Label(frame_left, text="Parámetros de la Bobina:").pack(anchor="w")
|
||||
|
||||
# Variables
|
||||
self.var_N = tk.DoubleVar(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)
|
||||
|
||||
mu_0 = 4 * m.pi * 1e-7
|
||||
mu_r = 1
|
||||
|
||||
inductance = (self.var_N.get()**2 * mu_0 * mu_r * (2 * m.pi * (self.var_r_ext.get() * 1e-3)**2)) / (self.var_h_c.get() * 1e-3)
|
||||
self.var_L = tk.DoubleVar(value=inductance)
|
||||
self.var_N = tk.DoubleVar(value=500) # Número de espiras
|
||||
self.var_e_pvc = tk.DoubleVar(value=2) # Espesor del plástico [mm]
|
||||
self.var_r_int = tk.DoubleVar(value=4.04) # Radio interior [mm]
|
||||
self.var_r_ext = tk.DoubleVar(value=10.64) # Radio exterior [mm]
|
||||
self.var_h_c = tk.DoubleVar(value=53.12) # Altura de la bobina [mm]
|
||||
self.var_d_cu = tk.DoubleVar(value=0.5) # Diámetro conductor [mm]
|
||||
|
||||
# Variable para mostrar la inductancia (se mostrará en µH)
|
||||
self.var_L = tk.StringVar(value="")
|
||||
|
||||
# Frame para agrupar los Entries
|
||||
self.frame_params = tk.Frame(frame_left, bd=1, relief="sunken")
|
||||
@ -74,26 +193,48 @@ class TabCoil:
|
||||
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
|
||||
add_param_row("L (H):", self.var_L, rowcount); rowcount += 1
|
||||
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
|
||||
# Campo para ver la inductancia en microHenrios (µH).
|
||||
lblL = tk.Label(self.frame_params, text="Induct. (µH):")
|
||||
lblL.grid(row=rowcount, column=0, sticky="w", padx=5, pady=2)
|
||||
entL = tk.Entry(self.frame_params, textvariable=self.var_L, width=10, state="readonly")
|
||||
entL.grid(row=rowcount, column=1, padx=5, pady=2)
|
||||
rowcount += 1
|
||||
|
||||
# Botones
|
||||
btn_refrescar = tk.Button(
|
||||
frame_left, text="Refrescar Vista 2D",
|
||||
command=self.refrescar_2d
|
||||
)
|
||||
btn_refrescar.pack(pady=10)
|
||||
|
||||
btn_calc_L = tk.Button(
|
||||
frame_left, text="Calcular Inductancia",
|
||||
command=self.calcular_inductancia
|
||||
)
|
||||
btn_calc_L.pack(pady=5)
|
||||
|
||||
#
|
||||
# Creamos la figura y canvas para la bobina en 2D (opcional,
|
||||
# pues 'plot_coil_in_frame' creará su propia Figure).
|
||||
# Sección de Logs
|
||||
#
|
||||
tk.Label(frame_left, text="Logs:").pack(anchor="w")
|
||||
self.log_area = ScrolledText(frame_left, wrap="word", height=8, width=35)
|
||||
self.log_area.pack(fill="x", padx=5, pady=5)
|
||||
self.log_area.configure(state="disabled")
|
||||
|
||||
# Definimos etiquetas de color
|
||||
self.log_area.tag_config("info", foreground="green")
|
||||
self.log_area.tag_config("error", foreground="red")
|
||||
|
||||
#
|
||||
# Figura y canvas para la bobina en 2D
|
||||
#
|
||||
self.fig = plt.Figure(figsize=(4,3), dpi=100)
|
||||
self.ax = self.fig.add_subplot(111)
|
||||
@ -104,12 +245,21 @@ class TabCoil:
|
||||
# Dibujamos la bobina inicial
|
||||
self.refrescar_2d()
|
||||
|
||||
def log_message(self, text, mode="info"):
|
||||
"""
|
||||
Inserta un mensaje en el log_area, en color definido por 'mode'.
|
||||
mode puede ser 'info' (verde) o 'error' (rojo).
|
||||
"""
|
||||
self.log_area.configure(state="normal")
|
||||
self.log_area.insert("end", text + "\n", mode)
|
||||
self.log_area.see("end")
|
||||
self.log_area.configure(state="disabled")
|
||||
|
||||
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()
|
||||
@ -117,7 +267,7 @@ class TabCoil:
|
||||
h_c_val = self.var_h_c.get()
|
||||
d_cu_val = self.var_d_cu.get()
|
||||
|
||||
# Limpiamos el Axes actual (si lo usáramos)
|
||||
# Limpiar el Axes
|
||||
self.ax.clear()
|
||||
|
||||
# Llamada a la función que inserta la figura en self.frame_2d
|
||||
@ -130,3 +280,56 @@ class TabCoil:
|
||||
h_c=h_c_val,
|
||||
d_cu=d_cu_val
|
||||
)
|
||||
|
||||
def calcular_inductancia(self):
|
||||
"""
|
||||
Cálculo de la inductancia a partir de los parámetros introducidos.
|
||||
Muestra el resultado en µH en el campo correspondiente y en la log_area.
|
||||
"""
|
||||
try:
|
||||
# Lectura de parámetros
|
||||
N_espiras = int(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()
|
||||
|
||||
# Conversión mm -> m
|
||||
R_in = r_int_val / 1000.0
|
||||
R_out = r_ext_val / 1000.0
|
||||
plast = e_pvc_val / 1000.0
|
||||
altura = h_c_val / 1000.0
|
||||
diam_hilo = d_cu_val / 1000.0
|
||||
|
||||
# Construir la geometría de la bobina
|
||||
espiras_xyz = compute_coil_geometry(
|
||||
N_total=N_espiras,
|
||||
R_int=R_in,
|
||||
R_ext=R_out,
|
||||
espesor_plast=plast,
|
||||
H=altura,
|
||||
wire_d=diam_hilo
|
||||
)
|
||||
|
||||
# Radio efectivo para autoinductancia
|
||||
wire_radius = diam_hilo / 2.0
|
||||
|
||||
# Calcular la inductancia total (en henrios)
|
||||
L_h = inductance_from_spiralist(espiras_xyz, wire_radius)
|
||||
L_uH = L_h * 1e6
|
||||
|
||||
# Actualizar variable de resultado
|
||||
self.var_L.set(f"{L_uH:.3f}")
|
||||
|
||||
# Log de éxito
|
||||
self.log_message("Inductancia calculada correctamente", "info")
|
||||
|
||||
except ValueError as e:
|
||||
# Si hay algún problema con la geometría, etc.
|
||||
self.var_L.set("Error")
|
||||
self.log_message(f"Error en cálculo: {str(e)}", "error")
|
||||
except Exception as ex:
|
||||
# Errores generales
|
||||
self.var_L.set("Error")
|
||||
self.log_message(f"Error inesperado: {str(ex)}", "error")
|
||||
|
Loading…
Reference in New Issue
Block a user