Tab Power

This commit is contained in:
Pedro Romero 2025-03-10 14:00:45 +01:00
parent b05a0ff729
commit 8bb059f4c4
14 changed files with 848 additions and 1136 deletions

14
Docs/Models.aux Normal file
View File

@ -0,0 +1,14 @@
\relax
\@writefile{toc}{\contentsline {section}{\numberline {1}Introducción}{1}{}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {2}Bobina e Inductancias}{1}{}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Autoinductancia de una espira}{1}{}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}Inductancia mutua entre dos espiras coaxiales}{1}{}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}Inductancia total de la bobina}{1}{}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {2.4}Resistencia en el bobinado de cobre}{1}{}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {3}Rozamiento Aerodinámico: Coeficiente $b$}{2}{}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {4}Dinámica del Proyectil}{2}{}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}Caso sin rozamiento}{2}{}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}Caso con rozamiento lineal}{2}{}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {5}Energía Mecánica}{2}{}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {6}Conclusión}{2}{}\protected@file@percent }
\gdef \@abspage@last{2}

58
Docs/Models.fdb_latexmk Normal file
View File

@ -0,0 +1,58 @@
# Fdb version 4
["pdflatex"] 1741606732.43984 "c:/Users/pedro/Desktop/Projects/source/Docs/Models.tex" "Models.pdf" "Models" 1741606733.02877 0
"C:/Users/pedro/AppData/Local/MiKTeX/fonts/map/pdftex/pdftex.map" 1739986129 80909 eab91d9745dd2edfd62a31d53cd5fe15 ""
"C:/Users/pedro/AppData/Local/MiKTeX/miktex/data/le/pdftex/pdflatex.fmt" 1739984920 12004849 764d2605c2e5b05bd7dacdf1301c2e98 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1233951848 1004 54797486969f23fa377b128694d548df ""
"D:/Appdata/MiKTeX/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1233951848 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/amsfonts/symbols/msam10.tfm" 1233951854 916 f87d7c45f9c908e672703b83b72241a3 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/amsfonts/symbols/msam7.tfm" 1233951854 928 2dc8d444221b7a635bb58038579b861a ""
"D:/Appdata/MiKTeX/fonts/tfm/public/amsfonts/symbols/msbm10.tfm" 1233951854 908 2921f8a10601f252058503cc6570e581 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/amsfonts/symbols/msbm7.tfm" 1233951854 940 228d6584342e91276bf566bcf9716b83 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmbx12.tfm" 1136765053 1324 c910af8c371558dc20f2d7822f66fe64 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmex10.tfm" 1136765053 992 662f679a0b3d2d53c1b94050fdaa3f50 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmmi12.tfm" 1136765053 1524 4414a8315f39513458b80dfc63bff03a ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmmi6.tfm" 1136765053 1512 f21f83efb36853c0b70002322c1ab3ad ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmmi8.tfm" 1136765053 1520 eccf95517727cb11801f4f1aee3a21b4 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmr12.tfm" 1136765053 1288 655e228510b4c2a1abe905c368440826 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmr17.tfm" 1136765053 1292 296a67155bdbfc32aa9c636f21e91433 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmr6.tfm" 1136765053 1300 b62933e007d01cfd073f79b963c01526 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmr8.tfm" 1136765053 1292 21c1c5bfeaebccffdb478fd231a0997d ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmsy10.tfm" 1136765053 1124 6c73e740cf17375f03eec0ee63599741 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmsy6.tfm" 1136765053 1116 933a60c408fc0a863a92debe84b2d294 ""
"D:/Appdata/MiKTeX/fonts/tfm/public/cm/cmsy8.tfm" 1136765053 1120 8b7d695260f3cff42e636090a8002094 ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmbx12.pfb" 1247596666 32080 340ef9bf63678554ee606688e7b5339d ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmex10.pfb" 1247596667 30251 6afa5cb1d0204815a708a080681d4674 ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmmi12.pfb" 1247596667 36741 0ee9e374ec3e30da87cdfb0ea3575226 ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmmi8.pfb" 1247596666 35469 dcf3a5f2fc1862f5952e3ee5eb1d98c4 ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmr12.pfb" 1247596667 32722 d7379af29a190c3f453aba36302ff5a9 ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmr17.pfb" 1247596666 32362 bc3f3eec7ab7d65fe700963d4017d32c ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmr6.pfb" 1247596667 32734 69e00a6b65cedb993666e42eedb3d48f ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmr8.pfb" 1247596667 32726 39f0f9e62e84beb801509898a605dbd5 ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmsy10.pfb" 1247596667 32569 5e5ddc8df908dea60932f3c484a54c0d ""
"D:/Appdata/MiKTeX/fonts/type1/public/amsfonts/cm/cmsy8.pfb" 1247596667 32626 5abc8bb2f28aa647d4c70f8ea38cc0d3 ""
"D:/Appdata/MiKTeX/tex/generic/iftex/iftex.sty" 1734114575 7984 7dbb9280f03c0a315425f1b4f35d43ee ""
"D:/Appdata/MiKTeX/tex/generic/iftex/ifvtex.sty" 1734114575 1057 525c2192b5febbd8c1f662c9468335bb ""
"D:/Appdata/MiKTeX/tex/latex/amsfonts/amsfonts.sty" 1358197772 5949 3f3fd50a8cc94c3d4cbf4fc66cd3df1c ""
"D:/Appdata/MiKTeX/tex/latex/amsfonts/amssymb.sty" 1358197772 13829 94730e64147574077f8ecfea9bb69af4 ""
"D:/Appdata/MiKTeX/tex/latex/amsfonts/umsa.fd" 1358197772 961 6518c6525a34feb5e8250ffa91731cff ""
"D:/Appdata/MiKTeX/tex/latex/amsfonts/umsb.fd" 1358197772 961 d02606146ba5601b5645f987c92e6193 ""
"D:/Appdata/MiKTeX/tex/latex/amsmath/amsbsy.sty" 1731152860 2222 2166a1f7827be30ddc30434e5efcee1b ""
"D:/Appdata/MiKTeX/tex/latex/amsmath/amsgen.sty" 1731152859 4173 d22509bc0c91281d991b2de7c88720dd ""
"D:/Appdata/MiKTeX/tex/latex/amsmath/amsmath.sty" 1731152860 88370 c780f23aea0ece6add91e09b44dca2cd ""
"D:/Appdata/MiKTeX/tex/latex/amsmath/amsopn.sty" 1731152860 4474 23ca1d3a79a57b405388059456d0a8df ""
"D:/Appdata/MiKTeX/tex/latex/amsmath/amstext.sty" 1731152860 2444 71618ea5f2377e33b04fb97afdd0eac2 ""
"D:/Appdata/MiKTeX/tex/latex/base/article.cls" 1738160243 20144 63d8bacaf52e5abf4db3bc322373e1d4 ""
"D:/Appdata/MiKTeX/tex/latex/base/inputenc.sty" 1738160243 5048 0270515b828149155424600fd2d58ac5 ""
"D:/Appdata/MiKTeX/tex/latex/base/size12.clo" 1738160243 8449 ffe4ba2166a344827c3a832d1d5e0a91 ""
"D:/Appdata/MiKTeX/tex/latex/geometry/geometry.cfg" 1578053545 1104 7ac475a4e3466b0b43e138e9356bda83 ""
"D:/Appdata/MiKTeX/tex/latex/geometry/geometry.sty" 1578053545 42759 9cf6c5257b1bc7af01a58859749dd37a ""
"D:/Appdata/MiKTeX/tex/latex/graphics/keyval.sty" 1730544106 2671 70891d50dac933918b827d326687c6e8 ""
"D:/Appdata/MiKTeX/tex/latex/l3backend/l3backend-pdftex.def" 1716480499 29785 9f93ab201fe5dd053afcc6c1bcf7d266 ""
"Models.aux" 1741606732 1429 8fbbeca065203156c4d218ad6b7c149b "pdflatex"
"Models.tex" 1741606731 4396 231b46402b7e7a479b713b2d10ce0d0d ""
"c:/Users/pedro/Desktop/Projects/source/Docs/Models.tex" 1741606731 4396 231b46402b7e7a479b713b2d10ce0d0d ""
(generated)
"Models.aux"
"Models.log"
"Models.pdf"
(rewritten before read)

