From 584e229c1cb1f7c8e50c0fb416dbba009c065663 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Tue, 11 Feb 2025 23:48:09 -0700 Subject: [PATCH] Fixed a bunch of crap --- Nodes/__init__.py | 0 Nodes/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 134 bytes Nodes/__pycache__/array_node.cpython-312.pyc | Bin 0 -> 2807 bytes .../__pycache__/average_node.cpython-312.pyc | Bin 0 -> 2070 bytes .../character_status_node.cpython-312.pyc | Bin 0 -> 5562 bytes .../convert_to_percent_node.cpython-312.pyc | Bin 0 -> 3178 bytes Nodes/__pycache__/data_node.cpython-312.pyc | Bin 0 -> 5636 bytes .../__pycache__/display_node.cpython-312.pyc | Bin 0 -> 4260 bytes .../display_string_node.cpython-312.pyc | Bin 0 -> 5262 bytes .../display_text_node.cpython-312.pyc | Bin 0 -> 5843 bytes .../display_value_node.cpython-312.pyc | Bin 0 -> 5520 bytes .../__pycache__/multiply_node.cpython-312.pyc | Bin 0 -> 1899 bytes .../number_input_node.cpython-312.pyc | Bin 0 -> 3087 bytes .../__pycache__/subtract_node.cpython-312.pyc | Bin 0 -> 1872 bytes Nodes/array_node.py | 49 +++ Nodes/average_node.py | 36 +++ Nodes/character_status_node.py | 89 +++++ Nodes/convert_to_percent_node.py | 62 ++++ Nodes/data_node.py | 113 +++++++ Nodes/multiply_node.py | 36 +++ Nodes/subtract_node.py | 37 +++ flow_UI.py | 304 ++++++------------ 22 files changed, 513 insertions(+), 213 deletions(-) create mode 100644 Nodes/__init__.py create mode 100644 Nodes/__pycache__/__init__.cpython-312.pyc create mode 100644 Nodes/__pycache__/array_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/average_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/character_status_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/convert_to_percent_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/data_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/display_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/display_string_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/display_text_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/display_value_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/multiply_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/number_input_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/subtract_node.cpython-312.pyc create mode 100644 Nodes/array_node.py create mode 100644 Nodes/average_node.py create mode 100644 Nodes/character_status_node.py create mode 100644 Nodes/convert_to_percent_node.py create mode 100644 Nodes/data_node.py create mode 100644 Nodes/multiply_node.py create mode 100644 Nodes/subtract_node.py diff --git a/Nodes/__init__.py b/Nodes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Nodes/__pycache__/__init__.cpython-312.pyc b/Nodes/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d824339f20935dab387406637be18ab08f265886 GIT binary patch literal 134 zcmX@j%ge<81Q%r1q=V?kAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdrJ80H6Ht_&m6}`< z@04GZnwXPW9OIXtl3E-SAD@|*SrQ+wS5SG2!zMRBr8Fniu80+=ixG&6L5z>gjEsy$ H%s>_ZN5>u+ literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/array_node.cpython-312.pyc b/Nodes/__pycache__/array_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a2ef80d5b90c8b7f0b50c45c0fccccaac9081c2 GIT binary patch literal 2807 zcmb_d-D?zA6u&dGvpc)_*i@6Y`jAXdBbiaC z`-~$OqsKi0`y4g`WpT$PLy79L5_h=HOcyt62MF;oF{+>#C8HQEQ!z%SV;-4_u^BW- z$)0I0=9SrL6!T@!RVv2yBf)F=doY&9!K_qEAr#=ru#b2+>$dki-^HKMk}1>Z=G#@R zo!@swN~9Ft)Oo|yu_AQx?Y2(8I-wA*tPHA}V(_EVRdqBq$|n?U$Q%|*w$7wXVvN|b zm572ltnl#^#)@Y062!wx1}_ogSk;Egdi~(c;8iWIj}iuNM*(|YB0pPiOf`pjgM`65 zLCE~2apyh|Tcj8Q&z~DsG=4CZNObb5$zx?yzd|fS9C|`l4AZgg$WMe+Ju*ZLr!GY_ zHLfR=i0#Z`?fB#5vy}#94*qMukm+-34w)sE>81*$(I*uM-7j(|q>>X6wCs^aBYu}I zuQGMN=!YWG+e*-zB#4KtLuSenL*UcvO2{6vBWQJo%#t1F?ufUYE?r(_Z`sy35`0mM zR<$CU;8SC;Jwac%_#B?Ygafl~{=!8eV6jFjsbCDR!Qw8df7hMJ@78p zN0hiJ9w#W05~?u(2sZ{COB$gg$#Gl>(eAJl3+#v+UNt#42FUbc3axNeGitVWiEGbu zLLpS2@yt{dxXMqfZ&c5kd2W|WU(0iQfDYaWW_@{%FVuyJXrNHnU@wik$PxsBsvA|a zm!EPiYs5ahWR$TnNu{alHEC+3geFGyB{ zv`jY;&Lop&M%>y|Rw!vqdi=;Np~Ebp(=1^uP0EbyaqE_g40qj#w12n(>&i~DX_nl8 z{ns0K(_VZh?ftJe*b*ypW1aM+eb=v%sCI0)c##~b`}tg#T%JGW;e|r$gCYLU+vX`9iD(_wD3-slN zsK?PBWR@(MHLlbk-HLDph3qO+s!8Y3EHzUuJ5iVD(O$@-1ah%kQ zDfDA){oKi$C$rabwfi&v!jYcEBd4{@E<$~C^8IkieNRc$LRZFiLieGgB48-CJ~>pY#?8_l(xne}`YTn(U|;kk~R z9oe(F9W5DO;n2~=Lw&Pj%i+jUIFb(^c=CRx@>xZ=uv=JYzAbLGzEa;bU$JoRp`5Ef znmJJj)-MKI3pKTKft!Kln&zdN=6NAs)439A$hO|;xZQC}U+{brychgFbhzMLYc6^- zI{!w#wr!n3;l|BUxzORC>*}+$cN%Xu-V*KF>lEFrZ{G8D;HyA3y%_3z%5}Y1Z$foV zcm0byJAOk{Ra>E@b=ET%TxAIT(g4ssYVB=CkDEiiN14an6qyA-v@qyCq=f}Uaa5O6 z37{)Q@uQTKC~|zFDC==@TumB^Ofs(ystP81fAq{dy<)WIbg$idaHpNzV*iROiG(N$ z6ox`%K;?hi&c59`?Fs6<@fw+4Oru|EFUze~qflfjTBr$6omlty**$-R>e&c}N@XiV z1;(m?&en&e61? EFUQ7rP5=M^ literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/average_node.cpython-312.pyc b/Nodes/__pycache__/average_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..322739962186ad62c8bdeb7dd48bd6ce28edf451 GIT binary patch literal 2070 zcmah}O>7%Q6rR~1du_*dL!87eDB6;=_0nLq0eY|yB5tb^P*eT{sui@_dd9BP&8|DU zZkx!Ff)oi?1d&RhL@G!{>H*Xqdh8K;LgLakiE0KoapIPef~uaFS?_LA5HQltzM1#U zdvD(R=KYXN#sM3jX*X*3BmjQpi(tsUu(gi~D*yomO)vw?5CQ=l2S`{1NW}2E*u|BF zSxJ+B;|mQ^rDQmG(nh$_)plpWUu+#@!bi*~24#pqSwOHXBB3lHu`IVi6(VU-DMygJ z2+GkmxCqP2J3x!j#Q1qkjGAY7ZZ@v6;qR#2SJB&1)#ud-tI>3wqB=gRmPXYhYTiK8 zO~*A(n^-j*)xsEIq=oR4O_$+|>f6|9ny#96&oEfmm1p*Xvf@)pZ&Wg<3ZCyFg8OXM!r9?;R`80JYwd|}bPyd&k zzLZ{7)|EY-{sDF@&Y{Um$z^jx8Qx;n!4vf1Pt>CiVZ~knH82kmJPJO*0L%-l1Usq5 z53|t<%RVo*#1D71#OaVqT?C&4vRRPXlvz2T+;sVa;BB0_KqEZw{SE{dnX1*plBnn)A| zA^c{9JM#!kc(8M^ki}XLmAe#8S8U5-M8imL*u-_H#HmFiMx%i(MCGb!8!nC3uxq$3 zq4D=P$&V3Y6B>JWw&9DS)10BAi7n3&d5U?{6vDGXc)&EqZF{t*5*KMKbW2mA=tV#i z4TE5dp?MslalYp83-JcZ2kGbbk_}>4FncrJCFXKFAHWl;0fu&Nv1tKxdi&aupK|&3 zM5k|XrFglxT3_qSw~uzl4&NCotxET^qj$5T>)FEhLVJRyvzf)q0#$8 zhwcs?S{r&{Ik72-y}3?i_sWUOC$8@AH5w@1>Y!}9HyAe*%q?>xMZu+9_L^%)yAO)mH7`njfI`kJV& zBfG-3Q>-l@meqQ-jtN`GCQlwcrcaKaIOeqwlJ~OeccKb5OmE1PjJaByL=vn#hS@laCakv}sjStC6C-T}r-n&AEEI58S$-f{`I r;Ay@AU6fZ><>l^0ULj*l&pkUl29^p4e*x-4X%Ezv^M3&L^G^K*w)NoH literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/character_status_node.cpython-312.pyc b/Nodes/__pycache__/character_status_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eebb7f8b903bfdfca8d4e784dd4d8b24092e3f6a GIT binary patch literal 5562 zcmbVQeQXrR72mJB+xzkv8?cQHSsO@v3HT#5Nil}tuK*z&cAzP-5^a{-HQvkl?lQX< z+oyBmMo8FITCk$Xj*==y{R2m!hCdzUk0wggBDIx#^+i5ykxJF5lK&%xL=mmjzBhM! zXN(C+$G+X!c^~uU&CKt;nLqmd9tO(aTd$n?IKVLfzz@4P3YA;WKxKwenE^&+RW8a7 za|0ZUbw0`u3j@NiW57Ybg{X7Dsfw!OEIZ)hm_v-}Jj4p@8szkH|(F`qS8j?ArNnxA+5koSlM3Z#Hl!g>! zm-JX7X-bJW0d6IxN`vtvk>W|S*xK%qq}|ftzFpEW_&p83-cx-|9?+18N24etb@m;R zw3wQR>oHSm95T&BdvkN^<4-oVz+Y?oo|cxDW>qnjprpN~kueDxq+u{FE+rGtu1Pv{ z{+bd^Y6d7Cq*kNOuo8_*Fe)P%4r_)nn2heCG#n)i#?;Ly$O2t6QCY)1o@qoa-SJpV z1AnQ(hP$+bpc)^ENpS)mGm|7n$D$DuC;4u^r~V$@2(Rv91*a^?T4I-CXgD`P&T{sf z-EpE>j(+n{QV)d-(@G~7>-f9X0FQIbIrbbk!%VRg+!XT*c9NT9Cz(NBWzUM^e40PO z1i93QFeQ@ooNcreP8w!>m`sA38lRqg>??mSn~ z)9&?W^SftIsJAA0Qa|KuF z%G2;jaqUuYlO-4mKsH7+OX%;@Vt{1b@o1b_B7p8sb=4fQoKQmwwseuCF=TlWZhE6p zJz;1Te?~Jc0U;Zn%CKg+Ao3`tNr)G;l&EgJi1=^3SPa2(J^;(;j;6$z#o@_b|}^4_NR>)xrGOE1>6EtTz=KSqF4Jf4?nTMF0}ow z{iF7eJFYck_C0$|S*q@vJd*QOo)1lh-rMtj$2%SK?0oCIvQ*oW^|b}=F^(x&XXDVZ#x84AM1f~LC zh&8vsqs#|PP#}*%g*=K6+*xEdJPcfkFTebNsP4Z7Gyz8#e#KJ2Rpu6IKL;73#tg8KHF7FDz^mMV00{u( zjAz9Ghbo+92Aq@3DCCKd3OKCFV$PFK264Tms`Pl%8Hnx4(@ilQmaEp3Uv{bTB6lHo z46ly77(X%A-tvG&Z?iLuSv1kKZG;HqK$YXY)edQjFmdTp4SM?LqKa zalGKOiZyKT8QHi_TQM@1Xdt~#R4&=B@4c(|TRLK~O@-fEQe^Q6K z(fK zf8mv-$DWxCWW}So|D)BB-F0B$^`%|MQ9~c<^-uX1H}A`e9bay$oq1{crMG10(Sy1@ zQ=VmUOGezXEH-4shPk#&`!DX#imgQ&28P+nFT|~RJX?(;m@QBoc>;Vg!?+oR_&QgWZu*?gLS~1T5j32AWzem^+W|V({ z8DWE!so#6-wFCc!XE*B$h-BY&xpzh*Fs8c`Rf zU~q{*Ps=eSYTrLdy`u@p;P9~g%!%V6=)y?EV}>Tt!}2TIn1L4}cG)d83?&+NNe#pF zIY^({XriIWliG6s`w)Wb5YVy7*d0$Hp^^C@&; z9xZ#&z;)5mhF#<^{s|9EtOD*X_IlMrGwJE{;*NvY>a$hPP9Dw$s+I%WGlA`M#$N)D z=72mtJ^t3~7la!-8ZRBbczFKg)jdl)_FfQXywl#5zvv*n%m0e!sfzhSp_e^K7^?e#$QzZeco&-u#FADcS1xaFy= z@8EUchQ*5g3$HHwIy1h`f8TT<#~ouE4E2rf%I-$yv&|du+34=6=09uQ+EdAYUg>~( zP{bgIAxxGnk1P+z)npXwK3RS>sYDBuGKe=S9Gmn(9S$(E6JsB$9iduBIsDl zUkGAh4T78u>+uk}8!`J}eu6Nj(!h#Yu7OF$w`ZB$rlyHduDoI5$Zes_vF#galcS5J z!GN*`xe(b4oUoc)vaH6#(7PA2(?di_4E37?QJP@3Ok*%TVv-PkVkcAuX~r6+%lW)p z?8uMu0Fj{$?K3b%zsa*K`;{QF!q*!aw*GHS-Cvo&Enyp5efuCAWV_he#;;+Q6^#2I Dt;gz1 literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/convert_to_percent_node.cpython-312.pyc b/Nodes/__pycache__/convert_to_percent_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35b30ebc551da543d919e8c1fce22864296874be GIT binary patch literal 3178 zcma(TTWs6b^-`iFin1)nu~S8fW!kZ4D|KWiQ<~0bn=Y}NdR^rN>w-l~K`rNc2LxDu@|P{?5M)0)mlCDe z35H#Oyyu?RJ@=e*&$)jKhgk$;{p6qKR{{D5S-8X31RlQzz$%hZ21%6UQK_saXv!EF^8Exs|#yLk*f;-9`pO_ef z{UuJ%W8gD6ydZOiZ01$W!sY@ur|JTJ&LYsC0&!8v3M!XV1yMGW+{_#~gKQYO!Rexy zCmXjAS=ls&d6|PVn{WzQN`eDq=lmnEpMq6~Ca6ooG933suMH;Ihs3)H%4W_8rc9g& zu>S%+3+lv%;~RfZbMbf#fZsvLUh&}rMFXm+6Kn`cZ~=VnOKCW5xl%;zBZeqgs5o>w#l^Sm7tBne<(u~`B( zGR*c}7u39LGp3ArO~}f2Fg-gp&8L6*{*+HHcRlziQ-XtF5R-UEi zCNG~i^hH_3{27S8pep8N5-9U>GcG*Vd9a3DH=f+FWNz8$0O3O4%i^1K&Fuyj^Bnx1 zuAm(zywy2anW{1;wwTldrsLDjTb*UR#T+JZ@eDqE>aft+b)Y*BrKhw3v&D*@D;t5cd-@ywxBChq=EjD9Ba{ixRqZP4SChfRpS&6x!Npxk+^ivz^#mG z$@IOPb8+7uQCM0&$?M7${aP6BcX_}H8fZ;F6#cljMe(~CyZDNWYtO9{1%k*5EE4DJ z8IsSHFEaR$OSBjlEBUXULdXc=K9?p1T-wJJ?su^k)A0TOvci=s26xFZ#H{~IjAuA+>zMzX8RtMbWmdCY#6n=gUc{t)~YtMj% zo%RlQFQ*C(?xjKu(aZ8e>U;xOm(dY~hg_Kv_X>5Aa~V9Mube%_(Os#j5x)J%jQkYU4e$8Ii0N0Nx2c|edA8g z8+k}BU7>4AxvrRsu5nkExq@uy4<&;3CR1~Tz%=P&lbk^w2?n_fiC){+C}a^RG8)t# zS>kiLflZqxB?kVakOdY56WX?6v!Gftv7m`^!&X{SL~MKKWo!rF=P-Gp*+b=oj-P{x zba30N%9_nyB=ym;!S*@b*WQ-_3tfvHO5^Oo=^LV)!*C3vi`YtHoai*j#Q_^g<#nty z;snvvHoaMiLpi~4tE3$y=V=t=P(!p)r-O}!V=VB)k%O&=gdK8xs%YeOVh5U4*WTB9 zzqp0ireu>82X5Ce$UU~hIYWm>gxP2n1-Jk2>cYugJmlJ$*CCE8sNQ>UHFGCZp+5Vt z#MURLHYd-o9o>$ls(#-df1Lcn@?`}no{rTwEzutaxw)*Dm)`eegow!(!C${4g)%e7AJXMXS z?qB-GQ;VOy6Z$?rT)BGx)YoTf@zdqdPV~^)do`jmS&b%Z(TUQ`BTq;8)p~T`%g7%h z8{S%UtnAy_f1sTIY_hWN_osJaLz~0tT5NW+XLjfCKxKG?S|7ceTu;^pPt*?oxE$Px zAE``l46MI(#%Bo*r~ub=TvPolq02H$zb`>mIKr`!8&w-4Y1 zL!JQSKEM-z<@u~G$GWO(n*SD;}>OB<@p3< z^pKd7tNXT-HO^bZ`H{3TN%4Pr6S84^C0*|tTA6u5hkOVB9qadv83%yoaHbP}JHYdj zE`l{7@@6<^2)Ts|*dU$7_5eV;ykDV)oRgu<8;wJ}WlRB`7-5nd1>T^jf6@Vp{-GOD V1Aj-oPv{A1Z0+?Q5KNBze*q@b4hsMP literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/data_node.cpython-312.pyc b/Nodes/__pycache__/data_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9bfc00b2745a6c7ce4855d82be19db6f79894a4 GIT binary patch literal 5636 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 literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/display_node.cpython-312.pyc b/Nodes/__pycache__/display_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff296552b96f472cd7b38c55400db23a79db9417 GIT binary patch literal 4260 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 Zdhc+xcX)$9XG8o3cj(sqX9OB%@&76f%aZ^A literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/display_string_node.cpython-312.pyc b/Nodes/__pycache__/display_string_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..425735b0e5e2f6684b14a3dad3779a04b2adc075 GIT binary patch literal 5262 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 LnSM(k;{*R2fJaR8 literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/display_text_node.cpython-312.pyc b/Nodes/__pycache__/display_text_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edc29d3397cc7befa0cf50dc0a0f77fbe1d498bd GIT binary patch literal 5843 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 literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/multiply_node.cpython-312.pyc b/Nodes/__pycache__/multiply_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3540a7cb3c29ecd77e348a369f247164cd935048 GIT binary patch literal 1899 zcmZ`(-D?|15TCs-on*@&abn9!F=w0Qmp`IxQhjojvK!NuUe2J3BKw zb2Go0`8gVm0InGGfaq7$a3bB>iMnO43d?d(GLvU3E9Ptm9aSzp!|3B5R;us*`kyK&s4)G6br6lFy(yCu2>EW=S&lMm_QccWXVe=lRdF6 zFv+p(c@{Q|yq$9#!*IA&ZROGAeCq7$w6tjDT_athR?aRsXE|8TSwF)@5wFQ|mGq4=!@jxIJ8f`)YK}Zq06}lUr({JsfLDYdvkX_e$TTzNWLK9(bDWztrDS zx76|WzTt*+IkJOR;0Y{MWO!I+F!Sq(Qk|G&7T$dipe`UvkK@iCHEg|H$5+Zf-V?`QWkQR@r%GKi8u6(+#-<^|Vs zIUwGe7Y+hd3G8}418ZMhfQ#Z8xIlC@qYIQLnDQ9BTB8FrxhZhu7%|N=uZ5->#NBr00sMj^!G~ z+>)iMl*h^x&omY_x9CZj#7PzooUy)u^!&1(a*kR>)aKq5>dg>`|2rQ;e9<9pI$EZs zyoDX$xme!shvZfB6SBRi?)-$?3bY4@8p>~@6OFm{(8zk~YN}P(9Evw)+eeSxIht-s z_hN~SSYj)dye%~5e(jI7$0xs;zWz+JuRWHyH#WU7HoZCae6wd;5C;?Y<5M>mzW?xt z7jDmNCNrDy)6G{~FWudHXj_Ft@ut$O-W?d<9)bM_w_~s;))KECXq~^7|NP^P!O4cw z-gf}IDZ)D_dMVn9Tsw9xerNdbFKXi9gD8wnd_8w(B>6Xx!9+WLm>b?i!y;ZEIeRdY zALY?>RJax0n?5Mtnjp9%jyOwr?s1kx3}dNeR%}l98pcPJoZXd#48tttk==(4Z(^DY z^976IJ)Ai`d&0=foI2r62YSfMad*n(E!#E>olx%E;a&QQ2ZA@lyp8vyGltv4RrpJg zWOX|N!_zC7_Q3v?xd)OWkNi6@AWu<_h4&z%i>!xZb&}@F=iYEBx8O;>g^reovUNaJLx`Qkr-hxh6~?qS5D literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/number_input_node.cpython-312.pyc b/Nodes/__pycache__/number_input_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e03916f74ead978913aa2bf5cbc52892bb7537e GIT binary patch literal 3087 zcma);O>7fK6vt=Q-em3AU`j|J1~hy%*(x}+RfQCxiU1J`YE3^-5iZhd@r;wD&aS&V zCb1Q&RHAB-=$9%=q_$FeYAZN!?12lt_7WFRq7?_Eo_b3ORES>sW_G2wl7`DF0M_>_jwAL5}|)UdJo5;U$MiVBEgs+94hQcy5b&~Zd#mk^CJ z<$5fzEyQZ7-eGRW2qp8?$#cvlV|Jy=N#3DsI7!HUXc0?vc{%xbvdZCMn7n6}tBeeI zjFU;zuA1d?ja;xyViL}#IXUM{>&fJ)3CklkxFTi7J;Em#sd}&mvpLizv4bTon>%e2 zbICDIsugN-<`E0#!8mhmvrPIgh*kSZ#dRvo|;rikwL21a|Dmcg!XK6hYFluN^TxCL&S{q88Lj z_^>(*jZY!q3>7d0U7@%Tqe>w@he{X%t`$@ozk~`Mw3DiraG{fSz>_wIrg0(hF48rB zV-R{7d9h5r*K%^>7N4k|%h^`ZDYIMQ4xn9gWu{RR9Epe#G@hJ+$owt zpLHR0JsiRCe7-X>iy9PP4?RnZL^KdJD7+($-H10al6K&EZFy#;^x9E`s0usS@O=Nb z=zFELhZ@p5^1dpG^Hq6DK0W~F5*H`_R10Dhn$cX>|#Y9c_e4eV}ve<*!O#&0pftg_g$(AwJO4CaYlu#g%cgy z7CI1Vi6kVP2Mz=^N2R<_;#uQDkU&w^Ck2AR zM*Xg~_599os+)m8`H9i#BCBxAv3<4Tf;{RmSFjVzX*$5n6N&&Y=XT4%isNx3cFYkS_0}Z=ohQ4 zDQn~?4#-AWx8N-)fNv55r0px6L01k&n5y+(Bii0!CJjR{!%nObMb3BO~d`Q(v`Hf6sXTio~}@SDgkZ|j>o_W23GHj#bmiOqkV8Bx$} zrAKhvhu0D-f+LAN01XgUOz{T;5U)+^OKdAm)|eTz4mUlkeohK_EXJX7VB{mkk46 zcQf>QLMaYQv6NQ`S&8e3w;QS_m(cQ-+-!b1vvYRrQCw4p9(H%BBY3qhsSf?UwL=|{ zTb7gZdfe|c4C)kND_!C~aNIR36K``@U?EL@MuEAMX6;b$TCcmWz_{3kmxT&|!}zbb XhU1UB5#IGEz8?>KIq?|5pPc+JdZG-|zXA zb3W&sU&7%K;Q90U+w<2XfIs-fJ93NJeF2#b&_E*wERrlCAix>WgjLW)o!ko|oSYMe%q~QT~>4pQ6!;=uELxwyjijDiKZlkF#YP zUsKU{b*pUH(byC!rsJ-7d9Yw6=L`15@`YrvkT(rIX|;ke#fy6CI+rl@M6Sg=)<1#_B)JeG=uIBU9PWAde(QP$%ElPuksV?kBT z8#&8TRf~Izt~?%}OPzn4nhSc~R?{Zcb4I~B&tb97d$m-Hcukg;Xa|}s_j-lpN=Yw% z1Vz@XNcLCYp%Pv<>eHLb__mU0C>_@#Ymu6@tqeWOcdm8Tm2G9D(c4#*u7-Be9G-$p z!3P(v43>HW3Q!>$nT8KO1gHoI%ahP@V?2&u^or<+pLIFn0{8ID@xU#Td8kN^M2|a? zCJdrhMRw!`ABF=n&!Q3tg6+41?StwBG@J%+usm2D;s~eE$$A#y_4tn@aEK=JX!s5A z?=p^brOj!>7Vv#ze%%N0Jx)Wdui$GxOM$%5cwRe-?$7{ z#B*?&#Fb23puE78=ioLM?L=~*o{GkUOm4PWXWlFpv2AosEt%A|n8f>p z$#aI8vsv4`Zs%;9ve5gy15Z0<)~gnZ5};9-8EtH|IRlJKlBiH9WGHbuVd@GA(oGLDYEB3QPt^-sJ+bO^x7{yT_}6M(Y}NY4bFF{>L=x#(UP>Os%GZ#If9d}VVMha1)cJJlbM}9t!Ad)I_<6n zI_B26wO;bNVW?`HP~N~6pVHS|5ZtBZ5N8aRJ)Szp{eDftek2l@)}K|oJY3$kYFhYc@zLL QvL{8!MD68AfFIZHKV(n6F8}}l literal 0 HcmV?d00001 diff --git a/Nodes/array_node.py b/Nodes/array_node.py new file mode 100644 index 0000000..b069fe2 --- /dev/null +++ b/Nodes/array_node.py @@ -0,0 +1,49 @@ +from NodeGraphQt import BaseNode + +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. + """ + __identifier__ = 'io.github.nicole.array' + NODE_NAME = 'Array' + + def __init__(self): + super(ArrayNode, self).__init__() + self.values = {} # Ensure values is a dictionary. + self.add_input('in') + self.add_input('ArraySize') + self.add_output('Array') + self.array = [] + self.value = "[]" # Output as a string. + self.array_size = 10 # Default array size. + self.set_name("Array: []") + + def process_input(self): + # Get array size from 'ArraySize' input if available. + size_port = self.input('ArraySize') + connected_size = size_port.connected_ports() if size_port is not None else [] + if connected_size: + connected_port = connected_size[0] + parent_node = connected_port.node() + try: + self.array_size = int(float(getattr(parent_node, 'value', 10))) + except (ValueError, TypeError): + self.array_size = 10 + + # Get new value from 'in' input if available. + in_port = self.input('in') + connected_in = in_port.connected_ports() if in_port is not None else [] + if connected_in: + connected_port = connected_in[0] + parent_node = connected_port.node() + new_value = getattr(parent_node, 'value', None) + if new_value is not None: + self.array.append(new_value) + while len(self.array) > self.array_size: + self.array.pop(0) + self.value = str(self.array) + self.set_name(f"Array: {self.value}") diff --git a/Nodes/average_node.py b/Nodes/average_node.py new file mode 100644 index 0000000..2f8be35 --- /dev/null +++ b/Nodes/average_node.py @@ -0,0 +1,36 @@ +from NodeGraphQt import BaseNode + +class AverageNode(BaseNode): + """ + Average Node: + - Inputs: A, B, C (adjustable as needed) + - Output: Result (the average of the inputs) + """ + __identifier__ = 'io.github.nicole.average' + NODE_NAME = 'Average' + + def __init__(self): + super(AverageNode, self).__init__() + self.values = {} # Ensure values is a dictionary. + self.add_input('A') + self.add_input('B') + self.add_input('C') + self.add_output('Result') + self.value = 0 + self.set_name("Average: 0") + + def process_input(self): + values = [] + for port_name in ['A', 'B', 'C']: + port = self.input(port_name) + connected = port.connected_ports() if port is not None else [] + if connected: + connected_port = connected[0] + parent_node = connected_port.node() + try: + values.append(float(getattr(parent_node, 'value', 0))) + except (ValueError, TypeError): + pass + avg = sum(values) / len(values) if values else 0 + self.value = avg + self.set_name(f"Average: {avg}") diff --git a/Nodes/character_status_node.py b/Nodes/character_status_node.py new file mode 100644 index 0000000..f48e256 --- /dev/null +++ b/Nodes/character_status_node.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Character Status Node + +This node represents the character's status. It has no input ports and four output ports: + - HP, MP, FP, EXP. +It polls an API endpoint (http://127.0.0.1:5000/data) every 500 ms to update its values. +If the API call is successful, the node's title is set to "Character Status (API Connected)". +If the API is down or returns an error, the title is set to "Character Status (API Disconnected)". +""" + +from NodeGraphQt import BaseNode +from Qt import QtCore, QtGui +import requests + +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() + # Draw the port circle. + 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) + # Draw the label and current value. + port = info.get('port') + if port is not None: + node = port.node() + stat = port.name() + # Use the node's 'values' dictionary if available. + 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): + __identifier__ = 'io.github.nicole.status' + NODE_NAME = 'Character Status' + + def __init__(self): + super(CharacterStatusNode, self).__init__() + # Initialize the output values as a dictionary. + self.values = {"HP": "N/A", "MP": "N/A", "FP": "N/A", "EXP": "N/A"} + # Add output ports for each stat with custom painters. + self.add_output("HP", painter_func=get_draw_stat_port((255, 0, 0))) # Red for HP + self.add_output("MP", painter_func=get_draw_stat_port((0, 0, 255))) # Blue for MP + self.add_output("FP", painter_func=get_draw_stat_port((0, 255, 0))) # Green for FP + self.add_output("EXP", painter_func=get_draw_stat_port((127, 255, 212))) # Aquamarine for EXP + # Set an initial title. + self.set_name("Character Status (API Disconnected)") + # Create a QTimer that polls the API every 500ms. + 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. Expects a JSON response with keys: + - "hp", "mp", "fp", "exp" + """ + try: + response = requests.get("http://127.0.0.1:5000/data", timeout=1) + if response.status_code == 200: + data = response.json() + # Update the values dictionary. + self.values["HP"] = data.get("hp", "0/0") + self.values["MP"] = data.get("mp", "0/0") + self.values["FP"] = data.get("fp", "0/0") + self.values["EXP"] = data.get("exp", "0.0000") + self.set_name("Character Status (API Connected)") + self.update() + else: + self.set_name("Character Status (API Disconnected)") + except Exception as e: + self.set_name("Character Status (API Disconnected)") + print("Error polling API in CharacterStatusNode:", e) diff --git a/Nodes/convert_to_percent_node.py b/Nodes/convert_to_percent_node.py new file mode 100644 index 0000000..7d309ef --- /dev/null +++ b/Nodes/convert_to_percent_node.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Convert to Percent Node + +This node takes an input string formatted as "numerator/denom" (e.g., "50/50"), +splits it into two numbers, computes (numerator / denom) * 100, and outputs the result +as a float formatted to 4 decimal places. If an error occurs, an error message is stored. +The node's title is always "Convert to Percent". +""" + +from NodeGraphQt import BaseNode +from Qt import QtCore + +class ConvertToPercentNode(BaseNode): + __identifier__ = 'io.github.nicole.convert' + NODE_NAME = 'Convert to Percent' + + def __init__(self): + super(ConvertToPercentNode, self).__init__() + # Add one input port (expects a string in "numerator/denom" format). + self.add_input("in") + # Add one output port. + self.add_output("Percent") + # Initialize internal value. + self.value = "No Input" + # Set the node title to a static string. + self.set_name(self.NODE_NAME) + # Initialize a values dictionary so that connected Display nodes can read output. + self.values = {} + + def process_input(self): + input_port = self.input(0) + connected_ports = input_port.connected_ports() if input_port is not None else [] + if connected_ports: + connected_output = connected_ports[0] + parent_node = connected_output.node() + port_name = connected_output.name() + # Use parent's values dictionary if available, else use its value attribute. + if hasattr(parent_node, 'values') and isinstance(parent_node.values, dict): + input_value = parent_node.values.get(port_name, "") + else: + input_value = getattr(parent_node, 'value', "") + input_str = str(input_value).strip() + try: + parts = input_str.split('/') + if len(parts) != 2: + raise ValueError("Input must be in the format 'num/denom'") + numerator = float(parts[0].strip()) + denominator = float(parts[1].strip()) + if denominator == 0: + raise ZeroDivisionError("Division by zero") + percent = (numerator / denominator) * 100 + formatted_percent = f"{percent:.4f}" + self.value = formatted_percent + except Exception as e: + self.value = f"Error: {e}" + else: + self.value = "No Input" + # Always keep the title static. + self.set_name(self.NODE_NAME) + # Store the computed value in the values dictionary under the output port key. + self.values["Percent"] = self.value diff --git a/Nodes/data_node.py b/Nodes/data_node.py new file mode 100644 index 0000000..5828d5b --- /dev/null +++ b/Nodes/data_node.py @@ -0,0 +1,113 @@ +#!/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. +""" + +from NodeGraphQt import BaseNode + +class DataNode(BaseNode): + __identifier__ = 'io.github.nicole.data' + NODE_NAME = 'Data Node' + + 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.value}") + + 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: + # Connect the textChanged signal if available. + text_widget.textChanged.connect(self.process_widget_event) + 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.set_name(f"Data Node: {self.value}") + + 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() + + def update_stream(self): + """ + Updates the node's behavior based on the connection states. + """ + 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) + input_value = input_port.connected_ports()[0].node().get_property('value') + self.set_property('value', input_value) + output_port.send_data(input_value) + elif output_port.connected_ports(): + # Only output is connected; allow manual input. + self.get_widget('value').setEnabled(True) + output_port.send_data(self.get_property('value')) + 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) + + def on_input_connected(self, input_port, output_port): + """ + Called when an input port is connected. + """ + self.update_stream() + + 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() diff --git a/Nodes/multiply_node.py b/Nodes/multiply_node.py new file mode 100644 index 0000000..c5211c9 --- /dev/null +++ b/Nodes/multiply_node.py @@ -0,0 +1,36 @@ +from NodeGraphQt import BaseNode + +class MultiplyNode(BaseNode): + """ + Multiply Node: + - Inputs: A, B + - Output: Result (A * B) + """ + __identifier__ = 'io.github.nicole.multiply' + NODE_NAME = 'Multiply' + + def __init__(self): + super(MultiplyNode, self).__init__() + self.values = {} # Ensure values is a dictionary. + self.add_input('A') + self.add_input('B') + self.add_output('Result') + self.value = 0 + + def process_input(self): + inputs = {} + for port_name in ['A', 'B']: + port = self.input(port_name) + connected = port.connected_ports() if port is not None else [] + if connected: + connected_port = connected[0] + parent_node = connected_port.node() + try: + inputs[port_name] = float(getattr(parent_node, 'value', 0)) + except (ValueError, TypeError): + inputs[port_name] = 0.0 + else: + inputs[port_name] = 0.0 + result = inputs['A'] * inputs['B'] + self.value = result + self.set_name(f"Multiply: {result}") diff --git a/Nodes/subtract_node.py b/Nodes/subtract_node.py new file mode 100644 index 0000000..08d37af --- /dev/null +++ b/Nodes/subtract_node.py @@ -0,0 +1,37 @@ +# Nodes/subtract_node.py + +from NodeGraphQt import BaseNode + +class SubtractNode(BaseNode): + """ + Subtract Node: + - Inputs: A, B + - Output: Result (A - B) + """ + __identifier__ = 'io.github.nicole.subtract' + NODE_NAME = 'Subtract' + + def __init__(self): + super(SubtractNode, self).__init__() + self.add_input('A') + self.add_input('B') + self.add_output('Result') + self.value = 0 + + def process_input(self): + inputs = {} + for port_name in ['A', 'B']: + port = self.input(port_name) + connected = port.connected_ports() if port is not None else [] + if connected: + connected_port = connected[0] + parent_node = connected_port.node() + try: + inputs[port_name] = float(getattr(parent_node, 'value', 0)) + except (ValueError, TypeError): + inputs[port_name] = 0.0 + else: + inputs[port_name] = 0.0 + result = inputs['A'] - inputs['B'] + self.value = result + self.set_name(f"Subtract: {result}") diff --git a/flow_UI.py b/flow_UI.py index 557ce77..ea6f2a9 100644 --- a/flow_UI.py +++ b/flow_UI.py @@ -1,239 +1,117 @@ #!/usr/bin/env python3 """ -Modified Flow UI Application: -- The character node is now named "Character Status" (display title). -- DisplayValueNodes dynamically update their title to show the value from the connected output port. -- Newly spawned DisplayValueNodes (via the context menu) follow the same dynamic behavior. -- Node creation strings now include the class name to match NodeGraphQt requirements. +Main Application (flow_UI.py) + +This file dynamically imports custom node classes from the 'Nodes' package, +registers them with NodeGraphQt, and sets up an empty graph. +Nodes can be added dynamically via the graph’s right-click context menu, +and a "Remove Selected Node" option is provided. +A global update timer periodically calls process_input() on nodes. +Additionally, this file patches QGraphicsScene.setSelectionArea to handle +selection behavior properly (so that multiple nodes can be selected). """ -import sys -import requests +# --- 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 +import importlib +import inspect +from Qt import QtWidgets, QtCore from NodeGraphQt import NodeGraph, BaseNode -# ----------------------------------------------------------------------------- -# Custom Port Painter Function for Character Status Node Ports (Semi-Transparent) -# ----------------------------------------------------------------------------- -def get_draw_stat_port(color, border_color=None, alpha=127): +def import_nodes_from_folder(package_name): """ - Returns a custom port painter function that draws a circular port with a semi-transparent fill, - then draws text (port label and current value) next to it. - - Args: - color (tuple): RGB tuple for the fill color. - border_color (tuple, optional): RGB tuple for the border color. - Defaults to the same as color if not provided. - alpha (int, optional): Alpha value (0-255) for transparency. Default is 127. + Dynamically import all modules from the given package and return a list of + classes that subclass BaseNode. """ - if border_color is None: - border_color = color + imported_nodes = [] + package = importlib.import_module(package_name) + for loader, module_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."): + module = importlib.import_module(module_name) + for name, obj in inspect.getmembers(module, inspect.isclass): + if issubclass(obj, BaseNode) and obj.__module__ == module.__name__: + imported_nodes.append(obj) + return imported_nodes - def painter_func(painter, rect, info): - painter.save() - # --- Section: Port Drawing --- - pen = QtGui.QPen(QtGui.QColor(*border_color)) - pen.setWidth(1.8) - painter.setPen(pen) - # Create semi-transparent fill using the provided alpha value. - semi_transparent_color = QtGui.QColor(color[0], color[1], color[2], alpha) - painter.setBrush(semi_transparent_color) - painter.drawEllipse(rect) - # --- Section: Port Label and Value Drawing --- - port = info.get('port') - if port is not None: - node = port.node() # Parent node (e.g. Character Status) - stat = port.name() # Port label (e.g. "HP") - # Retrieve the current value from the node's values dict (if available) - value = node.values.get(stat, "N/A") if hasattr(node, 'values') else "N/A" - # Create a text rectangle to the right of the port. - 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 - -# ----------------------------------------------------------------------------- -# Custom Node Classes -# ----------------------------------------------------------------------------- - -class CharacterStatusNode(BaseNode): +def make_node_command(graph, nt): """ - Character Status Node - - This node represents the character's current status. - It has no input ports and four output ports: "HP", "MP", "FP", and "EXP". - The current values are stored in self.values and are drawn using a custom painter. + Given a NodeGraph instance and a node type string nt, return a command + function that creates a node of that type. """ - __identifier__ = 'io.github.nicole.status' - NODE_NAME = 'Character Status' + def command(): + try: + node = graph.create_node(nt) + # (No need to force top-level if nodes are created as top-level by default.) + except Exception as e: + print(f"Error creating node of type {nt}: {e}") + return command - def __init__(self): - super(CharacterStatusNode, self).__init__() - # --- Section: Initialization of Stat Values and Ports --- - self.values = {"HP": "N/A", "MP": "N/A", "FP": "N/A", "EXP": "N/A"} - self.add_output("HP", painter_func=get_draw_stat_port((255, 0, 0))) # Red for HP - self.add_output("MP", painter_func=get_draw_stat_port((0, 0, 255))) # Blue for MP - self.add_output("FP", painter_func=get_draw_stat_port((0, 255, 0))) # Green for FP - self.add_output("EXP", painter_func=get_draw_stat_port((127, 255, 212))) # Aquamarine for EXP - # Set the node title to "Character Status" - self.set_name("Character Status") - - def update_value(self, stat, value): - """ - Update the value for a given stat. - - Args: - stat (str): Stat name (e.g., "HP", "MP", etc.). - value (str): New value for the stat. - """ - self.values[stat] = value - -class DisplayValueNode(BaseNode): - """ - Display Value Node - - This node displays a single input value. - Its title is updated to show the value from the connected output port. - If no port is connected, it displays "Display Value: No Input Configured". - """ - __identifier__ = 'io.github.nicole.display' - NODE_NAME = 'Display Value Node' - - def __init__(self): - super(DisplayValueNode, self).__init__() - # --- Section: Initialization and Default State --- - self.add_input("in") - self.add_output("out") - self.value = "No Input Configured" - self.update_display() - - def update_display(self): - """ - Update the node's title to reflect the current displayed value. - """ - self.set_name(f"Display Value: {self.value}") - - def process_input(self): - """ - Processes the input connection: - - If connected, retrieve the value from the connected node's output port. - - If not, set the value to "No Input Configured". - """ - input_port = self.input(0) - connections = input_port.connections() if input_port is not None else [] - if connections: - # --- Section: Processing Connected Input --- - connection = connections[0] - out_port = connection.out_port() - if out_port: - parent_node = out_port.node() - port_name = out_port.name() - self.value = parent_node.values.get(port_name, "N/A") - else: - self.value = "No Input Configured" - else: - # --- Section: No Connection Found --- - self.value = "No Input Configured" - self.update_display() - -# ----------------------------------------------------------------------------- -# API Polling Functions -# ----------------------------------------------------------------------------- -def poll_api(): - """ - Polls the Flask API for JSON data. - Expected JSON format: {"hp": "150/150", "mp": "75/100", "fp": "20/20", "exp": "10.0000"} - - Returns: - dict: The JSON data, or None on error. - """ - api_url = "http://127.0.0.1:5000/data" - try: - response = requests.get(api_url, timeout=1) - if response.status_code == 200: - return response.json() - else: - print("API error: status code", response.status_code) - except Exception as e: - print("Error polling API:", e) - return None - -def update_nodes(graph, character_status_node): - """ - Polls the API, updates the Character Status node's values, - and then updates all DisplayValueNodes in the graph. - - Args: - graph (NodeGraph): The NodeGraphQt graph instance. - character_status_node (CharacterStatusNode): The character status node. - """ - data = poll_api() - if data is None: - return - # --- Section: Update Character Status Node with Latest Data --- - character_status_node.update_value("HP", data.get('hp', "N/A")) - character_status_node.update_value("MP", data.get('mp', "N/A")) - character_status_node.update_value("FP", data.get('fp', "N/A")) - character_status_node.update_value("EXP", data.get('exp', "N/A")) - - # --- Section: Update All DisplayValueNodes --- - for node in graph.all_nodes(): - if hasattr(node, "process_input"): - node.process_input() - -# ----------------------------------------------------------------------------- -# Context Menu: Add DisplayValueNode Command -# ----------------------------------------------------------------------------- -def add_display_node(graph): - """ - Adds a new DisplayValueNode to the graph at a default position. - - Args: - graph (NodeGraph): The NodeGraphQt graph instance. - """ - # Use NodeGraphQt's create_node() with the correct node type string. - new_node = graph.create_node('io.github.nicole.display.DisplayValueNode', - name='Display Value Node', pos=[400, 300]) - print("Added a new Display Value node.") - -# ----------------------------------------------------------------------------- -# Main Application -# ----------------------------------------------------------------------------- if __name__ == '__main__': app = QtWidgets.QApplication([]) - # --- Section: Setup NodeGraphQT --- + # Create the NodeGraph controller. graph = NodeGraph() - graph.widget.setWindowTitle("Project Borealis - Flyff Information Overlay") - # Register nodes using their classes. - graph.register_node(CharacterStatusNode) - graph.register_node(DisplayValueNode) + graph.widget.setWindowTitle("Modular Nodes Demo") - # --- Section: Setup Context Menu for Adding DisplayValueNodes --- + # Dynamically import custom node classes from the 'Nodes' package. + custom_nodes = import_nodes_from_folder('Nodes') + for node_class in custom_nodes: + graph.register_node(node_class) + + # Add context menu commands for dynamic node creation. graph_context_menu = graph.get_context_menu('graph') + for node_class in custom_nodes: + # Build the node type string: "<__identifier__>." + node_type = f"{node_class.__identifier__}.{node_class.__name__}" + node_name = node_class.NODE_NAME + graph_context_menu.add_command( + f"Add {node_name}", + make_node_command(graph, node_type) + ) + + # Add a "Remove Selected Node" command to the graph context menu. graph_context_menu.add_command( - 'Add Display Value Node', - lambda: add_display_node(graph), - shortcut='Ctrl+D' + "Remove Selected Node", + lambda: [graph.remove_node(node) for node in graph.selected_nodes()] if graph.selected_nodes() else None ) - # --- Section: Create and Connect Nodes --- - # Create the Character Status node. - character_status_node = graph.create_node('io.github.nicole.status.CharacterStatusNode', - name='Character Status', pos=[0, 0]) - # Create a Display Value node. - display_node = graph.create_node('io.github.nicole.display.DisplayValueNode', - name='Display Value Node', pos=[500, 200]) - # Connect the EXP output (index 3) from the Character Status node to the display node's input. - character_status_node.set_output(3, display_node.input(0)) + # Resize and show the graph widget. + graph.widget.resize(1200, 800) + graph.widget.show() - # --- Section: Timer Setup for API Polling and Node Updates --- + # 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"): + try: + node.process_input() + except Exception as e: + print("Error updating node", node, e) timer = QtCore.QTimer() - timer.timeout.connect(lambda: update_nodes(graph, character_status_node)) + timer.timeout.connect(global_update) timer.start(500) - graph.widget.resize(1000, 600) - graph.widget.show() - app.exec_() + sys.exit(app.exec())