From e9e734e672d46616212152b7df56b09fa0c26a89 Mon Sep 17 00:00:00 2001 From: mlauer154 Date: Tue, 26 Sep 2023 17:15:59 -0500 Subject: [PATCH] Fixed major bug with R constraint in GUI, added internal screenshot feature, added doc pictures --- docs/source/gui.rst | 11 +- docs/source/gui_help/gui_airfoil.rst | 112 ++++++++++++++++++ docs/source/gui_help/gui_opt.rst | 2 + docs/source/images/ap_menu_dark.PNG | Bin 0 -> 11653 bytes docs/source/images/ap_menu_light.PNG | Bin 0 -> 9888 bytes docs/source/images/fp_ap_menu_dark.PNG | Bin 0 -> 8882 bytes docs/source/images/fp_ap_menu_light.PNG | Bin 0 -> 8320 bytes docs/source/images/fp_menu_dark.PNG | Bin 0 -> 7599 bytes docs/source/images/fp_menu_light.PNG | Bin 0 -> 6412 bytes docs/source/index.rst | 11 +- docs/source/quick_start.rst | 7 +- pymead/core/anchor_point.py | 29 +++-- pymead/core/param.py | 4 +- pymead/gui/dialog_layouts.py | 7 -- .../gui/dialog_widgets/screenshot_dialog.json | 34 ++++++ pymead/gui/file_selection.py | 7 ++ pymead/gui/gui.py | 44 ++++++- pymead/gui/gui_settings/menu.json | 3 +- pymead/gui/input_dialog.py | 56 +++++++++ pymead/gui/parameter_tree.py | 32 +++-- pymead/gui/pymead.spec | 58 +++++++++ 21 files changed, 380 insertions(+), 37 deletions(-) create mode 100644 docs/source/gui_help/gui_airfoil.rst create mode 100644 docs/source/gui_help/gui_opt.rst create mode 100644 docs/source/images/ap_menu_dark.PNG create mode 100644 docs/source/images/ap_menu_light.PNG create mode 100644 docs/source/images/fp_ap_menu_dark.PNG create mode 100644 docs/source/images/fp_ap_menu_light.PNG create mode 100644 docs/source/images/fp_menu_dark.PNG create mode 100644 docs/source/images/fp_menu_light.PNG delete mode 100644 pymead/gui/dialog_layouts.py create mode 100644 pymead/gui/dialog_widgets/screenshot_dialog.json create mode 100644 pymead/gui/pymead.spec diff --git a/docs/source/gui.rst b/docs/source/gui.rst index 137e72b9..20bca5f5 100644 --- a/docs/source/gui.rst +++ b/docs/source/gui.rst @@ -1,4 +1,9 @@ -GUI help documentation test: +GUI Help +======== -.. raw::html - :file: gui_help/test.html +This section contains tutorials and helpful hints for the pymead GUI. + +.. toctree:: + + gui_help/gui_airfoil.rst + gui_help/gui_opt.rst diff --git a/docs/source/gui_help/gui_airfoil.rst b/docs/source/gui_help/gui_airfoil.rst new file mode 100644 index 00000000..235359f6 --- /dev/null +++ b/docs/source/gui_help/gui_airfoil.rst @@ -0,0 +1,112 @@ +Airfoil Generation +================== + +The following steps show how to create and save an airfoil from the pymead GUI. + +Next, we will insert some ``FreePoint``\ s into the airfoil, which adds additional degrees +of freedom to individual Bézier curves. To insert a ``FreePoint`` from the GUI, right-click +on the desired airfoil in the Parameter Tree, which brings up a context menu as shown below: + +.. image:: ../images/fp_ap_menu_dark.png + :align: center + :class: only-dark + +.. image:: ../images/fp_ap_menu_light.png + :align: center + :class: only-light + +This brings up a dialog as shown below. + +.. image:: ../images/fp_menu_dark.png + :align: center + :class: only-dark + +.. image:: ../images/fp_menu_light.png + :align: center + :class: only-light + +Here is a description of each of the ``FreePoint`` menu items: + +.. list-table:: + :widths: 20 80 + :header-rows: 1 + + * - Item + - Parameter + * - x + - Distance from the origin in the "Geometry" window along the x-axis + * - y + - Distance from the origin in the "Geometry" window along the y-axis + * - Previous Anchor Point + - Parent ``AnchorPoint`` to which this ``FreePoint`` belongs. More specifically, the ``FreePoint`` + will be inserted into the Bézier curve which has this ``AnchorPoint`` as its first ``ControlPoint`` + using counter-clockwise ordering. For an airfoil with no custom ``AnchorPoint``\ s, inserting this + ``FreePoint`` with the Previous Anchor Point set to ``"te_1"`` corresponds to adding a control + point to the airfoil's upper surface, while inserting a ``FreePoint`` with the + Previous Anchor Point set to ``"le"`` corresponds to adding a control point to the airfoil's + lower surface. Note that ``"te_1"`` represents the upper trailing edge point, + which is distinct from the lower trailing edge point in the case of an airfoil with a blunt + trailing edge. + * - Previous Free Point + - Similar to the "Previous Anchor Point" item, this item sets the ``FreePoint`` insertion index + within the Bézier curve's control point matrix using counter-clockwise ordering. The + difference here is that an existing ``FreePoint`` is specified, rather than an ``AnchorPoint``. + Note that if no ``FreePoint``\ s have been added yet to the Bézier curve corresponding to the + ``AnchorPoint`` specified by "Previous Anchor Point", ``None`` is automatically selected. + +.. image:: ../images/ap_menu_dark.png + :align: center + :class: only-dark + +.. image:: ../images/ap_menu_light.png + :align: center + :class: only-light + +Here is a description of each of the ``AnchorPoint`` menu items: + +.. list-table:: + :widths: 20 80 + :header-rows: 1 + + * - Item + - Parameter + * - x + - Distance from the origin in the "Geometry" window along the x-axis + * - y + - Distance from the origin in the "Geometry" window along the y-axis + * - L + - Distance between the control points located immediately before and after the ``AnchorPoint`` + in the counter-clockwise ordering divided by the chord length of the airfoil. + * - R + - Radius of curvature of the airfoil at the ``AnchorPoint`` divided by the chord length of the + airfoil. + * - r + - Ratio of the distance between the control point immediately upstream of the ``AnchorPoint`` and + the ``AnchorPoint`` itself to the distance between the control points located immediately before + and after the ``AnchorPoint``\ . Here, "upstream" means after the ``AnchorPoint`` in the + counter-clockwise ordering for an ``AnchorPoint`` on the upper surface and before the + ``AnchorPoint`` for an ``AnchorPoint`` on the lower surface. This is normally in the + range :math:`[0,1]`; however, this is not enforced. + * - phi + - "Tilt" of the line connecting the control points immediately before and after the ``AnchorPoint``. + Regardless of whether the ``AnchorPoint`` is located on the airfoil upper surface or lower surface, + positive values of "phi" tilt the line toward the leading edge, while negative values of "phi" + tilt the line away from the leading edge. + * - psi1 + - + * - Previous Anchor Point + - Parent ``AnchorPoint`` to which this ``FreePoint`` belongs. More specifically, the ``FreePoint`` + will be inserted into the Bézier curve which has this ``AnchorPoint`` as its first ``ControlPoint`` + using counter-clockwise ordering. For an airfoil with no custom ``AnchorPoint``\ s, inserting this + ``FreePoint`` with the Previous Anchor Point set to ``"te_1"`` corresponds to adding a control + point to the airfoil's upper surface, while inserting a ``FreePoint`` with the + Previous Anchor Point set to ``"le"`` corresponds to adding a control point to the airfoil's + lower surface. Note that ``"te_1"`` represents the upper trailing edge point, + which is distinct from the lower trailing edge point in the case of an airfoil with a blunt + trailing edge. + * - Previous Free Point + - Similar to the "Previous Anchor Point" item, this item sets the ``FreePoint`` insertion index + within the Bézier curve's control point matrix using counter-clockwise ordering. The + difference here is that an existing ``FreePoint`` is specified, rather than an ``AnchorPoint``. + Note that if no ``FreePoint``\ s have been added yet to the Bézier curve corresponding to the + ``AnchorPoint`` specified by "Previous Anchor Point", ``None`` is automatically selected. diff --git a/docs/source/gui_help/gui_opt.rst b/docs/source/gui_help/gui_opt.rst new file mode 100644 index 00000000..54ea1dc0 --- /dev/null +++ b/docs/source/gui_help/gui_opt.rst @@ -0,0 +1,2 @@ +Optimization +============ diff --git a/docs/source/images/ap_menu_dark.PNG b/docs/source/images/ap_menu_dark.PNG new file mode 100644 index 0000000000000000000000000000000000000000..b4281860c12753302078f5370166e447dc891703 GIT binary patch literal 11653 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 URJ~95p9!F?X`oS|{wVT)0Dq|-SpWb4 literal 0 HcmV?d00001 diff --git a/docs/source/images/fp_ap_menu_light.PNG b/docs/source/images/fp_ap_menu_light.PNG new file mode 100644 index 0000000000000000000000000000000000000000..80064c5b0228eff7697e162c38937850ab3b452b GIT binary patch literal 8320 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 F{{zL$5mf*H literal 0 HcmV?d00001 diff --git a/docs/source/images/fp_menu_dark.PNG b/docs/source/images/fp_menu_dark.PNG new file mode 100644 index 0000000000000000000000000000000000000000..5e9e80b4b029602bb98d1180494418f97e68568a GIT binary patch literal 7599 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 z zRaumV%hPzDz`uPu#X_i&iY1>VL&#FJvn{g_yvjIe>KZWQe{)3|EbR*=N!P=rVm)1H zDb+c_dW#=DF;X$`r&TXqh!t9YM^QZOSfF~FOqT~0jMizuQvkx{u$0X48pn2o6@rkO zN378#jd4E5xRbWeK*hrxZTl&^A#~vo)6=N8OjAE6iVeftwZBM5r(j1*=xKXA#1!pg z76Y;LYXPK)1IPYUkAaPHD=X6^^fTF(NQWdzx3}&()jkj%FHM(t^QWUkrP2=!yehZ6 zYQ)EnpkGUx)Uf4dRY$KrYCTG;VF4e6d_xL5({D`%Qq45DaTAu>x0JGCtX#uE5At{| zBLxWRi?^lwy8FI_7MM@~f4r!*FZelOU@&D?{+GBNFC?gJuEV~=SI*>TcObB=-&=4y z^O+64BB_}_vA+JqgSBO|v+!^F2Qk=RWiPv3>MkE|SyXivpWAuQQY(vF|w*+`Q#2xxJwI+TkXNg3}kO!`>5oO84 z$2#LnW19{2lJoNVqF|oHtE1KgHl3;#jk;NXHY(z^_zklQ&V+BMeXit!xNy|-D#vpQ zD{s9tUKvZ1G}s6`-BT-;9`1g*kP@h$`SOc3_g?%RMSE%a54Ta_bu%?uTA#q1&5euIy3#J42Vc^DC z&i;ag^WOVvW>)03wjD_UR=k<9$-2(U<57$UVNQiVEVclSZ{Sb=p9*f?+t^SMY2RqK;55)s~>t97H+ zzvuKEHD_|8_Q1GqCf`dIu`!ZZa+xJ3;`;VE%-Jz_e?RJh89#*BA@QYzyMk$i4%d{? z$;vnTS75%RZq4DxjONzk_%GPOBBn5Wd(L#dL5})9rFgtg&XNr55H@$og49$K2OVQ6 zfKJodRf?Zm|K zK8Z>aTbFb2@(;Y8yNQ!vgk1D3QTzAt0rl^|!n$$R|4C86wAzz3a1mjFoY>)-$B!A) z=i5eg>EwR@_Iw*ycEL;dJlwid1l9TIw?K@Z&Y{rKo|smC?6XIjT1$`e2GX#cXN_U(1~KUVu1#bVS274@GnRN<+X=xo>|jNiHy*vUCW7II+z)qc(@7m3 zp}@6Qt_5hnydLr8Lh=(_M%uO6GC^vgOyCMc?gNcsrGNNXIre9RdIv?QGvj_-4Dv0S!C` zAy`-dWg?Z822K7gR$(1eW5#{BMWa|7?FVTV4g0;bj)ZETOPW;lXq@QE>_KsIafG8; z*0#6b^Qalm?8Z}bc8KZ~{Hc{ED}3g4+C53TdK%!VQqlbOGo{w}>9n$*e5HT{ z|Frc7qo~+$Q1PK_4LX@GdePqjq&BtLA%ZJ|UaL#WFJg_xb<~W|p=YcvQoLcMDoiKI z?KWqxm#eM9-+UsX!SbgcnR+t{DM;GkiVc!7#tj($9Qgb3>F=%7@C}`Sd*MiIdX2z~ zDfbyiKfesSck5fsewmED7pUM0IM#6 z;;Jk+x{QonVw?ktG;e)gHb?UtKq|}v@~QKnqqfz${Q>E#*;d7?Iva$Dy&L~RimnD4 z%{VBP?Q=4usa?4)jto^Cc;;5Nk@b{&5jVVI@b{1t}uiaZz8O)kDfc=}FLOBe0zrXC*gU_(QHBj5Vw%$S~N$t|1Tm`v@=5ui8W0m>W2h=eR6!MlW*4tJ%DSr@)j4{7cwO4)KSoNp z1*GSRRqLuxyms0mTARP)QQYi{qJ`*RvAAfYl$YP>jOyymWJw*HBnW6NkLry2_FcF# z@cwn_$-?3ozaF@tA~H|W%?s@@-VIwGBax57bCWbkiDLSn>$3k-3HKI8?L4?f1t$Nq UEA|uaB`ZKx8KhLIX!-7c0c$$LegFUf literal 0 HcmV?d00001 diff --git a/docs/source/images/fp_menu_light.PNG b/docs/source/images/fp_menu_light.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d20abbfeda5d667e913a3240b42d86fb4d6d1062 GIT binary patch literal 6412 zcmcgxcT`i|mkl6-bVO+qnh_8w(j|bj(3=!Rs*Ng!WLGz8>fo5WwwX2v^g5N9a*LiiKH+OGLUf0fBg*8^N?~f}BaH&@_R!5Bk=NGD)z9k4f^G$z5W-S)$6dmQti;nhN8$ zY+acPHi8i#8f+tP09unHQZo4&j5p#c_n_%RTOo&ikCSG zGfX}C;M;Y6GR8=8XI@URWzJgh$9to(pdTg(57H8X=x)7K9D&jgT|3~aoRv=w2q=XA zd5}yCybPF_;Ubnb3K^tI<-stR0@xtt=#oaw&666JBA5>RC*7BTpW*<<``~0%)7Ghw zrEJW|G|!=k2J)+|C!q>kWe)-gtLf;eP)7Re!oPms>a;L@!Qj33pk=Ww6^K#^dvv%d zBhuVz{%mjS9l66bI0q`|9xK@1`Nk2@1rj0&2bN-+E!%^~;|6Gu7AE z^Qn?ov7YNQ?bkdzzHH*_JfGArMh^xRlrZv4_i}k2(DebGv-=ENJJwG;zG^;rG!qua zL<*+BrZzwEcn~bii_5};BuP!XEcTsW5c?$3X!&je=3`31z}7cWO<>!yzDn{?<-*>c z+npNgHNm=plwh{US0;GNteQ81bh(*#4jou>oL!$k z-7L>p&EXg9>!;AI!EB^l&B01N*V}P2KNm`nGtWFW3{wmcvYnLLda{@@4S6NE+^)woB;HxJXp6_04`71q^5;9YiG zz2lZJcjU}k<>otFSdu`=n*XL{&D{bZXbg*MU8%VCR$|~hc7D}kL7b(T?WHs6^)VX4 zmkUtb<&o{C2CQ@F8Uw~>ZKYidWO}XI(X)>8a2gAn{0 zM^Ey$R$|>oE?nr;RuiJ|lR|^|!F%6XrXnywF6|~s-?9t!)@K!CXShmCrEA^t) zs-zey7Iyp?8VB}}xg|ZywT$^0HVyZ0fvppQbJL)XeM~nri6IE3l>nK(?dGR#Ma`(4 zaEWcYXLo)q_BS8gGdH*l+g+9WHv8egpDDRoGo9`7Pra$_QB8ECAT(;{@)i1b&PQU)@&!~2-j z8G_p%B-7pz^0fPCn?>&>WUJBmOdizci(ilxOF4e8|9iw&oDmn5=7n>z!YQTX>>>j! z&x~GC+p^z6`gjlmg*PY*P}hl&i{NvrzbP`^zF`%z-?BdPMadnQX~q(_mpK!LZl@XO z&mXDlGV$NaeT*CJ!I;{u2flho5T*elxw|qJG?n_*ps!2MwfX`FVJDRek02Ut6ntID zOO&_s{)1)5Df8Xg@9bNjz!3(+pLf7hL4&Gw!QWLXj$$;+X5W*!;d@=GTg$HZiGe&> zRrWSbZ}fSU|%yq_kwEB98+H{N2#qUvlp3rLn?<~3u0 z(AF{04VXiR+V9`bK;C$v`*KWEJWr3DO)Vt8=j=-IeERnOWL@JO?soEl-PY8s&~swa za2Z9AuG9cVcnwI?2+Ao9x>6oLx5H>;&xUk{NIR0>;rq!I$fleF%n1;ew-ApLBE3G- zVox9n*6Z44X7xC6t=|G5Dijsx9KxX=I!VS*LX4W{e*K{5R2z9|)ocC5CZ;u-TbB6D z0Hax?{DCY+;!J%NipkY`!!ulB+i6{Umj?83m66A3*a%Ux&WxCx_hRWsVV@-)hIEk- z-@D(Az4oPi$qtxQ8TbRjz*PTA{g*?1j-GH8;RPW%z|k#HE4&N43P6y)M&`gOR+g*3H4K zlUk6w_5AS9P3iml`y3Fg?->DJ-Wq#X zgfNtY$;5kQqDoO+JxMq|=IiMkX@`cMY%q*LXD-kZH1bd{aBpMoQ#@Ww1j;cGG=rpx z>kiW6X*AP82%m~$D~T>52CUD(VYa=IM6G!(gs?OMeW&XKN(~oRv?>fuyL6abnOX^l zkGYjdW>a`e)@s|G6t23*J`1^$omz|d(x9ZQ{LRI~BMu6M61$mY6}vIPs5Vw==Z!D+ zv3b!ZV7Gm>qRLZ--qnFed;6J`kn562qIs5M+N#xQ@GRrcd2;2N>lM?jIS(!hf* zZ%J|rZKnC6Xt`kK*J3FR7XbdLCUWAHT`_A;y(!|`hZ_ESiQdQF3Brr5j*i&xvA-a` zHxN}w*?P3BZFhOV3dZg9v_uL@!|pkFwQ|2hHww*ZgqXitUu8NSHF2BcbId9{>d(r@ z_z5D>W3@yUN+W0|&Ycb;EJQT6Lvd<0BY5y-|z42Nxduy$q~i#Dwj70>9@^>&q41XZu$_Wm4BsQDg|zWCnqH} z)!rSKH1Ma`OA|^Yp%>-i@#Ubn1apJ{_tT=qOB`TBfj^E zK|O^UTXCZ<$;l;LNPMZ{=FsQst;1o2C`>^|sioGMUD1#jG_XrpyoTsym9VX_H=o@R z79yU>60I@>>z-}->o>{%6V&R_*bda3tjL$<_{a`R`Un&Eu)$@|v&Qc7h6@flndcm` z1#5|yppz9D3w2tEF!qu&v`ncH#kc*Ij_$!uqh7Dx^9rW`J)Qr6SH)&ywSE0SxOHUA2HD5-*$>HE){~Ki)EyTZi=rl)R!w7y;z((XOZ!5YT~d*s@;j{ba+6Y|pcOwt9_GR#4u>9oVoyONDn8pR&DWFhq2-?O+Jy$ECKyfABZEB8qx zM84m`o}e*91}}x}?H7Moe8J;{YB4^yT={Y(71BO7GhyC7C4bEG1wGm;KzYG^Jar;H zNhGSasqokfxan1d=k$75e0Ae=wkj<4aak$)zO)0X9iL0;pXudS;$!`^W;0NCm&Ky0 z7kr&N#Q&kTKWUPX#khCp({H&^SbHbjP*iH-!5*SP$p*Fwc^trG5c z=B0%k44X6`Px&;&jv-$6A2=+PsSF*MPvAJ)5}*0}xyUk+=xYY|h{fG+_SQ6SKyxKK zaOawcHoRZOjl>KE1TfS?(b)Jl3^qBsP0B}ns1m>QQ_`_>d%F2H;_;SpND+oJj_m~dAiO{0TsoxBb17~pda5FcGiJDfCC~=AU)nq?Z{ozV9@6K5i{ej~);KGR^|82R{pK z?OtsMV;!?6RFQ_oT>9Yc^JjHismedK+LUdm;W0FNnI{MGp4N3zU6$mI5Fzh5C$<$a z<&dY>CANOpQUk!%uHrBBR1{6KGm-IlX9iLts0xnz`akTz)IUF}%;n4MI z*aGpWh8i|IcE8!n)uh_ z9$N%shUG5{U?2qzUL+XBL%s4Ja6p8AHJl`3S{v2*#J|z>|Ky>_0a58<_j%MaS5!6A z7K|Nzhgh#+%m`TB!OhNEY==zWMF9_qT9E4j6q$ikmS2=*?adOP186`@>@Wt9ggOTp z^lU>50eaZ|pC;pARoC7Wp$;EiqF#B)f zcc-$n^u`F=MdMure*FxKU8L&TuO8X&@GG0o<%lQL<=2dC5!I?pmuNQmk{FQYQHmRm zQHJ?_0_z%y2-%>+1P_H^@Ckch{6ht+i4)S*NzHA_z{-LdhFdK^v}XFo`<35Hd^|L} zo@i_%X>>b^n8scDi}RaaKxAvMo2>hp;9jX#-TW0tF270ZZ98Ocf#39U$3%$TsexKK zY;Jv#`Zzku5~Gi}rZGqG;j$5XE@;vEs-h1nL@vxFobx#=G@I~AhLxfxUNV875g4yP zD`b;4FId_-+22@3R|lBor9+Xb3h@=LhL-Y*ii__*Vq*SDepYMlT!rXo%^wB&H@rM` z*KMK_SdjO#`RQqc?PkoMUTqw@{6lrHM>XOO(D^<(L>=e8H}08+Tjr+w zqCEXVkl&i!yLMk`f;M8lUapesb_VXdSI)B_?r<-+i2C>SuZAXMNrYFr38(A=EUB!G zBto?J0m~NMJH)6p69LLx z+gV8SeR6W{nEbGxS(xV5?ihToLYzp^tv2gj4@xRql>h^NK{hxU(NrQu-%T66nMCC1 zDG-P^M{xYSFQt8B9A7c$>WQ+BRjj==W?7^dA}>f^Rok_87dz@8y8R`PRc=^wZwf+5 zap&+yQS3EjT_^eY${1B7hhLfYmW-WV_L~?lAKjzva(0)o4SDsWFoAatY4u?S7hYwz zR#9hJfpyE~8~owN0U-rDMM0~kL|EUatg1hf&=^U}Y%{NZb1AzWL(c8>_#pW*z=T{Q z5w_#E(HirKAQT2?3-7~zZ#5@}jctxdw|~H{E<4VrPmW@ zPnvuqfWS z(6MQzAP4g6zU+=xQsYkh7Vgo8T1;d0opJSR%@ z{1HnWkPCqZ3*qEbIgs-Lci?`*4it1zgL1(sqD`$o4sEXZUq`2k6l?E|f~a7;%U1_TtXt z+Qaw+;`P(^Z!pos7p=}-4L*LP3QT^TAvp+jxZgsLehA2d-R*7sP3Y|_rDH}GoBPJ8&D9g0 zVfr{btaIXz{PuRQW_=VTI;y=Wlj;Sspk5=jNYna`$8=Pxz1G`}B(~*!^Aqe+5_)po z-#iZj4V_*#2~yi_xh7@;_?b>Bt=pEy;+W?qSBy`KbV{HXuGeCBktGDIAezqLOboGU zQ_w?Lu6tL?1URkwt1+&v&p85|4$Vkr|R;_dM(SHC%OmZ>+ literal 0 HcmV?d00001 diff --git a/docs/source/index.rst b/docs/source/index.rst index 289ea444..62bcec51 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,9 +9,18 @@ Contents -------- .. toctree:: + :maxdepth: 5 install quick_start + +.. toctree:: + :maxdepth: 2 + tutorials - api gui + +.. toctree:: + :maxdepth: 3 + + api diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst index 7ce946ca..2b959388 100644 --- a/docs/source/quick_start.rst +++ b/docs/source/quick_start.rst @@ -22,10 +22,11 @@ containing the ``gui.py`` file (the ``gui`` module) and then run the script usin Alternatively, users working in an IDE can navigate to the ``gui`` module by clicking through the directory tree. -GUI documentation: +.. + GUI documentation: -.. raw:: html - :file: gui_help/test.html + .. raw:: html + :file: gui_help/test.html Programmatic Interface ====================== \ No newline at end of file diff --git a/pymead/core/anchor_point.py b/pymead/core/anchor_point.py index a870f673..c59057d1 100644 --- a/pymead/core/anchor_point.py +++ b/pymead/core/anchor_point.py @@ -498,7 +498,7 @@ def map_psi_to_airfoil_csys_inverse(): if self.anchor_type == 'upper_surf': if self.R.value > 0: # angle = np.pi + psi + phi - self.psi1.value = self.abs_psi1 - np.pi - self.phi.value + self.psi1.value = self.abs_psi1 + np.pi - self.phi.value else: # angle = np.pi - psi + phi self.psi1.value = -self.abs_psi1 + np.pi + self.phi.value @@ -533,7 +533,7 @@ def map_psi_to_airfoil_csys_inverse(): self.psi1.value = np.pi - self.abs_psi1 - self.phi.value else: # angle = np.pi + psi - phi - self.psi1.value = self.abs_psi1 - np.pi + self.phi.value + self.psi1.value = self.abs_psi1 + np.pi + self.phi.value elif self.anchor_type == 'le': if self.R.value > 0: # angle = -psi + phi @@ -546,6 +546,20 @@ def map_psi_to_airfoil_csys_inverse(): map_psi_to_airfoil_csys_inverse() + # Take care of the case where R is bounded and the mouse is dragged such that R would flip signs otherwise + if self.R.at_boundary: + for psi in [self.psi1, self.psi2]: + if self.anchor_type == "le": + if psi.value > np.pi / 2: + psi.value -= 2 * (psi.value - np.pi / 2) + elif psi.value < -np.pi / 2: + psi.value += 2 * (psi.value - np.pi / 2) + else: + if psi.value > np.pi: + psi.value -= 2 * (psi.value - np.pi) + elif psi.value < 0.0: + psi.value *= -1 + if (minus_plus == 'minus' and self.anchor_type in ['upper_surf', 'le']) or ( minus_plus == 'plus' and self.anchor_type == 'lower_surf'): apsi = self.psi1.value @@ -553,20 +567,19 @@ def map_psi_to_airfoil_csys_inverse(): apsi = self.psi2.value if self.R.active and not self.R.linked: + R_multiplier = -1.0 if negate_R else 1.0 if self.tag == 'le': if minus_plus == 'minus': - self.R.value = self.Lt_minus ** 2 / ( + self.R.value = R_multiplier * self.Lt_minus ** 2 / ( self.Lc_minus * (1 - 1 / self.n1) * np.sin(apsi + np.pi / 2)) else: - self.R.value = self.Lt_plus ** 2 / ( + self.R.value = R_multiplier * self.Lt_plus ** 2 / ( self.Lc_plus * (1 - 1 / self.n2) * np.sin(apsi + np.pi / 2)) else: if minus_plus == 'minus': - self.R.value = self.Lt_minus ** 2 / (self.Lc_minus * (1 - 1 / self.n1) * np.sin(apsi)) + self.R.value = R_multiplier * self.Lt_minus ** 2 / (self.Lc_minus * (1 - 1 / self.n1) * np.sin(apsi)) else: - self.R.value = self.Lt_plus ** 2 / (self.Lc_plus * (1 - 1 / self.n2) * np.sin(apsi)) - if negate_R: - self.R.value *= -1 + self.R.value = R_multiplier * self.Lt_plus ** 2 / (self.Lc_plus * (1 - 1 / self.n2) * np.sin(apsi)) def recalculate_ap_branch_props_from_g1_pt(self, minus_plus: str, measured_phi, measured_Lt): """ diff --git a/pymead/core/param.py b/pymead/core/param.py index 3aaa22a2..5b947fac 100644 --- a/pymead/core/param.py +++ b/pymead/core/param.py @@ -12,10 +12,8 @@ def __init__(self, value: float or typing.Tuple[float], bounds: tuple = (-np.inf active: bool or typing.Tuple[bool] = True, linked: bool or typing.Tuple[bool] = False, func_str: str = None, name: str = None, periodic: bool = False): """ - ### Description: - This is the class used to define parameters used for the airfoil and airfoil parametrization definitions - in `pymead`. + in pymead. ### Args: diff --git a/pymead/gui/dialog_layouts.py b/pymead/gui/dialog_layouts.py deleted file mode 100644 index f22cbc10..00000000 --- a/pymead/gui/dialog_layouts.py +++ /dev/null @@ -1,7 +0,0 @@ -from PyQt5.QtWidgets import QGridLayout - - -class MplotDialogLayout(QGridLayout): - def __init__(self): - super().__init__() - diff --git a/pymead/gui/dialog_widgets/screenshot_dialog.json b/pymead/gui/dialog_widgets/screenshot_dialog.json new file mode 100644 index 00000000..5edf7006 --- /dev/null +++ b/pymead/gui/dialog_widgets/screenshot_dialog.json @@ -0,0 +1,34 @@ +{ + "window": { + "label": { + "w": "QLabel", + "text": "Select window", + "grid": [0, 0, 1, 1], + "tool_tip": "Name of the window to screenshot" + }, + "combobox": { + "w": "QComboBox", + "addItems": ["Full Window","Parameter Tree", "Geometry", "Analysis", "Console"], + "grid": [0, 1, 1, 2] + } + }, + "choose_image_file": { + "label": { + "w": "QLabel", + "text": "Choose image file", + "grid": [1, 0, 1, 1], + "tool_tip": "Choose a name for the image file (JPEG format)" + }, + "line": { + "w": "QLineEdit", + "text": "", + "grid": [1, 1, 1, 1] + }, + "button": { + "w": "QPushButton", + "text": "Select", + "grid": [1, 2, 1, 1], + "func": "select_jpg_file" + } + } +} \ No newline at end of file diff --git a/pymead/gui/file_selection.py b/pymead/gui/file_selection.py index f34d24a8..09b1c744 100644 --- a/pymead/gui/file_selection.py +++ b/pymead/gui/file_selection.py @@ -32,6 +32,13 @@ def select_json_file(parent, line_edit: QLineEdit = None): return single_file_output_rule(file_dialog, line_edit) +def select_jpg_file(parent, line_edit: QLineEdit = None): + file_dialog = QFileDialog(parent) + file_dialog.setFileMode(QFileDialog.AnyFile) + file_dialog.setNameFilter(parent.tr("JPEG Files (*.jpg *.jpeg)")) + return single_file_output_rule(file_dialog, line_edit) + + def select_existing_json_file(parent, line_edit: QLineEdit = None): file_dialog = QFileDialog(parent) file_dialog.setFileMode(QFileDialog.ExistingFile) diff --git a/pymead/gui/gui.py b/pymead/gui/gui.py index 09767e18..d6b253f6 100644 --- a/pymead/gui/gui.py +++ b/pymead/gui/gui.py @@ -34,7 +34,7 @@ from pymead.gui.input_dialog import LoadDialog, SaveAsDialog, OptimizationSetupDialog, \ MultiAirfoilDialog, ColorInputDialog, ExportCoordinatesDialog, ExportControlPointsDialog, AirfoilPlotDialog, \ AirfoilMatchingDialog, MSESFieldPlotDialog, ExportIGESDialog, XFOILDialog, NewMEADialog, EditBoundsDialog, \ - ExitDialog + ExitDialog, ScreenshotDialog from pymead.gui.pymeadPColorMeshItem import PymeadPColorMeshItem from pymead.gui.analysis_graph import AnalysisGraph from pymead.gui.parameter_tree import MEAParamTree @@ -350,6 +350,48 @@ def recursively_add_menus(menu: dict, menu_bar: QObject): recursively_add_menus(menu_data, self.menu_bar) + def take_screenshot(self): + + if hasattr(self.dockable_tab_window, "current_dock_widget"): + analysis_id = self.dockable_tab_window.current_dock_widget.winId() + else: + analysis_id = self.dockable_tab_window.dock_widgets[-1].winId() + + id_dict = { + "Full Window": self.winId(), + "Parameter Tree": self.param_tree_instance.t.winId(), + "Geometry": self.dockable_tab_window.dock_widgets[0].winId(), + "Analysis": analysis_id, + "Console": self.text_area.winId() + } + + dialog = ScreenshotDialog(self) + if dialog.exec_(): + inputs = dialog.getInputs() + + # Take the screenshot + screen = QApplication.primaryScreen() + screenshot = screen.grabWindow(id_dict[inputs["window"]]) + + # Handle improper directory names and file extensions + file_path_split = os.path.split(inputs['image_file']) + dir_name = file_path_split[0] + file_name = file_path_split[1] + file_name_no_ext = os.path.splitext(file_name)[0] + file_ext = os.path.splitext(file_name)[1] + if file_ext not in [".jpg", ".jpeg"]: + file_ext = ".jpg" + + final_file_name = os.path.join(dir_name, file_name_no_ext + file_ext) + + if os.path.isdir(dir_name): + screenshot.save(final_file_name, "jpg") # Save the screenshot + self.disp_message_box(f"{inputs['window']} window screenshot saved to {final_file_name}", + message_mode="info") + else: + self.disp_message_box(f"Directory {dir_name} for" + f"file {file_name} not found") + def save_as_mea(self): dialog = SaveAsDialog(self) if dialog.exec_(): diff --git a/pymead/gui/gui_settings/menu.json b/pymead/gui/gui_settings/menu.json index 26e88b20..c818a014 100644 --- a/pymead/gui/gui_settings/menu.json +++ b/pymead/gui/gui_settings/menu.json @@ -37,7 +37,8 @@ "Tools": { "Plot Airfoil from AirfoilTools": "plot_airfoil_from_airfoiltools", "Match Airfoil": "match_airfoil", - "Airfoil Statistics": "display_airfoil_statistics" + "Airfoil Statistics": "display_airfoil_statistics", + "Take Screenshot": ["take_screenshot", "Ctrl+Shift+S"] }, "Plot": { "Geometry From File": "plot_geometry" diff --git a/pymead/gui/input_dialog.py b/pymead/gui/input_dialog.py index b235e8f7..061238e4 100644 --- a/pymead/gui/input_dialog.py +++ b/pymead/gui/input_dialog.py @@ -1767,6 +1767,62 @@ def __init__(self, parent=None): layout = QFormLayout(self) +class ScreenshotDialog(QDialog): + def __init__(self, parent: QWidget): + + super().__init__(parent=parent) + + self.setWindowTitle("Screenshot") + self.setFont(self.parent().font()) + + self.grid_widget = {} + + buttonBox = QDialogButtonBox(self) + buttonBox.addButton(QDialogButtonBox.Ok) + buttonBox.addButton(QDialogButtonBox.Cancel) + self.grid_layout = QGridLayout(self) + + self.setInputs() + + self.grid_layout.addWidget(buttonBox, self.grid_layout.rowCount(), 1, 1, 2) + + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) + + def setInputs(self): + widget_dict = load_data(os.path.join('dialog_widgets', 'screenshot_dialog.json')) + for row_name, row_dict in widget_dict.items(): + self.grid_widget[row_name] = {} + for w_name, w_dict in row_dict.items(): + widget = getattr(sys.modules[__name__], w_dict["w"])(self) + self.grid_widget[row_name][w_name] = widget + if "text" in w_dict.keys() and isinstance(widget, (QLabel, QLineEdit, QPushButton)): + widget.setText(w_dict["text"]) + if "func" in w_dict.keys() and isinstance(widget, QPushButton): + if "choose" in row_name: + widget.clicked.connect(partial(getattr(self, w_dict["func"]), + self.grid_widget[row_name]["line"])) + else: + widget.clicked.connect(getattr(self, w_dict["func"])) + if "tool_tip" in w_dict.keys(): + widget.setToolTip(w_dict["tool_tip"]) + for attr_name, attr_value in w_dict.items(): + if attr_name not in ["text", "func", "tool_tip", "grid", "w"]: + getattr(widget, attr_name)(attr_value) + self.grid_layout.addWidget(widget, w_dict["grid"][0], w_dict["grid"][1], w_dict["grid"][2], + w_dict["grid"][3]) + + def getInputs(self): + inputs = { + "image_file": self.grid_widget["choose_image_file"]["line"].text(), + "window": self.grid_widget["window"]["combobox"].currentText() + } + return inputs + + def select_jpg_file(self, line_edit: QLineEdit): + select_jpg_file(parent=self.parent(), line_edit=line_edit) + + class BoundsDialog(QDialog): def __init__(self, bounds, parent=None, pos_param: bool = False): super().__init__(parent) diff --git a/pymead/gui/parameter_tree.py b/pymead/gui/parameter_tree.py index 96ee8f69..6d8bc8e3 100644 --- a/pymead/gui/parameter_tree.py +++ b/pymead/gui/parameter_tree.py @@ -170,6 +170,7 @@ def add_airfoil(self, airfoil: Airfoil): context={'add_eq': 'Define by equation', 'deactivate': 'Deactivate parameter', 'activate': 'Activate parameter', 'setbounds': 'Set parameter bounds'}) pg_param.airfoil_param = self.mea.param_dict[airfoil.tag]['AnchorPoints'][ap_key][p_key] + self.child(airfoil.tag).child('AnchorPoints').child(ap_key).addChild(pg_param) else: airfoil_param = self.mea.param_dict[airfoil.tag]['AnchorPoints'][ap_key][p_key] pg_param = Parameter.create(name=f"{airfoil.tag}.AnchorPoints.{ap_key}.{p_key}", @@ -789,31 +790,40 @@ def add_anchor_point(self, pg_param: Parameter): HeaderParameter within the ParameterTree named with the AnchorPoint's Airfoil tag """ a_tag = pg_param.name() - self.dialog = AnchorPointInputDialog(items=[("x", "double", 0.5), ("y", "double", 0.1), - ("L", "double", 0.1), ("R", "double", 2.0), - ("r", "double", 0.5), ("phi", "double", 0.0), - ("psi1", "double", 1.5), ("psi2", "double", 1.5), + self.dialog = AnchorPointInputDialog(items=[("x", "double", 0.5), + ("y", "double", 0.1), + ("L", "double", 0.1), + ("R", "double", 2.0), + ("r", "double", 0.5), + ("phi", "double", 0.0), + ("psi1", "double", 1.5), + ("psi2", "double", 1.5), ("Previous Anchor Point", "combo"), ("Anchor Point Name", "string")], ap=self.mea.airfoils[a_tag].anchor_points, parent=self.parent) + if self.dialog.exec(): inputs = self.dialog.getInputs() else: inputs = None if inputs: # Continue only if the dialog was accepted: + + # Stop execution if the AnchorPoint tag already exists for the airfoil + if inputs[9] in self.mea.airfoils[a_tag].anchor_point_order: + self.parent.disp_message_box(f"AnchorPoint tag {inputs[9]} already exists in Airfoil {a_tag}. " + f"Please choose a different name.") + return + elif inputs[9] == "": + self.parent.disp_message_box(f"AnchorPoint tag is empty. Please input a name for the AnchorPoint name.") + return + for curve in self.mea.airfoils[a_tag].curve_list: curve.clear_curve_pg() ap = AnchorPoint(xy=PosParam((inputs[0], inputs[1])), L=Param(inputs[2]), R=Param(inputs[3]), r=Param(inputs[4]), phi=Param(inputs[5]), psi1=Param(inputs[6]), psi2=Param(inputs[7]), previous_anchor_point=inputs[8], tag=inputs[9], airfoil_tag=a_tag) - # Stop execution if the AnchorPoint tag already exists for the airfoil - if ap.tag in self.mea.airfoils[a_tag].anchor_point_order: - self.parent.disp_message_box(f"AnchorPoint tag {ap.tag} already exists in Airfoil {a_tag}. " - f"Please choose a different name.") - return - # Insert the AnchorPoint and update the Airfoil self.mea.airfoils[a_tag].insert_anchor_point(ap) self.mea.airfoils[a_tag].update() @@ -844,6 +854,8 @@ def add_anchor_point(self, pg_param: Parameter): 'activate': 'Activate parameter', 'setbounds': 'Set parameter bounds'} ) pg_param.airfoil_param = self.mea.param_dict[a_tag]['AnchorPoints'][ap.tag][p_key] + self.params[-1].child(a_tag).child('AnchorPoints').child( + ap.tag).addChild(pg_param) else: airfoil_param = self.mea.param_dict[a_tag]['AnchorPoints'][ap.tag][p_key] pg_param = Parameter.create(name=f"{a_tag}.AnchorPoints.{ap.tag}.{p_key}", diff --git a/pymead/gui/pymead.spec b/pymead/gui/pymead.spec new file mode 100644 index 00000000..28dfb2eb --- /dev/null +++ b/pymead/gui/pymead.spec @@ -0,0 +1,58 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + + +a = Analysis( + ['gui.py'], + pathex=[], + binaries=[], + datas=[('gui_settings/defaults/*.json', 'pymead/gui/gui_settings/defaults/'), + ('gui_settings/themes/*.json', 'pymead/gui/gui_settings/themes/'), + ('../core/symmetry.py', 'pymead/core/'), + ('gui_settings/*.json', 'pymead/gui/gui_settings/'), + ('default_airfoil/*.jmea', 'pymead/gui/default_airfoil/'), + ('../icons/*.png', 'pymead/icons/'), + ('../icons/*.ico', 'pymead/icons/'), + ('dialog_widgets/*.json', 'pymead/gui/dialog_widgets/'), + ('../resources', 'pymead/resources')], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='pymead', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='../icons/pymead-logo.ico', +) +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='pymead', +)