109
Docs/Models.fls Normal file
View File

@ -0,0 +1,109 @@
PWD c:\Users\pedro\Desktop\Projects\source\Docs
INPUT C:\Users\pedro\AppData\Local\MiKTeX\miktex\data\le\pdftex\pdflatex.fmt
INPUT c:\Users\pedro\Desktop\Projects\source\Docs\Models.tex
OUTPUT Models.log
INPUT D:\Appdata\MiKTeX\tex\latex\base\article.cls
INPUT D:\Appdata\MiKTeX\tex\latex\base\article.cls
INPUT D:\Appdata\MiKTeX\tex\latex\base\size12.clo
INPUT D:\Appdata\MiKTeX\tex\latex\base\size12.clo
INPUT D:\Appdata\MiKTeX\tex\latex\base\size12.clo
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmr12.tfm
INPUT D:\Appdata\MiKTeX\tex\latex\base\inputenc.sty
INPUT D:\Appdata\MiKTeX\tex\latex\base\inputenc.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsmath.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsmath.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsopn.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amstext.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amstext.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsgen.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsgen.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsbsy.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsbsy.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsmath\amsopn.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\amsfonts.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\amsfonts.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\amssymb.sty
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\amssymb.sty
INPUT D:\Appdata\MiKTeX\tex\latex\geometry\geometry.sty
INPUT D:\Appdata\MiKTeX\tex\latex\geometry\geometry.sty
INPUT D:\Appdata\MiKTeX\tex\latex\graphics\keyval.sty
INPUT D:\Appdata\MiKTeX\tex\latex\graphics\keyval.sty
INPUT D:\Appdata\MiKTeX\tex\generic\iftex\ifvtex.sty
INPUT D:\Appdata\MiKTeX\tex\generic\iftex\ifvtex.sty
INPUT D:\Appdata\MiKTeX\tex\generic\iftex\iftex.sty
INPUT D:\Appdata\MiKTeX\tex\generic\iftex\iftex.sty
INPUT D:\Appdata\MiKTeX\tex\latex\geometry\geometry.cfg
INPUT D:\Appdata\MiKTeX\tex\latex\geometry\geometry.cfg
INPUT D:\Appdata\MiKTeX\tex\latex\geometry\geometry.cfg
INPUT D:\Appdata\MiKTeX\tex\latex\l3backend\l3backend-pdftex.def
INPUT D:\Appdata\MiKTeX\tex\latex\l3backend\l3backend-pdftex.def
INPUT .\Models.aux
INPUT .\Models.aux
INPUT Models.aux
OUTPUT Models.aux
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmr17.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmr12.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmmi12.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmsy10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmex10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\cmextra\cmex7.tfm
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\umsa.fd
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\umsa.fd
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\umsa.fd
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam7.tfm
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\umsb.fd
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\umsb.fd
INPUT D:\Appdata\MiKTeX\tex\latex\amsfonts\umsb.fd
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm7.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmr17.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmbx12.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmbx12.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmr8.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmr6.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmmi12.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmmi8.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmmi6.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmsy10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmsy8.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmsy6.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmex10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\cmextra\cmex8.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\cmextra\cmex7.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam7.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm7.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmmi12.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmsy10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\cm\cmex10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msam10.tfm
INPUT D:\Appdata\MiKTeX\fonts\tfm\public\amsfonts\symbols\msbm10.tfm
OUTPUT Models.pdf
INPUT C:\Users\pedro\AppData\Local\MiKTeX\fonts\map\pdftex\pdftex.map
INPUT Models.aux
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmbx12.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmbx12.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmex10.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmex10.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmmi12.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmmi12.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmmi8.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmmi8.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr12.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr12.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr17.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr17.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr6.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr6.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr8.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmr8.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmsy10.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmsy10.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmsy8.pfb
INPUT D:\Appdata\MiKTeX\fonts\type1\public\amsfonts\cm\cmsy8.pfb

BIN
Docs/Models.pdf Normal file

Binary file not shown.

BIN
Docs/Models.synctex.gz Normal file

Binary file not shown.

83
Docs/Models.tex Normal file
View File

