diff --git a/Docs/Models.aux b/Docs/Models.aux new file mode 100644 index 0000000..4fd7b33 --- /dev/null +++ b/Docs/Models.aux @@ -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} diff --git a/Docs/Models.fdb_latexmk b/Docs/Models.fdb_latexmk new file mode 100644 index 0000000..8803ca0 --- /dev/null +++ b/Docs/Models.fdb_latexmk @@ -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) diff --git a/Docs/Models.fls b/Docs/Models.fls new file mode 100644 index 0000000..f41e970 --- /dev/null +++ b/Docs/Models.fls @@ -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 diff --git a/Docs/Models.pdf b/Docs/Models.pdf new file mode 100644 index 0000000..e2aeef5 Binary files /dev/null and b/Docs/Models.pdf differ diff --git a/Docs/Models.synctex.gz b/Docs/Models.synctex.gz new file mode 100644 index 0000000..bfc1a49 Binary files /dev/null and b/Docs/Models.synctex.gz differ diff --git a/Docs/Models.tex b/Docs/Models.tex new file mode 100644 index 0000000..b463b51 --- /dev/null +++ b/Docs/Models.tex @@ -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 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) diff --git a/src/tabs/tab_coil.py b/src/tabs/tab_coil.py index 11b2561..f4b6063 100644 --- a/src/tabs/tab_coil.py +++ b/src/tabs/tab_coil.py @@ -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") \ No newline at end of file + self.var_R.set("Error") + self.log_message(f"Error inesperado: {str(ex)}", "error") diff --git a/src/tabs/tab_drag.py b/src/tabs/tab_drag.py index 2c0abd7..2207ea8 100644 --- a/src/tabs/tab_drag.py +++ b/src/tabs/tab_drag.py @@ -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 diff --git a/src/tabs/tab_dynamic.py b/src/tabs/tab_dynamic.py deleted file mode 100644 index 84ce8f9..0000000 --- a/src/tabs/tab_dynamic.py +++ /dev/null @@ -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") diff --git a/src/tabs/tab_power.py b/src/tabs/tab_power.py new file mode 100644 index 0000000..1563a7b --- /dev/null +++ b/src/tabs/tab_power.py @@ -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("<>", 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 \ No newline at end of file diff --git a/src/tabs/tab_search.py b/src/tabs/tab_search.py index 20d3b9d..6e48965 100644 --- a/src/tabs/tab_search.py +++ b/src/tabs/tab_search.py @@ -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 diff --git a/src/tabs/tab_simulator.py b/src/tabs/tab_simulator.py index d8a07e4..6aa25f8 100644 --- a/src/tabs/tab_simulator.py +++ b/src/tabs/tab_simulator.py @@ -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("", 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_alcanzado0.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=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 idx0: - 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 \ No newline at end of file + return self.energy_required