From 6bac303dea998659f4797480022b2d9ae9eabfa9 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sat, 15 Feb 2025 23:18:58 -0700 Subject: [PATCH] Fixed Timing / Data Update Logic / Low Health Overlay Issues / Comparison Node and Math Node issues. --- Nodes/__pycache__/array_node.cpython-312.pyc | Bin 2808 -> 2821 bytes .../comparison_node.cpython-312.pyc | Bin 4963 -> 5303 bytes Nodes/__pycache__/data_node.cpython-312.pyc | Bin 6062 -> 3951 bytes ...lyff_character_status_node.cpython-312.pyc | Bin 7934 -> 4382 bytes ...lyff_low_health_alert_node.cpython-312.pyc | Bin 9517 -> 8931 bytes .../math_operation_node.cpython-312.pyc | Bin 4850 -> 4711 bytes Nodes/array_node.py | 4 +- Nodes/basic_nodes.py | 86 --------- Nodes/comparison_node.py | 182 +++++++++--------- Nodes/data_node.py | 99 +++------- Nodes/flyff_character_status_node.py | 147 +++++--------- Nodes/flyff_low_health_alert_node.py | 170 ++++++---------- Nodes/math_operation_node.py | 113 +++++------ borealis.py | 48 +++-- debug_processed.png | Bin 0 -> 2920 bytes debug_screenshot.png | Bin 0 -> 14100 bytes 16 files changed, 292 insertions(+), 557 deletions(-) delete mode 100644 Nodes/basic_nodes.py create mode 100644 debug_processed.png create mode 100644 debug_screenshot.png diff --git a/Nodes/__pycache__/array_node.cpython-312.pyc b/Nodes/__pycache__/array_node.cpython-312.pyc index aaf81b724d53154651f138275ef0afaa388a2f9f..af5df883ae4e4f208bd9810d48ad16f0595ac451 100644 GIT binary patch delta 89 zcmew%+A79-nwOW00SMMqY)n5hk#`-V;>0~C7!xNqGb$PDDufoKB$lKWD?p`9_Ro7 delta 76 zcmZn_`ytAEnwOW00SHbOtxexQk#`-V$izJ-CbuyvYA7g#XQbvSq?P96=qO~CC={jU f=9i@wE0koUD&*&+q!yPblqKerrf$B)sLcrg+|n9` diff --git a/Nodes/__pycache__/comparison_node.cpython-312.pyc b/Nodes/__pycache__/comparison_node.cpython-312.pyc index d49ae69df311427484c0a4a8f015749f92c8f967..d2429cc5b8498a26d02575b683da72e1dbbaf981 100644 GIT binary patch literal 5303 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 KCxXB34*vuDD{TV+ literal 6062 zcmd5=O>7&-6`m!z)QVinOe|40Whbj2M`9{VN$jYxVmnr1M{xtoO`0D>NU&XTR}vL! zmziB!CQBDC;G!~+APIuhIvQvX3EToc_K-_%1$yyTRLL%K2+&h+j+LN+d+B>K%Oxez zijm~dH9Q*5ym>S4?|X0NZ;3>Vz&CIId7(UciWHzs3>a#%TQ7K+ZKGL_951;XN_+MLb z77Zm^q|`7S&UI8Vaxe&tkONLPSs;JgaFk`uENc0DNx6{I;53eL(NX5Ci(|3av&N!! zA!pIa7@T?@2AxHJy=Ll)#Wq(o3VT|nX=EKk=Mqk3Q7#m%pxL&wNUh?+;;5qM>_T2E zfsidH;ld+7hiaz1oO2u`$T^B*F*3dlm-ieys(*%@jU2VPyrGZr16n3XfkOI61B&e@ z%jc~X+ZWkaJSdBnMwOMtTo%N$K_{TSZJRUs1NK~pJGr0c0&+WWA}-z>SWUMKTQMzQ z?aPTW&KD_~XDs$_beoLeNzZDwfxeREe#BewBKB?m68`)ST+kr10=QvF7iPnHXg0D+ z<^^y;X;#!DSIBHsPw3(mVYWk$!Y@hhfVNYYppEIB(8l!`w6Y$DHUYFU&~~koi^A-l zUyw|fC!x#YDaxt2qG^^6=e4=9oHeGyuxi5i-Oj+NvjRUGYcW#7@PD%o&Bwwu;;>Jhy;P+BTKo)d#;fBwdQFI0qK}Qd5p#YxSKRv8Rbn#EoMpQCRiS1O| z&a!9cyx!5=sYEi;j3=JOQ1qfa@;s5**%M#GQ1;M}5sV`m1zs7+6YXNbpcJ*^NvfJN zbB?NdotmygANjY3;Gmuap{ttOvXKdS5!=Ym zd!4G9%|pDYs*S2vF5i9R`N=nbPOT*fP4!ue8d^SQzlmnG-wZ4{Rwz-FxcorF=3|Wm z?t@|bRcOAwOump3?=G%ITw}f5^l%;ed#>(Tld4ibjMt<-SL$1v zzW(~nCvHi7RcV3^_PbJlRT@O%#MQ)F=Y}-635-d5=lKj~uAy7Bx)zed*K~_ZLPhw^ za3`qaW z@$Wefs%#AdWsWF(9WcPL=N2_{0l3?_1yjo-YPEBgCQphDV?_f5W ziFz^gH@=}4Z`sf5@R!oPz|ZSEa}je{&N4l*07=ZrglP{>ur&<^f?*>xfN->zO%^QM zQMuUV?!fz6MhAh+R-w5}ZpTP(-!-xfaYHT%LkEd?M2X2SLi7BC;?5ib*-Q;jB zIqoLMZ$@g5opm2OyOI3aZIQ@>-+mn>-3RXw0Z6|{_S{Mx|BF^jo^q3?{(C!&gLiE_ z?8k?Wlg~P*`of=!@>DAPc}j$Sw$-!n^bFjNLgzmGGgJvxh#L5CDU=D97X$OYY-sw{ zjLnQ3Bx>JHz@7jdP6^V#t`rLpw~$6!+z6`$ineD6wB6DG4l>%X!G+nsVI7epiR@Pl z40E=ah*`IMuxXk$Kr{}zu=}6^8%bo}fm-i~+dJ|}?|Sd!t8%@ke>L)6j3Sb3Q6;LC zsq!)CfGP!Dczc8p`at`jZ{~^;gd3=_9H`L`ua!l^R5TV_7y?{nSr#4k7#1(6vSFs9 z;T;5{0XME-M*JO$6_|1|A@1mj#-0q4IsW=?o(!h=arE;3R)w;Ys{YgjXv01T4Rd&T z|JwB5UQ&upDPf0Q<)qo~wZs3P9>0$_Y|&GuqkJw1kVDSi<#}oSeZ3~o~B`) z7lWZQCcCP55d?NR2*a5O2agzI$X0uC&ei|HCm3qk7~@zN@Z_1`NTtH1(a@I5sQ^;8 zjGu%xb_U{wfpaQ-ZT7?2>%vEGt;Xu`79o&#z6d%R@DJ+?B^~idA`{o*t^DkGkoj>*we(3heR4g0s+KWd7Nsa%^qI#voM)MJ zFYs9S1)kINxS3A{dIUE5lvhv`^CNv6&4_0T%>STe^4ozL@F@2MKCpB0by&QagoZgn zXQC#hT`9dG9r$tNJ$`@k!gW13Zi~qDNjMl~xo?Y*J%Jpp3(F7fl(TjIEoAl-HmvIS zp{B$a4gPI2B@a~g7;`UMGQk>_L?beDZnflI5YmtNKsV(Bts-up&HlIop14l|TXnp{ zc*o&PTjfeMt4a(ZU>`P1Yru=b@$CDN|GX>hqDHgA>{u~`?{!n;aEY*)8bgx34#yGJ6V z6v2NPvHVJ4#g~n&k-Gqw1Rb9p*>xgtELI=GyWF;`#IB_-MF8|<%Gs*Obtcn6594SU z(hvO!^ynxyco*mGNz z;i7qK;`xp4>90D-;3)x0^8?3LpJu3&5oR(;eCfdl8hGi6sp_(&7xUPcRrMWsZ}n%o z;IT!A_nh2(&Y<`vG;{v+8Fgmr+!^*NL;FD%cGIA6Jt8bM(4S&&5}Om)JcA7?+e`Q_ zKv^T7SJjL_Q!r{nu#i(+bLjc(6{;I2aLu<@NWJIq(<*P*Jql?6HC=RvAzsudPrawrBQ zBl(nrGB07I<6I{v=}Rz=r?el22JYm7@KrP+M80{52>oA276jo1VfErSgmu_{{{y-x Bfl>ef diff --git a/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc b/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc index 6b78cfb5a993d7db5f44c5e12144a00016dcadbe..d3efe3d8c70e84fc6a8de83a56fcd83806a933e3 100644 GIT binary patch literal 4382 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 literal 7934 zcmcIpeQXogmY?zXWBio_k`RX^Fodr-2~I*op#f4t90Ct;5=fy0s5^Kj#Ky7RnK2)h z)ACkGO`doQsCr6JSvB4Fq^6>~?Mf?cp598E7T87FUCD0Dv){Z(TWQ}v`7bTJw&D4(VwmE{8}t|zr%n=1UF=f}1LXuSMTL;au#7J%O5sr^5*8#T!umrJ zFEGQ=kWcc5LyR=SN(?8kR}@aa;EM)XA;i2w=xRpoxJPZAOyj#2`8wWU1^MvWR(ZB1DJlDMCn9giSEPK z)K;9*IDTL~8>fSpjhL`;RPL!)o|gML-N*E>hFzUmd&c$J8o*Jiji{}7p`NDO^X08e^VQBQWv2;-Hj2BDiDH%V3MXxzvd@9VNXp*C&|IMfmCSxlq2 zi8HBkA(AtV(Qz_Rqsgc(pw_8v?Wt%$x*y;&XSwnxZ7zPB-+zOg4*%3IT0KwY z&Df}Erncf{iBa1D{C-!Jz?M}6=>CAjwihL0@Xf?c?-QIQPQOq56{TvYXyts7Up+UH zxJEdwo{RtfZ&?2ORRM2s5kH4TY{`23lx3sx<%-Lcr=@EV-_pfHP%Y~^7-yxd7un0a zfc8?>5A^dPuw>ogU|5i8k(W;UIcY>TLJLQba!`nhBeE6EO;0fBkBGcXUF0QM9|AiF zH9N}7rV&XoU?=fsrVkOz2 zoP%seE9X@vF|fhN2rJtzgawWlvSPA9krwe2Mk|~x*RNk+-gBtqT)z+w!1CUMpcEVQ zi|5e(i|2-e*M^6^z7bYneXs>Dt``-(cqp#OHQAQa-HS#$)&K|Q#Y0foym&{heZjDg z4T_zvLQb>*;t{yc=hul0U3$wlVY^wfP*Q#?FcG*{`9ba7+Ua-rjL{L35#f z@2q>SA=Q5LwtMpEouiX|clxH?Gh0&?t%-qC<2@Ni?FaU|_G#{;z=wff20t4}ZhLu_ zoNfQ7j$e2DX5ZubqjQ{FZ0Jb8puN`|j?WA!piV*!h~)l&uX0(ze>9 zt#<0nC&o|BADhz+oymsIhwJ{O`t#~^*I*L8Q|?JGEs z(uvY1boEm>QQ~95sTZ1|Av9sth?NT}5#Pr*-+Yr1c0fa3`{FeTZ7@J1D;l%q>%@|c zpequlnoL#gxb08+HHP&|TL_yYp|28JfWrs>^Cf3piKjb(^e%WMJTXLa#E_07hbT@r zqz5;E>xX=!XjF*iq;* z)$@A4unXs;7JmniMMVBZj4zy|Vm^eIp)Kr20=2jq2jf^hLFyi?UV+L<3Hy=y2&-D8 zc3`y^Dv)YN>I1Bnp;~VGt`C-J=;}Yhxc#Cp90~y(@to8D3_fCKiEI#~5nd2p0(qJC zdi^24ZHF(+_{6QvaNJ4XNhN*|+DLJvc+( zSD=G^!k$>youb{(jKsRy$@6#4zvl(LEjYVn!jh)fC+YQRx;{zQPq%-x|HJ($x-CD3 zSUgq!gs#g%NTF@UuTHVeKZAObFcVUKG?2H=z>_OrlXag^sy!Ve!M#R^TU9vyr3QEf ze+Q!&x~(eeH$SG6v~OM~DWNyAs*K6K*jaSEL8 z5^%h8=SdPbPwO#QtbB2`LVF1uMXZ{-V+Fx5I$Tw>@oQ@fv?!13)V(T_#n!ISG+ZHD zS^RDxn9ykw-?5(+b8aT2^{TpC2%&w87Qcl8tqU|+SMVMf)tzhW7I6L_>g=qFy<<@$ zg?_M+A|_QBw9$Nsi-aLzgV_>+mjpk5nO6pbtOTJAlMfjfu&5Gba3CQQjc_=apAqN6 zNMgIlD5;+i8)UK}T{ioIE63K4NK&MurKN4>ZdWV(wRP-jZEbCVnOFf`Ishres>R1p zDKMtNm4;3QU}TalBN1;d_aoELmJl{&%V+@=+BxcQ0TtR=D#c%o#5SBg-F3oq#M678 zc@6SL5e2T4v$@1y>tJGbrXduTm{B$o0T_!ld_U%O=K(L)bhhWjiM|u(nI1t1L&8f4 z`*>0Ghb}6UL*N(*heV#i6X;m3RG9&Ue3zI(h|~BEizn8u(pTx07atA_qmW-ybYvOM z?~|MzLN6R*tXUPsdlvY&qIjRftUt(ejFNc68;y#L53Y`hwW-`WZQ(6ZRrQB7k6gpZ z4riH6=cW|}v@)_aJ0rxY7;6W_a7ecJVaGrY`GA7j$p*i~kBV8ur$h?RkgHcrOx#1mT% zJl>S5IyQdztJ11;>E>kV=4tWE(#8x7kKGx2@15KFKa|%^aldk>%AHF(M`b^mX>@(s z_;KUkHsAfpRQGh{l96cHmucReaqZRW?ag18(mOW%R~fOf{;qEt=6rv2X$?`=xKu$f zJ3iC@gZ1yM$&Gs#_8*w5{LRp`{v+#$)|um9G97jY&u26G!ti8&SDFvA#cX;#8{Qbf&^FdHl}t zM19|rihghw)~ri7+;gv|d(I?#&LrGJiFIdFW#=+w6_fTm_QZyxPs)xxe^yS^ZvBSP z!6p|RbwGSPkaF}X&6oetoZfRRx#!sY9#7iQmvr=fyJSGdZ=bCr%BsI1C^*xMt?btE ziQ|d&y(!z_1>2g$+Mb8v)S9z&m7j)bJyD+cVMYo=~c}q`RLv+o@b%5@#0J>}Y2mcBrjd3G*4f%K+ za9-muY4}+z!c+I7X$Ck?BF^8uk$tYf`9e)PR%l{TZsPw%liTFYiq*O}t+C!b+MWc+ z&f<60R&p<3G(dSi?oxnR)Nt4nD^U%7KmJ;?J=U-tcmNo}QGj+Z^@!w5HVPA6%*jjs zNCbeu<#Ne3$Vu}4%e-QfW1H1^vVfr)Yza{VZLi`{V7M-vhbmUB!E7EQvunjRGhCP# z6)i>}*$Y^uEyIlR(ny$d#p+iscVz@{j2QuUM1d*gCn2X*K^4IV>&X_(j^%<$9toxS zGf^^{qLO(P(3ce6!fFI7KUM*#a)2UiM*;$flC70XR!&mb$8!_H!1*GuTm))tQ?WQx zJvHS>FQf48EV@8+$$YT)?%s4wbF!xSw>7SbqvP(|TbFc|_QPbRs%G+?JMTIC?g5_I%;1B<7v(`_S?q-a{OkJeHr?ljSE2sGu=ZfH z{)?)$*lsp=yG&o~YUy^G9&9lH<$==xlm{*f+ihldyWv4ci@U|}u*Cpvrx~*on0xSg zWsBE48s?%wY}>rvx1ww?H&OzL0uFu^@DKYTtL~M}o<4Vv*VA>pM@cRS=u!xH{jxom zg7EQ>qVR&ZdlGLO!?^=l?*lU;N?K$q4&YBc&u?8#a9Na;w2j z{)r>k8xE67_^n7db)Lm{Kme4s_JKOR0vj0_kOT}^1ν{E6}-itq+@mDe0|#frGl ziY?3sXA{fxzU*#0g|~r*DvJ08EKwv${?SN~oBo5S{XJ3od!p>AzJ_!>>$pUchsmji LKN3nqndyH32+|D} diff --git a/Nodes/__pycache__/flyff_low_health_alert_node.cpython-312.pyc b/Nodes/__pycache__/flyff_low_health_alert_node.cpython-312.pyc index cca471d7e0d9117ed8b6582f74b5e86d465fdabe..f6b97ce07db16a9500f2cddfbecc47317ac43f40 100644 GIT binary patch literal 8931 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 F{{cM;@D>07 diff --git a/Nodes/__pycache__/math_operation_node.cpython-312.pyc b/Nodes/__pycache__/math_operation_node.cpython-312.pyc index 75e12f5cef4ead1acb2fb5878c518c124c0b3b44..50d15b8a35a36dff7d90419c1f44ce82464aaeba 100644 GIT binary patch literal 4711 zcmb_gOH3Te8LsZ>?&*0l81u04qrqPAcnyArVEhPvfaBGI*AG_fiLF*M)!@c6J#2Lk zHe`@ESPDc$#<3#ikb|?k2Q2&G-1eBYl+{YPFc>eBmRDLSx$KQKY-Bl?{M9`(%m9Wc zN+H$!RrS?hfB(-a{uT&$5wz>0fA0TN8AAV{gM8z2#8L|o(+Hyo!VG3(Oq`9d45eK$ zSDcG*3}Vp*gt>bN^D6tqc6ivi|hBJw3Bx9xn=rL47Ql$P^q8HdF2eD$RlBva2BEj*}AW1}3 z!_f48X&+YmG#$2Uy40H>suI(T=E32x!>2fV4=J&vT2C2) z=;km;qL{Wg=Rh2VXh!aP-*qM`@`mSHgyabY&XjiWxW%RMo|Q+q@N%tP!`Yl&uR7Fmb+w?PVBv0)T!TxbGN)CHDK!t5WI zX=E0b%)Iz0g?{Hl=t*vcf$@O504orfd6MJ&P6S(m@D#Qd2<$3Bz^SVn<}lxnu<#l4 z)SY);`@FzvBtlrkF6?>g-4y4;{!$zcq>x@$srvwS}mg@}0mUuPUYZ4`DT4EP?x;7XawuJNAkcL&ueP#%Pt)Kc7ubFDx zuzV0$QP~L!i#Y@foT=V5E#7wBk>Ew$REZAJ@iBE}Ld`~~>|u|^8%gkLLal6xvaIQv zDa)2e!5C-=Hp@%NgdK!dkkSx@y$Lz4>PcrzPjaxN+U8=YrmQP*HOyL^p~m_wk1R)H ziebpIL64Tk-X7{~yYUf845(34KAVfg8`MRO8@5+Eih*=u`zORG*#;X&wy$yN=Df&T zVBJVV_ht;e5(D$W-H*0r#DnwV;jHMtADjqI8S`T8YPxKqEG^E9b&CS>1Qx{Fj98og zV8;6zu_wHz8|AVO~64JbhvIy`Oz~&Yo`vR#nyG+@#m$c|R}_ zn5}AfDK;*FY-s(+YKQomrZH0-cN7k7C1ysMcOVKeGs>oz0UE2TT0Snxp^euuL$y4R zFBC;w@*LKU>Ow^74CY2%DRvVXHeCO|$iR7!S;!r!f_$TV$vl^j&QakV`AR4t+$lG9 z-`)ptBc%A0Fao+674=q_6^mw}_`+-x%zBDuL7y;N53^o-Hjol9bNlDOi??|}4g<~c zfC&M{XJb71Q?P#o)?tmM2#_;aWR@J65^ZjmQn0>hP70V6c@8Nt|9ww#ncU8k>t!<6 zrn@ThTvvWq_%mo2{`Mf#zMfEo>CfZ(*u9_$xPb7Z{B<-rcL&{dT}OAAP0<3NzssY; z!EQUR{Xk>LPDF%C)So|v(DMjY9)XJyA|cw*FC{c{CweVjk%@B0;(MJRMTVSCNAP2RlO;HL-=GdJWC*AY=o6n-o)@^fv`fOEgT73|IWMnG$Pjr4?Su@@VS=}GH-!{>fuA1}h z&TiX1etz-;m<*IJ_;+UfJJa!}SD#fq`PIDtc-9}v&Gmm_%(Q&|>v{jt9CBC2zbifT z(%-OHj{MS_o|g*AMhH?rDh0$4vKQ^w{NipV#p|>gtxysrioI z(=Fgyj#q7gvg`m0(J``jt!u4CeGFWdXmuGwP>4xAP#X(JS^+|i)2Lct9Yb- ztw^geH{jGFY3S+2?OoF3?8ea)Vug%El0;S8VvblP`trluL)C^pnoVju#=P;WuY6deYl@$KM zGh~!caRX3ZSGN?8T~E3Eeg;jpb9MolVZ zWO{s1^3gv4ZW0q=N!aQedzU1GWbgkL{u* zQYV4HH4SP(r%>1dO`2i6UU4X?P`v=MFW3;gRoka;Ke+vg!Vbcos{5P$D z{kG;BWJ*|gYtTaeRpqWTt?XB=JI+?~&#M^d?brCu5ZM%krm5 zC6-$W!fOJnx~cVPDxuGy?n~!8Q1?T((Q-tCtby@nV^pnG!P7cDAyabcY4YEz39&1xbCEK<*1f8N7AbBsA&g!;eHAsrtUjbxy;E7)5E02pO8)1$NvZ4=15@x literal 4850 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 SO|M-^j@y&tzeV()t@b}HjC3gg diff --git a/Nodes/array_node.py b/Nodes/array_node.py index 5a4a26a..8f4e09e 100644 --- a/Nodes/array_node.py +++ b/Nodes/array_node.py @@ -5,8 +5,8 @@ class ArrayNode(BaseNode): Array Node: - Inputs: 'in' (value to store), 'ArraySize' (defines maximum length) - Output: 'Array' (the current array as a string) - - Stores incoming values in an array with size defined by ArraySize. - When full, it removes the oldest value. + - Stores incoming values in an array with a size defined by ArraySize. + - Updates are now handled via a global update timer. """ __identifier__ = 'bunny-lab.io.array_node' NODE_NAME = 'Array' diff --git a/Nodes/basic_nodes.py b/Nodes/basic_nodes.py deleted file mode 100644 index 4d36de1..0000000 --- a/Nodes/basic_nodes.py +++ /dev/null @@ -1,86 +0,0 @@ -from OdenGraphQt import BaseNode, BaseNodeCircle - - -class BasicNodeA(BaseNode): - """ - A node class with 2 inputs and 2 outputs. - """ - - # unique node identifier. - __identifier__ = 'nodes.basic' - - # initial default node name. - NODE_NAME = 'node A' - - def __init__(self): - super(BasicNodeA, self).__init__() - - # create node inputs. - self.add_input('in A') - self.add_input('in B') - - # create node outputs. - self.add_output('out A') - self.add_output('out B') - - -class BasicNodeB(BaseNode): - """ - A node class with 3 inputs and 3 outputs. - The last input and last output can take in multiple pipes. - """ - - # unique node identifier. - __identifier__ = 'nodes.basic' - - # initial default node name. - NODE_NAME = 'node B' - - def __init__(self): - super(BasicNodeB, self).__init__() - - # create node inputs - self.add_input('single 1') - self.add_input('single 2') - self.add_input('multi in', multi_input=True) - - # create node outputs - self.add_output('single 1', multi_output=False) - self.add_output('single 2', multi_output=False) - self.add_output('multi out') - - -class CircleNode(BaseNodeCircle): - """ - A node class with 3 inputs and 3 outputs. - This node is a circular design. - """ - - # unique node identifier. - __identifier__ = 'nodes.basic' - - # initial default node name. - NODE_NAME = 'Circle Node' - - def __init__(self): - super(CircleNode, self).__init__() - self.set_color(10, 24, 38) - - # create node inputs - p = self.add_input('in 1') - p.add_accept_port_type( - port_name='single 1', - port_type='out', - node_type='nodes.basic.BasicNodeB' - ) - - self.add_input('in 2') - self.add_input('in 3', multi_input=True) - self.add_input('in 4', display_name=False) - self.add_input('in 5', display_name=False) - - # create node outputs - self.add_output('out 1') - self.add_output('out 2', multi_output=False) - self.add_output('out 3', multi_output=True, display_name=False) - self.add_output('out 4', multi_output=True, display_name=False) \ No newline at end of file diff --git a/Nodes/comparison_node.py b/Nodes/comparison_node.py index deb52ab..34ad35a 100644 --- a/Nodes/comparison_node.py +++ b/Nodes/comparison_node.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 + """ -Comparison Node: - - Inputs: Two input ports ("A" and "B"). - - Output: One output port ("Result"). - - Operation: A dropdown (combo menu) to select: - Equal (==), Not Equal (!=), Greater Than (>), Less Than (<), - Greater Than or Equal (>=), Less Than or Equal (<=). - - Displays the computed result in a read-only text box labeled "Result". +Standardized Comparison Node: + - Compares two input values using a selected operator (==, !=, >, <, >=, <=). + - Outputs a result of 1 (True) or 0 (False). + - Uses a global update timer for processing. + - Supports an additional 'Input Type' dropdown to choose between 'Number' and 'String'. """ from OdenGraphQt import BaseNode +from Qt import QtCore class ComparisonNode(BaseNode): __identifier__ = 'bunny-lab.io.comparison_node' @@ -17,112 +17,106 @@ class ComparisonNode(BaseNode): def __init__(self): super(ComparisonNode, self).__init__() - - # ---------------------------------------------------------------------- - # Initialization Section: - # - Create two input ports: A, B - # - Create one output port: Result - # - Add a combo box for logical operator selection - # - Add a text input for displaying the computed result - # ---------------------------------------------------------------------- self.add_input('A') self.add_input('B') self.add_output('Result') - # Operator combo box (==, !=, >, <, >=, <=) + # Add the Input Type dropdown first. + self.add_combo_menu('input_type', 'Input Type', items=['Number', 'String']) self.add_combo_menu('operator', 'Operator', items=[ - 'Equal (==)', - 'Not Equal (!=)', - 'Greater Than (>)', - 'Less Than (<)', - 'Greater Than or Equal (>=)', - 'Less Than or Equal (<=)' + 'Equal (==)', 'Not Equal (!=)', 'Greater Than (>)', + 'Less Than (<)', 'Greater Than or Equal (>=)', 'Less Than or Equal (<=)' ]) - - # Text input for displaying the computed result. - # We'll make it read-only by accessing the underlying QLineEdit. - self.add_text_input('calc_result', 'Result', text='0') - result_widget = self.get_widget('calc_result') # This is a NodeLineEdit wrapper - if result_widget: - # Get the underlying QLineEdit - line_edit = result_widget.get_custom_widget() - # Make the QLineEdit read-only - line_edit.setReadOnly(True) - + # Replace calc_result with a standardized "value" text input. + self.add_text_input('value', 'Value', text='0') self.value = 0 self.set_name("Comparison Node") - self.process_input() + self.processing = False # Guard for process_input - def process_input(self, event=None): - """ - Compute Section: - - For each input port (A, B), if connected, grab the 'value' from - the upstream node; otherwise default to 0.0. - - Convert to float when possible, apply the selected comparison operator, - update the "Result" text box, node title, and output port. - """ - # Gather input A + # Set default properties explicitly + self.set_property('input_type', 'Number') + self.set_property('operator', 'Equal (==)') + + def process_input(self): + if self.processing: + return + self.processing = True + + # Retrieve input values; if no connection or None, default to "0" input_a = self.input(0) - if input_a and input_a.connected_ports(): - a_raw = input_a.connected_ports()[0].node().get_property('value') - else: - a_raw = 0.0 - - # Gather input B input_b = self.input(1) - if input_b and input_b.connected_ports(): - b_raw = input_b.connected_ports()[0].node().get_property('value') + a_raw = (input_a.connected_ports()[0].node().get_property('value') + if input_a.connected_ports() else "0") + b_raw = (input_b.connected_ports()[0].node().get_property('value') + if input_b.connected_ports() else "0") + a_raw = a_raw if a_raw is not None else "0" + b_raw = b_raw if b_raw is not None else "0" + + # Get input type property + input_type = self.get_property('input_type') + + # Convert values based on input type + if input_type == 'Number': + try: + a_val = float(a_raw) + except (ValueError, TypeError): + a_val = 0.0 + try: + b_val = float(b_raw) + except (ValueError, TypeError): + b_val = 0.0 + elif input_type == 'String': + a_val = str(a_raw) + b_val = str(b_raw) else: - b_raw = 0.0 + try: + a_val = float(a_raw) + except (ValueError, TypeError): + a_val = 0.0 + try: + b_val = float(b_raw) + except (ValueError, TypeError): + b_val = 0.0 - # Convert raw inputs to float if possible, otherwise keep as-is for string comparison. - try: - a_val = float(a_raw) - b_val = float(b_raw) - except (ValueError, TypeError): - a_val = a_raw - b_val = b_raw - - # Retrieve the selected operator from the combo box. operator = self.get_property('operator') - result = False - - if operator == 'Equal (==)': - result = a_val == b_val - elif operator == 'Not Equal (!=)': - result = a_val != b_val - elif operator == 'Greater Than (>)': - result = a_val > b_val - elif operator == 'Less Than (<)': - result = a_val < b_val - elif operator == 'Greater Than or Equal (>=)': - result = a_val >= b_val - elif operator == 'Less Than or Equal (<=)': - result = a_val <= b_val - - # Convert boolean result to integer (1 for True, 0 for False) - self.value = 1 if result else 0 - - # Update the read-only text input and node title. - self.set_property('calc_result', str(self.value)) - - # Transmit the numeric result to any connected output nodes. - output_port = self.output(0) - if output_port and output_port.connected_ports(): - for cp in output_port.connected_ports(): - connected_node = cp.node() - if hasattr(connected_node, 'receive_data'): - connected_node.receive_data(self.value, source_port_name='Result') + + # Perform the comparison + result = { + 'Equal (==)': a_val == b_val, + 'Not Equal (!=)': a_val != b_val, + 'Greater Than (>)': a_val > b_val, + 'Less Than (<)': a_val < b_val, + 'Greater Than or Equal (>=)': a_val >= b_val, + 'Less Than or Equal (<=)': a_val <= b_val + }.get(operator, False) + + new_value = 1 if result else 0 + self.value = new_value + self.set_property('value', str(self.value)) + self.transmit_data(self.value) + + self.processing = False def on_input_connected(self, input_port, output_port): - self.process_input() + pass def on_input_disconnected(self, input_port, output_port): - self.process_input() + pass def property_changed(self, property_name): - if property_name in ['operator']: - self.process_input() + pass def receive_data(self, data, source_port_name=None): - self.process_input() + pass + + def transmit_data(self, data): + output_port = self.output(0) + if output_port and output_port.connected_ports(): + for connected_port in output_port.connected_ports(): + connected_node = connected_port.node() + if hasattr(connected_node, 'receive_data'): + try: + data_int = int(data) + connected_node.receive_data(data_int, source_port_name='Result') + except ValueError: + pass diff --git a/Nodes/data_node.py b/Nodes/data_node.py index 1b1304b..6803833 100644 --- a/Nodes/data_node.py +++ b/Nodes/data_node.py @@ -1,21 +1,13 @@ #!/usr/bin/env python3 """ -Data Node: - - Input: Accepts a value (string, integer, or float) from an input port. - - Output: Outputs the current value, either from the input port or set manually via a text box. - -Behavior: -- If both input and output are connected: - - Acts as a passthrough, displaying the input value and transmitting it to the output. - - Manual input is disabled. -- If only the output is connected: - - Allows manual value entry, which is sent to the output. -- If only the input is connected: - - Displays the input value but does not transmit it further. +Standardized Data Node: + - Accepts and transmits values consistently. + - Updates its value based on a global update timer. """ from OdenGraphQt import BaseNode +from Qt import QtCore class DataNode(BaseNode): __identifier__ = 'bunny-lab.io.data_node' @@ -23,103 +15,58 @@ class DataNode(BaseNode): def __init__(self): super(DataNode, self).__init__() - # Add input and output ports. self.add_input('Input') self.add_output('Output') - # Add a text input widget for manual entry. self.add_text_input('value', 'Value', text='') - # Initialize the value from the widget property. self.process_widget_event() - self.set_name(f"Data Node") + self.set_name("Data Node") + # Removed self-contained update timer; global timer now drives updates. def post_create(self): - """ - Called after the node's widget is fully created. - Connect the text input widget's textChanged signal to process_widget_event. - """ text_widget = self.get_widget('value') if text_widget is not None: try: - text_widget.textChanged.connect(self.process_widget_event) + # Removed textChanged signal connection; global timer will call process_input. + pass except Exception as e: print("Error connecting textChanged signal:", e) def process_widget_event(self, event=None): - """ - Reads the current text from the node's property and updates the node's internal value. - """ current_text = self.get_property('value') self.value = current_text + self.transmit_data(current_text) def property_changed(self, property_name): - """ - Called when a node property changes. If the 'value' property changes, - update the internal value. - """ if property_name == 'value': - self.process_widget_event() + # Immediate update removed; relying on global timer. + pass - def update_stream(self): - """ - Updates the node's behavior based on the connection states. - """ + def process_input(self): input_port = self.input(0) output_port = self.output(0) - - if input_port.connected_ports() and output_port.connected_ports(): - # Both input and output are connected; act as passthrough. - self.set_property('value', '') - self.get_widget('value').setEnabled(False) + if input_port.connected_ports(): input_value = input_port.connected_ports()[0].node().get_property('value') self.set_property('value', input_value) + self.transmit_data(input_value) elif output_port.connected_ports(): - # Only output is connected; allow manual input. - self.get_widget('value').setEnabled(True) - elif input_port.connected_ports(): - # Only input is connected; display input value. - self.get_widget('value').setEnabled(False) - input_value = input_port.connected_ports()[0].node().get_property('value') - self.set_property('value', input_value) - else: - # Neither input nor output is connected; allow manual input. - self.get_widget('value').setEnabled(True) + self.transmit_data(self.get_property('value')) def on_input_connected(self, input_port, output_port): - """ - Called when an input port is connected. - """ - self.update_stream() + # Removed immediate update; global timer handles updates. + pass def on_input_disconnected(self, input_port, output_port): - """ - Called when an input port is disconnected. - """ - self.update_stream() - - def on_output_connected(self, output_port, input_port): - """ - Called when an output port is connected. - """ - self.update_stream() - - def on_output_disconnected(self, output_port, input_port): - """ - Called when an output port is disconnected. - """ - self.update_stream() + # Removed immediate update; global timer handles updates. + pass def receive_data(self, data, source_port_name=None): - """ - Receives data from connected nodes and updates the internal value. - """ - self.set_property('value', str(data)) # Ensure it's always stored as a string + self.set_property('value', str(data)) + self.transmit_data(data) - # Transmit data further if there's an output connection + def transmit_data(self, data): output_port = self.output(0) if output_port and output_port.connected_ports(): for connected_port in output_port.connected_ports(): connected_node = connected_port.node() if hasattr(connected_node, 'receive_data'): - connected_node.receive_data(data, source_port_name) - - + connected_node.receive_data(data, source_port_name="Output") diff --git a/Nodes/flyff_character_status_node.py b/Nodes/flyff_character_status_node.py index 9275104..3b7ded2 100644 --- a/Nodes/flyff_character_status_node.py +++ b/Nodes/flyff_character_status_node.py @@ -1,47 +1,23 @@ #!/usr/bin/env python3 +""" +Standardized Flyff Character Status Node: + - Polls an API for character stats and updates values dynamically. + - Uses a global update timer for processing. + - Immediately transmits updated values to connected nodes. +""" + from OdenGraphQt import BaseNode -from Qt import QtCore, QtGui +from Qt import QtCore import requests import traceback -def get_draw_stat_port(color, border_color=None, alpha=127): - """ - Returns a custom port painter function that draws a circular port with a - semi-transparent fill and then draws text (port label and current value) - next to it. - """ - if border_color is None: - border_color = color - - def painter_func(painter, rect, info): - painter.save() - pen = QtGui.QPen(QtGui.QColor(*border_color)) - pen.setWidth(1.8) - painter.setPen(pen) - semi_transparent_color = QtGui.QColor(color[0], color[1], color[2], alpha) - painter.setBrush(semi_transparent_color) - painter.drawEllipse(rect) - port = info.get('port') - if port is not None: - node = port.node() - stat = port.name() - value = node.values.get(stat, "N/A") if hasattr(node, 'values') else "N/A" - text_rect = rect.adjusted(rect.width() + 4, 0, rect.width() + 70, 0) - painter.setPen(QtGui.QColor(0, 0, 0)) - painter.drawText(text_rect, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft, - f"{stat}: {value}") - painter.restore() - return painter_func - -class CharacterStatusNode(BaseNode): +class FlyffCharacterStatusNode(BaseNode): __identifier__ = 'bunny-lab.io.flyff_character_status_node' NODE_NAME = 'Flyff - Character Status' def __init__(self): - super(CharacterStatusNode, self).__init__() - - # Define exact expected keys to avoid transformation mismatches + super(FlyffCharacterStatusNode, self).__init__() self.values = { "HP: Current": "N/A", "HP: Total": "N/A", "MP: Current": "N/A", "MP: Total": "N/A", @@ -49,97 +25,60 @@ class CharacterStatusNode(BaseNode): "EXP": "N/A" } - # Add output ports - self.add_output("HP: Current", painter_func=get_draw_stat_port((217, 36, 78))) - self.add_output("HP: Total", painter_func=get_draw_stat_port((217, 36, 78))) - self.add_output("MP: Current", painter_func=get_draw_stat_port((35, 124, 213))) - self.add_output("MP: Total", painter_func=get_draw_stat_port((35, 124, 213))) - self.add_output("FP: Current", painter_func=get_draw_stat_port((36, 197, 28))) - self.add_output("FP: Total", painter_func=get_draw_stat_port((36, 197, 28))) - self.add_output("EXP", painter_func=get_draw_stat_port((52, 195, 250))) + for stat in self.values.keys(): + self.add_output(stat) self.set_name("Flyff - Character Status (API Disconnected)") + # Removed self-contained polling timer; global timer now drives updates. - # Start polling timer - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.poll_api) - self.timer.start(500) - - def poll_api(self): - """ - Polls the API endpoint to retrieve the latest character stats and updates - the node's internal values. - """ + def process_input(self): try: response = requests.get("http://127.0.0.1:5000/data", timeout=1) - if response.status_code == 200: data = response.json() - if isinstance(data, dict): - try: - for key, value in data.items(): - # Ensure the keys match the expected ones exactly - formatted_key = { - "hp_current": "HP: Current", - "hp_total": "HP: Total", - "mp_current": "MP: Current", - "mp_total": "MP: Total", - "fp_current": "FP: Current", - "fp_total": "FP: Total", - "exp": "EXP" - }.get(key, key) # Use mapping or fallback to raw key - - if formatted_key in self.values: + mapping = { + "hp_current": "HP: Current", + "hp_total": "HP: Total", + "mp_current": "MP: Current", + "mp_total": "MP: Total", + "fp_current": "FP: Current", + "fp_total": "FP: Total", + "exp": "EXP" + } + updated = False + for key, value in data.items(): + if key in mapping: + formatted_key = mapping[key] + if str(value) != self.values.get(formatted_key, None): self.values[formatted_key] = str(value) - else: - print(f"[WARNING] Unexpected API key: {key} (not mapped)") - + updated = True + if updated: self.set_name("Flyff - Character Status (API Connected)") - self.update() self.transmit_data() - - except Exception as e: - print("[ERROR] Error processing API response data:", e) - print("[ERROR] Stack Trace:\n", traceback.format_exc()) else: print("[ERROR] Unexpected API response format (not a dict):", data) self.set_name("Flyff - Character Status (API Disconnected)") - else: print(f"[ERROR] API request failed with status code {response.status_code}") self.set_name("Flyff - Character Status (API Disconnected)") - except Exception as e: self.set_name("Flyff - Character Status (API Disconnected)") print("[ERROR] Error polling API in CharacterStatusNode:", str(e)) - print("[ERROR] Stack Trace:\n", traceback.format_exc()) def transmit_data(self): - """ - Sends the updated character stats to connected nodes. - """ for stat, value in self.values.items(): - try: - port = self.get_output(stat) - - if port is None: - print(f"[ERROR] Port '{stat}' not found in node outputs. Skipping...") - continue - - if port.connected_ports(): - for connected_port in port.connected_ports(): - connected_node = connected_port.node() - if hasattr(connected_node, 'receive_data'): - try: - connected_node.receive_data(value, stat) - except Exception as e: - print(f"[ERROR] Error transmitting data to {connected_node}: {e}") - print("[ERROR] Stack Trace:\n", traceback.format_exc()) - else: - print(f"[WARNING] Connected node {connected_node} does not have receive_data method.") - - except Exception as e: - print(f"[ERROR] Error while handling port {stat}: {e}") - print("[ERROR] Stack Trace:\n", traceback.format_exc()) + port = self.get_output(stat) + if port and port.connected_ports(): + for connected_port in port.connected_ports(): + connected_node = connected_port.node() + if hasattr(connected_node, 'receive_data'): + try: + connected_node.receive_data(value, stat) + except Exception as e: + print(f"[ERROR] Error transmitting data to {connected_node}: {e}") + print("[ERROR] Stack Trace:\n", traceback.format_exc()) + def receive_data(self, data, source_port_name=None): + # This node only transmits data; it does not receive external data. + pass diff --git a/Nodes/flyff_low_health_alert_node.py b/Nodes/flyff_low_health_alert_node.py index 2aa0b1d..699e1bd 100644 --- a/Nodes/flyff_low_health_alert_node.py +++ b/Nodes/flyff_low_health_alert_node.py @@ -1,177 +1,133 @@ -import time -import sys -from OdenGraphQt import BaseNode -from Qt import QtWidgets, QtCore, QtGui +#!/usr/bin/env python3 + +""" +Standardized Flyff Low Health Alert Node: + - Monitors an input value (1 = health alert, 0 = normal). + - Displays a visual alert and plays a sound if enabled. + - Uses a global update timer for processing. + - Automatically processes float, int, and string values. +""" + +import time +from OdenGraphQt import BaseNode +from Qt import QtCore, QtWidgets, QtGui -# Attempt to import winsound (Windows-only) try: import winsound HAS_WINSOUND = True except ImportError: HAS_WINSOUND = False - class OverlayCanvas(QtWidgets.QWidget): """ UI overlay for displaying a red warning box, which can be repositioned by dragging. """ - def __init__(self, parent=None): super().__init__(parent) - - # **Full-screen overlay** screen_geo = QtWidgets.QApplication.primaryScreen().geometry() - self.setGeometry(screen_geo) # Set to full screen - + self.setGeometry(screen_geo) self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) - self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True) - self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, False) - self.setAttribute(QtCore.Qt.WA_AlwaysStackOnTop, True) - - # **Draggable Low Health Alert** - self.helper_LowHealthAlert = QtCore.QRect(250, 300, 900, 35) # Default Position + self.setVisible(False) + self.helper_LowHealthAlert = QtCore.QRect(250, 300, 900, 35) self.dragging = False - self.drag_offset = QtCore.QPoint() - - self.setVisible(False) # Initially hidden + self.drag_offset = None def paintEvent(self, event): - """Draw the helper overlay objects.""" if not self.isVisible(): - return # Don't draw anything if invisible - + return painter = QtGui.QPainter(self) painter.setPen(QtCore.Qt.NoPen) - painter.setBrush(QtGui.QColor(255, 0, 0)) # Solid red rectangle + painter.setBrush(QtGui.QColor(255, 0, 0)) painter.drawRect(self.helper_LowHealthAlert) - - # Draw bold white text centered within the rectangle font = QtGui.QFont("Arial", 14, QtGui.QFont.Bold) painter.setFont(font) painter.setPen(QtGui.QColor(255, 255, 255)) - - text = "LOW HEALTH" - metrics = QtGui.QFontMetrics(font) - text_width = metrics.horizontalAdvance(text) - text_height = metrics.height() - text_x = self.helper_LowHealthAlert.center().x() - text_width // 2 - text_y = self.helper_LowHealthAlert.center().y() + text_height // 4 - - painter.drawText(text_x, text_y, text) + text_x = self.helper_LowHealthAlert.center().x() - 50 + text_y = self.helper_LowHealthAlert.center().y() + 5 + painter.drawText(text_x, text_y, "LOW HEALTH") def toggle_alert(self, state): - """ - Show or hide the overlay based on the state (1 = show, 0 = hide). - """ self.setVisible(state == 1) self.update() def mousePressEvent(self, event): - """Detect clicks inside the red box and allow dragging.""" - if event.button() == QtCore.Qt.LeftButton and self.helper_LowHealthAlert.contains(event.pos()): - self.dragging = True - self.drag_offset = event.pos() - self.helper_LowHealthAlert.topLeft() + if event.button() == QtCore.Qt.LeftButton: + if self.helper_LowHealthAlert.contains(event.pos()): + self.dragging = True + self.drag_offset = event.pos() - self.helper_LowHealthAlert.topLeft() + super().mousePressEvent(event) def mouseMoveEvent(self, event): - """Handle dragging movement.""" if self.dragging: - new_x = event.pos().x() - self.drag_offset.x() - new_y = event.pos().y() - self.drag_offset.y() - self.helper_LowHealthAlert.moveTopLeft(QtCore.QPoint(new_x, new_y)) + new_top_left = event.pos() - self.drag_offset + self.helper_LowHealthAlert.moveTo(new_top_left) self.update() + super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): - """Stop dragging when the mouse button is released.""" - self.dragging = False - + if event.button() == QtCore.Qt.LeftButton: + self.dragging = False + super().mouseReleaseEvent(event) class FlyffLowHealthAlertNode(BaseNode): - """ - Custom OdenGraphQt node that toggles a visual alert overlay and plays a beep when health is low. - """ - __identifier__ = 'bunny-lab.io.flyff_low_health_alert_node' NODE_NAME = 'Flyff - Low Health Alert' - overlay_instance = None # Shared overlay instance - last_beep_time = 0 # Time tracking for beep interval - BEEP_INTERVAL_SECONDS = 2 # Beep every 2 seconds + overlay_instance = None + last_beep_time = 0 + BEEP_INTERVAL_SECONDS = 2 def __init__(self): super(FlyffLowHealthAlertNode, self).__init__() - - # Create checkboxes to decide which kind of alert(s) to utilize self.add_checkbox('cb_1', '', 'Sound Alert', True) self.add_checkbox('cb_2', '', 'Visual Alert', True) - - # Create Input Port self.add_input('Toggle (1 = On | 0 = Off)', color=(200, 100, 0)) - - # Add text input widget to display received value self.add_text_input('value', 'Current Value', text='0') + self.add_combo_menu('beep_interval', 'Beep Interval', items=["0.5s", "1.0s", "2.0s"]) - # Ensure only one overlay instance exists if not FlyffLowHealthAlertNode.overlay_instance: FlyffLowHealthAlertNode.overlay_instance = OverlayCanvas() FlyffLowHealthAlertNode.overlay_instance.show() def process_input(self): - """ - This function runs every 500ms (via the global update loop). - It updates the displayed value and toggles the alert if needed. - """ input_port = self.input(0) + value = input_port.connected_ports()[0].node().get_property('value') if input_port.connected_ports() else "0" + self.receive_data(value) - # If there is a connected node, fetch its output value - if input_port.connected_ports(): - connected_node = input_port.connected_ports()[0].node() - if hasattr(connected_node, 'get_property'): - value = connected_node.get_property('value') - else: - value = "0" - else: - value = "0" # Default to zero if nothing is connected - + def receive_data(self, data, source_port_name=None): try: - input_value = int(value) # Ensure we interpret input as an integer (0 or 1) + if isinstance(data, str): + data = float(data) if '.' in data else int(data) + if isinstance(data, (float, int)): + data = 1 if data > 1 else 0 if data <= 0 else int(data) + else: + data = 0 except ValueError: - input_value = 0 # Default to off if the input is not valid + data = 0 - # Update the value display box - self.set_property('value', str(input_value)) - - # Check if the "Visual Alert" checkbox is enabled - visual_alert_enabled = self.get_property('cb_2') - - # Ensure that if "Visual Alert" is unchecked, the overlay is always hidden - if not visual_alert_enabled: - FlyffLowHealthAlertNode.overlay_instance.toggle_alert(0) - else: - FlyffLowHealthAlertNode.overlay_instance.toggle_alert(input_value) - - # Check if "Sound Alert" is enabled and beep if necessary - self.handle_beep(input_value) + self.set_property('value', str(data)) + if self.get_property('cb_2'): + FlyffLowHealthAlertNode.overlay_instance.toggle_alert(data) + self.handle_beep(data) def handle_beep(self, input_value): - """ - Plays a beep sound every 2 seconds when the value is `1` and "Sound Alert" is enabled. - """ - sound_alert_enabled = self.get_property('cb_1') - current_time = time.time() + # Update beep interval from the dropdown property + interval_str = self.get_property('beep_interval') + if interval_str.endswith("s"): + interval_seconds = float(interval_str[:-1]) + else: + interval_seconds = float(interval_str) + self.BEEP_INTERVAL_SECONDS = interval_seconds - if input_value == 1 and sound_alert_enabled: - if (current_time - FlyffLowHealthAlertNode.last_beep_time) >= FlyffLowHealthAlertNode.BEEP_INTERVAL_SECONDS: + if input_value == 1 and self.get_property('cb_1'): + current_time = time.time() + if (current_time - FlyffLowHealthAlertNode.last_beep_time) >= self.BEEP_INTERVAL_SECONDS: FlyffLowHealthAlertNode.last_beep_time = current_time self.play_beep() - else: - FlyffLowHealthAlertNode.last_beep_time = 0 # Reset when health is safe def play_beep(self): - """ - Plays a beep using `winsound.Beep` (Windows) or prints a terminal bell (`\a`). - """ if HAS_WINSOUND: - winsound.Beep(376, 100) # 376 Hz, 100ms duration + winsound.Beep(376, 100) else: - print('\a', end='') # Terminal bell for non-Windows systems + print('\a', end='') diff --git a/Nodes/math_operation_node.py b/Nodes/math_operation_node.py index 149b895..1aea0fa 100644 --- a/Nodes/math_operation_node.py +++ b/Nodes/math_operation_node.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 + """ -Math Operation Node: - - Inputs: Two input ports ("A" and "B"). - - Output: One output port ("Result"). - - Operation: A dropdown (combo menu) to select: - Add, Subtract, Multiply, Divide, Average. - - Displays the computed result in a read-only text box labeled "Result". +Standardized Math Operation Node: + - Performs mathematical operations (+, -, *, /, avg) on two inputs. + - Outputs the computed result. + - Uses a global update timer for processing (defined in borealis.py). + - Ensures it always has a "value" property that the Comparison Node can read. """ from OdenGraphQt import BaseNode +from Qt import QtCore class MathOperationNode(BaseNode): __identifier__ = 'bunny-lab.io.math_node' @@ -16,60 +17,38 @@ class MathOperationNode(BaseNode): def __init__(self): super(MathOperationNode, self).__init__() - - # ---------------------------------------------------------------------- - # Initialization Section: - # - Create two input ports: A, B - # - Create one output port: Result - # - Add a combo box for operator selection - # - Add a text input for displaying the computed result - # ---------------------------------------------------------------------- self.add_input('A') self.add_input('B') self.add_output('Result') - - # Operator combo box (Add, Subtract, Multiply, Divide, Average) + + # Drop-down to choose which operation we do: self.add_combo_menu('operator', 'Operator', items=[ 'Add', 'Subtract', 'Multiply', 'Divide', 'Average' ]) - # Text input for displaying the computed result. - # We'll make it read-only by accessing the underlying QLineEdit. + # A text field for showing the result to the user: self.add_text_input('calc_result', 'Result', text='0') - result_widget = self.get_widget('calc_result') # This is a NodeLineEdit wrapper - if result_widget: - # Get the underlying QLineEdit - line_edit = result_widget.get_custom_widget() - # Make the QLineEdit read-only - line_edit.setReadOnly(True) + # IMPORTANT: define a "value" property that the Comparison Node can read + # We do not necessarily need a text input for it, but adding it ensures + # it becomes an official property recognized by OdenGraphQt. + self.add_text_input('value', 'Internal Value', text='0') + + # Keep a Python-side float of the current computed result: self.value = 0 + + # Give the node a nice name: self.set_name("Math Operation") - self.process_input() - def process_input(self, event=None): - """ - Compute Section: - - For each input port (A, B), if connected, grab the 'value' from - the upstream node; otherwise default to 0.0. - - Convert to float, apply the selected operation from the combo box, - update the "Result" text box, node title, and output port. - """ - # Gather input A + # Removed self-contained timer; global timer calls process_input(). + + def process_input(self): + # Attempt to read "value" from both inputs: input_a = self.input(0) - if input_a and input_a.connected_ports(): - a_raw = input_a.connected_ports()[0].node().get_property('value') - else: - a_raw = 0.0 - - # Gather input B input_b = self.input(1) - if input_b and input_b.connected_ports(): - b_raw = input_b.connected_ports()[0].node().get_property('value') - else: - b_raw = 0.0 + a_raw = input_a.connected_ports()[0].node().get_property('value') if input_a.connected_ports() else "0" + b_raw = input_b.connected_ports()[0].node().get_property('value') if input_b.connected_ports() else "0" - # Convert raw inputs to floats (default 0.0 on failure). try: a_val = float(a_raw) except (ValueError, TypeError): @@ -79,10 +58,7 @@ class MathOperationNode(BaseNode): except (ValueError, TypeError): b_val = 0.0 - # Retrieve the selected operator from the combo box. operator = self.get_property('operator') - result = 0.0 - if operator == 'Add': result = a_val + b_val elif operator == 'Subtract': @@ -93,30 +69,41 @@ class MathOperationNode(BaseNode): result = a_val / b_val if b_val != 0 else 0.0 elif operator == 'Average': result = (a_val + b_val) / 2.0 + else: + result = 0.0 - self.value = result + # If the computed result changed, update our internal properties and transmit + if self.value != result: + self.value = result - # Update the read-only text input and node title. - self.set_property('calc_result', str(result)) + # Update the two text fields so the user sees the numeric result: + self.set_property('calc_result', str(result)) + self.set_property('value', str(result)) # <= This is the critical step - # Transmit the numeric result to any connected output nodes. - output_port = self.output(0) - if output_port and output_port.connected_ports(): - for cp in output_port.connected_ports(): - connected_node = cp.node() - if hasattr(connected_node, 'receive_data'): - connected_node.receive_data(result, source_port_name='Result') + # Let downstream nodes know there's new data: + self.transmit_data(result) def on_input_connected(self, input_port, output_port): - self.process_input() + pass def on_input_disconnected(self, input_port, output_port): - self.process_input() + pass def property_changed(self, property_name): - if property_name in ['operator']: - self.process_input() + pass def receive_data(self, data, source_port_name=None): - self.process_input() + pass + def transmit_data(self, data): + output_port = self.output(0) + if output_port and output_port.connected_ports(): + for connected_port in output_port.connected_ports(): + connected_node = connected_port.node() + if hasattr(connected_node, 'receive_data'): + try: + # Attempt to convert to int if possible, else float + data_int = int(data) + connected_node.receive_data(data_int, source_port_name='Result') + except ValueError: + connected_node.receive_data(data, source_port_name='Result') diff --git a/borealis.py b/borealis.py index 1f8eb20..8aff9c5 100644 --- a/borealis.py +++ b/borealis.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 -## --- Patch QGraphicsScene.setSelectionArea to handle selection arguments --- -#from Qt import QtWidgets, QtCore, QtGui -# -#_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea -# -#def _patched_setSelectionArea(self, painterPath, second_arg, *args, **kwargs): -# try: -# # Try calling the original method with the provided arguments. -# return _original_setSelectionArea(self, painterPath, second_arg, *args, **kwargs) -# except TypeError as e: -# # If a TypeError is raised, assume the call was made with only a QPainterPath -# # and an ItemSelectionMode, and patch it by supplying defaults. -# # Default operation: ReplaceSelection, default transform: QTransform() -# return _original_setSelectionArea(self, painterPath, -# QtCore.Qt.ReplaceSelection, -# second_arg, -# QtGui.QTransform()) -# -## Monkey-patch the setSelectionArea method. -#QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea -# -## --- End of patch section --- +# --- Patch QGraphicsScene.setSelectionArea to handle selection arguments --- +from Qt import QtWidgets, QtCore, QtGui + +_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea + +def _patched_setSelectionArea(self, painterPath, second_arg, *args, **kwargs): + try: + # Try calling the original method with the provided arguments. + return _original_setSelectionArea(self, painterPath, second_arg, *args, **kwargs) + except TypeError as e: + # If a TypeError is raised, assume the call was made with only a QPainterPath + # and an ItemSelectionMode, and patch it by supplying defaults. + # Default operation: ReplaceSelection, default transform: QTransform() + return _original_setSelectionArea(self, painterPath, + QtCore.Qt.ReplaceSelection, + second_arg, + QtGui.QTransform()) + +# Monkey-patch the setSelectionArea method. +QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea + +# --- End of patch section --- import sys import pkgutil @@ -92,7 +92,6 @@ if __name__ == '__main__': graph.widget.show() # Global update timer: - # - Call process_input() on every node that implements it. def global_update(): for node in graph.all_nodes(): if hasattr(node, "process_input"): @@ -104,5 +103,4 @@ if __name__ == '__main__': timer.timeout.connect(global_update) timer.start(500) - sys.exit(app.exec()) - + sys.exit(app.exec()) \ No newline at end of file diff --git a/debug_processed.png b/debug_processed.png new file mode 100644 index 0000000000000000000000000000000000000000..41a544d953e7c0da69aa49ef7468df086e698aa2 GIT binary patch literal 2920 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 literal 0 HcmV?d00001 diff --git a/debug_screenshot.png b/debug_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5764786a364beee8ed5a6efb288e427cf2ffac GIT binary patch literal 14100 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 SIUc+K0000