@ -0,0 +1,83 @@
% Archivo: modelos.tex
% Para compilar: pdflatex modelos.tex
\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath, amsfonts, amssymb}
\usepackage{geometry}
\geometry{a4paper, margin=2cm}
\begin{document}
\title{Modelos Físicos Empleados: Informe Detallado}
\author{Equipo de Desarrollo}
\date{\today}
\maketitle
\section{Introducción}
En este documento se recogen los fundamentos físicos y las fórmulas clave que se han empleado en las distintas partes de nuestro proyecto de simulación. Cubriremos aspectos como la autoinductancia de espiras, el cálculo de inductancia total en una bobina, la resistencia en cobre, la fuerza de rozamiento aerodinámico, la dinámica de proyectiles (con y sin rozamiento) y el cómputo de energía mecánica.
\section{Bobina e Inductancias}
\subsection{Autoinductancia de una espira}
La autoinductancia de un anillo de radio $r$ se calcula mediante una fórmula aproximada:
\begin{equation}
L_{\text{loop}} \approx \mu_{0}\,r\,\biggl[\ln\bigl(\tfrac{8r}{a_{\mathrm{eff}}}\bigr) - 2\biggr],
\end{equation}
\noindent donde $\mu_{0} = 4\pi \times 10^{-7}\,\mathrm{H/m}$ y $a_{\mathrm{eff}}$ es el radio efectivo que toma en cuenta el grosor del conductor.
\subsection{Inductancia mutua entre dos espiras coaxiales}
Para dos espiras coaxiales de radios $r_{1}$ y $r_{2}$, separadas una distancia axial $z$, se emplean integrales elípticas completas de primera ($K$) y segunda especie ($E$). La inductancia mutua $M$ se expresa:
\begin{equation}
M = \mu_{0}\,\sqrt{r_{1}r_{2}}\,\frac{(2 - k)\,K(k^{2}) - 2\,E(k^{2})}{k}, \quad\text{con}\quad k^{2} = \frac{4\,r_{1}\,r_{2}}{(r_{1}+r_{2})^{2} + z^{2}}.
\end{equation}
\subsection{Inductancia total de la bobina}
El total se obtiene sumando la autoinductancia de cada espira y la mutua entre pares:
\begin{equation}
L_{\text{total}} = \sum_{i} L_{\text{espira},i} + 2 \sum_{i<j} M_{ij}.
\end{equation}
Esta aproximación es de orden $O(N^2)$ al considerar todos los pares de espiras.
\subsection{Resistencia en el bobinado de cobre}
La longitud total del hilo, $\ell_{\mathrm{total}}$, se calcula como la suma de las circunferencias de cada espira. Luego:
\begin{equation}
R = \rho_{\mathrm{Cu}}\,\frac{\ell_{\mathrm{total}}}{A}, \quad \text{con}\quad A = \pi\,r_{\text{hilo}}^{2},
\end{equation}
\noindent donde $\rho_{\mathrm{Cu}} \approx 1.68\times 10^{-8}\,\Omega\cdot\mathrm{m}$.
\section{Rozamiento Aerodinámico: Coeficiente $b$}
Para simplificar el arrastre, se ha empleado un modelo lineal, $F_{\mathrm{drag}} = -b\,\vec{v}$. El coeficiente $b$ se relaciona con la forma tradicional de arrastre $\frac12\rho C_{D} A\,v^2$ a través de
\begin{equation}
b = 0.5\,\rho\,C_{D}\,A,
\end{equation}
\noindent donde $\rho$ es la densidad del aire, $C_{D}$ el coeficiente de forma (depende de la geometría) y $A$ el área frontal.
\section{Dinámica del Proyectil}
\subsection{Caso sin rozamiento}
El movimiento se reduce a un tiro parabólico clásico:
\begin{align}
a_{x} &= 0, && v_{x} = \text{cte}, \\[-3pt]
a_{y} &= -g, && v_{y}(t+\Delta t) = v_{y}(t) - g\,\Delta t.
\end{align}
La posición se actualiza integrando $x(t+\Delta t)=x(t)+v_{x}(t)\,\Delta t$, y análogamente para $y$.
\subsection{Caso con rozamiento lineal}
Incluimos la fuerza $F_{\mathrm{drag}} = -b\,\vec{v}$, por lo que:
\begin{align}
m\,a_{x} &= -b\,v_{x} \implies a_{x} = -\tfrac{b}{m}\,v_{x},\\
m\,a_{y} &= -m\,g - b\,v_{y} \implies a_{y} = -g - \tfrac{b}{m}\,v_{y}.
\end{align}
Luego se integra numéricamente (método de Euler) en pequeños pasos de tiempo $\Delta t$.
\section{Energía Mecánica}
En todo momento podemos calcular:
\begin{align}
E_{c} &= \tfrac12\,m\,(v_{x}^{2}+v_{y}^{2}),\\
E_{p} &= m\,g\,y, \quad\text{si } y \ge 0,\\
E_{\text{total}} &= E_{c} + E_{p}.
\end{align}
El arrastre disipa energía, de modo que $E_{\text{total}}$ no permanece constante si $b\neq0$.
\section{Conclusión}
Hemos revisado los principales modelos físicos que sustentan cada parte del proyecto: el diseño de la bobina (inductancia y resistencia), el rozamiento aerodinámico y la dinámica de un proyectil con o sin rozamiento. Estas ecuaciones y supuestos constituyen la base para la simulación y los cálculos de energías, alcance, y optimización del ángulo.
\end{document}

View File

@ -1,25 +1,21 @@
# main.py
import sys
sys.path.append("C:\\Users\\promerogomb\\Desktop\\Proyectos UNAV\\LaunchSim\\src")
import os
import tkinter as tk
from tkinter import ttk
# Importamos las distintas pestañas
# Ajusta la ruta si fuera necesario:
# sys.path.append("C:\\Users\\pedro\\Desktop\\Projects\\source\\src")
from tabs.tab_simulator import TabSimulator
from tabs.tab_search import TabSearch
from tabs.tab_drag import TabDrag
from tabs.tab_coil import TabCoil
from tabs.tab_dynamic import TabDynamic
from tabs.tab_power import TabPower
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)
@ -44,10 +40,9 @@ class MainApp:
# 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: Cálculos dinámicos
tab_dyn = TabDynamic(self.notebook, self.tab_coil, self.tab_sim, self.tab_drag)
self.notebook.add(tab_dyn.frame, text="Cálculos Dinámicos")
# Pestaña 5: Nuevo TabPower
self.tab_power = TabPower(self.notebook, self.tab_coil, self.tab_sim)
if __name__ == "__main__":
root = tk.Tk()

View File

@ -1,47 +1,35 @@
# 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 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)
"""
"""Dibuja distintas geometrías 3D en un Frame de Tkinter, sin abrir ventana nueva."""
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
lado, largo = p1, 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:
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
r, h = p1, 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
T, Z = np.meshgrid(theta, z)
X = r*np.cos(T)
Y = r*np.sin(T)
ax.plot_surface(X, Y, Z, color='cyan', alpha=0.5)
ax.set_xlim(-r, r)
ax.set_ylim(-r, r)
@ -51,124 +39,65 @@ def plot_geometry_in_frame(parent_frame, geom, p1, p2):
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)
Phi, Theta = np.meshgrid(phi, theta)
X = r*np.sin(Phi)*np.cos(Theta)
Y = r*np.sin(Phi)*np.sin(Theta)
Z = r*np.cos(Phi)
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
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 de forma aproximada una bobina en 2D (vista de corte)."""
r_capas = r_ext - (r_int + e_pvc)
if r_capas < 0:
# Si ocurre un valor absurdo, forzamos a 0
r_capas = 0.0
if r_capas < 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
)
# PVC a izquierda y derecha
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
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
y = (j + 1)*delta_h
if y > h_c: break
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)

View File

@ -1,335 +1,191 @@
# src/tab_coil.py
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import math as m
# 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)
MU0 = 4.0e-7 * m.pi # Permeabilidad magnética vacío (H/m)
RHO_CU = 1.68e-8 # Resistividad cobre (Ohm·m) a 20°C
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)
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
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: 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
if (1.0 - k_sq) < 1e-12:
return MU0*min(r1, r2)*m.pi
Kk, Ek = ellipk(k_sq), ellipe(k_sq)
factor = MU0*np.sqrt(r1*r2)
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)
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}."
)
raise ValueError(f"Se necesitan {N_total} espiras, pero solo caben {max_turns_possible}.")
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
vueltas_rest = N_total
layer_idx = 0
while vueltas_rest > 0 and layer_idx < max_layers:
r_layer = R_int + espesor_plast + layer_idx*wire_d + wire_d/2.0
n_vueltas = max_turns_per_layer if vueltas_rest >= max_turns_per_layer else vueltas_rest
for turn in range(n_vueltas):
z_i = (turn + 0.5) * wire_d
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."
)
vueltas_rest -= n_vueltas
layer_idx += 1
if vueltas_rest > 0:
raise ValueError(f"Faltan {vueltas_rest} 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)).
"""
"""Suma autoinductancias + mutuas (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
M_ij = mutual_inductance_coaxial(r_i, r_j, abs(z_j - z_i))
L_total += 2.0*M_ij
return abs(L_total)
# -------------------------------------------------------------------
# Importamos la función para dibujar la bobina 2D (tal y como estaba).
# -------------------------------------------------------------------
# Importamos la función para dibujar la bobina
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):
"""
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)
# 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_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.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.035) # 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_N = tk.DoubleVar(value=500)
self.var_e_pvc = tk.DoubleVar(value=2)
self.var_r_int = tk.DoubleVar(value=4.035)
self.var_r_ext = tk.DoubleVar(value=10.64)
self.var_h_c = tk.DoubleVar(value=53.12)
self.var_d_cu = tk.DoubleVar(value=0.5)
self.var_L = tk.StringVar(value="")
self.var_R = tk.StringVar(value="")
# 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)
def add_param_row(lbl_txt, var, row, w=8):
tk.Label(self.frame_params, text=lbl_txt).grid(row=row, column=0, sticky="w", padx=5, pady=2)
tk.Entry(self.frame_params, textvariable=var, width=w).grid(row=row, column=1, padx=5, pady=2)
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("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
# 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)
tk.Label(self.frame_params, text="Induct. (µH):").grid(row=rowcount, column=0, sticky="w", padx=5, pady=2)
tk.Entry(self.frame_params, textvariable=self.var_L, width=10, state="readonly").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)
tk.Label(self.frame_params, text="R (Ohms):").grid(row=rowcount, column=0, sticky="w", padx=5, pady=2)
tk.Entry(self.frame_params, textvariable=self.var_R, width=10, state="readonly").grid(row=rowcount, column=1, padx=5, pady=2)
rowcount += 1
btn_calc_L = tk.Button(
frame_left, text="Calcular Inductancia",
command=self.calcular_inductancia
)
btn_calc_L.pack(pady=5)
tk.Button(frame_left, text="Refrescar Vista 2D", command=self.refrescar_2d).pack(pady=10)
tk.Button(frame_left, text="Calcular", command=self.calcular_inductancia).pack(pady=5)
#
# 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)
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 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.
"""
N_val = self.var_N.get()
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()
# Limpiar el Axes
h_c_val = self.var_h_c.get()
d_cu_val = self.var_d_cu.get()
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
)
plot_coil_in_frame(self.frame_2d, N_val, e_pvc_val, r_int_val, r_ext_val, h_c_val, 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()
h_c_val = self.var_h_c.get()
d_cu_val = self.var_d_cu.get()
R_in, R_out = r_int_val/1000.0, r_ext_val/1000.0
plast = e_pvc_val/1000.0
altura = h_c_val/1000.0
diam_hilo = d_cu_val/1000.0
# 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)
espiras_xyz = compute_coil_geometry(N_espiras, R_in, R_out, plast, altura, diam_hilo)
wire_radius = diam_hilo/2.0
L_h = inductance_from_spiralist(espiras_xyz, wire_radius)
L_uH = L_h * 1e6
L_uH = L_h*1e6
wire_length = 0.0
for (r_i, _) in espiras_xyz:
wire_length += 2.0*m.pi*r_i
wire_area = m.pi*(wire_radius**2)
R_coil = RHO_CU*(wire_length/wire_area)
# Actualizar variable de resultado
self.var_L.set(f"{L_uH:.3f}")
# Log de éxito
self.log_message("Inductancia calculada correctamente", "info")
self.var_R.set(f"{R_coil:.3f}")
self.log_message(f"Inductancia: {L_uH:.3f} µH, Resistencia: {R_coil:.3f} Ω", "info")
except ValueError as e:
# Si hay algún problema con la geometría, etc.
self.var_L.set("Error")
self.var_R.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")
self.var_R.set("Error")
self.log_message(f"Error inesperado: {str(ex)}", "error")

View File

@ -1,7 +1,6 @@
import tkinter as tk
from tkinter import ttk
import math
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
@ -11,66 +10,39 @@ 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
y exponer métodos para la reluctancia (l_fe y S_disp).
"""
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_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)
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 = 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 = 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 = 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()
tk.Button(frame_left, text="Calcular Coef. Rozamiento", command=self.calcular_coef_rozamiento).pack(pady=5)
tk.Button(frame_left, text="Refrescar Vista 3D", command=self.refrescar_3d).pack(pady=10)
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)
@ -99,58 +71,42 @@ class TabDrag:
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:
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
p2 = float(self.entry_param2.get()) if self.entry_param2.cget("state") != "disabled" else 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')
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
r = p1
h = p2
r, h = p1, 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)
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
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)
Phi,T = np.meshgrid(phi,t)
X = r*np.sin(Phi)*np.cos(T)
Y = r*np.sin(Phi)*np.sin(T)
Z = r*np.cos(Phi)
self.ax.plot_surface(X,Y,Z,color='orange',alpha=0.8)
self.ax.set_box_aspect((2*r,2*r,2*r))
else:
@ -160,113 +116,63 @@ class TabDrag:
self.canvas_3d.draw()
def calcular_coef_rozamiento(self):
"""
Calcula un coeficiente b ~ 0.5*rho*Cd*A
"""
geom = self.geometria_var.get()
try:
p1 = float(self.entry_param1.get())
except:
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())
Cd, A = 1.0, 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())
Cd, A = 1.15, 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
A=math.pi*(p1**2)
elif geom=="Esfera":
Cd=0.47
A=math.pi*(p1**2)
rho=1.225
b_calc=0.5*rho*Cd*A
Cd, A = 0.82, math.pi*(p1**2)
elif geom == "Esfera":
Cd, A = 0.47, 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)
def get_l_fe(self):
"""
Devuelve la 'altura' l_fe (m) según la geometría:
- Prisma cuadrado: param2 (Longitud)
- Cilindro: param2 (Altura)
- Esfera: 2*radio (diámetro)
"""
geom = self.geometria_var.get()
try:
p1 = float(self.entry_param1.get())
except ValueError:
p1 = 1.0
try: p1 = float(self.entry_param1.get())
except ValueError: p1 = 1.0
l_fe_val = 1.0
if geom == "Prisma cuadrado":
try:
l_fe_val = float(self.entry_param2.get())
except ValueError:
l_fe_val = 1.0
try: l_fe_val = float(self.entry_param2.get())
except ValueError: l_fe_val = 1.0
elif geom == "Cilindro":
try:
l_fe_val = float(self.entry_param2.get())
except ValueError:
l_fe_val = 1.0
try: l_fe_val = float(self.entry_param2.get())
except ValueError: l_fe_val = 1.0
elif geom == "Esfera":
# Usamos 2*r (diámetro). Ajusta si prefieres 0
l_fe_val = 2.0 * p1
l_fe_val = 2.0*p1
return l_fe_val
def get_projectile_section_for_reluctance(self, tab_coil):
"""
Devuelve S_disp (m^2) para la fórmula de reluctancia:
- Si la geometría es "Esfera", => S_disp = 2 * S_bob
con S_bob = pi * (r_ext^2)
(r_ext lo lees de tab_coil en mm, conv a m)
- En otros casos => área frontal real (p1^2 si prisma, pi*r^2 si cilindro).
"""
geom = self.geometria_var.get()
# 1) Leer r_ext de tab_coil (en mm) => m
try:
r_ext_mm = float(tab_coil.var_r_ext.get())
except:
r_ext_mm = 10.0
try: r_ext_mm = float(tab_coil.var_r_ext.get())
except: r_ext_mm = 10.0
r_ext_m = r_ext_mm / 1000.0
# S_bob = pi*(r_ext_m^2)
s_bob = math.pi*(r_ext_m**2)
# 2) Leer p1
try:
p1 = float(self.entry_param1.get())
except ValueError:
p1 = 1.0
try: p1 = float(self.entry_param1.get())
except ValueError: p1 = 1.0
if geom == "Esfera":
# S_disp = 2*S_bob
return 2.0 * s_bob
return 2.0*s_bob
elif geom == "Prisma cuadrado":
# Sección frontal ~ p1 x p1
return (p1**2)
return p1**2
elif geom == "Cilindro":
# Sección frontal ~ pi*(radio^2) = pi*(p1^2)
return math.pi*(p1**2)
else:
# Por defecto, algo por si la geo no es conocida
return 1.0

View File

@ -1,307 +0,0 @@
# src/tab_dynamic.py
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import math
import numpy as np
MU0 = 4.0e-7 * math.pi
class TabDynamic:
def __init__(self, notebook, tab_coil=None, tab_simulator=None, tab_drag=None):
"""
Pestaña que:
- Obtiene el trabajo W desde tab_simulator.
- Calcula la reluctancia media con la integración en x in [0, h_c].
- Usa el modelo W = ((N I)^2 * h_c)/(4 mu0 S_fe (sumR_avg)^2) para hallar I.
- Ofrece dimensionamiento de fuente (condensador / DC).
"""
self.notebook = notebook
self.tab_coil = tab_coil
self.tab_simulator = tab_simulator
self.tab_drag = tab_drag
self.frame = tk.Frame(notebook)
self.frame.pack(fill="both", expand=True)
# Panel Izq: Botones / parámetros
left_frame = tk.Frame(self.frame)
left_frame.pack(side="left", fill="y", padx=5, pady=5)
# Panel Der: Logs
right_frame = tk.Frame(self.frame, bd=2, relief="groove")
right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
# Sección 1: Importar Config
import_frame = tk.LabelFrame(left_frame, text="1) Importar Config")
import_frame.pack(fill="x", pady=5)
btn_import = tk.Button(import_frame, text="Importar Config", command=self.importar_config)
btn_import.pack(pady=5)
self.var_W_mech = tk.StringVar(value="")
tk.Label(import_frame, text="W (J):").pack(anchor="w")
tk.Entry(import_frame, textvariable=self.var_W_mech, width=12, state="readonly").pack(pady=2)
self.W_mech = 0.0 # energía mecánica importada
# Sección 2: Calcular Reluctancia Media
rel_frame = tk.LabelFrame(left_frame, text="2) Reluctancia Media")
rel_frame.pack(fill="x", pady=5)
self.var_sumR_avg = tk.StringVar(value="")
tk.Button(rel_frame, text="Calcular Rel. Media", command=self.calcular_reluctancia_media).pack(pady=5)
tk.Label(rel_frame, text="sum(R)_avg:").pack(anchor="w")
tk.Entry(rel_frame, textvariable=self.var_sumR_avg, width=15, state="readonly").pack(pady=2)
self.sumR_avg = 0.0 # valor en el interior
# Sección 3: Calcular Corriente
work_frame = tk.LabelFrame(left_frame, text="3) Corriente Necesaria")
work_frame.pack(fill="x", pady=5)
self.var_S_fe = tk.DoubleVar(value=1e-4) # Sección en la zona férrea
tk.Label(work_frame, text="S_fe (m²):").grid(row=0, column=0, sticky="w", padx=5, pady=2)
tk.Entry(work_frame, textvariable=self.var_S_fe, width=10).grid(row=0, column=1, padx=5, pady=2)
tk.Button(work_frame, text="Calcular Corriente", command=self.calcular_corriente_trabajo).grid(row=1, column=0, columnspan=2, pady=5)
self.var_I_req = tk.StringVar(value="")
tk.Label(work_frame, text="I_req (A):").grid(row=2, column=0, sticky="w", padx=5, pady=2)
tk.Entry(work_frame, textvariable=self.var_I_req, width=10, state="readonly").grid(row=2, column=1, padx=5, pady=2)
# Sección 4: Dimensionar Fuente
fuente_frame = tk.LabelFrame(left_frame, text="4) Dimensionar Fuente")
fuente_frame.pack(fill="x", pady=5)
tk.Button(fuente_frame, text="Dimensionar Capacitor", command=self.dimensionar_condensador).pack(pady=5)
tk.Button(fuente_frame, text="Fuente DC minima", command=self.dimensionar_fuente_dc).pack(pady=5)
self.var_C_sug = tk.StringVar(value="")
self.var_V_sug = tk.StringVar(value="")
self.var_Vdc_min = tk.StringVar(value="")
tk.Label(fuente_frame, text="C (F) ~").pack(anchor="w")
tk.Entry(fuente_frame, textvariable=self.var_C_sug, width=15, state="readonly").pack(pady=2)
tk.Label(fuente_frame, text="Vcap (V) ~").pack(anchor="w")
tk.Entry(fuente_frame, textvariable=self.var_V_sug, width=15, state="readonly").pack(pady=2)
tk.Label(fuente_frame, text="V_DC min (V) ~").pack(anchor="w")
tk.Entry(fuente_frame, textvariable=self.var_Vdc_min, width=15, state="readonly").pack(pady=2)
# LOGS
tk.Label(right_frame, text="Logs:").pack(anchor="w")
self.log_area = ScrolledText(right_frame, wrap="word", height=25, width=50)
self.log_area.pack(fill="both", expand=True)
self.log_area.configure(state="disabled")
self.log_area.tag_config("info", foreground="green")
self.log_area.tag_config("error", foreground="red")
# --------------------------------------------------------
# Log helper
# --------------------------------------------------------
def log_message(self, msg, mode="info"):
self.log_area.configure(state="normal")
self.log_area.insert("end", msg + "\n", mode)
self.log_area.see("end")
self.log_area.configure(state="disabled")
# --------------------------------------------------------
# 1) Importar Config (energía W)
# --------------------------------------------------------
def importar_config(self):
"""
Lee la energía mecánica W de tab_simulator y la guarda en self.W_mech
"""
try:
if not self.tab_simulator:
raise ValueError("No hay tab_simulator para leer W.")
w_val = self.tab_simulator.get_energy_required()
if w_val<=0:
raise ValueError("Energía <= 0.")
self.W_mech = w_val
self.var_W_mech.set(f"{w_val:.3f}")
self.log_message(f"Importado W={w_val:.3f} J", "info")
except Exception as e:
self.log_message(f"Error importar_config: {e}", "error")
# --------------------------------------------------------
# 2) Calcular Reluctancia Media (sum(R)_avg)
# => integrando la ecuación sum_R(x)= ...
# --------------------------------------------------------
def calcular_reluctancia_media(self):
try:
if not self.tab_coil:
raise ValueError("Falta referencia a tab_coil.")
if not self.tab_drag:
raise ValueError("Falta referencia a tab_drag.")
# 1) Leer h_c, N, r_int (en mm->m)
h_c_m = float(self.tab_coil.var_h_c.get())/1000.0
if h_c_m<=0:
raise ValueError("h_c_m <=0.")
r_int_m = float(self.tab_coil.var_r_int.get())/1000.0
if r_int_m<=0:
raise ValueError("r_int_m <=0.")
# 2) l_fe => de tab_drag
l_fe_m = self.tab_drag.get_l_fe() # en metros
if l_fe_m<0:
raise ValueError("l_fe<0?")
# 3) s_disp => get_projectile_section_for_reluctance
s_disp = self.tab_drag.get_projectile_section_for_reluctance(self.tab_coil)
if s_disp<=0:
raise ValueError("s_disp <=0.")
# 4) s_c= pi*r_int^2
s_c = math.pi*(r_int_m**2)
if s_c<=0:
raise ValueError("s_c<=0.")
# 5) Definir sumR(x)
def sumR_of_x(x):
# sum_R = h_c/(mu0*s_disp)
# + (h_c + l_fe - x)/(mu0*s_disp)
# + (h_c - x)/(mu0*s_c)
return (
(h_c_m/(MU0*s_disp))
+ ((h_c_m + l_fe_m - x)/(MU0*s_disp))
+ ((h_c_m - x)/(MU0*s_c))
)
# 6) Integración en [0..h_c_m]
n_steps=200
dx = h_c_m/n_steps
area_sum=0.0
x_curr=0.0
for _ in range(n_steps):
x_mid= x_curr+0.5*dx
val_mid= sumR_of_x(x_mid)
area_sum+= val_mid
x_curr+=dx
area_sum*= dx
sumR_avg= area_sum/h_c_m
if sumR_avg<=1e-30:
raise ValueError("sumR_avg nulo o negativo.")
self.sumR_avg= sumR_avg
self.var_sumR_avg.set(f"{sumR_avg:.3e}")
self.log_message(f"Reluctancia media= {sumR_avg:.3e} H^-1", "info")
except ValueError as ve:
self.sumR_avg=0.0
self.var_sumR_avg.set("")
self.log_message(f"Error calc Reluctancia media: {ve}", "error")
except Exception as ex:
self.sumR_avg=0.0
self.var_sumR_avg.set("")
self.log_message(f"Excepción calc Reluct. media: {ex}", "error")
# --------------------------------------------------------
# 3) Calcular Corriente, con W = ((N I)^2 * h_c)/(4 mu0 S_fe (sumR_avg)^2)
# --------------------------------------------------------
def calcular_corriente_trabajo(self):
try:
if self.W_mech<=0:
raise ValueError("No hay W_mech positivo. Importa config primero.")
if self.sumR_avg<=0:
raise ValueError("sumR_avg<=0. Calcula la Reluctancia media antes.")
# 1) Leer h_c (mm->m), N, etc. de tab_coil
h_c_m = float(self.tab_coil.var_h_c.get())/1000.0
N_val = float(self.tab_coil.var_N.get())
if h_c_m<=0 or N_val<=0:
raise ValueError("Parámetros de bobina inválidos.")
# 2) Leer S_fe
S_fe_val= self.var_S_fe.get()
if S_fe_val<=0:
raise ValueError("S_fe <=0")
# 3) Tomamos sumR_avg, W
sumR_avg= self.sumR_avg
W= self.W_mech
# 4) W= ((N I)^2 * h_c)/(4 mu0 S_fe (sumR_avg)^2)
# => (N I)^2= [4 mu0 S_fe (sumR_avg)^2 W]/h_c
top= 4.0*MU0*S_fe_val*(sumR_avg**2)* W
bottom= h_c_m
if bottom<=1e-30:
raise ValueError("h_c_m=0?")
NI_sq= top/bottom
if NI_sq<=0:
raise ValueError("Resultado (N I)^2 <=0, no válido.")
NI= math.sqrt(NI_sq)
I_req= NI/ N_val
self.var_I_req.set(f"{I_req:.3f}")
self.log_message(f"I necesaria= {I_req:.3f} A", "info")
except ValueError as ve:
self.var_I_req.set("")
self.log_message(f"Error calc I trabajo: {ve}", "error")
except Exception as ex:
self.var_I_req.set("")
self.log_message(f"Excepción calc I trabajo: {ex}", "error")
# --------------------------------------------------------
# 4) Dimensionar la fuente (ejemplo condensador y DC)
# --------------------------------------------------------
def dimensionar_condensador(self):
"""
Ejemplo: suponer que la energía total W -> (1/2)C V^2
W_marg = W_mech * factor... etc.
"""
try:
W= self.W_mech
if W<=0:
raise ValueError("Primero importa config. W<=0?")
# suponer un factor "pérdidas"
eff=0.7
# E capacitor= W/ eff
E_cap= W/eff
# asumes C= 1mF => V= sqrt(2 E_cap/C)
# o V= 200 => C= 2E_cap/V^2, etc.
# Aquí arbitrariamente p.ej C=1e-3 y calculamos V
C_val= 1e-3
Vcalc= math.sqrt(2.0*E_cap/C_val)
self.var_C_sug.set(f"{C_val:.3g}")
self.var_V_sug.set(f"{Vcalc:.1f}")
self.log_message(f"Caps: C=1mF => V~{Vcalc:.1f} V (descarga)", "info")
except ValueError as ve:
self.log_message(f"Error dimensionar_condensador: {ve}", "error")
except Exception as ex:
self.log_message(f"Excepción dimensionar_condensador: {ex}", "error")
def dimensionar_fuente_dc(self):
"""
Ejemplo simple: V_dc >= R_coil * I_req
R_coil en ohms => la obtendrías o la metes manual.
"""
try:
Ireq_str= self.var_I_req.get()
Ireq= float(Ireq_str)
if Ireq<=0:
raise ValueError("I_req <=0? Calcula la corriente primero.")
# supongamos R_coil= 2 ohms (ejemplo)
R_coil= 2.0
V_dc= R_coil* Ireq
self.var_Vdc_min.set(f"{V_dc:.1f}")
self.log_message(f"Fuente DC~ {V_dc:.1f} V (min. ohmico)", "info")
except ValueError as ve:
self.log_message(f"Error dimensionar_fuente_dc: {ve}", "error")
except Exception as ex:
self.log_message(f"Excepción dimensionar_fuente_dc: {ex}", "error")

296
src/tabs/tab_power.py Normal file
View File

@ -0,0 +1,296 @@
# tab_power.py
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import math
class TabPower:
def __init__(self, notebook, tab_coil, tab_simulator):
"""
Pestaña para dimensionar C o V0 a partir de la energía mecánica deseada,
considerando n etapas/bobinas idénticas.
:param notebook: ttk.Notebook donde se añade esta pestaña
:param tab_coil: referencia a la instancia de TabCoil
:param tab_simulator: referencia a la instancia de TabSimulator (para leer E_target)
"""
self.notebook = notebook
self.tab_coil = tab_coil
self.tab_simulator = tab_simulator
# Frame principal para la pestaña
self.frame = tk.Frame(notebook)
self.frame.pack(fill="both", expand=True)
# Añadir la pestaña al notebook
notebook.add(self.frame, text="Power Stages (Energy-driven)")
# Layout: parte izquierda (entradas) y derecha (resultados / logs)
self.frame_left = tk.Frame(self.frame)
self.frame_left.pack(side="left", fill="y", padx=5, pady=5)
self.frame_right = tk.Frame(self.frame, bd=2, relief="groove")
self.frame_right.pack(side="right", fill="both", expand=True, padx=5, pady=5)
# ------------------------------
# Variables y widgets de entrada
# ------------------------------
# Modo de dimensionado
self.mode_var = tk.StringVar(value="Dimensionar C") # o "Dimensionar V0"
tk.Label(self.frame_left, text="Modo de Dimensionado:").pack(anchor="w")
self.combo_mode = ttk.Combobox(
self.frame_left, textvariable=self.mode_var,
values=["Dimensionar C", "Dimensionar V0"],
state="readonly"
)
self.combo_mode.bind("<<ComboboxSelected>>", self.on_mode_change)
self.combo_mode.pack(pady=2)
# Número de etapas
tk.Label(self.frame_left, text="Número de etapas (n):").pack(anchor="w")
self.var_n = tk.IntVar(value=3)
tk.Entry(self.frame_left, textvariable=self.var_n, width=10).pack(pady=2)
# Capacidad (si se dimensiona V0, la capacit se fija)
tk.Label(self.frame_left, text="Capacidad (µF):").pack(anchor="w")
self.var_C = tk.DoubleVar(value=100.0) # ejemplo
self.entry_C = tk.Entry(self.frame_left, textvariable=self.var_C, width=10)
self.entry_C.pack(pady=2)
# Tensión inicial (si se dimensiona C, la tensión se fija)
tk.Label(self.frame_left, text="Tensión inicial (V):").pack(anchor="w")
self.var_V0 = tk.DoubleVar(value=200.0) # ejemplo
self.entry_V0 = tk.Entry(self.frame_left, textvariable=self.var_V0, width=10)
self.entry_V0.pack(pady=2)
# Tiempo de descarga aproximado
tk.Label(self.frame_left, text="Tiempo de etapa (ms):").pack(anchor="w")
self.var_time_ms = tk.DoubleVar(value=10.0) # 10 ms
tk.Entry(self.frame_left, textvariable=self.var_time_ms, width=10).pack(pady=2)
# Botón dimensionar
tk.Button(self.frame_left, text="Dimensionar", command=self.dimensionar).pack(pady=5)
# ------------------------------
# Sección derecha: Logs/Resultados
# ------------------------------
tk.Label(self.frame_right, text="Resultados:", font=("Arial",10,"bold")).pack(anchor="nw", padx=5, pady=5)
self.log_area = ScrolledText(self.frame_right, wrap="word", height=20, width=50)
self.log_area.pack(fill="both", expand=True, padx=5, pady=5)
self.log_area.configure(state="disabled")
# Forzar actualización inicial de estados de input
self.on_mode_change()
def on_mode_change(self, event=None):
"""Habilita o deshabilita las entradas de C o V0 según el modo de dimensionado."""
mode = self.mode_var.get()
if mode == "Dimensionar C":
# Bloqueamos el field de C para escritura manual?
# O, según la lógica, convendría DESBLOQUEAR C si lo vamos a dimensionar?
# Típicamente: dimensionar C => el usuario fija V0
self.entry_C.config(state="disabled") # Se calculará
self.entry_V0.config(state="normal") # Se ingresa manualmente
else:
# Dimensionar V0 => el usuario fija C
self.entry_C.config(state="normal")
self.entry_V0.config(state="disabled")
def log_message(self, text, mode="info"):
"""Inserta texto en el área de log."""
self.log_area.configure(state="normal")
if mode=="error":
self.log_area.insert("end", "ERROR: " + text + "\n", "error")
else:
self.log_area.insert("end", text + "\n", mode)
self.log_area.see("end")
self.log_area.configure(state="disabled")
def dimensionar(self):
"""
Lee la energía objetivo desde TabSimulator, y ajusta C o V0
para que la energía total (n etapas) se aproxime a E_target.
"""
# 0) Energía objetivo proveniente de TabSimulator
E_target_total = self.tab_simulator.get_energy_required()
if E_target_total <= 0:
self.log_message("E_target <= 0. Asegúrate de que TabSimulator calcule algo válido.", "error")
return
# n (etapas)
n = self.var_n.get()
if n < 1:
self.log_message("El número de etapas debe ser >= 1", "error")
return
# => La energía por etapa
E_target_stage = E_target_total / n
# 1) Leer inductancia y resistencia desde TabCoil
try:
L_uH = float(self.tab_coil.var_L.get()) # microHenrios
R_coil = float(self.tab_coil.var_R.get()) # Ohms
except ValueError:
self.log_message("No se pudo leer L/R de TabCoil.", "error")
return
L_coil = L_uH * 1e-6 # Henrios
# h_c
try:
h_c_mm = float(self.tab_coil.var_h_c.get())
except ValueError:
self.log_message("No se pudo leer h_c de TabCoil.", "error")
return
h_c = h_c_mm / 1000.0 # m
# 2) Leer los datos de (C, V0, dt) (aunque uno de los dos se va a dimensionar)
dt_ms = self.var_time_ms.get()
if dt_ms <= 0:
self.log_message("Tiempo de etapa (ms) inválido", "error")
return
T_total = dt_ms*1e-3
mode = self.mode_var.get()
# Obtenemos un valor 'fijo' para el que no dimensionamos
# y definimos la test_function acorde
if mode=="Dimensionar C":
# El user fija V0:
try:
V0_fixed = float(self.var_V0.get())
if V0_fixed <= 0:
self.log_message("Tensión (V) inválida", "error")
return
except ValueError:
self.log_message("Tensión (V) inválida", "error")
return
# Rango de bisección en [Cmin, Cmax], µF
lower = 0.1
upper = 1e6
def test_function(C_uF):
E_stage = self.simular_descarga(
C_uF, V0_fixed, L_coil, R_coil, T_total, h_c
)
return E_stage
else:
# Dimensionar V0 => user fija C
try:
C_uF_fixed = float(self.var_C.get())
if C_uF_fixed<=0:
self.log_message("Capacidad (µF) inválida", "error")
return
except ValueError:
self.log_message("Capacidad (µF) inválida", "error")
return
# Rango de bisección en [1..5000] V, p. ej.
lower = 1.0
upper = 5000.0
def test_function(V0):
E_stage = self.simular_descarga(
C_uF_fixed, V0, L_coil, R_coil, T_total, h_c
)
return E_stage
# Bisección
E_low = test_function(lower)
E_up = test_function(upper)
# Comprobación de que la energía buscada (E_target_stage) está entre E_low y E_up
if not ((E_low <= E_target_stage <= E_up) or (E_up <= E_target_stage <= E_low)):
self.log_message("ADVERTENCIA: El rango de bisección puede no encapsular la E_target. Ajusta límites.", "error")
iteration_count = 0
max_iter = 40
tol = 0.01*E_target_stage # 1% de la energía por etapa
best_param = 0.0
while iteration_count < max_iter:
mid = 0.5*(lower + upper)
E_mid = test_function(mid)
diff = E_mid - E_target_stage
if abs(diff) < tol:
best_param = mid
break
# Suponemos monotonicidad: +C => +E, +V0 => +E
if E_mid > E_target_stage:
upper = mid
else:
lower = mid
iteration_count += 1
best_param = mid
E_final_stage = test_function(best_param)
E_final_total = E_final_stage * n
diff_total = E_final_total - E_target_total
# 3) Mostrar resultados
self.log_message("-"*40)
self.log_message(f"E_target_total = {E_target_total:.3f} J, n = {n}")
self.log_message(f"E_target_stage = {E_target_stage:.3f} J", "info")
if mode=="Dimensionar C":
self.log_message(f"Dimensionado de C => V0={V0_fixed:.1f} V fijo.", "info")
self.log_message(f"Capacidad final ~ {best_param:.2f} µF", "info")
else:
self.log_message(f"Dimensionado de V0 => C={self.var_C.get():.1f} µF fijo.", "info")
self.log_message(f"Tensión final ~ {best_param:.2f} V", "info")
self.log_message(f"E_result_stage ~ {E_final_stage:.3f} J => total ~ {E_final_total:.3f} J (diff={diff_total:.3f})", "info")
self.log_message("-"*40)
def simular_descarga(self, C_uF, V0, L_coil, R_coil, T_total, h_c):
"""
Modelo simplificado R-L(x)-C de una 'etapa' para un tiempo T_total.
Devuelve la energía consumida del condensador.
Reutilizamos la lógica de Euler para la descarga,
asumiendo L varía ~ 20% a lo largo x in [0..h_c].
"""
if C_uF<=0 or V0<=0 or T_total<=0:
return 0.0
# Convertimos C a F
C = C_uF * 1e-6
# Asumimos L variable ~ 20% de variación
L0 = 1.2*L_coil
Lf = 0.8*L_coil
def L_of_x(x):
return L0 + (Lf - L0)*(x/h_c) if h_c>1e-9 else L_coil
def dLdx_of_x(x):
return (Lf - L0)/h_c if h_c>1e-9 else 0.0
N_steps = 1000
dt = T_total/N_steps
vCap = V0
i = 0.0
def x_of_t(t):
return (h_c / T_total)*t if T_total>1e-9 else 0.0
for step in range(N_steps+1):
x = x_of_t(step*dt)
Lx = L_of_x(x)
dLdt = dLdx_of_x(x)*(h_c / T_total)
if abs(Lx)>1e-12:
di_dt = (vCap - R_coil*i - i*dLdt)/Lx
else:
di_dt = 0.0
i_new = i + di_dt*dt
vCap_new = vCap - (i/C)*dt
if vCap_new<0:
vCap_new=0.0
i = i_new
vCap = vCap_new
# Energía consumida ~ 1/2*C*(V0^2 - vCap^2)
E_consumida = 0.5*C*(V0**2 - vCap**2)
if E_consumida<0:
E_consumida=0.0
return E_consumida

View File

@ -1,68 +1,38 @@
# 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.tab_sim = tab_simulator
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_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")
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.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)")
@ -71,149 +41,85 @@ class TabSearch:
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.m, self.b, self.h0 = 1.0, 0.0, 0.0
self.has_drag = False
self.x_target = None # Alcance importado, si está en modo "Alcance (m)"
self.x_target = None
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"
)
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
X_target = self.x_target
angulos, velocidades = [], []
best_angle, best_v = None, 1e9
for angle_deg in range(0, 91):
v0_min = 0.0
v0_max = 300.0
final_v = 0.0
# Bisección
v0_min, v0_max, final_v = 0.0, 300.0, 0.0
for _ in range(100):
guess = 0.5*(v0_min + v0_max)
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
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
best_v, best_angle = final_v, 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.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"
)
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
x, y = 0.0, self.h0
alpha = math.radians(angle_deg)
vx = v0_guess * math.cos(alpha)
vy = v0_guess * math.sin(alpha)
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
ax = -(self.b/self.m)*vx
ay = -9.8 - (self.b/self.m)*vy
else:
ax = 0.0
ay = -9.8
ax, ay = 0.0, -9.8
vx += ax*dt
vy += ay*dt
x += vx*dt
y += vy*dt
if y <= 0:
break
x += vx*dt
y += vy*dt
if y <= 0: break
return x

View File

@ -5,137 +5,77 @@ 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.m = 0.0
self.h0 = 0.0
self.trayectoria = []
self.t_final = 0
self.proyectil = None
self.vel_text = None
self.current_canvas_width = 1
self.current_canvas_height = 1
self.energy_required = 0.0
# ============ Estructura general ============
# TOP
self.frame_top = tk.Frame(self.frame)
self.frame_top.pack(side="top", fill="x", padx=5, pady=5)
self.frame_top = tk.Frame(self.frame); self.frame_top.pack(side="top", fill="x", padx=5, pady=5)
self.frame_middle = tk.Frame(self.frame); self.frame_middle.pack(side="top", fill="both", expand=True)
self.frame_bottom = tk.Frame(self.frame); self.frame_bottom.pack(side="bottom", fill="both", expand=False)
# MIDDLE (canvas)
self.frame_middle = tk.Frame(self.frame)
self.frame_middle.pack(side="top", fill="both", expand=True)
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", padx=5, pady=5)
self.frame_log = tk.Frame(self.frame_bottom); self.frame_log.pack(side="right", fill="both", expand=True, padx=5, pady=5)
# 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)
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)
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.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.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)
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
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")
# Botón Calcular
self.button_calcular = tk.Button(
self.frame_top, text="Calcular",
command=self.calcular_trayectoria
)
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)
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)
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 = 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()
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)
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)
self.energy_required = 0.0
# 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"):
def set_log_message(self, msg, 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.insert(tk.END, msg + "\n")
self.text_log.config(state="disabled")
def set_b_value(self, new_b):
@ -157,7 +97,6 @@ class TabSimulator:
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())
@ -165,26 +104,19 @@ class TabSimulator:
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
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:
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())
try: valor = float(self.entry_v0.get())
except ValueError:
self.set_log_message("Error en el valor (vel/alcance).", "red","white")
return
@ -193,35 +125,26 @@ class TabSimulator:
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
v0_min, v0_max = 0.0, 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
guess = 0.5*(v0_min+v0_max)
dist_alc = self.simular_dist_sin_rozamiento(guess, alpha_rad, self.h0)
if abs(dist_alc - X_final)<0.1:
v0=guess; break
if dist_alc<X_final: v0_min=guess
else: v0_max=guess
v0=guess
else:
v0_min=0.0
v0_max=1000.0
v0_min, v0_max = 0.0, 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
guess = 0.5*(v0_min+v0_max)
dist_alc = self.simular_dist_con_rozamiento(guess, alpha_rad, self.h0)
if abs(dist_alc - X_final)<0.1:
v0=guess; break
if dist_alc<X_final: v0_min=guess
else: v0_max=guess
v0=guess
if v0<=0:
@ -230,13 +153,11 @@ class TabSimulator:
b=0.0
if self.check_rozamiento.get():
try:
b = float(self.entry_b.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
@ -246,40 +167,28 @@ class TabSimulator:
t=0.0
x=0.0
y=self.h0
vx = v0*math.cos(alpha_rad)
vy = v0*math.sin(alpha_rad)
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
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
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)
@ -296,16 +205,11 @@ class TabSimulator:
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
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):
@ -315,10 +219,8 @@ class TabSimulator:
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
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
@ -326,33 +228,22 @@ class TabSimulator:
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
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
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
rx, ry = max_x - min_x, 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
w, h = self.current_canvas_width, self.current_canvas_height
margen=20
scale = min((w-2*margen)/rx, (h-2*margen)/ry)
scale = min((w-2*margen)/rx,(h-2*margen)/ry)
self.canvas.delete("all")
pts=[]
@ -360,29 +251,25 @@ class TabSimulator:
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]
x0,y0=pts[0]
r=5
self.proyectil = self.canvas.create_oval(x0-r,y0-r, x0+r,y0+r, fill="red")
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
if not self.trayectoria or not self.proyectil: return
t_slider = float(val)
if t_slider<=0:
x_real=self.trayectoria[0][1]
@ -397,74 +284,54 @@ class TabSimulator:
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
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
w, h = self.current_canvas_width, 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
x_c = (coords_act[0]+coords_act[2])/2
y_c = (coords_act[1]+coords_act[3])/2
dx, dy = sx - x_c, 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
coords_text = self.canvas.coords(self.vel_text)
xt, yt = coords_text[0], coords_text[1]
dx_t, dy_t = sx - xt, (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 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]
vx_, vy_ = self.trayectoria[0][3], self.trayectoria[0][4]
x_, y_ = self.trayectoria[0][1], 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]
vx_, vy_ = self.trayectoria[-1][3], self.trayectoria[-1][4]
x_, y_ = self.trayectoria[-1][1], 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]
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_, vy_ = self.trayectoria[idx][3], self.trayectoria[idx][4]
x_, y_ = self.trayectoria[idx][1], 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
Ep = self.m*9.8*y_ if y_>0 else 0.0
E_tot = Ec + Ep
self.energy_required = E_tot
E_sobredim=1.15*E_tot
@ -472,6 +339,6 @@ class TabSimulator:
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
return self.energy_required