From 7dc24b46a070d2c0aa08c5a7886bedd15b8e8e17 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 18 Aug 2020 16:48:46 +0200 Subject: [PATCH 01/91] fix(fusion): clean wrong config name --- pype/hosts/fusion/scripts/fusion_switch_shot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/fusion/scripts/fusion_switch_shot.py b/pype/hosts/fusion/scripts/fusion_switch_shot.py index 4cb20c3a61..14ef00951b 100644 --- a/pype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/pype/hosts/fusion/scripts/fusion_switch_shot.py @@ -87,7 +87,7 @@ def _format_filepath(session): # Create new unqiue filepath if os.path.exists(new_filepath): - new_filepath = studio.version_up(new_filepath) + new_filepath = pype.version_up(new_filepath) return new_filepath From 161700f2beada0d600908ecf9a3c6a45cb1b7fd4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 18 Aug 2020 17:52:46 +0200 Subject: [PATCH 02/91] feat(fusion): adding icon to resources --- pype/resources/app_icons/fusion.png | Bin 0 -> 59862 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pype/resources/app_icons/fusion.png diff --git a/pype/resources/app_icons/fusion.png b/pype/resources/app_icons/fusion.png new file mode 100644 index 0000000000000000000000000000000000000000..50541b0e4f0fd42f2bc93310066967769527bc8c GIT binary patch literal 59862 zcmV)XK&`)tP)0{{R3C-iPo00004XF*Lt006O% z3;baP00001b5ch_0Itp)=>Px&08mU+MMrQ<0RaI3000000231v2nYxc4h}mzJJQn9 z9v&V80|5X400000000030|5X40000000000000640R;pB;oxhvqfyY#WX!ro!m2Oc z$xmx&Fo|Xg92Ny7B@&;UOS!gKQBWqKmpDK&3EHd_)4VIDdJDdb49}JdQbG=xd z9cfbzE+q(ugg=jcBONhF7$`LzGD;UEG8`{P79uVgDK{A^I29o*8Z0~(Brg^vF%=*x z7bi3sDmoi3K^iMM9y3c7AuAdzJsmJaAvR4FAu1d$LK!PO79uYjEk7A7KOi+r9x+7} z9w;9)Ngp WqHCGe#C6Efys-A~{eUFhUzHL?Jg$^YafEC^+}$C;0XN5+5u0^8oJD z0UkC`_U-}u_y86pG5PE!`sObB=qm1{0{Pr8?4JPr`!n_M7w_Bx?#}@rIaU1o0Qc}8 z_s}Kw=>hxoGWP5q@ZkdPs094`BKX-W`1u?8?k@261N6=$_TgOk)+zPZUi$MX@5cf8 z^&<7#SN;4e^UhQD(Q_SL>HuUo<2ooUm)kpf_GxPfc4I3}?`6%>g^3F8# z=Sc9!GWE$I_~dNz$WrgcC-KTY^XfYL;zsw@Gy3W}`r=df+e92LK=SJn5g|49@=EgJ zKK0K7^4}Hj*BkKKDfjnO_u2#c(s2_eJof8R_v(21?TY&HUHbKH`R8Ttwk9<>DDk>B z@!})!-ZuXJZ~gS4@W^QKzefA~W9_XP9V8_vE++8P5kf{XQc^DJsssM}dIu00`}Gse zvtn~^B4c4Z?#31AodIE9Ba(+miiRF=ZA8Go6ZrZMFFZSadr;iWHBVSZgnKourXos4 z9-)_8+Sm@dws+ali(_A!y8r+HFmzH*Qve$)BMcQ01OW)6IER(GcVN7`Y%PckB9Wz(BLM|X*3Ib}iqS|GB?T^V;Y3Qg{g?^~p&+?% zfffZJivl62I9a;i`@Y{d^NleH$tIf!=CwV4{D+Nr=J~zvXZFsWn^HgO`fFH!)Ag5+ zR`}NF)8P-ilhpUexfinHc;Boa;$^-R=ibXa`7M8UoFm4U+1|aPF2j$YC&a%m(5B5H z<%sXVz`X|#9t;l;kD!KBrBeEcp2drJ%e{eth!p_h`<_dWN{2R9D$Y+gNQwHSmOd5O%?%rOz-9Bn_?YH+;wdF2#M76g| z@IgME8E5FiTqBPhPn!5QFNHVh@|+ZSi(n6<-1j9@q&wGl%Cx zVtJ@|V)&m-Iue?E7S4=$bWbs6zNmGsH)d7MlY0Hl8+ym;I_Pg{>|hsgsat8uY9SB+|;h&I}SaIje|GgRt5MvaTn?uIV?|4 z3UMfYH;nWI_}4<+t2989(BPcOPK@no%uJaWzp4_*Mx#=x(54aYD&cc}#lsW6S8?zJ zQ+Eh^wfY9k>&!ev4&vDi5r^Ui@EGre)E6hs(alD)&|{;D^OnNQlp92|!Br8&w3U5W zc+sP$up#Prcwk;3(;bFRo^LYpcT9YAOcW=>RS!>ie}(uqNTWx`K#R^C*bQG{R1Q}R z+WJHhr|ZEd*dBJ2>JDS}=uT%25m%mXuCFQLq__f(9^S({iSsWZbwpYzJ)U9E`vRIc zzfv}9izA^!{N}N|lWg|YOTN!1*kSKbe+O;PQ5_xKe0_6G1Bc<6aV2;k-U+!s65m1^ z;XN|MN>2#rEf%_3_5I3ap*hDZTJ4_v*6*WfO`0~3Z4S3_+BzOl_ORnjcg_WPeVtZn z0(^o6M+c`s7v4#n|52$!r#ZYcSsJ>@==Jsb8*0z}+!fM|=%AnE+^)G%)0l?6N8o|m z=f=IDU^_CpaVHnxY&d%O?pQX%fo}Mn;Qk!c5$XF8=|>p5Wfp_cj&HJ6(cI8s-cV*o zdMo4&LvD|S_WTCtII(5fc`SE?ua7Ku^b@L$*EcuO!&z_yddT~JoOiu@lB*tq0?GvaoX!cTOSDKMk5;78b&QG@neaOeR`39pik}FXUv{* zy5g&*Eu7^Wm`L#THLSxm@07_qiStiJ&G9`rnHAD&kDBAXlx<2=FrDX63P9#2o~$v{?D zb2lxRyohwe7|FtQHCYRIwGF_%I6XZ+IXrydI{0a7YU<_G%VzU+^Lew`e2LSkfBtmP zdVhF$a(sM>3!uAEs}T0{b6;spjhxx~PH1^l%zJ&6^L%$AJ#NH#g8SpB<5Hthx7*v> zYczQ~juzSDZ@o;h&6O4?docLo^caAF_B9pwHg{%bcA}bByXFqT#~p8aIR)@Uz1#KR z`nh5&9b3Lmt*LXpbzOHE1KZXX@uoQM)jPrc5!81FSZWpDn{XPWu}1QQF73SDWwUf+ zk0_tKZ#@>oAiT3Py;CU6p=>*sD_K-|lqHq3Z5yu@3XC2f;12@&$uXF3_~%M;#;w#2 z47DAn()5>y5>Cg8SpBIlrk>Lu%EiX(0BPu%hgFv6fAsmm1wJFt-jO z$b{Gi*~(f_m(tIa9@p@ zTQ&0g;h=QtRhF8K=DRd&d4!-beQlgw93QgH&E`BH|7_>1{Kp7i!5!rD@SR;}AZOwyr>wYdbZnRJ04>mg`@f>MPV)qJzc0&FfDaIAmEStP zwbpF27bgwShX>;GbYU)M6~6@LIP#*EgXHt<_`%`n1pybOJ3eK2hoQTyw;S-Zw#IrR z%?bBFJj@f+%4$NbS~aA0Y5Eq>G@6Cs%}2=Y4Go%o{_>nn;-vMIlADCie?`uQ%WhIyN6KHEK}$F@c8B zxz7-~tT$zK{3(h9IFD^p5Ij)4#cJ6j$QABQ#(fmwmN37Kw8L-9WU-}D^P<|R)rf0N zKr6RJX@vK&D19BH%ldX@Er$DRgD)3z0{m&~_$=t0lhmC+`&JF+v1ZMStx09Ipssm6Deid-Nblnol%Cls*j9g~KLK&ED35Dg35$KH zV`j4pJ2MD$@;l@W$go=WfnIr&-IKBqyLGKQj{4q63X3fr;KI6Ql4hyBv(wgdcDf(w zev}zy5xZhJyctz4#?Oo8JZ|!t4`GjwY&MVRPg|#FPNzewFjtRg)Vta4WrZ6HvDC=D z+lD)q-~+>{hvQgmDXUGT^>Jlkse6HmtRLy}Vh?5(Hii+$`FznTTm0Y?JaCw|fHxwI zSBbW9EnJ5<@un5o21+@7*{q4h|t@n}N3-;uIk)axA$PdkQq6iWJ=WgeTH{__BfK?j zH18S<-P8XezUQZDB2&NP+1c`kIFq@>yrySa;orCVRNg$4YoN#%16>Oe?Gf_g)x1k{ z6z9+1pLUtHBVKnzZ9rfCk1WK-(t~E}zR|SJ1eaz#LEo0us{xZ%y_$IQP=-#;QqLD` zwaU_*7kfYhiACuQFRfxek~m%^>-xo?$E_&XjkeGaIsPGb;R)$|e5M!&*mt5hU*T`n z+D8+*b>H+XH?@{#{abr`ZF9w?h4l+#GNawX!p6nnFD$i=Z=hZ*&-Un?4@>3q)c{MS zQh)AOT?Ki$e6;A7kn_J~HXi4{99{$+VH~JD7ut+_KWyDMTkg2ll;FBq*L0g^*nC() zezy#%Bg#fpz4Q`6FU6ujW;g}D7=}A_@>xMX z%W0nb`N<5@oSSz~1uHAdZ@G0-mdjjzGv(G=>%lDh8#QY;sMU0@uIV{^0j>IXpyvG6 z?fF^pGH6L=sT^-C0xEC`Z^>4Wb8;@A)uF{y_478*@jjdZGEM3(DF zpQVukX+AyEeEs|JnM~%cj&yn~FTbVMeYBg61~rlUW@>$RV1#FItgNeo+Ka_FP@nvp z@|#gBrJwbH&KsO_MulAM@bt>EdyeebYj$*?U*P{*l<|6K&l_XgEc2E$|JMm-HLdrG zy8_hoCCkd&O+A;7(Eq4*)8zg!rZo*_d;8R^117CoHE$MPDAaad3-f>O&L%X9Y>VTV zK@dMM2E>J+=7F0D3`J27hG9@jnnwnasp3VtnP^C`-fVn}LOMLiO56$q709-F>1=w@ z8+QR09bw)GPlO_-ni?O3-%t!zFfBye-?!A?U z;v7gthc2gVlICnYuYhq_03Azy@w@p)Ri8<`!d$&^QH8hpMgw`zOWa@gb`)*YcnlNN zW&&L|%jGPn8R9p ztz0?}G+y}D`E-}@(N$E?1)V1JhQ`g+-Mou6oT0{gp2SegQQYX$J6iwVX9uuX9lQI(la{eZZjA=j_LOUbr`OVu1TKYs6Id z1yQ0U%qO$^HeQR7gz7SqUIkjTPpTi z&{bR$@`ka%ye#IgzHG8SECyva5%^u`xgRY*XR&pT$h-ve{Ob-WQIeOo!;&C+H}1PHGP_$!9W+`ExrcU zw|og@+?#(tZhd;KeYBn>$&j-cmOIc;Q(Td>9w zK7NulTf%$nvh-BO!Y?eW4{S<~T1x(2at$1rb0-8`v*bz?$fq!Bl} z_(1LA1(wr9DIY!BW^+AtPBvUj*l8+IKWyFF*<@yYU72-ud?IHWo@W@fcL1EL;?D3k zM=g!Uycnu6>)yQl4u_|lG|S}8X4HWu?A(;ua*t@ob>=iL;poW3XtSoE z3273$`=bs&)m6^h9$D$c@+cEyvY$i*OcqzwJK60g8mT<6ZNZ~Kn zvEc&33#lZI8Jw84oRsCF{@9v!<-GG?aPwjwxK~*%f3p*_gx2Jlyo=7=ft>c@B2G{F zD);En)*aW8v2m8dUt$TDq87Hzg4s1_w|vX0_l7#GM7nTKO2~ih19&xc2j28d``XsO z-vMLPb<$0m39OAg$5bu`EThwL6V@kSkitrM!ri)sc8vmWeR!p1or~Ho-KDI{MLgKG zN8ZJ_g?Ecu4ngfh450Y~x_5-790n{WMv1mz*U)jTKkYPS!zIJ`tzFhy&!`NZkNQwZ zS3*8;F{ia0gIaI*Uy#p0-x$=GwpUpye*rz%=omNct|wp?j&_|QY^m#oIh3^wW@>;E z>Q<{VYYReXu1D?j8dTlYaAVVr;_hAXsSmI{1*TJ|c?crxFqS+L)YhaCXR)#%? zAza8>v&LlX>>IWFu~8eqoeBak$IXM9U&mPfph>PIPCBb#+*3NH4K;1e8loBNuDM;O ziZ^S$ptt6-4k+qyP_wocF+Pc?b8s6kvMR;g4(1Id?$_H1s#-oIqT?npZFp%PqpTsC zE&tpV6+9J1d~UZVWgTQ#@7b%Xla?of$k75<9Te)z+pXx{S=`ow7?0)c_U5hVF zbH+NJvJwu-%+a!TRRj+URPE(}cI^6XoDm4^Lm^thC71QT+0f<3)IIPXLThe!lgYa7t5{|&Ru@%p zEAM1WxSPQeirxIaYcqE98E4+j|8eN%d~D6z>5JP~8Ve0^2awaQtjSWI$WT7J^aMk> zXu*bYykfRo=WyXeWN_YjwH9_>;T$C(tobum;MHCguT~P|$91%(Es$=xw%sguTG{Ex z&URxt8`$Xzj@ZpElA1mbEGI0lWGYYiP%Yx2e0W?WL`%0 zB~)Z>Jh?(*t_?yS$u5+yU#@QR9a(KlefL+l#x4BF*s7*V`Cqr=nD$^v=ZR>+hLX0y zdg>gHW^x=3zk?LsW+_~ja97H@a9rl)q1I*G5$hJNdZSO*m$1x&?fY(590Rs0hvKWC zmxx=vdP9AZL|C^Esl2!G0EC@z*09m|@zBz?SwBWyOJy^^#zD#z&o@~Yk}g!W>Dl7R zH;!6TQ+8bwU>|{7|BFk+t&G)XXt-QJXK<07$)md6z z|NZPR($=iU#!pS2MP0X2+Fi4b3sQKXQMIU;9gcm1pEa5q*WziQ!T!OaK_@40gWAFOL%^O1Vw6Ne@)`O{9E}R?woj&3nUj3o{ z^>!>Od6eKohc(YuNMUHd+8R~wI9$BZaoq8K2=;ljr_?KqjZvzL#;zg zmV0Kb?!RN42Un9c)pa{0kQ==D;o*FT!i^NNm$W8ZaRh_3CZx30=PzHJA12xWTe1F4=vtCy`riF><<7LHWgR-jE7tj{ zcJfb8ngbP|cGj-{@rt>60l5;mZ+F_tutO)WsRwSd!_VQrU6({{<8+?_#OF`2+EUCZiD{t4h zt(vv0Dqq1)*{D2|%jK8POS!}6D`?v=PD`RF>Tqpy-#&C{Q`VEZhRZt6V|b`#o$0LW z^-K;gHEdJct~o#%pnMDBK_%-p`#1%hGA9)%2Y6ut3q!ppG322x5SDbYWrp+cs<}lx`hnd+U1N4x2>X+>y9dZYy4uVbf`9CQuN!0xhj%njU&=UDeZ-AoSI42*$iIa$F6YOsa>}U zYK>T*^A9QtTvw5-ZFA=?J@r{EZdJ=Mly657Y&V5b8e^Vxp70LruxV4)Q>^FO(4q$5 zt!>z+;49YpRbI;%wO4k3qnwp4`t(;oE$>Ji1PZmFuHRi*-?>X7eU1qg;O4Y>goRN4 z{yu}uh_hd;lt)N=iqbw@))O!zMO`l{U8k(Q#(fIhuBohR1-h=zT8>8DO6_!D;0co`^Iy%cAD9|vM+7>iZ*Y)6704kSn>qg6NgEg>{U2*L9y2HY-{DaX~9~^8ouLN zNeTA`DB)zAK^f^lhws4mb5Q$4*VbQ^E`^zQ^r^DRxDRgm>AmT-m8~e^^A)J%jP~mK z@37)|c>B`mz7)ZFG1byu?5&u(R^M$+pso*c3cxYv7;47ht6NO=5w$XEUCIrrCjFq_ z_>}fw`zz(fz6MFVm)sYT9B|KeR$+bnndgbp6lX${oczJ4MNZJxDU6 z&v-SP7WV9CV3XP%n6HJPKWzB)VP)eknIeV6xM94~+lUZ$guG{$mXY?EL!vE)q(_gO zIC=aM%Vxi%i;Kt_q?FkUZk)3b#YmcQsXFQSSH<-T|Q&dTPm5nP;i^y5FCuD`u> z{N#xvN5$aupjm^kVbAcz^~W1f!|y4}CaitBm#|g?*meH1tPR@e=Qr|Tle^o{Y!;UF z)q(BT@{i)~viydlT@Wm%G!0A&FF#58h(Swx}GUz zUa73*?rUD|j5~)Gjzd*6Q#TN%*-ZZs0`uLr-3JAWBXXo82P^ayS#(EC^P8^oMIiYM zkW!AcHzI+17PP(kc>V2H$4`$PIp{)88|?ExJ?;D=av4`P^J~sp+O>A-dR;waHX(W5 zjT^bguF3ceo>W;p3p_ip^r`wQp=a?2NtP1V0uKT|C&bzP7vJti?q#a>C}nBixRKeo z0x$i#qHWGsLPoo{5piiF@L5J1#^eXxfSH4{=VF+mTI`;v;TG#!-BT4DCgQbP-yU1- z`4IwFs7;CGOhMHf`eS1L&)fNgwsB{1{M1W#dkT)XEo|L|TpCzZcSzVh*by0b+<1gM zCS;67Se80E2%l_(NT3jc!MAE%HV`84b#u|7m%tvf7_t(WH7H&ig4c(Tkm7>_Nu1hM zQ`hW!@ArS^HzOsH{fqU-Q3x7+=KJ3Fz2B3h(KQ~>?wEKJ;<#w$WVy1*Km^%9ACgPR z+v?1Bd07$~u7#bzq;2rzh3ku(pzFfo^yYM-7HgaycY%ExH$S8?qdTDM(TrlP2Q3bo z9iZ!&Lmcra9+>k;fe4vuAZ0M2-OUY|Ml(B}x?&H< zXrdmB8nl=KmEZ1&T8D1)Wyvm$OoT+O22K<9wY64*R+|{S8E#H{2Sg3xo-RyJ7Yc>i zy$2h-06VVTZE4Pa^V<^xvT-JY_zvjWVJ~zvT?-!`Vnpwvp`yK9`VKZX;%RM^@od+0 zt;DmI?-sJP&-Kw2cv{)ycMK#JU^2DED()=x(hAdyh0R4O!hrTNFTjpV+PV?{WSus! zku|NQrI*+b9~>9WaDFsESLr$oUAM!$f+cmc+{lJIEJoaP;4x-z0esx(x}_?d+cfUg z^bbXgH&t?xV%rrGaMcV8bx*MZ1KK}N^R!(kOc%bt0+#%fD23oXvrj48>RbI+?`~LX5E%pCJA+u9YpJAt+=?)*LWa+7d znzmgT%IQQyZNI#Zn<2y+LiutNEcyG#WYSD?)4qEMuh%7L`#%$>Nd&Xpn0Lalv6fh4W1*Tw$aL(EXY|gB8^*L9WQ4Yzp~mIc z*CCQ)%EqfA&^|VkW|*_pdyCwb5$)Mmk8xHZtGeNCOw+a~p57Uc^u^QPpT~o@-;t|t z{YyVV+@I-G!U>-ldDkJajsIvy7!<8Zz8Yqy`h(o~l3o@H&O@ zX)?z-dwJmAkOV>+=It=JyKMfs&79*?i{u4YJIw_x7Z>8;O)>SLQm`e+%>Q7DqCs>#KNTsdD3$aTIMx5C)=EQ$}SfBC&$vUUyBhQP#cHc(V=FFKTgtHRsozisj zd66AN(tFPi4*MA4jg7SpTx%QK8w@-k-*2C5wT4p;$d@j@occRn)uLBv?URSX{tI02U6zlfZ%VM0 zIb*smvkj2)^P3mPGSoLz-mFX3T|4pgY4D?4SrGNP_5mXekXKim%>`{WoA6fv9e|_4 z`wrS?T4VOZayrm9N^Uw{+!T5KJsC8zX@%lF)k!-_*s`1QJM5C8s%Mxz!)D5uLj@;4 zBqeJvEoXyUOB_85^zZfH&5Kmj9gzF+kUi3|+=l-YP~YF}=b#C5sWexDHP0`&kU0Ym z0{`#f?)}D?ChwdHn|V~>RN+O6zdjjhySJ*f0KcRXnN67j;-c1qN zy1X>AH2V@8XMx@}Ghqwj%zj#Syi&fx#b}7<^Qp2<$8F2V9o!onQP|qs2T>Dg;5=8V z&Ks*VuMuwwf%m=GOT!(}wxI|u;E}eS(XVd)fvK7y2a`sNj%!0qU@AAbpf7Bv#%+>b+_0|TFo-0K; z^J|G4fHU54+!||HeX=_dY3re!j@lif0*vQuR&5(;`-tAaM-}!*%}tfCLDyu&wGbXf z&3Jnv=3QG<1E6rpOB)vYMc7e(<3_=Hl7Zks+$Y9 z=c<(okyfZnLezNQTmpB0yV3HT=y;MVPk5EKH7o1sB{$IyhQX583Tk269*|Q?y{1jV zW-akQV#1y&^P&sF_zoHIEPo%DYvgNq{+{3-8WBtc@r2#Yj6QZI-V&8OhUCXItDwDQ z!JW;t&hD=<)TL@ArfDOD&zDLj@P!4!y>@V}G3?kJxvPiqcuzSLIgV(fCEqC)MI0~G zt}J7<%>#LlVaqltR9)ZP|J&4ONE)uqkUap%G#*NR)N!GO( z8?=G;{kJW&Su-u`w)PIemaQ(_t`h1B#qdgmcu7M(ZvtoP262zF%*{uW6m0z+6U@na zp6+FNB$p=bx)Anev53_+tG{|3$n`z!{$_23qBrZ3E>TB3V7ks}5lJrT$?2dtY*&sY zv6ms2G`bK_?VeBD;>sHG^=avv_TQYgpUn(6cKfy-tu`5IMLUwQt(dvE2{(xQ3=icw zYz`ZtT8{oiMt|rp+ zwm{AkcLMQ+COGr$!#x?wb27YX=erwfCP&;6v0S75@TY5>HT)R$R}Wze))2%${`MYL zUDQuO+%wN_UK|z)TRv@_wLtFBhL@nMj?~GSuqTYUGfLNSUFTKNrK29_nttbP>`ZHK z0AvjjT&S8~IC4YS+>u+gO{sgkuiY4P9R*v4HkZE;uI_PEM07%GI+0_^fJuAKO&Tb> zP^(efl<#)-j<(qAqN=O5!t(VDhVj{#H@`lg5xT~?3ojbQ4Q0EMv}7Go_C%!e!ip;& z?WN?^=CvX#qPMEE64&G;FRMoqS@&YK9hr08oV%2h@?aE9uCK82V`-^C)-Ut7HnC)HfSibYgyTLgDRUaQ5Cvj<4%KCy-n zuF0AcpSNZ0lG!n}tht3Ir_Bymq-}Z9bJp?H7kYv=?df|{^{4;A5RU4``b!4#UU@Xl ziU0niyPGRBGeX+s*&R&SR1oJ}o_E3o)C{(Ec&;U3PfSFkcz_{1Z`HMlS}Yr94YFcA zsiT(v`F01zKT?~C6X^Neb1q|w@-d<&^B1R2`?U1tgPG)@&Tsp zonoy>S-ZwsD-g(g=*ac`4{V+BCMoE9_Ori3g&kztNiQs-5^<=lGVU1_U z&f!;H-1r+~oshJNG_kG{->Up$U4$DVIoP!Q`guoa+wCrp6KyQOaub-gvHs$LyjG+6 z!;PNx$YqKB!Rc>!5GOBQzV+(M`j8v&819OvIXs*Q`%=2FfwhX`v3yOnHvdiHHCW5A zZYWy2N{ir=r2cU~{QM-azC)~kti;F9cK~IWpwXu0(ph-^r7FYIPeyLCKAkJB9 z&(lC|6Sj>tqMi^fG$HGJEM4dGaar@;nQw@i(r|=vdPMi##tW%yRP~Q~-9?3>>i=mupU^h0JdVS*r(RlEZ1484+ZKA+!>-C^yQlReM6MFss#@VlmQCnk zEgx*1b_ha(z%UzdFOC#Lz#AR+V1e<1FNPc(1gTXP4E7?!lH_30^&S)gb)DGtZnLnz zfA7tk(MXo$^hq-sw_xP?B9sdq=vd0}&l-FW=SE7v(R@zJ<4@wAw@1(aL0 zO|Vm`Fca6fnYFfZrE3kCS;N-O!Wno#)FUS4cD0-S7ZvQ}>IvO101|getp4Td9;6 z)+lE#+`t$=N!m#H9_-bHYBmcSq+P@(nhjl?Qj;rZ`M7x|@rL#|(;l6;`=279mN)ia zMzj4o(iyeb44Z)C)LAt0IX7KVt!nvxmiIkn&aV}A+^1|tZItYUPuYB&KnsVX5Lq+s z@KWuA+)7;ihtDvaL4RFVs0(J%M${#>yH!#F4Jq3wZP=5r=|jC2=xvL<0XKkob@fX0 zLTg4#!HA~K=~8tv*)!oB#^Ef1Rx#yoD%R{uCcxbpEiDA#x-MlL>X8_Zed2z!0Av3Im4 zW!rZ`Qf%bm#gJ*kIfL_~5;>zLBgbYNGa9XIpD&72I{WkHBc3MBDH9)y%i>2$e>M7c zRuFhv{+KY<5r(Y`HY3;BEDZLzPuQR755I$(uv@r>7wq`f(S99RX9|dzcM{26dTS%k z6J{QBwAphgZj^ONdABVu!psT>Bi{rW9!uE{(vH%(G-=D3w0PcBj64z_o4ES{7�q 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 j!+$2qA6Ds#00000NkvXXu0mjfByqfF literal 0 HcmV?d00001 From 9943f94f64419990807ac58016fc9a206bbd05ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Aug 2020 10:31:12 +0200 Subject: [PATCH 03/91] feat(fusion): adding utility scripts --- pype/hosts/fusion/utility_scripts/creator.py | 7 ++ pype/hosts/fusion/utility_scripts/loader.py | 7 ++ pype/hosts/fusion/utility_scripts/manager.py | 7 ++ pype/hosts/fusion/utility_scripts/publish.py | 62 ++++++++++++++++ .../hosts/fusion/utility_scripts/workfiles.py | 7 ++ pype/hosts/fusion/utils.py | 73 +++++++++++++++++++ 6 files changed, 163 insertions(+) create mode 100644 pype/hosts/fusion/utility_scripts/creator.py create mode 100644 pype/hosts/fusion/utility_scripts/loader.py create mode 100644 pype/hosts/fusion/utility_scripts/manager.py create mode 100644 pype/hosts/fusion/utility_scripts/publish.py create mode 100644 pype/hosts/fusion/utility_scripts/workfiles.py create mode 100644 pype/hosts/fusion/utils.py diff --git a/pype/hosts/fusion/utility_scripts/creator.py b/pype/hosts/fusion/utility_scripts/creator.py new file mode 100644 index 0000000000..25050e6f27 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/creator.py @@ -0,0 +1,7 @@ +import avalon.api +import avalon.fusion +import avalon.tools.creator as tool + + +avalon.api.install(avalon.fusion) +tool.show() diff --git a/pype/hosts/fusion/utility_scripts/loader.py b/pype/hosts/fusion/utility_scripts/loader.py new file mode 100644 index 0000000000..6a7af1566f --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/loader.py @@ -0,0 +1,7 @@ +import avalon.api +import avalon.fusion +import avalon.tools.loader as tool + + +avalon.api.install(avalon.fusion) +tool.show(use_context=True) diff --git a/pype/hosts/fusion/utility_scripts/manager.py b/pype/hosts/fusion/utility_scripts/manager.py new file mode 100644 index 0000000000..0bbe0a3213 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/manager.py @@ -0,0 +1,7 @@ +import avalon.api +import avalon.fusion +import avalon.tools.sceneinventory as tool + + +avalon.api.install(avalon.fusion) +tool.show() diff --git a/pype/hosts/fusion/utility_scripts/publish.py b/pype/hosts/fusion/utility_scripts/publish.py new file mode 100644 index 0000000000..38f6079711 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/publish.py @@ -0,0 +1,62 @@ +import os +import sys + +import avalon.api +import avalon.fusion + +import pyblish_qml + + +def _install_fusion(): + + from pyblish_qml import settings + + sys.stdout.write("Setting up Pyblish QML in Fusion\n") + + if settings.ContextLabel == settings.ContextLabelDefault: + settings.ContextLabel = "Fusion" + if settings.WindowTitle == settings.WindowTitleDefault: + settings.WindowTitle = "Pyblish (Fusion)" + + +def _set_current_working_dir(): + # Set current working directory next to comp + + try: + # Provided by Fusion + comp + except NameError: + comp = None + + if comp is None: + raise RuntimeError("Fusion 'comp' variable not set. " + "Are you running this as Comp script?") + + filename = comp.MapPath(comp.GetAttrs()["COMPS_FileName"]) + if filename and os.path.exists(filename): + cwd = os.path.dirname(filename) + else: + # Fallback to Avalon projects root + # for unsaved files. + cwd = os.environ["AVALON_PROJECTS"] + + os.chdir(cwd) + + +print("Starting Pyblish setup..") + +# Install avalon +avalon.api.install(avalon.fusion) + +# force current working directory to NON FUSION path +# os.getcwd will return the binary folder of Fusion in this case +_set_current_working_dir() + +# install fusion title +_install_fusion() + +# Run QML in modal mode so it keeps listening to the +# server in the main thread and keeps this process +# open until QML finishes. +print("Running publish_qml.show(modal=True)..") +pyblish_qml.show(modal=True) diff --git a/pype/hosts/fusion/utility_scripts/workfiles.py b/pype/hosts/fusion/utility_scripts/workfiles.py new file mode 100644 index 0000000000..d93eeadc9c --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/workfiles.py @@ -0,0 +1,7 @@ +import avalon.api +import avalon.fusion +import avalon.tools.workfiles as tool + + +avalon.api.install(avalon.fusion) +tool.show() diff --git a/pype/hosts/fusion/utils.py b/pype/hosts/fusion/utils.py new file mode 100644 index 0000000000..32b6b9c91f --- /dev/null +++ b/pype/hosts/fusion/utils.py @@ -0,0 +1,73 @@ +#! python3 + +""" +Resolve's tools for setting environment +""" + +import os +import shutil + +from pypeapp import Logger + +log = Logger().get_logger(__name__, "resolve") + + +def _sync_utility_scripts(env=None): + """ Synchronizing basic utlility scripts for resolve. + + To be able to run scripts from inside `Resolve/Workspace/Scripts` menu + all scripts has to be accessible from defined folder. + """ + if not env: + env = os.environ + + # initiate inputs + scripts = {} + us_env = env.get("FUSION_UTILITY_SCRIPTS_SOURCE_DIR") + us_dir = env.get("FUSION_UTILITY_SCRIPTS_DIR", "") + us_paths = [os.path.join( + os.path.dirname(__file__), + "utility_scripts" + )] + + # collect script dirs + if us_env: + log.info(f"Utility Scripts Env: `{us_env}`") + us_paths = us_env.split( + os.pathsep) + us_paths + + # collect scripts from dirs + for path in us_paths: + scripts.update({path: os.listdir(path)}) + + log.info(f"Utility Scripts Dir: `{us_paths}`") + log.info(f"Utility Scripts: `{scripts}`") + + # make sure no script file is in folder + if next((s for s in os.listdir(us_dir)), None): + for s in os.listdir(us_dir): + path = os.path.join(us_dir, s) + log.info(f"Removing `{path}`...") + os.remove(path) + + # copy scripts into Resolve's utility scripts dir + for d, sl in scripts.items(): + # directory and scripts list + for s in sl: + # script in script list + src = os.path.join(d, s) + dst = os.path.join(us_dir, s) + log.info(f"Copying `{src}` to `{dst}`...") + shutil.copy2(src, dst) + + +def setup(env=None): + """ Wrapper installer started from pype.hooks.resolve.ResolvePrelaunch() + """ + if not env: + env = os.environ + + # synchronize resolve utility scripts + _sync_utility_scripts(env) + + log.info("Resolve Pype wrapper has been installed") From a2ecd25201316fb73e0cbaee3f6725a64da25360 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Aug 2020 10:38:37 +0200 Subject: [PATCH 04/91] feat(fusion): update from colorbleed --- .../fusion/scripts/fusion_switch_shot.py | 48 +++++++++- .../fusion/scripts/publish_filesequence.py | 87 ------------------- 2 files changed, 46 insertions(+), 89 deletions(-) delete mode 100644 pype/hosts/fusion/scripts/publish_filesequence.py diff --git a/pype/hosts/fusion/scripts/fusion_switch_shot.py b/pype/hosts/fusion/scripts/fusion_switch_shot.py index 14ef00951b..586298ca66 100644 --- a/pype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/pype/hosts/fusion/scripts/fusion_switch_shot.py @@ -95,6 +95,15 @@ def _format_filepath(session): def _update_savers(comp, session): """Update all savers of the current comp to ensure the output is correct + This will refactor the Saver file outputs to the renders of the new session + that is provided. + + In the case the original saver path had a path set relative to a /fusion/ + folder then that relative path will be matched with the exception of all + "version" (e.g. v010) references will be reset to v001. Otherwise only a + version folder will be computed in the new session's work "render" folder + to dump the files in and keeping the original filenames. + Args: comp (object): current comp instance session (dict): the current Avalon session @@ -114,8 +123,36 @@ def _update_savers(comp, session): savers = comp.GetToolList(False, "Saver").values() for saver in savers: filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0] - filename = os.path.basename(filepath) - new_path = os.path.join(renders_version, filename) + + # Get old relative path to the "fusion" app folder so we can apply + # the same relative path afterwards. If not found fall back to + # using just a version folder with the filename in it. + # todo: can we make this less magical? + relpath = filepath.replace("\\", "/").rsplit("/fusion/", 1)[-1] + + if os.path.isabs(relpath): + # If not relative to a "/fusion/" folder then just use filename + filename = os.path.basename(filepath) + log.warning("Can't parse relative path, refactoring to only" + "filename in a version folder: %s" % filename) + new_path = os.path.join(renders_version, filename) + + else: + # Else reuse the relative path + # Reset version in folder and filename in the relative path + # to v001. The version should be is only detected when prefixed + # with either `_v` (underscore) or `/v` (folder) + version_pattern = r"(/|_)v[0-9]+" + if re.search(version_pattern, relpath): + new_relpath = re.sub(version_pattern, + r"\1v001", + relpath) + log.info("Resetting version folders to v001: " + "%s -> %s" % (relpath, new_relpath)) + relpath = new_relpath + + new_path = os.path.join(new_work, relpath) + saver["Clip"] = new_path @@ -138,6 +175,13 @@ def update_frame_range(comp, representations): versions = io.find({"type": "version", "_id": {"$in": version_ids}}) versions = list(versions) + versions = [v for v in versions + if v["data"].get("startFrame", None) is not None] + + if not versions: + log.warning("No versions loaded to match frame range to.\n") + return + start = min(v["data"]["frameStart"] for v in versions) end = max(v["data"]["frameEnd"] for v in versions) diff --git a/pype/hosts/fusion/scripts/publish_filesequence.py b/pype/hosts/fusion/scripts/publish_filesequence.py deleted file mode 100644 index c37ceee07c..0000000000 --- a/pype/hosts/fusion/scripts/publish_filesequence.py +++ /dev/null @@ -1,87 +0,0 @@ -"""This module is used for command line publishing of image sequences.""" - -import os -import sys -import logging - -handler = logging.basicConfig() -log = logging.getLogger("Publish Image Sequences") -log.setLevel(logging.DEBUG) - -error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" - - -def publish(paths, gui=False): - """Publish rendered image sequences based on the job data - - Args: - paths (list): a list of paths where to publish from - gui (bool, Optional): Choose to show Pyblish GUI, default is False - - Returns: - None - - """ - - assert isinstance(paths, (list, tuple)), "Must be list of paths" - log.info(paths) - assert any(paths), "No paths found in the list" - # Set the paths to publish for the collector if any provided - if paths: - os.environ["FILESEQUENCE"] = os.pathsep.join(paths) - - # Install Avalon with shell as current host - from avalon import api, shell - api.install(shell) - - # Register target and host - import pyblish.api - pyblish.api.register_target("filesequence") - pyblish.api.register_host("shell") - - # Publish items - if gui: - import pyblish_qml - pyblish_qml.show(modal=True) - else: - - import pyblish.util - context = pyblish.util.publish() - - if not context: - log.warning("Nothing collected.") - sys.exit(1) - - # Collect errors, {plugin name: error} - error_results = [r for r in context.data["results"] if r["error"]] - - if error_results: - log.error(" Errors occurred ...") - for result in error_results: - log.error(error_format.format(**result)) - sys.exit(2) - - -def __main__(): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--paths", - nargs="*", - default=[], - help="The filepaths to publish. This can be a " - "directory or a path to a .json publish " - "configuration.") - parser.add_argument("--gui", - default=False, - action="store_true", - help="Whether to run Pyblish in GUI mode.") - - kwargs, args = parser.parse_known_args() - - print("Running publish imagesequence...") - print("Paths: {}".format(kwargs.paths or [os.getcwd()])) - publish(kwargs.paths, gui=kwargs.gui) - - -if __name__ == '__main__': - __main__() From b7c3db004940e79c983c1b9ff2f67e8b93224e2e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Aug 2020 10:55:14 +0200 Subject: [PATCH 05/91] feat(fusion): prelaunch hook and utils --- pype/hooks/fusion/prelaunch.py | 61 ++++++++++++++++++++++++++++++++++ pype/hosts/fusion/utils.py | 10 +++--- 2 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 pype/hooks/fusion/prelaunch.py diff --git a/pype/hooks/fusion/prelaunch.py b/pype/hooks/fusion/prelaunch.py new file mode 100644 index 0000000000..69e91eda05 --- /dev/null +++ b/pype/hooks/fusion/prelaunch.py @@ -0,0 +1,61 @@ +import os +import traceback +import importlib +from pype.lib import PypeHook +from pypeapp import Logger +from pype.hosts.fusion import utils + + +class FusionPrelaunch(PypeHook): + """ + This hook will check if current workfile path has Fusion + project inside. + """ + + def __init__(self, logger=None): + if not logger: + self.log = Logger().get_logger(self.__class__.__name__) + else: + self.log = logger + + self.signature = "( {} )".format(self.__class__.__name__) + + def execute(self, *args, env: dict = None) -> bool: + + if not env: + env = os.environ + + # making sure pyton 3.6 is installed at provided path + py36_dir = os.path.normpath(env.get("PYTHON36", "")) + assert os.path.isdir(py36_dir), ( + "Python 3.6 is not installed at the provided folder path. Either " + "make sure the `environments\resolve.json` is having correctly " + "set `PYTHON36` or make sure Python 3.6 is installed " + f"in given path. \nPYTHON36E: `{py36_dir}`" + ) + self.log.info(f"Path to Fusion Python folder: `{py36_dir}`...") + env["PYTHON36"] = py36_dir + + # setting utility scripts dir for scripts syncing + us_dir = os.path.normpath(env.get("FUSION_UTILITY_SCRIPTS_DIR", "")) + assert os.path.isdir(us_dir), ( + "Fusion utility script dir does not exists. Either make sure " + "the `environments\fusion.json` is having correctly set " + "`FUSION_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" + f"FUSION_UTILITY_SCRIPTS_DIR: `{us_dir}`" + ) + + try: + __import__("avalon.fusion") + __import__("pyblish") + + except ImportError as e: + print(traceback.format_exc()) + print("pyblish: Could not load integration: %s " % e) + + else: + # Resolve Setup integration + importlib.reload(utils) + utils.setup(env) + + return True diff --git a/pype/hosts/fusion/utils.py b/pype/hosts/fusion/utils.py index 32b6b9c91f..e57723d43e 100644 --- a/pype/hosts/fusion/utils.py +++ b/pype/hosts/fusion/utils.py @@ -1,7 +1,7 @@ #! python3 """ -Resolve's tools for setting environment +Fusion tools for setting environment """ import os @@ -9,13 +9,13 @@ import shutil from pypeapp import Logger -log = Logger().get_logger(__name__, "resolve") +log = Logger().get_logger(__name__, "fusion") def _sync_utility_scripts(env=None): """ Synchronizing basic utlility scripts for resolve. - To be able to run scripts from inside `Resolve/Workspace/Scripts` menu + To be able to run scripts from inside `Fusion/Workspace/Scripts` menu all scripts has to be accessible from defined folder. """ if not env: @@ -62,7 +62,7 @@ def _sync_utility_scripts(env=None): def setup(env=None): - """ Wrapper installer started from pype.hooks.resolve.ResolvePrelaunch() + """ Wrapper installer started from pype.hooks.fusion.FusionPrelaunch() """ if not env: env = os.environ @@ -70,4 +70,4 @@ def setup(env=None): # synchronize resolve utility scripts _sync_utility_scripts(env) - log.info("Resolve Pype wrapper has been installed") + log.info("Fusion Pype wrapper has been installed") From 2701e152de05fc51b1aee1e6b40dbeb9a2d5a0f2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Aug 2020 13:40:39 +0200 Subject: [PATCH 06/91] feat(fusion): menu script --- pype/hosts/fusion/__init__.py | 63 ------- pype/hosts/fusion/menu.py | 154 ++++++++++++++++ pype/hosts/fusion/pipeline.py | 171 ++++++++++++++++++ .../hosts/fusion/utility_scripts/Pype_menu.py | 26 +++ pype/hosts/fusion/utility_scripts/creator.py | 7 - pype/hosts/fusion/utility_scripts/loader.py | 7 - pype/hosts/fusion/utility_scripts/manager.py | 7 - pype/hosts/fusion/utility_scripts/publish.py | 62 ------- .../hosts/fusion/utility_scripts/workfiles.py | 7 - 9 files changed, 351 insertions(+), 153 deletions(-) create mode 100644 pype/hosts/fusion/menu.py create mode 100644 pype/hosts/fusion/pipeline.py create mode 100644 pype/hosts/fusion/utility_scripts/Pype_menu.py delete mode 100644 pype/hosts/fusion/utility_scripts/creator.py delete mode 100644 pype/hosts/fusion/utility_scripts/loader.py delete mode 100644 pype/hosts/fusion/utility_scripts/manager.py delete mode 100644 pype/hosts/fusion/utility_scripts/publish.py delete mode 100644 pype/hosts/fusion/utility_scripts/workfiles.py diff --git a/pype/hosts/fusion/__init__.py b/pype/hosts/fusion/__init__.py index 7af75cebc8..e69de29bb2 100644 --- a/pype/hosts/fusion/__init__.py +++ b/pype/hosts/fusion/__init__.py @@ -1,63 +0,0 @@ -import os - -from avalon import api as avalon -from pyblish import api as pyblish -from pype import PLUGINS_DIR - -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "fusion", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") - - -def install(): - print("Registering Fusion plug-ins..") - pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(avalon.Creator, CREATE_PATH) - avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) - - pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - - # Disable all families except for the ones we explicitly want to see - family_states = ["imagesequence", - "camera", - "pointcache"] - - avalon.data["familiesStateDefault"] = False - avalon.data["familiesStateToggled"] = family_states - - -def uninstall(): - print("Deregistering Fusion plug-ins..") - pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) - - pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) - - -def on_pyblish_instance_toggled(instance, new_value, old_value): - """Toggle saver tool passthrough states on instance toggles.""" - - from avalon.fusion import comp_lock_and_undo_chunk - - comp = instance.context.data.get("currentComp") - if not comp: - return - - savers = [tool for tool in instance if - getattr(tool, "ID", None) == "Saver"] - if not savers: - return - - # Whether instances should be passthrough based on new value - passthrough = not new_value - with comp_lock_and_undo_chunk(comp, - undo_queue_name="Change instance " - "active state"): - for tool in savers: - attrs = tool.GetAttrs() - current = attrs["TOOLB_PassThrough"] - if current != passthrough: - tool.SetAttrs({"TOOLB_PassThrough": passthrough}) diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py new file mode 100644 index 0000000000..73ea937513 --- /dev/null +++ b/pype/hosts/fusion/menu.py @@ -0,0 +1,154 @@ +import os +import sys + +from Qt import QtWidgets, QtCore + +from .pipeline import ( + publish, + launch_workfiles_app +) + +from avalon.tools import ( + creator, + loader, + sceneinventory, + libraryloader +) + + +def load_stylesheet(): + path = os.path.join(os.path.dirname(__file__), "menu_style.qss") + if not os.path.exists(path): + print("Unable to load stylesheet, file not found in resources") + return "" + + with open(path, "r") as file_stream: + stylesheet = file_stream.read() + return stylesheet + + +class Spacer(QtWidgets.QWidget): + def __init__(self, height, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + self.setFixedHeight(height) + + real_spacer = QtWidgets.QWidget(self) + real_spacer.setObjectName("Spacer") + real_spacer.setFixedHeight(height) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(real_spacer) + + self.setLayout(layout) + + +class PypeMenu(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + self.setObjectName("PypeMenu") + + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + + self.setWindowTitle("Pype") + workfiles_btn = QtWidgets.QPushButton("Workfiles", self) + create_btn = QtWidgets.QPushButton("Create", self) + publish_btn = QtWidgets.QPushButton("Publish", self) + load_btn = QtWidgets.QPushButton("Load", self) + inventory_btn = QtWidgets.QPushButton("Inventory", self) + libload_btn = QtWidgets.QPushButton("Library", self) + rename_btn = QtWidgets.QPushButton("Rename", self) + set_colorspace_btn = QtWidgets.QPushButton( + "Set colorspace from presets", self + ) + reset_resolution_btn = QtWidgets.QPushButton( + "Reset Resolution from peresets", self + ) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(10, 20, 10, 20) + + layout.addWidget(workfiles_btn) + layout.addWidget(create_btn) + layout.addWidget(publish_btn) + layout.addWidget(load_btn) + layout.addWidget(inventory_btn) + + layout.addWidget(Spacer(15, self)) + + layout.addWidget(libload_btn) + + layout.addWidget(Spacer(15, self)) + + layout.addWidget(rename_btn) + + layout.addWidget(Spacer(15, self)) + + layout.addWidget(set_colorspace_btn) + layout.addWidget(reset_resolution_btn) + + self.setLayout(layout) + + workfiles_btn.clicked.connect(self.on_workfile_clicked) + create_btn.clicked.connect(self.on_create_clicked) + publish_btn.clicked.connect(self.on_publish_clicked) + load_btn.clicked.connect(self.on_load_clicked) + inventory_btn.clicked.connect(self.on_inventory_clicked) + libload_btn.clicked.connect(self.on_libload_clicked) + rename_btn.clicked.connect(self.on_rename_clicked) + set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) + reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + + def on_workfile_clicked(self): + print("Clicked Workfile") + launch_workfiles_app() + + def on_create_clicked(self): + print("Clicked Create") + creator.show() + + def on_publish_clicked(self): + print("Clicked Publish") + publish(None) + + def on_load_clicked(self): + print("Clicked Load") + loader.show(use_context=True) + + def on_inventory_clicked(self): + print("Clicked Inventory") + sceneinventory.show() + + def on_libload_clicked(self): + print("Clicked Library") + libraryloader.show() + + def on_rename_clicked(self): + print("Clicked Rename") + + def on_set_colorspace_clicked(self): + print("Clicked Set Colorspace") + + def on_reset_resolution_clicked(self): + print("Clicked Reset Resolution") + + +def launch_pype_menu(): + app = QtWidgets.QApplication(sys.argv) + + pype_menu = PypeMenu() + + stylesheet = load_stylesheet() + pype_menu.setStyleSheet(stylesheet) + + pype_menu.show() + + sys.exit(app.exec_()) diff --git a/pype/hosts/fusion/pipeline.py b/pype/hosts/fusion/pipeline.py new file mode 100644 index 0000000000..6ade71767f --- /dev/null +++ b/pype/hosts/fusion/pipeline.py @@ -0,0 +1,171 @@ +""" +Basic avalon integration +""" +import os +# import sys +from avalon.tools import workfiles +from avalon import api as avalon +from pyblish import api as pyblish +from pypeapp import Logger + +log = Logger().get_logger(__name__, "fusion") + +# self = sys.modules[__name__] + +AVALON_CONFIG = os.environ["AVALON_CONFIG"] +PARENT_DIR = os.path.dirname(__file__) +PACKAGE_DIR = os.path.dirname(PARENT_DIR) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") + +LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") + +PUBLISH_PATH = os.path.join( + PLUGINS_DIR, "fusion", "publish" +).replace("\\", "/") + +AVALON_CONTAINERS = ":AVALON_CONTAINERS" +# IS_HEADLESS = not hasattr(cmds, "about") or cmds.about(batch=True) + + +def install(): + """Install fusion-specific functionality of avalon-core. + + This is where you install menus and register families, data + and loaders into fusion. + + It is called automatically when installing via `api.install(avalon.fusion)` + + See the Maya equivalent for inspiration on how to implement this. + + """ + + # Disable all families except for the ones we explicitly want to see + family_states = ["imagesequence", + "camera", + "pointcache"] + avalon.data["familiesStateDefault"] = False + avalon.data["familiesStateToggled"] = family_states + + log.info("pype.hosts.fusion installed") + + pyblish.register_host("fusion") + pyblish.register_plugin_path(PUBLISH_PATH) + log.info("Registering DaVinci Resovle plug-ins..") + + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + + +def uninstall(): + """Uninstall all tha was installed + + This is where you undo everything that was done in `install()`. + That means, removing menus, deregistering families and data + and everything. It should be as though `install()` was never run, + because odds are calling this function means the user is interested + in re-installing shortly afterwards. If, for example, he has been + modifying the menu or registered families. + + """ + pyblish.deregister_host("fusion") + pyblish.deregister_plugin_path(PUBLISH_PATH) + log.info("Deregistering DaVinci Resovle plug-ins..") + + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) + avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + + +def on_pyblish_instance_toggled(instance, new_value, old_value): + """Toggle saver tool passthrough states on instance toggles.""" + + from avalon.fusion import comp_lock_and_undo_chunk + + comp = instance.context.data.get("currentComp") + if not comp: + return + + savers = [tool for tool in instance if + getattr(tool, "ID", None) == "Saver"] + if not savers: + return + + # Whether instances should be passthrough based on new value + passthrough = not new_value + with comp_lock_and_undo_chunk(comp, + undo_queue_name="Change instance " + "active state"): + for tool in savers: + attrs = tool.GetAttrs() + current = attrs["TOOLB_PassThrough"] + if current != passthrough: + tool.SetAttrs({"TOOLB_PassThrough": passthrough}) + + +def containerise(obj, + name, + namespace, + context, + loader=None, + data=None): + """Bundle Fusion's object into an assembly and imprint it with metadata + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + obj (obj): Resolve's object to imprint as container + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + context (dict): Asset information + loader (str, optional): Name of node used to produce this container. + + Returns: + obj (obj): containerised object + + """ + pass + + +def ls(): + """List available containers. + + This function is used by the Container Manager in Nuke. You'll + need to implement a for-loop that then *yields* one Container at + a time. + + See the `container.json` schema for details on how it should look, + and the Maya equivalent, which is in `avalon.maya.pipeline` + """ + pass + + +def parse_container(container): + """Return the container node's full container data. + + Args: + container (str): A container node name. + + Returns: + dict: The container schema data for this container node. + + """ + pass + + +def launch_workfiles_app(*args): + workdir = os.environ["AVALON_WORKDIR"] + workfiles.show(workdir) + + +def publish(parent): + """Shorthand to publish from within host""" + from avalon.tools import publish + return publish.show(parent) diff --git a/pype/hosts/fusion/utility_scripts/Pype_menu.py b/pype/hosts/fusion/utility_scripts/Pype_menu.py new file mode 100644 index 0000000000..bf42c75cde --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/Pype_menu.py @@ -0,0 +1,26 @@ +import os +import sys +import avalon.api as avalon +import pype + +from pypeapp import Logger + +log = Logger().get_logger(__name__) + + +def main(env): + from pype.hosts import fusion + # Registers pype's Global pyblish plugins + pype.install() + + # activate resolve from pype + avalon.install(bmdvr) + + log.info(f"Avalon registred hosts: {avalon.registered_host()}") + + bmdvr.launch_pype_menu() + + +if __name__ == "__main__": + result = main(os.environ) + sys.exit(not bool(result)) diff --git a/pype/hosts/fusion/utility_scripts/creator.py b/pype/hosts/fusion/utility_scripts/creator.py deleted file mode 100644 index 25050e6f27..0000000000 --- a/pype/hosts/fusion/utility_scripts/creator.py +++ /dev/null @@ -1,7 +0,0 @@ -import avalon.api -import avalon.fusion -import avalon.tools.creator as tool - - -avalon.api.install(avalon.fusion) -tool.show() diff --git a/pype/hosts/fusion/utility_scripts/loader.py b/pype/hosts/fusion/utility_scripts/loader.py deleted file mode 100644 index 6a7af1566f..0000000000 --- a/pype/hosts/fusion/utility_scripts/loader.py +++ /dev/null @@ -1,7 +0,0 @@ -import avalon.api -import avalon.fusion -import avalon.tools.loader as tool - - -avalon.api.install(avalon.fusion) -tool.show(use_context=True) diff --git a/pype/hosts/fusion/utility_scripts/manager.py b/pype/hosts/fusion/utility_scripts/manager.py deleted file mode 100644 index 0bbe0a3213..0000000000 --- a/pype/hosts/fusion/utility_scripts/manager.py +++ /dev/null @@ -1,7 +0,0 @@ -import avalon.api -import avalon.fusion -import avalon.tools.sceneinventory as tool - - -avalon.api.install(avalon.fusion) -tool.show() diff --git a/pype/hosts/fusion/utility_scripts/publish.py b/pype/hosts/fusion/utility_scripts/publish.py deleted file mode 100644 index 38f6079711..0000000000 --- a/pype/hosts/fusion/utility_scripts/publish.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import sys - -import avalon.api -import avalon.fusion - -import pyblish_qml - - -def _install_fusion(): - - from pyblish_qml import settings - - sys.stdout.write("Setting up Pyblish QML in Fusion\n") - - if settings.ContextLabel == settings.ContextLabelDefault: - settings.ContextLabel = "Fusion" - if settings.WindowTitle == settings.WindowTitleDefault: - settings.WindowTitle = "Pyblish (Fusion)" - - -def _set_current_working_dir(): - # Set current working directory next to comp - - try: - # Provided by Fusion - comp - except NameError: - comp = None - - if comp is None: - raise RuntimeError("Fusion 'comp' variable not set. " - "Are you running this as Comp script?") - - filename = comp.MapPath(comp.GetAttrs()["COMPS_FileName"]) - if filename and os.path.exists(filename): - cwd = os.path.dirname(filename) - else: - # Fallback to Avalon projects root - # for unsaved files. - cwd = os.environ["AVALON_PROJECTS"] - - os.chdir(cwd) - - -print("Starting Pyblish setup..") - -# Install avalon -avalon.api.install(avalon.fusion) - -# force current working directory to NON FUSION path -# os.getcwd will return the binary folder of Fusion in this case -_set_current_working_dir() - -# install fusion title -_install_fusion() - -# Run QML in modal mode so it keeps listening to the -# server in the main thread and keeps this process -# open until QML finishes. -print("Running publish_qml.show(modal=True)..") -pyblish_qml.show(modal=True) diff --git a/pype/hosts/fusion/utility_scripts/workfiles.py b/pype/hosts/fusion/utility_scripts/workfiles.py deleted file mode 100644 index d93eeadc9c..0000000000 --- a/pype/hosts/fusion/utility_scripts/workfiles.py +++ /dev/null @@ -1,7 +0,0 @@ -import avalon.api -import avalon.fusion -import avalon.tools.workfiles as tool - - -avalon.api.install(avalon.fusion) -tool.show() From 152a00f945096f5e330c3454fe02ee9d1fdf3d34 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Aug 2020 18:44:26 +0200 Subject: [PATCH 07/91] feat(fusion): adding scripts (with bug which needs to be fixed) --- pype/hosts/fusion/__init__.py | 47 ++++ pype/hosts/fusion/menu.py | 13 +- pype/hosts/fusion/menu_style.qss | 29 +++ pype/hosts/fusion/pipeline.py | 65 +----- .../32bit/backgrounds_selected_to32bit.py | 12 ++ .../scripts/32bit/backgrounds_to32bit.py | 12 ++ .../scripts/32bit/loaders_selected_to32bit.py | 12 ++ .../fusion/scripts/32bit/loaders_to32bit.py | 12 ++ .../duplicate_with_input_connections.py | 43 ++++ pype/hosts/fusion/scripts/set_rendermode.py | 125 +++++++++++ pype/hosts/fusion/scripts/switch_ui.py | 201 ++++++++++++++++++ .../scripts/update_selected_loader_ranges.py | 32 +++ .../hosts/fusion/utility_scripts/Pype_menu.py | 10 +- pype/hosts/fusion/utils.py | 5 +- .../fusion/create/create_tiff_saver.py | 3 +- 15 files changed, 547 insertions(+), 74 deletions(-) create mode 100644 pype/hosts/fusion/menu_style.qss create mode 100644 pype/hosts/fusion/scripts/32bit/backgrounds_selected_to32bit.py create mode 100644 pype/hosts/fusion/scripts/32bit/backgrounds_to32bit.py create mode 100644 pype/hosts/fusion/scripts/32bit/loaders_selected_to32bit.py create mode 100644 pype/hosts/fusion/scripts/32bit/loaders_to32bit.py create mode 100644 pype/hosts/fusion/scripts/duplicate_with_input_connections.py create mode 100644 pype/hosts/fusion/scripts/set_rendermode.py create mode 100644 pype/hosts/fusion/scripts/switch_ui.py create mode 100644 pype/hosts/fusion/scripts/update_selected_loader_ranges.py diff --git a/pype/hosts/fusion/__init__.py b/pype/hosts/fusion/__init__.py index e69de29bb2..7f54e71a75 100644 --- a/pype/hosts/fusion/__init__.py +++ b/pype/hosts/fusion/__init__.py @@ -0,0 +1,47 @@ +import sys +import os + +from .pipeline import ( + install, + uninstall, + publish, + launch_workfiles_app +) + +from .utils import ( + setup +) + + +from .lib import ( + get_additional_data, + update_frame_range +) + +from .menu import launch_pype_menu + +host_dir = os.path.dirname(__file__) +script_dir = os.path.join(host_dir, "scripts") +sys.path.append(script_dir) + +__all__ = [ + # pipeline + "install", + "uninstall", + "publish", + "launch_workfiles_app", + + # utils + "setup", + "get_resolve_module", + + # lib + "get_additional_data", + "update_frame_range", + + # menu + "launch_pype_menu", + + # scripts + "set_rendermode" +] diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py index 73ea937513..2a00c37ed6 100644 --- a/pype/hosts/fusion/menu.py +++ b/pype/hosts/fusion/menu.py @@ -15,6 +15,8 @@ from avalon.tools import ( libraryloader ) +import set_rendermode + def load_stylesheet(): path = os.path.join(os.path.dirname(__file__), "menu_style.qss") @@ -65,7 +67,7 @@ class PypeMenu(QtWidgets.QWidget): load_btn = QtWidgets.QPushButton("Load", self) inventory_btn = QtWidgets.QPushButton("Inventory", self) libload_btn = QtWidgets.QPushButton("Library", self) - rename_btn = QtWidgets.QPushButton("Rename", self) + rendermode_btn = QtWidgets.QPushButton("Set render mode", self) set_colorspace_btn = QtWidgets.QPushButton( "Set colorspace from presets", self ) @@ -88,7 +90,7 @@ class PypeMenu(QtWidgets.QWidget): layout.addWidget(Spacer(15, self)) - layout.addWidget(rename_btn) + layout.addWidget(rendermode_btn) layout.addWidget(Spacer(15, self)) @@ -103,7 +105,7 @@ class PypeMenu(QtWidgets.QWidget): load_btn.clicked.connect(self.on_load_clicked) inventory_btn.clicked.connect(self.on_inventory_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - rename_btn.clicked.connect(self.on_rename_clicked) + rendermode_btn.clicked.connect(self.on_rendernode_clicked) set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) @@ -131,8 +133,9 @@ class PypeMenu(QtWidgets.QWidget): print("Clicked Library") libraryloader.show() - def on_rename_clicked(self): - print("Clicked Rename") + def on_rendernode_clicked(self): + print("Clicked Set Render Mode") + set_rendermode.main() def on_set_colorspace_clicked(self): print("Clicked Set Colorspace") diff --git a/pype/hosts/fusion/menu_style.qss b/pype/hosts/fusion/menu_style.qss new file mode 100644 index 0000000000..df4fd7e949 --- /dev/null +++ b/pype/hosts/fusion/menu_style.qss @@ -0,0 +1,29 @@ +QWidget { + background-color: #282828; + border-radius: 3; +} + +QPushButton { + border: 1px solid #090909; + background-color: #201f1f; + color: #ffffff; + padding: 5; +} + +QPushButton:focus { + background-color: "#171717"; + color: #d0d0d0; +} + +QPushButton:hover { + background-color: "#171717"; + color: #e64b3d; +} + +#PypeMenu { + border: 1px solid #fef9ef; +} + +#Spacer { + background-color: #282828; +} diff --git a/pype/hosts/fusion/pipeline.py b/pype/hosts/fusion/pipeline.py index 6ade71767f..d593f2b615 100644 --- a/pype/hosts/fusion/pipeline.py +++ b/pype/hosts/fusion/pipeline.py @@ -2,20 +2,17 @@ Basic avalon integration """ import os -# import sys + from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish from pypeapp import Logger +from pype import PLUGINS_DIR log = Logger().get_logger(__name__, "fusion") -# self = sys.modules[__name__] AVALON_CONFIG = os.environ["AVALON_CONFIG"] -PARENT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.dirname(PARENT_DIR) -PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") @@ -25,9 +22,6 @@ PUBLISH_PATH = os.path.join( PLUGINS_DIR, "fusion", "publish" ).replace("\\", "/") -AVALON_CONTAINERS = ":AVALON_CONTAINERS" -# IS_HEADLESS = not hasattr(cmds, "about") or cmds.about(batch=True) - def install(): """Install fusion-specific functionality of avalon-core. @@ -52,7 +46,7 @@ def install(): pyblish.register_host("fusion") pyblish.register_plugin_path(PUBLISH_PATH) - log.info("Registering DaVinci Resovle plug-ins..") + log.info("Registering Fusion plug-ins..") avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -74,7 +68,7 @@ def uninstall(): """ pyblish.deregister_host("fusion") pyblish.deregister_plugin_path(PUBLISH_PATH) - log.info("Deregistering DaVinci Resovle plug-ins..") + log.info("Deregistering Fusion plug-ins..") avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) @@ -109,57 +103,6 @@ def on_pyblish_instance_toggled(instance, new_value, old_value): tool.SetAttrs({"TOOLB_PassThrough": passthrough}) -def containerise(obj, - name, - namespace, - context, - loader=None, - data=None): - """Bundle Fusion's object into an assembly and imprint it with metadata - - Containerisation enables a tracking of version, author and origin - for loaded assets. - - Arguments: - obj (obj): Resolve's object to imprint as container - name (str): Name of resulting assembly - namespace (str): Namespace under which to host container - context (dict): Asset information - loader (str, optional): Name of node used to produce this container. - - Returns: - obj (obj): containerised object - - """ - pass - - -def ls(): - """List available containers. - - This function is used by the Container Manager in Nuke. You'll - need to implement a for-loop that then *yields* one Container at - a time. - - See the `container.json` schema for details on how it should look, - and the Maya equivalent, which is in `avalon.maya.pipeline` - """ - pass - - -def parse_container(container): - """Return the container node's full container data. - - Args: - container (str): A container node name. - - Returns: - dict: The container schema data for this container node. - - """ - pass - - def launch_workfiles_app(*args): workdir = os.environ["AVALON_WORKDIR"] workfiles.show(workdir) diff --git a/pype/hosts/fusion/scripts/32bit/backgrounds_selected_to32bit.py b/pype/hosts/fusion/scripts/32bit/backgrounds_selected_to32bit.py new file mode 100644 index 0000000000..c0dcef5410 --- /dev/null +++ b/pype/hosts/fusion/scripts/32bit/backgrounds_selected_to32bit.py @@ -0,0 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk + + +def main(): + """Set all selected backgrounds to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Selected Backgrounds to 32bit'): + tools = comp.GetToolList(True, "Background").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/scripts/32bit/backgrounds_to32bit.py b/pype/hosts/fusion/scripts/32bit/backgrounds_to32bit.py new file mode 100644 index 0000000000..92ca18a82d --- /dev/null +++ b/pype/hosts/fusion/scripts/32bit/backgrounds_to32bit.py @@ -0,0 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk + + +def main(): + """Set all backgrounds to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Backgrounds to 32bit'): + tools = comp.GetToolList(False, "Background").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/scripts/32bit/loaders_selected_to32bit.py b/pype/hosts/fusion/scripts/32bit/loaders_selected_to32bit.py new file mode 100644 index 0000000000..6e3802d9ff --- /dev/null +++ b/pype/hosts/fusion/scripts/32bit/loaders_selected_to32bit.py @@ -0,0 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk + + +def main(): + """Set all selected loaders to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Selected Loaders to 32bit'): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/scripts/32bit/loaders_to32bit.py b/pype/hosts/fusion/scripts/32bit/loaders_to32bit.py new file mode 100644 index 0000000000..d86bef35f3 --- /dev/null +++ b/pype/hosts/fusion/scripts/32bit/loaders_to32bit.py @@ -0,0 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk + + +def main(): + """Set all loaders to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Loaders to 32bit'): + tools = comp.GetToolList(False, "Loader").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/scripts/duplicate_with_input_connections.py b/pype/hosts/fusion/scripts/duplicate_with_input_connections.py new file mode 100644 index 0000000000..9f4f4a8f0a --- /dev/null +++ b/pype/hosts/fusion/scripts/duplicate_with_input_connections.py @@ -0,0 +1,43 @@ +from avalon.fusion import comp_lock_and_undo_chunk + + +def is_connected(input): + """Return whether an input has incoming connection""" + return input.GetAttrs()["INPB_Connected"] + + +def duplicate_with_input_connections(): + """Duplicate selected tools with incoming connections.""" + + original_tools = comp.GetToolList(True).values() + if not original_tools: + return # nothing selected + + with comp_lock_and_undo_chunk(comp, "Duplicate With Input Connections"): + + # Generate duplicates + comp.Copy() + comp.SetActiveTool() + comp.Paste() + duplicate_tools = comp.GetToolList(True).values() + + # Copy connections + for original, new in zip(original_tools, duplicate_tools): + + original_inputs = original.GetInputList().values() + new_inputs = new.GetInputList().values() + assert len(original_inputs) == len(new_inputs) + + for original_input, new_input in zip(original_inputs, new_inputs): + + if is_connected(original_input): + + if is_connected(new_input): + # Already connected if it is between the copied tools + continue + + new_input.ConnectTo(original_input.GetConnectedOutput()) + assert is_connected(new_input), "Must be connected now" + + +duplicate_with_input_connections() diff --git a/pype/hosts/fusion/scripts/set_rendermode.py b/pype/hosts/fusion/scripts/set_rendermode.py new file mode 100644 index 0000000000..4b2049e2e5 --- /dev/null +++ b/pype/hosts/fusion/scripts/set_rendermode.py @@ -0,0 +1,125 @@ +from avalon.vendor.Qt import QtCore, QtWidgets +from avalon.vendor import qtawesome +import avalon.fusion as avalon +from avalon import style + + +_help = {"renderlocal": "Render the comp on your own machine and publish " + "it from that the destination folder", + "deadline": "Submit a Fusion render job to Deadline to use all other " + "computers and add a publish job"} + + +class SetRenderMode(QtWidgets.QWidget): + + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent) + + self._comp = avalon.get_current_comp() + self._comp_name = self._get_comp_name() + + self.setWindowTitle("Set Render Mode") + self.setFixedSize(300, 175) + + layout = QtWidgets.QVBoxLayout() + + # region comp info + comp_info_layout = QtWidgets.QHBoxLayout() + + update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh", + color="white"), "") + update_btn.setFixedWidth(25) + update_btn.setFixedHeight(25) + + comp_information = QtWidgets.QLineEdit() + comp_information.setEnabled(False) + + comp_info_layout.addWidget(comp_information) + comp_info_layout.addWidget(update_btn) + # endregion comp info + + # region modes + mode_options = QtWidgets.QComboBox() + mode_options.addItems(_help.keys()) + + mode_information = QtWidgets.QTextEdit() + mode_information.setReadOnly(True) + # endregion modes + + accept_btn = QtWidgets.QPushButton("Accept") + + layout.addLayout(comp_info_layout) + layout.addWidget(mode_options) + layout.addWidget(mode_information) + layout.addWidget(accept_btn) + + self.setLayout(layout) + + self.comp_information = comp_information + self.update_btn = update_btn + + self.mode_options = mode_options + self.mode_information = mode_information + + self.accept_btn = accept_btn + + self.connections() + self.update() + + # Force updated render mode help text + self._update_rendermode_info() + + def connections(self): + """Build connections between code and buttons""" + + self.update_btn.clicked.connect(self.update) + self.accept_btn.clicked.connect(self._set_comp_rendermode) + self.mode_options.currentIndexChanged.connect( + self._update_rendermode_info) + + def update(self): + """Update all information in the UI""" + + self._comp = avalon.get_current_comp() + self._comp_name = self._get_comp_name() + self.comp_information.setText(self._comp_name) + + # Update current comp settings + mode = self._get_comp_rendermode() + index = self.mode_options.findText(mode) + self.mode_options.setCurrentIndex(index) + + def _update_rendermode_info(self): + rendermode = self.mode_options.currentText() + self.mode_information.setText(_help[rendermode]) + + def _get_comp_name(self): + return self._comp.GetAttrs("COMPS_Name") + + def _get_comp_rendermode(self): + return self._comp.GetData("pype.rendermode") or "renderlocal" + + def _set_comp_rendermode(self): + rendermode = self.mode_options.currentText() + self._comp.SetData("pype.rendermode", rendermode) + + self._comp.Print("Updated render mode to '%s'\n" % rendermode) + + def _validation(self): + ui_mode = self.mode_options.currentText() + comp_mode = self._get_comp_rendermode() + + return comp_mode == ui_mode + + +def main(): + import sys + app = QtWidgets.QApplication(sys.argv) + window = SetRenderMode() + window.setStyleSheet(style.load_stylesheet()) + window.show() + sys.exit(app.exec_()) + + +if __name__ == '__main__': + main() diff --git a/pype/hosts/fusion/scripts/switch_ui.py b/pype/hosts/fusion/scripts/switch_ui.py new file mode 100644 index 0000000000..8f1466abe0 --- /dev/null +++ b/pype/hosts/fusion/scripts/switch_ui.py @@ -0,0 +1,201 @@ +import os +import glob +import logging + +import avalon.io as io +import avalon.api as api +import avalon.pipeline as pipeline +import avalon.fusion +import avalon.style as style +from avalon.vendor.Qt import QtWidgets, QtCore +from avalon.vendor import qtawesome as qta + + +log = logging.getLogger("Fusion Switch Shot") + + +class App(QtWidgets.QWidget): + + def __init__(self, parent=None): + + ################################################ + # |---------------------| |------------------| # + # |Comp | |Asset | # + # |[..][ v]| |[ v]| # + # |---------------------| |------------------| # + # | Update existing comp [ ] | # + # |------------------------------------------| # + # | Switch | # + # |------------------------------------------| # + ################################################ + + QtWidgets.QWidget.__init__(self, parent) + + layout = QtWidgets.QVBoxLayout() + + # Comp related input + comp_hlayout = QtWidgets.QHBoxLayout() + comp_label = QtWidgets.QLabel("Comp file") + comp_label.setFixedWidth(50) + comp_box = QtWidgets.QComboBox() + + button_icon = qta.icon("fa.folder", color="white") + open_from_dir = QtWidgets.QPushButton() + open_from_dir.setIcon(button_icon) + + comp_box.setFixedHeight(25) + open_from_dir.setFixedWidth(25) + open_from_dir.setFixedHeight(25) + + comp_hlayout.addWidget(comp_label) + comp_hlayout.addWidget(comp_box) + comp_hlayout.addWidget(open_from_dir) + + # Asset related input + asset_hlayout = QtWidgets.QHBoxLayout() + asset_label = QtWidgets.QLabel("Shot") + asset_label.setFixedWidth(50) + + asset_box = QtWidgets.QComboBox() + asset_box.setLineEdit(QtWidgets.QLineEdit()) + asset_box.setFixedHeight(25) + + refresh_icon = qta.icon("fa.refresh", color="white") + refresh_btn = QtWidgets.QPushButton() + refresh_btn.setIcon(refresh_icon) + + asset_box.setFixedHeight(25) + refresh_btn.setFixedWidth(25) + refresh_btn.setFixedHeight(25) + + asset_hlayout.addWidget(asset_label) + asset_hlayout.addWidget(asset_box) + asset_hlayout.addWidget(refresh_btn) + + # Options + options = QtWidgets.QHBoxLayout() + options.setAlignment(QtCore.Qt.AlignLeft) + + current_comp_check = QtWidgets.QCheckBox() + current_comp_check.setChecked(True) + current_comp_label = QtWidgets.QLabel("Use current comp") + + options.addWidget(current_comp_label) + options.addWidget(current_comp_check) + + accept_btn = QtWidgets.QPushButton("Switch") + + layout.addLayout(options) + layout.addLayout(comp_hlayout) + layout.addLayout(asset_hlayout) + layout.addWidget(accept_btn) + + self._open_from_dir = open_from_dir + self._comps = comp_box + self._assets = asset_box + self._use_current = current_comp_check + self._accept_btn = accept_btn + self._refresh_btn = refresh_btn + + self.setWindowTitle("Fusion Switch Shot") + self.setLayout(layout) + + self.resize(260, 140) + self.setMinimumWidth(260) + self.setFixedHeight(140) + + self.connections() + + # Update ui to correct state + self._on_use_current_comp() + self._refresh() + + def connections(self): + self._use_current.clicked.connect(self._on_use_current_comp) + self._open_from_dir.clicked.connect(self._on_open_from_dir) + self._refresh_btn.clicked.connect(self._refresh) + self._accept_btn.clicked.connect(self._on_switch) + + def _on_use_current_comp(self): + state = self._use_current.isChecked() + self._open_from_dir.setEnabled(not state) + self._comps.setEnabled(not state) + + def _on_open_from_dir(self): + + start_dir = self._get_context_directory() + comp_file, _ = QtWidgets.QFileDialog.getOpenFileName( + self, "Choose comp", start_dir) + + if not comp_file: + return + + # Create completer + self.populate_comp_box([comp_file]) + self._refresh() + + def _refresh(self): + # Clear any existing items + self._assets.clear() + + asset_names = [a["name"] for a in self.collect_assets()] + completer = QtWidgets.QCompleter(asset_names) + + self._assets.setCompleter(completer) + self._assets.addItems(asset_names) + + def _on_switch(self): + + if not self._use_current.isChecked(): + file_name = self._comps.itemData(self._comps.currentIndex()) + else: + comp = avalon.fusion.get_current_comp() + file_name = comp.GetAttrs("COMPS_FileName") + + asset = self._assets.currentText() + + import colorbleed.scripts.fusion_switch_shot as switch_shot + switch_shot.switch(asset_name=asset, filepath=file_name, new=True) + + def _get_context_directory(self): + + project = io.find_one({"type": "project", + "name": api.Session["AVALON_PROJECT"]}, + projection={"config": True}) + + template = project["config"]["template"]["work"] + dir = pipeline._format_work_template(template, api.Session) + + return dir + + def collect_slap_comps(self, directory): + items = glob.glob("{}/*.comp".format(directory)) + return items + + def collect_assets(self): + return list(io.find({"type": "asset", "silo": "film"})) + + def populate_comp_box(self, files): + """Ensure we display the filename only but the path is stored as well + + Args: + files (list): list of full file path [path/to/item/item.ext,] + + Returns: + None + """ + + for f in files: + filename = os.path.basename(f) + self._comps.addItem(filename, userData=f) + + +if __name__ == '__main__': + import sys + api.install(avalon.fusion) + + app = QtWidgets.QApplication(sys.argv) + window = App() + window.setStyleSheet(style.load_stylesheet()) + window.show() + sys.exit(app.exec_()) diff --git a/pype/hosts/fusion/scripts/update_selected_loader_ranges.py b/pype/hosts/fusion/scripts/update_selected_loader_ranges.py new file mode 100644 index 0000000000..f42a032e84 --- /dev/null +++ b/pype/hosts/fusion/scripts/update_selected_loader_ranges.py @@ -0,0 +1,32 @@ +"""Forces Fusion to 'retrigger' the Loader to update. + +Warning: + This might change settings like 'Reverse', 'Loop', trims and other + settings of the Loader. So use this at your own risk. + +""" + +from avalon.fusion import comp_lock_and_undo_chunk + + +with comp_lock_and_undo_chunk(comp, "Reload clip time ranges"): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: + + # Get tool attributes + tool_a = tool.GetAttrs() + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + old_global_in = tool.GlobalIn[comp.CurrentTime] + + # Reapply + for index, _ in clipTable.items(): + time = startTime[index] + tool.Clip[time] = tool.Clip[time] + + for index, _ in altclipTable.items(): + time = startTime[index] + tool.ProxyFilename[time] = tool.ProxyFilename[time] + + tool.GlobalIn[comp.CurrentTime] = old_global_in diff --git a/pype/hosts/fusion/utility_scripts/Pype_menu.py b/pype/hosts/fusion/utility_scripts/Pype_menu.py index bf42c75cde..3d6a4607e1 100644 --- a/pype/hosts/fusion/utility_scripts/Pype_menu.py +++ b/pype/hosts/fusion/utility_scripts/Pype_menu.py @@ -1,6 +1,5 @@ import os import sys -import avalon.api as avalon import pype from pypeapp import Logger @@ -9,16 +8,17 @@ log = Logger().get_logger(__name__) def main(env): - from pype.hosts import fusion + from pype.hosts.fusion import menu + import avalon.fusion # Registers pype's Global pyblish plugins pype.install() # activate resolve from pype - avalon.install(bmdvr) + avalon.api.install(avalon.fusion) - log.info(f"Avalon registred hosts: {avalon.registered_host()}") + log.info(f"Avalon registred hosts: {avalon.api.registered_host()}") - bmdvr.launch_pype_menu() + menu.launch_pype_menu() if __name__ == "__main__": diff --git a/pype/hosts/fusion/utils.py b/pype/hosts/fusion/utils.py index e57723d43e..cb2098eaee 100644 --- a/pype/hosts/fusion/utils.py +++ b/pype/hosts/fusion/utils.py @@ -58,7 +58,10 @@ def _sync_utility_scripts(env=None): src = os.path.join(d, s) dst = os.path.join(us_dir, s) log.info(f"Copying `{src}` to `{dst}`...") - shutil.copy2(src, dst) + if not os.path.isdir(src): + shutil.copy2(src, dst) + else: + shutil.copytree(src, dst) def setup(env=None): diff --git a/pype/plugins/fusion/create/create_tiff_saver.py b/pype/plugins/fusion/create/create_tiff_saver.py index 4911650ed2..92f97366a3 100644 --- a/pype/plugins/fusion/create/create_tiff_saver.py +++ b/pype/plugins/fusion/create/create_tiff_saver.py @@ -23,7 +23,7 @@ class CreateTiffSaver(avalon.api.Creator): workdir = os.path.normpath(os.environ["AVALON_WORKDIR"]) filename = "{}..tiff".format(self.name) - filepath = os.path.join(workdir, "render", "preview", filename) + filepath = os.path.join(workdir, "render", filename) with fusion.comp_lock_and_undo_chunk(comp): args = (-32768, -32768) # Magical position numbers @@ -43,4 +43,3 @@ class CreateTiffSaver(avalon.api.Creator): # Set file format attributes saver[file_format]["Depth"] = 1 # int8 | int16 | float32 | other saver[file_format]["SaveAlpha"] = 0 - From bfea96a1a3ae7f4ab12e581149067f6f24234f6f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 20 Aug 2020 12:58:36 +0200 Subject: [PATCH 08/91] feat(fusion): integration work in progress --- pype/hosts/fusion/__init__.py | 9 --------- pype/hosts/fusion/menu.py | 15 ++++++++++++--- pype/hosts/fusion/scripts/set_rendermode.py | 16 +--------------- pype/hosts/fusion/utils.py | 17 +++++++++++++---- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/pype/hosts/fusion/__init__.py b/pype/hosts/fusion/__init__.py index 7f54e71a75..61eaf44ddb 100644 --- a/pype/hosts/fusion/__init__.py +++ b/pype/hosts/fusion/__init__.py @@ -1,6 +1,3 @@ -import sys -import os - from .pipeline import ( install, uninstall, @@ -20,9 +17,6 @@ from .lib import ( from .menu import launch_pype_menu -host_dir = os.path.dirname(__file__) -script_dir = os.path.join(host_dir, "scripts") -sys.path.append(script_dir) __all__ = [ # pipeline @@ -41,7 +35,4 @@ __all__ = [ # menu "launch_pype_menu", - - # scripts - "set_rendermode" ] diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py index 2a00c37ed6..9be99fb599 100644 --- a/pype/hosts/fusion/menu.py +++ b/pype/hosts/fusion/menu.py @@ -15,7 +15,7 @@ from avalon.tools import ( libraryloader ) -import set_rendermode +from .scripts import set_rendermode def load_stylesheet(): @@ -59,7 +59,7 @@ class PypeMenu(QtWidgets.QWidget): | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) - + self.render_mode_widget = None self.setWindowTitle("Pype") workfiles_btn = QtWidgets.QPushButton("Workfiles", self) create_btn = QtWidgets.QPushButton("Create", self) @@ -134,8 +134,16 @@ class PypeMenu(QtWidgets.QWidget): libraryloader.show() def on_rendernode_clicked(self): + from avalon import style print("Clicked Set Render Mode") - set_rendermode.main() + if self.render_mode_widget is None: + window = set_rendermode.SetRenderMode() + window.setStyleSheet(style.load_stylesheet()) + window.show() + self.render_mode_widget = window + else: + self.render_mode_widget.raise_() + self.render_mode_widget.activate() def on_set_colorspace_clicked(self): print("Clicked Set Colorspace") @@ -146,6 +154,7 @@ class PypeMenu(QtWidgets.QWidget): def launch_pype_menu(): app = QtWidgets.QApplication(sys.argv) + app.setQuitOnLastWindowClosed(False) pype_menu = PypeMenu() diff --git a/pype/hosts/fusion/scripts/set_rendermode.py b/pype/hosts/fusion/scripts/set_rendermode.py index 4b2049e2e5..380b1b3b1c 100644 --- a/pype/hosts/fusion/scripts/set_rendermode.py +++ b/pype/hosts/fusion/scripts/set_rendermode.py @@ -1,7 +1,6 @@ -from avalon.vendor.Qt import QtCore, QtWidgets +from avalon.vendor.Qt import QtWidgets from avalon.vendor import qtawesome import avalon.fusion as avalon -from avalon import style _help = {"renderlocal": "Render the comp on your own machine and publish " @@ -110,16 +109,3 @@ class SetRenderMode(QtWidgets.QWidget): comp_mode = self._get_comp_rendermode() return comp_mode == ui_mode - - -def main(): - import sys - app = QtWidgets.QApplication(sys.argv) - window = SetRenderMode() - window.setStyleSheet(style.load_stylesheet()) - window.show() - sys.exit(app.exec_()) - - -if __name__ == '__main__': - main() diff --git a/pype/hosts/fusion/utils.py b/pype/hosts/fusion/utils.py index cb2098eaee..40e28f6473 100644 --- a/pype/hosts/fusion/utils.py +++ b/pype/hosts/fusion/utils.py @@ -46,18 +46,27 @@ def _sync_utility_scripts(env=None): # make sure no script file is in folder if next((s for s in os.listdir(us_dir)), None): for s in os.listdir(us_dir): - path = os.path.join(us_dir, s) + path = os.path.normpath( + os.path.join(us_dir, s)) log.info(f"Removing `{path}`...") - os.remove(path) + + # remove file or directory if not in our folders + if not os.path.isdir(path): + os.remove(path) + else: + shutil.rmtree(path) # copy scripts into Resolve's utility scripts dir for d, sl in scripts.items(): # directory and scripts list for s in sl: # script in script list - src = os.path.join(d, s) - dst = os.path.join(us_dir, s) + src = os.path.normpath(os.path.join(d, s)) + dst = os.path.normpath(os.path.join(us_dir, s)) + log.info(f"Copying `{src}` to `{dst}`...") + + # copy file or directory from our folders to fusion's folder if not os.path.isdir(src): shutil.copy2(src, dst) else: From 5aa92a974ec7586b30de8871619806c9e7c774c1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 20 Aug 2020 15:49:53 +0200 Subject: [PATCH 09/91] feat(fusion): cleanup scripts --- pype/hosts/fusion/menu.py | 17 ++++++--- ...onnections.py => duplicate_with_inputs.py} | 9 ++--- .../fusion/scripts/fusion_switch_shot.py | 12 ++++-- .../scripts/update_selected_loader_ranges.py | 32 ---------------- .../32bit/backgrounds_selected_to32bit.py | 0 .../32bit/backgrounds_to32bit.py | 0 .../32bit/loaders_selected_to32bit.py | 0 .../32bit/loaders_to32bit.py | 0 .../{scripts => utility_scripts}/switch_ui.py | 0 .../utility_scripts/update_loader_ranges.py | 37 +++++++++++++++++++ 10 files changed, 60 insertions(+), 47 deletions(-) rename pype/hosts/fusion/scripts/{duplicate_with_input_connections.py => duplicate_with_inputs.py} (88%) delete mode 100644 pype/hosts/fusion/scripts/update_selected_loader_ranges.py rename pype/hosts/fusion/{scripts => utility_scripts}/32bit/backgrounds_selected_to32bit.py (100%) rename pype/hosts/fusion/{scripts => utility_scripts}/32bit/backgrounds_to32bit.py (100%) rename pype/hosts/fusion/{scripts => utility_scripts}/32bit/loaders_selected_to32bit.py (100%) rename pype/hosts/fusion/{scripts => utility_scripts}/32bit/loaders_to32bit.py (100%) rename pype/hosts/fusion/{scripts => utility_scripts}/switch_ui.py (100%) create mode 100644 pype/hosts/fusion/utility_scripts/update_loader_ranges.py diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py index 9be99fb599..a74f9b8d84 100644 --- a/pype/hosts/fusion/menu.py +++ b/pype/hosts/fusion/menu.py @@ -15,7 +15,10 @@ from avalon.tools import ( libraryloader ) -from .scripts import set_rendermode +from .scripts import ( + set_rendermode, + duplicate_with_inputs +) def load_stylesheet(): @@ -68,8 +71,8 @@ class PypeMenu(QtWidgets.QWidget): inventory_btn = QtWidgets.QPushButton("Inventory", self) libload_btn = QtWidgets.QPushButton("Library", self) rendermode_btn = QtWidgets.QPushButton("Set render mode", self) - set_colorspace_btn = QtWidgets.QPushButton( - "Set colorspace from presets", self + duplicate_with_inputs_btn = QtWidgets.QPushButton( + "Duplicate with input connections", self ) reset_resolution_btn = QtWidgets.QPushButton( "Reset Resolution from peresets", self @@ -94,7 +97,7 @@ class PypeMenu(QtWidgets.QWidget): layout.addWidget(Spacer(15, self)) - layout.addWidget(set_colorspace_btn) + layout.addWidget(duplicate_with_inputs_btn) layout.addWidget(reset_resolution_btn) self.setLayout(layout) @@ -106,7 +109,8 @@ class PypeMenu(QtWidgets.QWidget): inventory_btn.clicked.connect(self.on_inventory_clicked) libload_btn.clicked.connect(self.on_libload_clicked) rendermode_btn.clicked.connect(self.on_rendernode_clicked) - set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) + duplicate_with_inputs_btn.clicked.connect( + self.on_duplicate_with_inputs_clicked) reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) def on_workfile_clicked(self): @@ -145,7 +149,8 @@ class PypeMenu(QtWidgets.QWidget): self.render_mode_widget.raise_() self.render_mode_widget.activate() - def on_set_colorspace_clicked(self): + def on_duplicate_with_inputs_clicked(self): + duplicate_with_inputs.duplicate_with_input_connections() print("Clicked Set Colorspace") def on_reset_resolution_clicked(self): diff --git a/pype/hosts/fusion/scripts/duplicate_with_input_connections.py b/pype/hosts/fusion/scripts/duplicate_with_inputs.py similarity index 88% rename from pype/hosts/fusion/scripts/duplicate_with_input_connections.py rename to pype/hosts/fusion/scripts/duplicate_with_inputs.py index 9f4f4a8f0a..992dd2cd2d 100644 --- a/pype/hosts/fusion/scripts/duplicate_with_input_connections.py +++ b/pype/hosts/fusion/scripts/duplicate_with_inputs.py @@ -1,4 +1,4 @@ -from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion def is_connected(input): @@ -9,11 +9,13 @@ def is_connected(input): def duplicate_with_input_connections(): """Duplicate selected tools with incoming connections.""" + comp = fusion.get_current_comp() original_tools = comp.GetToolList(True).values() if not original_tools: return # nothing selected - with comp_lock_and_undo_chunk(comp, "Duplicate With Input Connections"): + with fusion.comp_lock_and_undo_chunk( + comp, "Duplicate With Input Connections"): # Generate duplicates comp.Copy() @@ -38,6 +40,3 @@ def duplicate_with_input_connections(): new_input.ConnectTo(original_input.GetConnectedOutput()) assert is_connected(new_input), "Must be connected now" - - -duplicate_with_input_connections() diff --git a/pype/hosts/fusion/scripts/fusion_switch_shot.py b/pype/hosts/fusion/scripts/fusion_switch_shot.py index 586298ca66..a3f2116db8 100644 --- a/pype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/pype/hosts/fusion/scripts/fusion_switch_shot.py @@ -32,7 +32,7 @@ def _format_version_folder(folder): new_version = 1 if os.path.isdir(folder): - re_version = re.compile("v\d+$") + re_version = re.compile(r"v\d+$") versions = [i for i in os.listdir(folder) if os.path.isdir(i) and re_version.match(i)] if versions: @@ -224,7 +224,8 @@ def switch(asset_name, filepath=None, new=True): else: fusion = _get_fusion_instance() current_comp = fusion.LoadComp(filepath, quiet=True) - assert current_comp is not None, "Fusion could not load '%s'" % filepath + assert current_comp is not None, ( + "Fusion could not load '{}'").format(filepath) host = api.registered_host() containers = list(host.ls()) @@ -233,8 +234,9 @@ def switch(asset_name, filepath=None, new=True): representations = [] for container in containers: try: - representation = pype.switch_item(container, - asset_name=asset_name) + representation = pype.switch_item( + container, + asset_name=asset_name) representations.append(representation) except Exception as e: current_comp.Print("Error in switching! %s\n" % e.message) @@ -267,6 +269,8 @@ def switch(asset_name, filepath=None, new=True): if __name__ == '__main__': + # QUESTION: can we convert this to gui rather then standalone script? + # TODO: convert to gui tool import argparse parser = argparse.ArgumentParser(description="Switch to a shot within an" diff --git a/pype/hosts/fusion/scripts/update_selected_loader_ranges.py b/pype/hosts/fusion/scripts/update_selected_loader_ranges.py deleted file mode 100644 index f42a032e84..0000000000 --- a/pype/hosts/fusion/scripts/update_selected_loader_ranges.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Forces Fusion to 'retrigger' the Loader to update. - -Warning: - This might change settings like 'Reverse', 'Loop', trims and other - settings of the Loader. So use this at your own risk. - -""" - -from avalon.fusion import comp_lock_and_undo_chunk - - -with comp_lock_and_undo_chunk(comp, "Reload clip time ranges"): - tools = comp.GetToolList(True, "Loader").values() - for tool in tools: - - # Get tool attributes - tool_a = tool.GetAttrs() - clipTable = tool_a['TOOLST_Clip_Name'] - altclipTable = tool_a['TOOLST_AltClip_Name'] - startTime = tool_a['TOOLNT_Clip_Start'] - old_global_in = tool.GlobalIn[comp.CurrentTime] - - # Reapply - for index, _ in clipTable.items(): - time = startTime[index] - tool.Clip[time] = tool.Clip[time] - - for index, _ in altclipTable.items(): - time = startTime[index] - tool.ProxyFilename[time] = tool.ProxyFilename[time] - - tool.GlobalIn[comp.CurrentTime] = old_global_in diff --git a/pype/hosts/fusion/scripts/32bit/backgrounds_selected_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py similarity index 100% rename from pype/hosts/fusion/scripts/32bit/backgrounds_selected_to32bit.py rename to pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py diff --git a/pype/hosts/fusion/scripts/32bit/backgrounds_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py similarity index 100% rename from pype/hosts/fusion/scripts/32bit/backgrounds_to32bit.py rename to pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py diff --git a/pype/hosts/fusion/scripts/32bit/loaders_selected_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py similarity index 100% rename from pype/hosts/fusion/scripts/32bit/loaders_selected_to32bit.py rename to pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py diff --git a/pype/hosts/fusion/scripts/32bit/loaders_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py similarity index 100% rename from pype/hosts/fusion/scripts/32bit/loaders_to32bit.py rename to pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py diff --git a/pype/hosts/fusion/scripts/switch_ui.py b/pype/hosts/fusion/utility_scripts/switch_ui.py similarity index 100% rename from pype/hosts/fusion/scripts/switch_ui.py rename to pype/hosts/fusion/utility_scripts/switch_ui.py diff --git a/pype/hosts/fusion/utility_scripts/update_loader_ranges.py b/pype/hosts/fusion/utility_scripts/update_loader_ranges.py new file mode 100644 index 0000000000..9ddf1e6dc6 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/update_loader_ranges.py @@ -0,0 +1,37 @@ +"""Forces Fusion to 'retrigger' the Loader to update. + +Warning: + This might change settings like 'Reverse', 'Loop', trims and other + settings of the Loader. So use this at your own risk. + +""" +from avalon import fusion + + +def update_loader_ranges(): + comp = fusion.get_current_comp() + with fusion.comp_lock_and_undo_chunk(comp, "Reload clip time ranges"): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: + + # Get tool attributes + tool_a = tool.GetAttrs() + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + old_global_in = tool.GlobalIn[comp.CurrentTime] + + # Reapply + for index, _ in clipTable.items(): + time = startTime[index] + tool.Clip[time] = tool.Clip[time] + + for index, _ in altclipTable.items(): + time = startTime[index] + tool.ProxyFilename[time] = tool.ProxyFilename[time] + + tool.GlobalIn[comp.CurrentTime] = old_global_in + + +if __name__ == '__main__': + update_loader_ranges() From 737480f86a6dd74fcbbc5db46d74528261f0377d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 22:51:24 +0200 Subject: [PATCH 10/91] run p[lugin proces in thread and keep QtApp event processing until it's finished --- pype/tools/pyblish_pype/control.py | 36 ++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index 0162848f2b..a184fe60dc 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -9,8 +9,9 @@ import os import sys import traceback import inspect +import threading -from Qt import QtCore +from Qt import QtCore, QtWidgets import pyblish.api import pyblish.util @@ -28,6 +29,28 @@ class IterationBreak(Exception): pass +class ProcessThread(threading.Thread): + def __init__(self, plugin, context, instance): + super(ProcessThread, self).__init__() + + self.result = None + self.exception = None + + self.plugin = plugin + self.context = context + self.instance = instance + + def run(self): + try: + result = pyblish.plugin.process( + self.plugin, self.context, self.instance + ) + self.result = result + except Exception as exc: + self.exception = exc + + + class Controller(QtCore.QObject): # Emitted when the GUI is about to start processing; # e.g. resetting, validating or publishing. @@ -231,7 +254,16 @@ class Controller(QtCore.QObject): self.processing["nextOrder"] = plugin.order try: - result = pyblish.plugin.process(plugin, self.context, instance) + thread = ProcessThread(plugin, self.context, instance) + thread.start() + while thread.isAlive(): + QtWidgets.QApplication.processEvents() + + if thread.exception: + raise thread.exception + + result = thread.result + thread.join() # Make note of the order at which the # potential error error occured. if result["error"] is not None: From dbbf702430ff39083976b1b9b91543de1361da75 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 22:54:22 +0200 Subject: [PATCH 11/91] moved thread join earlier --- pype/tools/pyblish_pype/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index a184fe60dc..0947dcd734 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -259,11 +259,11 @@ class Controller(QtCore.QObject): while thread.isAlive(): QtWidgets.QApplication.processEvents() + thread.join() if thread.exception: raise thread.exception result = thread.result - thread.join() # Make note of the order at which the # potential error error occured. if result["error"] is not None: From 52470b2cfc7241b438bb90c4b603c9cc07f595df Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 11:27:57 +0200 Subject: [PATCH 12/91] feat(fusion): renaming `saver` family to `render` also changing render targeting names --- pype/hosts/fusion/scripts/set_rendermode.py | 10 +- ...eate_tiff_saver.py => create_exr_saver.py} | 10 +- pype/plugins/fusion/load/load_sequence.py | 8 +- .../fusion/publish/collect_instances.py | 11 ++- .../fusion/publish/collect_render_target.py | 12 +-- .../increment_current_file_deadline.py | 4 +- .../fusion/publish/publish_image_sequences.py | 98 ------------------- pype/plugins/fusion/publish/render_local.py | 36 +++++-- pype/plugins/fusion/publish/save_scene.py | 2 +- .../plugins/fusion/publish/submit_deadline.py | 3 +- .../publish/validate_background_depth.py | 2 +- .../fusion/publish/validate_comp_saved.py | 2 +- .../publish/validate_create_folder_checked.py | 2 +- .../validate_filename_has_extension.py | 2 +- .../publish/validate_saver_has_input.py | 2 +- .../publish/validate_saver_passthrough.py | 2 +- .../fusion/publish/validate_unique_subsets.py | 4 +- .../global/publish/collect_avalon_entities.py | 2 + pype/plugins/global/publish/extract_burnin.py | 3 +- pype/plugins/global/publish/extract_review.py | 9 +- pype/scripts/otio_burnin.py | 2 +- 21 files changed, 79 insertions(+), 147 deletions(-) rename pype/plugins/fusion/create/{create_tiff_saver.py => create_exr_saver.py} (88%) delete mode 100644 pype/plugins/fusion/publish/publish_image_sequences.py diff --git a/pype/hosts/fusion/scripts/set_rendermode.py b/pype/hosts/fusion/scripts/set_rendermode.py index 380b1b3b1c..051f05067d 100644 --- a/pype/hosts/fusion/scripts/set_rendermode.py +++ b/pype/hosts/fusion/scripts/set_rendermode.py @@ -3,10 +3,10 @@ from avalon.vendor import qtawesome import avalon.fusion as avalon -_help = {"renderlocal": "Render the comp on your own machine and publish " - "it from that the destination folder", - "deadline": "Submit a Fusion render job to Deadline to use all other " - "computers and add a publish job"} +_help = {"local": "Render the comp on your own machine and publish " + "it from that the destination folder", + "farm": "Submit a Fusion render job to a Render farm to use all other" + " computers and add a publish job"} class SetRenderMode(QtWidgets.QWidget): @@ -96,7 +96,7 @@ class SetRenderMode(QtWidgets.QWidget): return self._comp.GetAttrs("COMPS_Name") def _get_comp_rendermode(self): - return self._comp.GetData("pype.rendermode") or "renderlocal" + return self._comp.GetData("pype.rendermode") or "local" def _set_comp_rendermode(self): rendermode = self.mode_options.currentText() diff --git a/pype/plugins/fusion/create/create_tiff_saver.py b/pype/plugins/fusion/create/create_exr_saver.py similarity index 88% rename from pype/plugins/fusion/create/create_tiff_saver.py rename to pype/plugins/fusion/create/create_exr_saver.py index 92f97366a3..d5092d1d03 100644 --- a/pype/plugins/fusion/create/create_tiff_saver.py +++ b/pype/plugins/fusion/create/create_exr_saver.py @@ -4,16 +4,16 @@ import avalon.api from avalon import fusion -class CreateTiffSaver(avalon.api.Creator): +class CreateOpenEXRSaver(avalon.api.Creator): - name = "tiffDefault" - label = "Create Tiff Saver" + name = "openexrDefault" + label = "Create OpenEXR Saver" hosts = ["fusion"] - family = "saver" + family = "render" def process(self): - file_format = "TiffFormat" + file_format = "OpenEXRFormat" comp = fusion.get_current_comp() diff --git a/pype/plugins/fusion/load/load_sequence.py b/pype/plugins/fusion/load/load_sequence.py index ce6bca6c77..24d48fb9da 100644 --- a/pype/plugins/fusion/load/load_sequence.py +++ b/pype/plugins/fusion/load/load_sequence.py @@ -4,6 +4,10 @@ import contextlib from avalon import api import avalon.io as io +from avalon import fusion + +comp = fusion.get_current_comp() + @contextlib.contextmanager def preserve_inputs(tool, inputs): @@ -113,7 +117,7 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(api.Loader): """Load image sequence into Fusion""" - families = ["imagesequence"] + families = ["imagesequence", "review"] representations = ["*"] label = "Load sequence" @@ -134,7 +138,7 @@ class FusionLoadSequence(api.Loader): namespace = context['asset']['name'] # Use the first file for now - path = self._get_first_image(self.fname) + path = self._get_first_image(os.path.dirname(self.fname)) # Create the Loader with the filename path set comp = get_current_comp() diff --git a/pype/plugins/fusion/publish/collect_instances.py b/pype/plugins/fusion/publish/collect_instances.py index 6dbb1b1a97..3c7224e65c 100644 --- a/pype/plugins/fusion/publish/collect_instances.py +++ b/pype/plugins/fusion/publish/collect_instances.py @@ -43,8 +43,8 @@ class CollectInstances(pyblish.api.ContextPlugin): savers = [tool for tool in tools if tool.ID == "Saver"] start, end = get_comp_render_range(comp) - context.data["frameStart"] = start - context.data["frameEnd"] = end + context.data["frameStart"] = int(start) + context.data["frameEnd"] = int(end) for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] @@ -76,8 +76,11 @@ class CollectInstances(pyblish.api.ContextPlugin): "outputDir": os.path.dirname(path), "ext": ext, # todo: should be redundant "label": label, - "families": ["saver"], - "family": "saver", + "frameStart": context.data["frameStart"], + "frameEnd": context.data["frameEnd"], + "fps": context.data["fps"], + "families": ["render", "review", "ftrack"], + "family": "render", "active": active, "publish": active # backwards compatibility }) diff --git a/pype/plugins/fusion/publish/collect_render_target.py b/pype/plugins/fusion/publish/collect_render_target.py index b6217f1ddf..50cc4fd3e9 100644 --- a/pype/plugins/fusion/publish/collect_render_target.py +++ b/pype/plugins/fusion/publish/collect_render_target.py @@ -5,8 +5,8 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): """Collect current comp's render Mode Options: - renderlocal - deadline + local + farm Note that this value is set for each comp separately. When you save the comp this information will be stored in that file. If for some reason the @@ -23,22 +23,22 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.4 label = "Collect Render Mode" hosts = ["fusion"] - families = ["saver"] + families = ["render"] def process(self, instance): """Collect all image sequence tools""" - options = ["renderlocal", "deadline"] + options = ["local", "farm"] comp = instance.context.data.get("currentComp") if not comp: raise RuntimeError("No comp previously collected, unable to " "retrieve Fusion version.") - rendermode = comp.GetData("pype.rendermode") or "renderlocal" + rendermode = comp.GetData("pype.rendermode") or "local" assert rendermode in options, "Must be supported render mode" self.log.info("Render mode: {0}".format(rendermode)) # Append family - family = "saver.{0}".format(rendermode) + family = "render.{0}".format(rendermode) instance.data["families"].append(family) diff --git a/pype/plugins/fusion/publish/increment_current_file_deadline.py b/pype/plugins/fusion/publish/increment_current_file_deadline.py index 6545d84da3..9641ba7ef6 100644 --- a/pype/plugins/fusion/publish/increment_current_file_deadline.py +++ b/pype/plugins/fusion/publish/increment_current_file_deadline.py @@ -11,7 +11,7 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] - families = ["saver.deadline"] + families = ["render.farm"] optional = True def process(self, context): @@ -23,7 +23,7 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): if any(plugin.__name__ == "FusionSubmitDeadline" for plugin in errored_plugins): raise RuntimeError("Skipping incrementing current file because " - "submission to deadline failed.") + "submission to render farm failed.") comp = context.data.get("currentComp") assert comp, "Must have comp" diff --git a/pype/plugins/fusion/publish/publish_image_sequences.py b/pype/plugins/fusion/publish/publish_image_sequences.py deleted file mode 100644 index 9fe9ddc4cb..0000000000 --- a/pype/plugins/fusion/publish/publish_image_sequences.py +++ /dev/null @@ -1,98 +0,0 @@ -import re -import os -import json -import subprocess - -import pyblish.api - -from pype.action import get_errored_plugins_from_data - - -def _get_script(): - """Get path to the image sequence script""" - - # todo: use a more elegant way to get the python script - - try: - from pype.scripts import publish_filesequence - except Exception: - raise RuntimeError("Expected module 'publish_imagesequence'" - "to be available") - - module_path = publish_filesequence.__file__ - if module_path.endswith(".pyc"): - module_path = module_path[:-len(".pyc")] + ".py" - - return module_path - - -class PublishImageSequence(pyblish.api.InstancePlugin): - """Publish the generated local image sequences.""" - - order = pyblish.api.IntegratorOrder - label = "Publish Rendered Image Sequence(s)" - hosts = ["fusion"] - families = ["saver.renderlocal"] - - def process(self, instance): - - # Skip this plug-in if the ExtractImageSequence failed - errored_plugins = get_errored_plugins_from_data(instance.context) - if any(plugin.__name__ == "FusionRenderLocal" for plugin in - errored_plugins): - raise RuntimeError("Fusion local render failed, " - "publishing images skipped.") - - subset = instance.data["subset"] - ext = instance.data["ext"] - - # Regex to match resulting renders - regex = "^{subset}.*[0-9]+{ext}+$".format(subset=re.escape(subset), - ext=re.escape(ext)) - - # The instance has most of the information already stored - metadata = { - "regex": regex, - "frameStart": instance.context.data["frameStart"], - "frameEnd": instance.context.data["frameEnd"], - "families": ["imagesequence"], - } - - # Write metadata and store the path in the instance - output_directory = instance.data["outputDir"] - path = os.path.join(output_directory, - "{}_metadata.json".format(subset)) - with open(path, "w") as f: - json.dump(metadata, f) - - assert os.path.isfile(path), ("Stored path is not a file for %s" - % instance.data["name"]) - - # Suppress any subprocess console - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = subprocess.SW_HIDE - - process = subprocess.Popen(["python", _get_script(), - "--paths", path], - bufsize=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - startupinfo=startupinfo) - - while True: - output = process.stdout.readline() - # Break when there is no output or a return code has been given - if output == '' and process.poll() is not None: - process.stdout.close() - break - if output: - line = output.strip() - if line.startswith("ERROR"): - self.log.error(line) - else: - self.log.info(line) - - if process.returncode != 0: - raise RuntimeError("Process quit with non-zero " - "return code: {}".format(process.returncode)) diff --git a/pype/plugins/fusion/publish/render_local.py b/pype/plugins/fusion/publish/render_local.py index c97fe1a13d..c5d1fd9b7a 100644 --- a/pype/plugins/fusion/publish/render_local.py +++ b/pype/plugins/fusion/publish/render_local.py @@ -1,9 +1,11 @@ +import os import pyblish.api import avalon.fusion as fusion +from pprint import pformat -class FusionRenderLocal(pyblish.api.InstancePlugin): +class Fusionlocal(pyblish.api.InstancePlugin): """Render the current Fusion composition locally. Extract the result of savers by starting a comp render @@ -14,12 +16,10 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): order = pyblish.api.ExtractorOrder label = "Render Local" hosts = ["fusion"] - families = ["saver.renderlocal"] + families = ["render.local"] def process(self, instance): - # This should be a ContextPlugin, but this is a workaround - # for a bug in pyblish to run once for a family: issue #250 context = instance.context key = "__hasRun{}".format(self.__class__.__name__) if context.data.get(key, False): @@ -28,15 +28,35 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): context.data[key] = True current_comp = context.data["currentComp"] - start_frame = current_comp.GetAttrs("COMPN_RenderStart") - end_frame = current_comp.GetAttrs("COMPN_RenderEnd") + frame_start = current_comp.GetAttrs("COMPN_RenderStart") + frame_end = current_comp.GetAttrs("COMPN_RenderEnd") + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + ext = os.path.splitext(os.path.basename(path))[-1] self.log.info("Starting render") - self.log.info("Start frame: {}".format(start_frame)) - self.log.info("End frame: {}".format(end_frame)) + self.log.info("Start frame: {}".format(frame_start)) + self.log.info("End frame: {}".format(frame_end)) with fusion.comp_lock_and_undo_chunk(current_comp): result = current_comp.Render() + if "representations" not in instance.data: + instance.data["representations"] = [] + + collected_frames = os.listdir(output_dir) + repre = { + 'name': ext[1:], + 'ext': ext[1:], + 'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start, + 'files': collected_frames, + "stagingDir": output_dir, + "tags": ["review", "ftrackreview"] + } + instance.data["representations"].append(repre) + + self.log.debug(f"_ instance.data: {pformat(instance.data)}") + if not result: raise RuntimeError("Comp render failed") diff --git a/pype/plugins/fusion/publish/save_scene.py b/pype/plugins/fusion/publish/save_scene.py index 850ac5c372..0cdfafa095 100644 --- a/pype/plugins/fusion/publish/save_scene.py +++ b/pype/plugins/fusion/publish/save_scene.py @@ -7,7 +7,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin): label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["fusion"] - families = ["saver"] + families = ["render"] def process(self, context): diff --git a/pype/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py index 0dd34ba713..ed3fb06586 100644 --- a/pype/plugins/fusion/publish/submit_deadline.py +++ b/pype/plugins/fusion/publish/submit_deadline.py @@ -19,10 +19,9 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["fusion"] - families = ["saver.deadline"] + families = ["render.farm"] def process(self, instance): - instance.data["toBeRenderedOn"] = "deadline" context = instance.context key = "__hasRun{}".format(self.__class__.__name__) diff --git a/pype/plugins/fusion/publish/validate_background_depth.py b/pype/plugins/fusion/publish/validate_background_depth.py index 88a52ad52d..de042ae315 100644 --- a/pype/plugins/fusion/publish/validate_background_depth.py +++ b/pype/plugins/fusion/publish/validate_background_depth.py @@ -10,7 +10,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): label = "Validate Background Depth 32 bit" actions = [action.RepairAction] hosts = ["fusion"] - families = ["saver"] + families = ["render"] optional = True @classmethod diff --git a/pype/plugins/fusion/publish/validate_comp_saved.py b/pype/plugins/fusion/publish/validate_comp_saved.py index 425168fbdf..cabe65af6e 100644 --- a/pype/plugins/fusion/publish/validate_comp_saved.py +++ b/pype/plugins/fusion/publish/validate_comp_saved.py @@ -8,7 +8,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["saver"] + families = ["render"] hosts = ["fusion"] def process(self, context): diff --git a/pype/plugins/fusion/publish/validate_create_folder_checked.py b/pype/plugins/fusion/publish/validate_create_folder_checked.py index 00a8526c6b..cce3695c31 100644 --- a/pype/plugins/fusion/publish/validate_create_folder_checked.py +++ b/pype/plugins/fusion/publish/validate_create_folder_checked.py @@ -13,7 +13,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder actions = [action.RepairAction] label = "Validate Create Folder Checked" - families = ["saver"] + families = ["render"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/fusion/publish/validate_filename_has_extension.py b/pype/plugins/fusion/publish/validate_filename_has_extension.py index d3762ad290..4795a2aa05 100644 --- a/pype/plugins/fusion/publish/validate_filename_has_extension.py +++ b/pype/plugins/fusion/publish/validate_filename_has_extension.py @@ -14,7 +14,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["saver"] + families = ["render"] hosts = ["fusion"] def process(self, instance): diff --git a/pype/plugins/fusion/publish/validate_saver_has_input.py b/pype/plugins/fusion/publish/validate_saver_has_input.py index 6887a9704c..7243b44a3e 100644 --- a/pype/plugins/fusion/publish/validate_saver_has_input.py +++ b/pype/plugins/fusion/publish/validate_saver_has_input.py @@ -10,7 +10,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Has Input" - families = ["saver"] + families = ["render"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/fusion/publish/validate_saver_passthrough.py b/pype/plugins/fusion/publish/validate_saver_passthrough.py index 2da5cf2494..aed3835de3 100644 --- a/pype/plugins/fusion/publish/validate_saver_passthrough.py +++ b/pype/plugins/fusion/publish/validate_saver_passthrough.py @@ -6,7 +6,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Passthrough" - families = ["saver"] + families = ["render"] hosts = ["fusion"] def process(self, context): diff --git a/pype/plugins/fusion/publish/validate_unique_subsets.py b/pype/plugins/fusion/publish/validate_unique_subsets.py index 2000e1c05d..b218a311ba 100644 --- a/pype/plugins/fusion/publish/validate_unique_subsets.py +++ b/pype/plugins/fusion/publish/validate_unique_subsets.py @@ -6,7 +6,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["saver"] + families = ["render"] hosts = ["fusion"] @classmethod @@ -14,7 +14,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): context = instance.context subset = instance.data["subset"] - for other_instance in context[:]: + for other_instance in context: if other_instance == instance: continue diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index 917172d40c..0b6423818e 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -86,3 +86,5 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): frame_end_h = frame_end + context.data["handleEnd"] context.data["frameStartHandle"] = frame_start_h context.data["frameEndHandle"] = frame_end_h + + context.data["fps"] = data["fps"] diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index dedfd98979..fd73fd4a04 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -25,7 +25,8 @@ class ExtractBurnin(pype.api.Extractor): "shell", "nukestudio", "premiere", - "standalonepublisher" + "standalonepublisher", + "fusion" ] optional = True diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 3a5bd3464a..0ea82cbbef 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -29,7 +29,8 @@ class ExtractReview(pyblish.api.InstancePlugin): "nukestudio", "premiere", "harmony", - "standalonepublisher" + "standalonepublisher", + "fusion" ] # Supported extensions @@ -50,9 +51,9 @@ class ExtractReview(pyblish.api.InstancePlugin): to_height = 1080 def process(self, instance): - # Skip review when requested. - if not instance.data.get("review"): - return + # # Skip review when requested. + # if not instance.data.get("review"): + # return # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 718943855c..156896a759 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -526,7 +526,7 @@ def burnins_from_data( bit_rate = burnin._streams[0].get("bit_rate") if bit_rate: - ffmpeg_args.append("--b:v {}".format(bit_rate)) + ffmpeg_args.append("-b:v {}".format(bit_rate)) pix_fmt = burnin._streams[0].get("pix_fmt") if pix_fmt: From 109cab9fb7135f00976638012836cdd8a4a83993 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 13:02:52 +0200 Subject: [PATCH 13/91] fix(fusion): hiding window --- pype/hosts/fusion/menu.py | 3 +-- pype/hosts/fusion/scripts/set_rendermode.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py index a74f9b8d84..668472105e 100644 --- a/pype/hosts/fusion/menu.py +++ b/pype/hosts/fusion/menu.py @@ -146,8 +146,7 @@ class PypeMenu(QtWidgets.QWidget): window.show() self.render_mode_widget = window else: - self.render_mode_widget.raise_() - self.render_mode_widget.activate() + self.render_mode_widget.show() def on_duplicate_with_inputs_clicked(self): duplicate_with_inputs.duplicate_with_input_connections() diff --git a/pype/hosts/fusion/scripts/set_rendermode.py b/pype/hosts/fusion/scripts/set_rendermode.py index 051f05067d..cb0b9da513 100644 --- a/pype/hosts/fusion/scripts/set_rendermode.py +++ b/pype/hosts/fusion/scripts/set_rendermode.py @@ -103,6 +103,7 @@ class SetRenderMode(QtWidgets.QWidget): self._comp.SetData("pype.rendermode", rendermode) self._comp.Print("Updated render mode to '%s'\n" % rendermode) + self.hide() def _validation(self): ui_mode = self.mode_options.currentText() From f09e48ac8c41817039252c188b82f62e1c587a64 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 14:50:22 +0200 Subject: [PATCH 14/91] fix(global): review attribute in instance fix code from https://github.com/pypeclub/pype/pull/441 --- pype/plugins/global/publish/extract_jpeg.py | 4 ++-- pype/plugins/global/publish/extract_review.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index dd5f79b6ac..b13ef9d1b9 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -9,7 +9,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" label = "Extract Jpeg EXR" - hosts = ["shell"] + hosts = ["shell", "fusion"] order = pyblish.api.ExtractorOrder families = ["imagesequence", "render", "render2d", "source"] enabled = False @@ -27,7 +27,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): return # Skip review when requested. - if not instance.data.get("review"): + if not instance.data.get("review", True): return # get representation and loop them diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 0ea82cbbef..36d6138384 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -52,8 +52,8 @@ class ExtractReview(pyblish.api.InstancePlugin): def process(self, instance): # # Skip review when requested. - # if not instance.data.get("review"): - # return + if not instance.data.get("review", True): + return # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: From 23ef625f81cb6b8eba09118a6d139e688cc813a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 14:54:03 +0200 Subject: [PATCH 15/91] fix(fusion): moving order --- pype/plugins/fusion/publish/render_local.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pype/plugins/fusion/publish/render_local.py b/pype/plugins/fusion/publish/render_local.py index c5d1fd9b7a..19449ead67 100644 --- a/pype/plugins/fusion/publish/render_local.py +++ b/pype/plugins/fusion/publish/render_local.py @@ -13,7 +13,7 @@ class Fusionlocal(pyblish.api.InstancePlugin): """ - order = pyblish.api.ExtractorOrder + order = pyblish.api.ExtractorOrder - 0.1 label = "Render Local" hosts = ["fusion"] families = ["render.local"] @@ -52,10 +52,15 @@ class Fusionlocal(pyblish.api.InstancePlugin): 'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start, 'files': collected_frames, "stagingDir": output_dir, - "tags": ["review", "ftrackreview"] } instance.data["representations"].append(repre) + # review representation + repre_preview = repre.copy() + repre_preview["name"] = repre_preview["ext"] = "mp4" + repre_preview["tags"] = ["review", "preview", "ftrackreview", "delete"] + instance.data["representations"].append(repre_preview) + self.log.debug(f"_ instance.data: {pformat(instance.data)}") if not result: From 442258dd373f06e906d7405c91e2bb23ac2967b6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 14:58:18 +0200 Subject: [PATCH 16/91] clean(hound): suggestions --- .../utility_scripts/32bit/backgrounds_selected_to32bit.py | 3 +++ pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py | 2 ++ .../fusion/utility_scripts/32bit/loaders_selected_to32bit.py | 2 ++ pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py | 2 ++ pype/hosts/fusion/utility_scripts/switch_ui.py | 2 +- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py index c0dcef5410..90e08c4edb 100644 --- a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py +++ b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py @@ -1,5 +1,8 @@ from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() + def main(): """Set all selected backgrounds to 32 bit""" diff --git a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py index 92ca18a82d..30ce36fee7 100644 --- a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py +++ b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py @@ -1,4 +1,6 @@ from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() def main(): diff --git a/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py index 6e3802d9ff..403febf19b 100644 --- a/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py +++ b/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py @@ -1,4 +1,6 @@ from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() def main(): diff --git a/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py index d86bef35f3..e5670fe41b 100644 --- a/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py +++ b/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py @@ -1,4 +1,6 @@ from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() def main(): diff --git a/pype/hosts/fusion/utility_scripts/switch_ui.py b/pype/hosts/fusion/utility_scripts/switch_ui.py index 8f1466abe0..e0b6b3f882 100644 --- a/pype/hosts/fusion/utility_scripts/switch_ui.py +++ b/pype/hosts/fusion/utility_scripts/switch_ui.py @@ -125,7 +125,7 @@ class App(QtWidgets.QWidget): start_dir = self._get_context_directory() comp_file, _ = QtWidgets.QFileDialog.getOpenFileName( - self, "Choose comp", start_dir) + self, "Choose comp", start_dir) if not comp_file: return From 53a1dc3f7505e1740fa783b3572d4502a6bab686 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 16:24:19 +0200 Subject: [PATCH 17/91] added reraising traceback stack instead of only exception --- pype/tools/pyblish_pype/control.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index 0947dcd734..d73c7efad7 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -11,6 +11,7 @@ import traceback import inspect import threading +import six from Qt import QtCore, QtWidgets import pyblish.api @@ -46,9 +47,8 @@ class ProcessThread(threading.Thread): self.plugin, self.context, self.instance ) self.result = result - except Exception as exc: - self.exception = exc - + except Exception: + self.exception = sys.exc_info() class Controller(QtCore.QObject): @@ -261,7 +261,7 @@ class Controller(QtCore.QObject): thread.join() if thread.exception: - raise thread.exception + six.reraise(*thread.exception) result = thread.result # Make note of the order at which the From 37e88288b4f667d8151664a1b7f28e64ad944482 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 7 Sep 2020 14:01:34 +0200 Subject: [PATCH 18/91] tag instance for scanline conversion using oiio tools --- .../global/publish/extract_scanline_exr.py | 85 +++++++++++++++++++ .../global/publish/submit_publish_job.py | 7 +- pype/plugins/maya/create/create_render.py | 1 + 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 pype/plugins/global/publish/extract_scanline_exr.py diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py new file mode 100644 index 0000000000..e1ca0e7c1c --- /dev/null +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +"""Convert tiled exrs to scanline if needed.""" +import os +import copy + +import pyblish.api +import pype.api +import pype.lib + + +class ExtractScanlineExr(pyblish.api.InstancePlugin): + """Convert tiled EXRs to scanline using OIIO tool.""" + + label = "Extract Scanline EXR" + hosts = ["shell"] + order = pyblish.api.ExtractorOrder + families = ["imagesequence", "render", "render2d", "source"] + + def process(self, instance): + """Plugin entry point.""" + anatomy = instance.context.data['anatomy'] + + # get representation and loop them + representations = instance.data["representations"] + representations_new = [] + + for repre in representations: + tags = repre.get("tags", []) + if "toScanline" not in tags: + continue + + # run only on exrs + if repre.get("ext") != "exr": + continue + + if not isinstance(repre['files'], (list, tuple)): + input_files = [repre['files']] + self.log.info("We have a sequence.") + else: + input_files = repre['files'] + self.log.info("We have a single frame") + + stagingdir = os.path.normpath(repre.get("stagingDir")) + stagingdir = anatomy.fill_root(stagingdir) + + oiio_tool_path = os.getenv("PYPE_OIIO_PATH", "") + + new_files = [] + for file in input_files: + + oiio_cmd = [] + oiio_cmd.append(oiio_tool_path) + oiio_cmd.append( + os.path.join(stagingdir, file) + ) + oiio_cmd.append("--scanline") + oiio_cmd.append("-o") + new_file = f"_scanline_{file}" + new_files.append(new_file) + oiio_cmd.append(stagingdir, new_file) + + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + pype.api.subprocess(subprocess_exr) + + # raise error if there is no ouptput + if not os.path.exists(os.path.join(stagingdir, new_file)): + self.log.error( + f"File {new_file} was not produced by oiio tool!") + raise AssertionError("OIIO tool conversion failed") + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = copy.deepcopy(repre) + + representation['name'] = 'scanline exr' + representation['files'] = new_files if len(new_files) > 1 else new_files[0] # noqa: E501 + representation['tags'].remove("toScanline") + + # add representation + self.log.debug("Adding: {}".format(representation)) + representations_new.append(representation) + + instance.data["representations"] = representations_new diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index ab5d6cf9b2..2d6d1efff8 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -193,7 +193,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "slate": ["slateFrame"], "review": ["lutPath"], "render2d": ["bakeScriptPath", "bakeRenderPath", - "bakeWriteNodeName", "version"] + "bakeWriteNodeName", "version"], + "renderlayer": ["convertToScanline"] } # list of family names to transfer to new family if present @@ -491,6 +492,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review"] if preview else [] } + # support conversion from tiled to scanline + if instance_data.get("convertToScanline"): + rep["tags"].append("toScanline") + # poor man exclusion if ext in self.skip_integration_repre_list: rep["tags"].append("delete") diff --git a/pype/plugins/maya/create/create_render.py b/pype/plugins/maya/create/create_render.py index 6826d33c58..fa0e269126 100644 --- a/pype/plugins/maya/create/create_render.py +++ b/pype/plugins/maya/create/create_render.py @@ -188,6 +188,7 @@ class CreateRender(avalon.maya.Creator): self.data["tileRendering"] = False self.data["tilesX"] = 2 self.data["tilesY"] = 2 + self.data["convertToScanline"] = False # Disable for now as this feature is not working yet # self.data["assScene"] = False From ab9d8e266ad6b645fee04a696dccd568893f3643 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 8 Sep 2020 17:34:03 +0200 Subject: [PATCH 19/91] fix repre processing and name --- .../global/publish/extract_scanline_exr.py | 17 ++++++++--------- pype/plugins/maya/publish/collect_render.py | 3 ++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py index e1ca0e7c1c..4a34038f7e 100644 --- a/pype/plugins/global/publish/extract_scanline_exr.py +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Convert tiled exrs to scanline if needed.""" +"""Convert exrs in representation to tiled exrs usin oiio tools.""" import os import copy @@ -18,13 +18,14 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): def process(self, instance): """Plugin entry point.""" - anatomy = instance.context.data['anatomy'] - # get representation and loop them representations = instance.data["representations"] + representations_new = [] for repre in representations: + self.log.info( + "Processnig representation {}".format(repre.get("name"))) tags = repre.get("tags", []) if "toScanline" not in tags: continue @@ -41,7 +42,6 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): self.log.info("We have a single frame") stagingdir = os.path.normpath(repre.get("stagingDir")) - stagingdir = anatomy.fill_root(stagingdir) oiio_tool_path = os.getenv("PYPE_OIIO_PATH", "") @@ -57,7 +57,7 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): oiio_cmd.append("-o") new_file = f"_scanline_{file}" new_files.append(new_file) - oiio_cmd.append(stagingdir, new_file) + oiio_cmd.append(os.path.join(stagingdir, new_file)) subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") @@ -74,12 +74,11 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): representation = copy.deepcopy(repre) - representation['name'] = 'scanline exr' + representation['name'] = 'scanline_exr' representation['files'] = new_files if len(new_files) > 1 else new_files[0] # noqa: E501 - representation['tags'].remove("toScanline") + representation['tags'] = [] - # add representation self.log.debug("Adding: {}".format(representation)) representations_new.append(representation) - instance.data["representations"] = representations_new + instance.data["representations"] += representations_new diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 91230fcc46..093827809c 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -246,7 +246,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501 "tilesX": render_instance.data.get("tilesX") or 2, "tilesY": render_instance.data.get("tilesY") or 2, - "priority": render_instance.data.get("priority") + "priority": render_instance.data.get("priority"), + "convertToScanline": render_instance.data.get("convertToScanline") or False # noqa: E501 } # Apply each user defined attribute as data From dc883fe0a41ef4157340047a84cdbd30e19d4888 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 8 Sep 2020 19:02:45 +0200 Subject: [PATCH 20/91] improve log texts --- pype/plugins/global/publish/extract_scanline_exr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py index 4a34038f7e..2f68a2af0d 100644 --- a/pype/plugins/global/publish/extract_scanline_exr.py +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -25,13 +25,15 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): for repre in representations: self.log.info( - "Processnig representation {}".format(repre.get("name"))) + "Processing representation {}".format(repre.get("name"))) tags = repre.get("tags", []) if "toScanline" not in tags: + self.log.info("- missing toScanline tag") continue # run only on exrs if repre.get("ext") != "exr": + self.log.info("- not EXR files") continue if not isinstance(repre['files'], (list, tuple)): From fb1f3d26f10e475cd11304a5ff0809c80c5bd92b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Sep 2020 19:18:06 +0200 Subject: [PATCH 21/91] Fix #237 - Updating a look where the shader name changed Added cleanup of references with failed reference edits --- pype/plugins/maya/load/load_look.py | 52 +++++++++++++++++++-- pype/widgets/message_window.py | 71 ++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/pype/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py index b9c0d81104..d82978b1a1 100644 --- a/pype/plugins/maya/load/load_look.py +++ b/pype/plugins/maya/load/load_look.py @@ -3,6 +3,8 @@ from avalon import api, io import json import pype.hosts.maya.lib from collections import defaultdict +from pype.widgets.message_window import ScrollMessageBox +from Qt import QtWidgets class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): @@ -44,12 +46,24 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): + """ + Called by Scene Inventory when look should be updated to current + version. + If any reference edits cannot be applied, eg. shader renamed and + material not present, reference is unloaded and cleaned. + All failed edits are highlighted to the user via message box. + Args: + container: object that has look to be updated + representation: (dict): relationship data to get proper + representation from DB and persisted + data in .json + Returns: + None + """ import os from maya import cmds - node = container["objectName"] - path = api.get_representation_path(representation) # Get reference node from container members @@ -127,13 +141,45 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): with open(shader_relation, "r") as f: relationships = json.load(f) + # update of reference could result in failed edits - material is not + # present because of renaming etc. + failed_edits = cmds.referenceQuery(reference_node, + editStrings=True, + failedEdits=True, + successfulEdits=False) + + # highlight failed edits to user + if failed_edits: + shader_data = relationships.get("relationships", {}) + for rel in shader_data.values(): + for member in rel["members"]: + nodes.add(member['name']) + + # clean references - removes failed reference edits + cmds.file(unloadReference=reference_node) + cmds.file(cr=reference_node) # cleanReference + cmds.file(loadReference=reference_node) + + # reapply shading groups from json representation + pype.hosts.maya.lib.apply_shaders(relationships, + shader_nodes, + nodes) + + msg = ["During reference update some edits failed.", + "All successful edits were kept intact.\n", + "Failed and removed edits:"] + msg.extend(failed_edits) + msg = ScrollMessageBox(QtWidgets.QMessageBox.Warning, + "Some reference edit failed", + msg) + msg.exec_() + attributes = relationships.get("attributes", []) # region compute lookup nodes_by_id = defaultdict(list) for n in nodes: nodes_by_id[pype.hosts.maya.lib.get_id(n)].append(n) - pype.hosts.maya.lib.apply_attributes(attributes, nodes_by_id) # Update metadata diff --git a/pype/widgets/message_window.py b/pype/widgets/message_window.py index 41c709b933..f909c60710 100644 --- a/pype/widgets/message_window.py +++ b/pype/widgets/message_window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from Qt import QtWidgets, QtCore import sys import logging @@ -49,6 +49,17 @@ class Window(QtWidgets.QWidget): def message(title=None, message=None, level="info", parent=None): + """ + Produces centered dialog with specific level denoting severity + Args: + title: (string) dialog title + message: (string) message + level: (string) info|warning|critical + parent: (QtWidgets.QApplication) + + Returns: + None + """ app = parent if not app: app = QtWidgets.QApplication(sys.argv) @@ -68,3 +79,61 @@ def message(title=None, message=None, level="info", parent=None): # skip all possible issues that may happen feature is not crutial log.warning("Couldn't center message.", exc_info=True) # sys.exit(app.exec_()) + + +class ScrollMessageBox(QtWidgets.QDialog): + """ + Basic version of scrollable QMessageBox. No other existing dialog + implementation is scrollable. + Args: + icon: + title: + messages: of messages + cancelable: - True if Cancel button should be added + """ + def __init__(self, icon, title, messages, cancelable=False, + *args, **kwargs): + super(ScrollMessageBox, self).__init__() + self.setWindowTitle(title) + self.icon = icon + + self.setWindowFlags(QtCore.Qt.WindowTitleHint) + + layout = QtWidgets.QVBoxLayout(self) + + scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setWidgetResizable(True) + content_widget = QtWidgets.QWidget(self) + scroll_widget.setWidget(content_widget) + + max_len = 0 + content_layout = QtWidgets.QVBoxLayout(content_widget) + for message in messages: + label_widget = QtWidgets.QLabel(message, content_widget) + content_layout.addWidget(label_widget) + max_len = max(max_len, len(message)) + + # guess size of scrollable area + max_width = QtWidgets.QApplication.desktop().availableGeometry().width + scroll_widget.setMinimumWidth(min(max_width, max_len * 6)) + layout.addWidget(scroll_widget) + + if not cancelable: # if no specific buttons OK only + buttons = QtWidgets.QDialogButtonBox.Ok + else: + buttons = QtWidgets.QDialogButtonBox.Ok | \ + QtWidgets.QDialogButtonBox.Cancel + + btn_box = QtWidgets.QDialogButtonBox(buttons) + btn_box.accepted.connect(self.accept) + + if cancelable: + btn_box.reject.connect(self.reject) + + btn = QtWidgets.QPushButton('Copy to clipboard') + btn.clicked.connect(lambda: QtWidgets.QApplication. + clipboard().setText("\n".join(messages))) + btn_box.addButton(btn, QtWidgets.QDialogButtonBox.NoRole) + + layout.addWidget(btn_box) + self.show() From a38446095a21aa41f497bdf3a64baf04e14afd8f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 9 Sep 2020 10:59:55 +0200 Subject: [PATCH 22/91] add scanline processing to flat sequence too --- pype/plugins/global/publish/submit_publish_job.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 2d6d1efff8..5c46746e02 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -494,6 +494,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # support conversion from tiled to scanline if instance_data.get("convertToScanline"): + self.log.info("Adding scanline conversion.") rep["tags"].append("toScanline") # poor man exclusion @@ -586,6 +587,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance.get("multipartExr", False): rep["tags"].append("multipartExr") + # support conversion from tiled to scanline + if instance.get("convertToScanline"): + self.log.info("Adding scanline conversion.") + rep["tags"].append("toScanline") + representations.append(rep) self._solve_families(instance, preview) From 22f8084a8d393a93b4264e8292833f70bd7bd9b0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Sep 2020 12:53:41 +0200 Subject: [PATCH 23/91] Fix #237 - Reworked reference cleanup --- pype/plugins/maya/load/load_look.py | 121 +++++++++++++++++----------- 1 file changed, 73 insertions(+), 48 deletions(-) diff --git a/pype/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py index d82978b1a1..cb5b6fa2e8 100644 --- a/pype/plugins/maya/load/load_look.py +++ b/pype/plugins/maya/load/load_look.py @@ -70,6 +70,9 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): members = cmds.sets(node, query=True, nodesOnly=True) reference_node = self._get_reference_node(members) + shader_nodes = cmds.ls(members, type='shadingEngine') + orig_nodes = set(self._get_nodes_with_shader(shader_nodes)) + file_type = { "ma": "mayaAscii", "mb": "mayaBinary", @@ -80,35 +83,7 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): assert os.path.exists(path), "%s does not exist." % path - try: - content = cmds.file(path, - loadReference=reference_node, - type=file_type, - returnNewNodes=True) - except RuntimeError as exc: - # When changing a reference to a file that has load errors the - # command will raise an error even if the file is still loaded - # correctly (e.g. when raising errors on Arnold attributes) - # When the file is loaded and has content, we consider it's fine. - if not cmds.referenceQuery(reference_node, isLoaded=True): - raise - - content = cmds.referenceQuery(reference_node, - nodes=True, - dagPath=True) - if not content: - raise - - self.log.warning("Ignoring file read error:\n%s", exc) - - # Fix PLN-40 for older containers created with Avalon that had the - # `.verticesOnlySet` set to True. - if cmds.getAttr("{}.verticesOnlySet".format(node)): - self.log.info("Setting %s.verticesOnlySet to False", node) - cmds.setAttr("{}.verticesOnlySet".format(node), False) - - # Add new nodes of the reference to the container - cmds.sets(content, forceElement=node) + self._load_reference(file_type, node, path, reference_node) # Remove any placeHolderList attribute entries from the set that # are remaining from nodes being removed from the referenced file. @@ -117,18 +92,9 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): if invalid: cmds.sets(invalid, remove=node) - # Get container members + # get new applied shaders and nodes from new version shader_nodes = cmds.ls(members, type='shadingEngine') - - nodes_list = [] - for shader in shader_nodes: - connections = cmds.listConnections(cmds.listHistory(shader, f=1), - type='mesh') - if connections: - for connection in connections: - nodes_list.extend(cmds.listRelatives(connection, - shapes=True)) - nodes = set(nodes_list) + nodes = set(self._get_nodes_with_shader(shader_nodes)) json_representation = io.find_one({ "type": "representation", @@ -150,20 +116,16 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): # highlight failed edits to user if failed_edits: - shader_data = relationships.get("relationships", {}) - for rel in shader_data.values(): - for member in rel["members"]: - nodes.add(member['name']) - # clean references - removes failed reference edits cmds.file(unloadReference=reference_node) cmds.file(cr=reference_node) # cleanReference - cmds.file(loadReference=reference_node) + # reload reference, now it shouldn't fail + self._load_reference(file_type, node, path, reference_node) - # reapply shading groups from json representation + # reapply shading groups from json representation on orig nodes pype.hosts.maya.lib.apply_shaders(relationships, shader_nodes, - nodes) + orig_nodes) msg = ["During reference update some edits failed.", "All successful edits were kept intact.\n", @@ -186,3 +148,66 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): cmds.setAttr("{}.representation".format(node), str(representation["_id"]), type="string") + + def _get_nodes_with_shader(self, shader_nodes): + """ + Returns list of nodes belonging to specific shaders + Args: + shader_nodes: of Shader groups + Returns + node names + """ + import maya.cmds as cmds + # Get container members + + nodes_list = [] + for shader in shader_nodes: + connections = cmds.listConnections(cmds.listHistory(shader, f=1), + type='mesh') + if connections: + for connection in connections: + nodes_list.extend(cmds.listRelatives(connection, + shapes=True)) + return nodes_list + + def _load_reference(self, file_type, node, path, reference_node): + """ + Load reference from 'path' on 'reference_node'. Used when change + of look (version/update) is triggered. + Args: + file_type: extension of referenced file + node: + path: (string) location of referenced file + reference_node: (string) - name of node that should be applied + on + Returns: + None + """ + import maya.cmds as cmds + try: + content = cmds.file(path, + loadReference=reference_node, + type=file_type, + returnNewNodes=True) + except RuntimeError as exc: + # When changing a reference to a file that has load errors the + # command will raise an error even if the file is still loaded + # correctly (e.g. when raising errors on Arnold attributes) + # When the file is loaded and has content, we consider it's fine. + if not cmds.referenceQuery(reference_node, isLoaded=True): + raise + + content = cmds.referenceQuery(reference_node, + nodes=True, + dagPath=True) + if not content: + raise + + self.log.warning("Ignoring file read error:\n%s", exc) + # Fix PLN-40 for older containers created with Avalon that had the + # `.verticesOnlySet` set to True. + if cmds.getAttr("{}.verticesOnlySet".format(node)): + self.log.info("Setting %s.verticesOnlySet to False", node) + cmds.setAttr("{}.verticesOnlySet".format(node), False) + # Add new nodes of the reference to the container + cmds.sets(content, forceElement=node) From adb87fa9dd3fc4dcab07f0c2c1a055a2803a13a5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Sep 2020 13:13:52 +0200 Subject: [PATCH 24/91] Hound --- pype/widgets/message_window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/widgets/message_window.py b/pype/widgets/message_window.py index f909c60710..969d6ccdd1 100644 --- a/pype/widgets/message_window.py +++ b/pype/widgets/message_window.py @@ -91,8 +91,7 @@ class ScrollMessageBox(QtWidgets.QDialog): messages: of messages cancelable: - True if Cancel button should be added """ - def __init__(self, icon, title, messages, cancelable=False, - *args, **kwargs): + def __init__(self, icon, title, messages, cancelable=False): super(ScrollMessageBox, self).__init__() self.setWindowTitle(title) self.icon = icon From 6f27fbdcbc960fe47a213b0cd1c506c06a96e7fc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Sep 2020 11:35:53 +0200 Subject: [PATCH 25/91] #180 - Enrich tasks with additional information Tasks in project config or on assets were stored only as names. This changes them into dictionaries to enhance information that could be stored and used elsewhere later. --- pype/modules/ftrack/lib/avalon_sync.py | 41 ++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 65a59452da..7dd4056524 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -16,6 +16,7 @@ from bson.objectid import ObjectId from bson.errors import InvalidId from pymongo import UpdateOne import ftrack_api +from pype.api import config log = Logger().get_logger(__name__) @@ -238,6 +239,27 @@ def get_hierarchical_attributes(session, entity, attr_names, attr_defaults={}): return hier_values +def get_task_short_name(task_type): + """ + Returns short name (code) for 'task_type'. Short name stored in + metadata dictionary in project.config per each 'task_type'. + Could be used in anatomy, paths etc. + If no appropriate short name is found in mapping, 'task_type' is + returned back unchanged. + + Currently stores data in: + 'pype-config/presets/ftrack/project_defaults.json' + Args: + task_type: (string) - Animation | Modeling ... + + Returns: + (string) - anim | model ... + """ + presets = config.get_presets()['ftrack']['project_defaults']\ + .get("task_short_names") + + return presets.get(task_type, task_type) + class SyncEntitiesFactory: dbcon = AvalonMongoDB() @@ -389,7 +411,9 @@ class SyncEntitiesFactory: continue elif entity_type_low == "task": - entities_dict[parent_id]["tasks"].append(entity["name"]) + # enrich task info with additional metadata + task = {"name": entity["name"], "type": entity["type"]["name"]} + entities_dict[parent_id]["tasks"].append(task) continue entity_id = entity["id"] @@ -534,8 +558,9 @@ class SyncEntitiesFactory: name = entity_dict["name"] entity_type = entity_dict["entity_type"] # Tasks must be checked too - for task_name in entity_dict["tasks"]: - passed = task_names.get(task_name) + for task in entity_dict["tasks"]: + task_name = task.get("name") + passed = task_name if passed is None: passed = check_regex( task_name, "task", schema_patterns=_schema_patterns @@ -1014,9 +1039,14 @@ class SyncEntitiesFactory: if not msg or not items: continue self.report_items["warning"][msg] = items - + tasks = [] + for tt in task_types: + tasks.append({ + "name": tt["name"], + "short_name": get_task_short_name(tt["name"]) + }) self.entities_dict[id]["final_entity"]["config"] = { - "tasks": [{"name": tt["name"]} for tt in task_types], + "tasks": tasks, "apps": proj_apps } continue @@ -1904,7 +1934,6 @@ class SyncEntitiesFactory: filter = {"_id": ObjectId(mongo_id)} change_data = from_dict_to_set(changes) mongo_changes_bulk.append(UpdateOne(filter, change_data)) - if not mongo_changes_bulk: # TODO LOG return From 998c5383eea87b0eac203bd34504006e5b3508b3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Sep 2020 14:39:39 +0200 Subject: [PATCH 26/91] Refactor - added a couple of docstrings --- pype/modules/ftrack/lib/avalon_sync.py | 98 ++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 7dd4056524..e2dfdde0aa 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -104,6 +104,14 @@ def get_pype_attr(session, split_hierarchical=True): def from_dict_to_set(data): + """ + Converts 'data' into $set part of MongoDB update command. + Args: + data: (dictionary) - up-to-date data from Ftrack + + Returns: + (dictionary) - { "$set" : "{..}"} + """ result = {"$set": {}} dict_queue = queue.Queue() dict_queue.put((None, data)) @@ -124,6 +132,8 @@ def from_dict_to_set(data): def get_avalon_project_template(project_name): """Get avalon template + Args: + project_name: (string) Returns: dictionary with templates """ @@ -136,6 +146,16 @@ def get_avalon_project_template(project_name): def get_project_apps(in_app_list): + """ + Returns metadata information about apps in 'in_app_list' enhanced + from toml files. + Args: + in_app_list: (list) - names of applications + + Returns: + tuple (list, dictionary) - list of dictionaries about apps + dictionary of warnings + """ apps = [] # TODO report missing_toml_msg = "Missing config file for application" @@ -440,6 +460,13 @@ class SyncEntitiesFactory: @property def avalon_ents_by_id(self): + """ + Returns dictionary of avalon tracked entities (assets stored in + MongoDB) accessible by its '_id' + (mongo intenal ID - example ObjectId("5f48de5830a9467b34b69798")) + Returns: + (dictionary) - {"(_id)": whole entity asset} + """ if self._avalon_ents_by_id is None: self._avalon_ents_by_id = {} for entity in self.avalon_entities: @@ -449,6 +476,14 @@ class SyncEntitiesFactory: @property def avalon_ents_by_ftrack_id(self): + """ + Returns dictionary of Mongo ids of avalon tracked entities + (assets stored in MongoDB) accessible by its 'ftrackId' + (id from ftrack) + (example '431ee3f2-e91a-11ea-bfa4-92591a5b5e3e') + Returns: + (dictionary) - {"(ftrackId)": "_id"} + """ if self._avalon_ents_by_ftrack_id is None: self._avalon_ents_by_ftrack_id = {} for entity in self.avalon_entities: @@ -461,6 +496,13 @@ class SyncEntitiesFactory: @property def avalon_ents_by_name(self): + """ + Returns dictionary of Mongo ids of avalon tracked entities + (assets stored in MongoDB) accessible by its 'name' + (example 'Hero') + Returns: + (dictionary) - {"(name)": "_id"} + """ if self._avalon_ents_by_name is None: self._avalon_ents_by_name = {} for entity in self.avalon_entities: @@ -470,6 +512,15 @@ class SyncEntitiesFactory: @property def avalon_ents_by_parent_id(self): + """ + Returns dictionary of avalon tracked entities + (assets stored in MongoDB) accessible by its 'visualParent' + (example ObjectId("5f48de5830a9467b34b69798")) + + Fills 'self._avalon_archived_ents' for performance + Returns: + (dictionary) - {"(_id)": whole entity} + """ if self._avalon_ents_by_parent_id is None: self._avalon_ents_by_parent_id = collections.defaultdict(list) for entity in self.avalon_entities: @@ -482,6 +533,14 @@ class SyncEntitiesFactory: @property def avalon_archived_ents(self): + """ + Returns list of archived assets from DB + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_ents' for performance + Returns: + (list) of assets + """ if self._avalon_archived_ents is None: self._avalon_archived_ents = [ ent for ent in self.dbcon.find({"type": "archived_asset"}) @@ -490,6 +549,14 @@ class SyncEntitiesFactory: @property def avalon_archived_by_name(self): + """ + Returns list of archived assets from DB + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_by_name' for performance + Returns: + (dictionary of lists) of assets accessible by asset name + """ if self._avalon_archived_by_name is None: self._avalon_archived_by_name = collections.defaultdict(list) for ent in self.avalon_archived_ents: @@ -498,6 +565,14 @@ class SyncEntitiesFactory: @property def avalon_archived_by_id(self): + """ + Returns dictionary of archived assets from DB + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_by_id' for performance + Returns: + (dictionary) of assets accessible by asset mongo _id + """ if self._avalon_archived_by_id is None: self._avalon_archived_by_id = { str(ent["_id"]): ent for ent in self.avalon_archived_ents @@ -506,6 +581,15 @@ class SyncEntitiesFactory: @property def avalon_archived_by_parent_id(self): + """ + Returns dictionary of archived assets from DB per their's parent + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_by_parent_id' for performance + Returns: + (dictionary of lists) of assets accessible by asset parent + mongo _id + """ if self._avalon_archived_by_parent_id is None: self._avalon_archived_by_parent_id = collections.defaultdict(list) for entity in self.avalon_archived_ents: @@ -518,6 +602,14 @@ class SyncEntitiesFactory: @property def subsets_by_parent_id(self): + """ + Returns dictionary of subsets from Mongo ("type": "subset") + grouped by their parent. + + Fills 'self._subsets_by_parent_id' for performance + Returns: + (dictionary of lists) + """ if self._subsets_by_parent_id is None: self._subsets_by_parent_id = collections.defaultdict(list) for subset in self.dbcon.find({"type": "subset"}): @@ -539,6 +631,11 @@ class SyncEntitiesFactory: @property def all_ftrack_names(self): + """ + Returns lists of names of all entities in Ftrack + Returns: + (list) + """ return [ ent_dict["name"] for ent_dict in self.entities_dict.values() if ( ent_dict.get("name") @@ -1937,6 +2034,7 @@ class SyncEntitiesFactory: if not mongo_changes_bulk: # TODO LOG return + log.debug("mongo_changes_bulk:: {}".format(mongo_changes_bulk)) self.dbcon.bulk_write(mongo_changes_bulk) def reload_parents(self, hierarchy_changing_ids): From a135517dcfefc213473925510a60c5d71f60230f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Sep 2020 15:03:27 +0200 Subject: [PATCH 27/91] Hound --- pype/modules/ftrack/lib/avalon_sync.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index e2dfdde0aa..e8d5ef0093 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -259,6 +259,7 @@ def get_hierarchical_attributes(session, entity, attr_names, attr_defaults={}): return hier_values + def get_task_short_name(task_type): """ Returns short name (code) for 'task_type'. Short name stored in @@ -1138,10 +1139,9 @@ class SyncEntitiesFactory: self.report_items["warning"][msg] = items tasks = [] for tt in task_types: - tasks.append({ - "name": tt["name"], - "short_name": get_task_short_name(tt["name"]) - }) + tasks.append({"name": tt["name"], + "short_name": get_task_short_name(tt["name"]) + }) self.entities_dict[id]["final_entity"]["config"] = { "tasks": tasks, "apps": proj_apps From 997b11399ee783d759c38e438c8695208e42d58d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 24 Sep 2020 16:25:41 +0200 Subject: [PATCH 28/91] implemented `_add_intent_to_context` for adding intent to context data --- pype/tools/pyblish_pype/window.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 2a037ba4bc..e4593d85ad 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -563,6 +563,20 @@ class Window(QtWidgets.QDialog): ): instance_item.setData(enable_value, Roles.IsEnabledRole) + def _add_intent_to_context(self): + if ( + self.intent_model.has_items + and "intent" not in self.controller.context.data + ): + idx = self.intent_model.index(self.intent_box.currentIndex(), 0) + intent_value = self.intent_model.data(idx, Roles.IntentItemValue) + intent_label = self.intent_model.data(idx, QtCore.Qt.DisplayRole) + + self.controller.context.data["intent"] = { + "value": intent_value, + "label": intent_label + } + def on_instance_toggle(self, index, state=None): """An item is requesting to be toggled""" if not index.data(Roles.IsOptionalRole): From dc4220e4745dbcaae8c9112c67b9e95bd739ffb1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 24 Sep 2020 16:26:07 +0200 Subject: [PATCH 29/91] removed `on_intent_changed` since it's not helpful --- pype/tools/pyblish_pype/window.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index e4593d85ad..5b4e806271 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -225,7 +225,6 @@ class Window(QtWidgets.QDialog): intent_model = model.IntentModel() intent_box.setModel(intent_model) - intent_box.currentIndexChanged.connect(self.on_intent_changed) comment_intent_widget = QtWidgets.QWidget() comment_intent_layout = QtWidgets.QHBoxLayout(comment_intent_widget) @@ -666,18 +665,6 @@ class Window(QtWidgets.QDialog): """The user has typed a comment.""" self.controller.context.data["comment"] = self.comment_box.text() - def on_intent_changed(self): - idx = self.intent_model.index(self.intent_box.currentIndex(), 0) - intent_value = self.intent_model.data(idx, Roles.IntentItemValue) - intent_label = self.intent_model.data(idx, QtCore.Qt.DisplayRole) - - # TODO move to play - if self.controller.context: - self.controller.context.data["intent"] = { - "value": intent_value, - "label": intent_label - } - def on_about_to_process(self, plugin, instance): """Reflect currently running pair in GUI""" if instance is None: @@ -768,8 +755,6 @@ class Window(QtWidgets.QDialog): self.comment_box.setText(comment or None) self.comment_box.setEnabled(True) - if self.intent_model.has_items: - self.on_intent_changed() self.intent_box.setEnabled(True) # Refresh tab From 16e52ccff289b28875c20f24022e444280c87174 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 24 Sep 2020 16:26:25 +0200 Subject: [PATCH 30/91] `_add_intent_to_context` is executed on play or validate button --- pype/tools/pyblish_pype/window.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 5b4e806271..b7aada5ff7 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -631,12 +631,16 @@ class Window(QtWidgets.QDialog): self.comment_box.setEnabled(False) self.intent_box.setEnabled(False) + self._add_intent_to_context() + self.validate() def on_play_clicked(self): self.comment_box.setEnabled(False) self.intent_box.setEnabled(False) + self._add_intent_to_context() + self.publish() def on_reset_clicked(self): From 8627a9613e72ef2fb501cebfa22fc4b08d370f66 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 24 Sep 2020 16:26:45 +0200 Subject: [PATCH 31/91] extract burnin checks if intent value is valid before adding label --- pype/plugins/global/publish/extract_burnin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 6e8da1b054..b81cfbc050 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -316,7 +316,9 @@ class ExtractBurnin(pype.api.Extractor): intent_label = context.data.get("intent") if intent_label and isinstance(intent_label, dict): - intent_label = intent_label.get("label") + value = intent_label.get("value") + if value: + intent_label = intent_label.get("label") if intent_label: burnin_data["intent"] = intent_label From fec340f5ba5bea5864a95e65ec2c525bcf4c16d0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 25 Sep 2020 11:20:32 +0200 Subject: [PATCH 32/91] #180 - Changed tasks to dictionaries Changed both for config ("tasks":{TYPE: {"short_name":""}}) and assets ("tasks": {"TASK_NAME": {"type":config.tasks.TYPE}}) --- pype/modules/ftrack/lib/avalon_sync.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index e8d5ef0093..68b54f9456 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -24,9 +24,9 @@ log = Logger().get_logger(__name__) # Current schemas for avalon types EntitySchemas = { - "project": "avalon-core:project-2.0", + "project": "avalon-core:project-2.1", "asset": "avalon-core:asset-3.0", - "config": "avalon-core:config-1.0" + "config": "avalon-core:config-1.1" } # Group name of custom attributes @@ -123,7 +123,8 @@ def from_dict_to_set(data): if _key is not None: new_key = "{}.{}".format(_key, key) - if not isinstance(value, dict): + if not isinstance(value, dict) or \ + (isinstance(value, dict) and not bool(value)): # empty dic result["$set"][new_key] = value continue dict_queue.put((new_key, value)) @@ -421,7 +422,7 @@ class SyncEntitiesFactory: "custom_attributes": {}, "hier_attrs": {}, "avalon_attrs": {}, - "tasks": [] + "tasks": {} }) for entity in all_project_entities: @@ -433,8 +434,8 @@ class SyncEntitiesFactory: elif entity_type_low == "task": # enrich task info with additional metadata - task = {"name": entity["name"], "type": entity["type"]["name"]} - entities_dict[parent_id]["tasks"].append(task) + task = {"type": entity["type"]["name"]} + entities_dict[parent_id]["tasks"][entity["name"]] = task continue entity_id = entity["id"] @@ -656,8 +657,8 @@ class SyncEntitiesFactory: name = entity_dict["name"] entity_type = entity_dict["entity_type"] # Tasks must be checked too - for task in entity_dict["tasks"]: - task_name = task.get("name") + for task in entity_dict["tasks"].items(): + task_name, task = task passed = task_name if passed is None: passed = check_regex( @@ -1137,11 +1138,11 @@ class SyncEntitiesFactory: if not msg or not items: continue self.report_items["warning"][msg] = items - tasks = [] + tasks = {} for tt in task_types: - tasks.append({"name": tt["name"], + tasks[tt["name"]] = { "short_name": get_task_short_name(tt["name"]) - }) + } self.entities_dict[id]["final_entity"]["config"] = { "tasks": tasks, "apps": proj_apps @@ -1156,7 +1157,7 @@ class SyncEntitiesFactory: data["parents"] = parents data["hierarchy"] = hierarchy - data["tasks"] = self.entities_dict[id].pop("tasks", []) + data["tasks"] = self.entities_dict[id].pop("tasks", {}) self.entities_dict[id]["final_entity"]["data"] = data self.entities_dict[id]["final_entity"]["type"] = "asset" From f8e558ca42d9a7e758535d7b7a2162788a68801f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 25 Sep 2020 12:24:02 +0200 Subject: [PATCH 33/91] #180 - Changed tasks to dictionaries Modifications based on change --- pype/hosts/nukestudio/tags.py | 4 ++-- .../clockify/launcher_actions/ClockifySync.py | 2 +- .../global/publish/extract_hierarchy_avalon.py | 12 +++++++----- pype/plugins/maya/publish/collect_yeti_cache.py | 2 +- pype/plugins/nukestudio/publish/collect_shots.py | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pype/hosts/nukestudio/tags.py b/pype/hosts/nukestudio/tags.py index c2b1d0d728..36edb16da6 100644 --- a/pype/hosts/nukestudio/tags.py +++ b/pype/hosts/nukestudio/tags.py @@ -71,8 +71,8 @@ def add_tags_from_presets(): # Get project task types. tasks = io.find_one({"type": "project"})["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} - for task in tasks: - nks_pres_tags["[Tasks]"][task["name"]] = { + for task_name, _ in tasks.items(): + nks_pres_tags["[Tasks]"][task_name] = { "editable": "1", "note": "", "icon": { diff --git a/pype/modules/clockify/launcher_actions/ClockifySync.py b/pype/modules/clockify/launcher_actions/ClockifySync.py index a77c038076..422a346023 100644 --- a/pype/modules/clockify/launcher_actions/ClockifySync.py +++ b/pype/modules/clockify/launcher_actions/ClockifySync.py @@ -30,7 +30,7 @@ class ClockifySync(api.Action): projects_info = {} for project in projects_to_sync: - task_types = [task['name'] for task in project['config']['tasks']] + task_types = project['config']['tasks'].keys() projects_info[project['name']] = task_types clockify_projects = self.clockapi.get_projects() diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 1d8191f2e3..b43678ff6c 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -38,7 +38,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): data["inputs"] = entity_data.get("inputs", []) # Tasks. - tasks = entity_data.get("tasks", []) + tasks = entity_data.get("tasks", {}) if tasks is not None or len(tasks) > 0: data["tasks"] = tasks parents = [] @@ -78,11 +78,13 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} - new_tasks = data.pop("tasks", []) + new_tasks = data.pop("tasks", {}) if "tasks" in cur_entity_data and new_tasks: - for task_name in new_tasks: - if task_name not in cur_entity_data["tasks"]: - cur_entity_data["tasks"].append(task_name) + for task_name in new_tasks.keys(): + if task_name \ + not in cur_entity_data["tasks"].keys(): + cur_entity_data["tasks"][task_name] = \ + new_tasks[task_name] cur_entity_data.update(data) data = cur_entity_data else: diff --git a/pype/plugins/maya/publish/collect_yeti_cache.py b/pype/plugins/maya/publish/collect_yeti_cache.py index 4af3e1ea18..e24517951b 100644 --- a/pype/plugins/maya/publish/collect_yeti_cache.py +++ b/pype/plugins/maya/publish/collect_yeti_cache.py @@ -30,7 +30,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin): label = "Collect Yeti Cache" families = ["yetiRig", "yeticache"] hosts = ["maya"] - tasks = ["animation", "fx"] + tasks = {"animation": {"type": "Animation"}, "fx": {"type": "FX"}} def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/nukestudio/publish/collect_shots.py index 455e25bf82..42b1ea160d 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/nukestudio/publish/collect_shots.py @@ -43,7 +43,7 @@ class CollectShots(api.InstancePlugin): "{} - {} - tasks:{} - assetbuilds:{}".format( data["asset"], data["subset"], - data["tasks"], + data["tasks"].keys(), [x["name"] for x in data.get("assetbuilds", [])] ) ) From c615e7972803300d761e90ee81bf4a5b211e0783 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 25 Sep 2020 12:46:05 +0200 Subject: [PATCH 34/91] #180 - Changed tasks to dictionaries Modifications based on change --- pype/modules/ftrack/lib/avalon_sync.py | 2 +- schema/config-1.1.json | 83 +++++++++++++++++++++++++ schema/inventory-1.1.json | 10 +++ schema/project-2.1.json | 86 ++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 schema/config-1.1.json create mode 100644 schema/inventory-1.1.json create mode 100644 schema/project-2.1.json diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 68b54f9456..5a5d489714 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -51,7 +51,7 @@ def check_regex(name, entity_type, in_schema=None, schema_patterns=None): if in_schema: schema_name = in_schema elif entity_type == "project": - schema_name = "project-2.0" + schema_name = "project-2.1" elif entity_type == "task": schema_name = "task" diff --git a/schema/config-1.1.json b/schema/config-1.1.json new file mode 100644 index 0000000000..5f4fe4b2fb --- /dev/null +++ b/schema/config-1.1.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "avalon-core:config-1.1", + "description": "A project configuration.", + + "type": "object", + + "additionalProperties": false, + "required": [ + "template", + "tasks", + "apps" + ], + + "properties": { + "schema": { + "description": "Schema identifier for payload", + "type": "string" + }, + "template": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.*$": { + "type": "string" + } + } + }, + "tasks": { + "type": "object", + "properties": { + "short_name": {"type": "string"}, + "icon": {"type": "string"}, + "group": {"type": "string"}, + "label": {"type": "string"} + }, + "required": ["short_name"] + }, + "apps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "group": {"type": "string"}, + "label": {"type": "string"} + }, + "required": ["name"] + } + }, + "families": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "label": {"type": "string"}, + "hideFilter": {"type": "boolean"} + }, + "required": ["name"] + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "color": {"type": "string"}, + "order": {"type": ["integer", "number"]} + }, + "required": ["name"] + } + }, + "copy": { + "type": "object" + } + } +} diff --git a/schema/inventory-1.1.json b/schema/inventory-1.1.json new file mode 100644 index 0000000000..f46df6973d --- /dev/null +++ b/schema/inventory-1.1.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "avalon-core:config-1.1", + "description": "A project configuration.", + + "type": "object", + + "additionalProperties": true +} diff --git a/schema/project-2.1.json b/schema/project-2.1.json new file mode 100644 index 0000000000..22327b2f06 --- /dev/null +++ b/schema/project-2.1.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "avalon-core:project-2.1", + "description": "A unit of data", + + "type": "object", + + "additionalProperties": true, + + "required": [ + "schema", + "type", + "name", + "data", + "config" + ], + + "properties": { + "schema": { + "description": "Schema identifier for payload", + "type": "string", + "enum": ["avalon-core:project-2.1"], + "example": "avalon-core:project-2.1" + }, + "type": { + "description": "The type of document", + "type": "string", + "enum": ["project"], + "example": "project" + }, + "parent": { + "description": "Unique identifier to parent document", + "example": "592c33475f8c1b064c4d1696" + }, + "name": { + "description": "Name of directory", + "type": "string", + "pattern": "^[a-zA-Z0-9_.]*$", + "example": "hulk" + }, + "data": { + "description": "Document metadata", + "type": "object", + "example": { + "fps": 24, + "width": 1920, + "height": 1080 + } + }, + "config": { + "type": "object", + "description": "Document metadata", + "example": { + "schema": "avalon-core:config-1.1", + "apps": [ + { + "name": "maya2016", + "label": "Autodesk Maya 2016" + }, + { + "name": "nuke10", + "label": "The Foundry Nuke 10.0" + } + ], + "tasks": { + "Model": {"short_name": "mdl"}, + "Render": {"short_name": "rnd"}, + "Animate": {"short_name": "anim"}, + "Rig": {"short_name": "rig"}, + "Lookdev": {"short_name": "look"}, + "Layout": {"short_name": "lay"} + }, + "template": { + "work": + "{root}/{project}/{silo}/{asset}/work/{task}/{app}", + "publish": + "{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}" + } + }, + "$ref": "config-1.1.json" + } + }, + + "definitions": {} +} From 62888dc38793d74a0c3ce8a39b9b478178e07c5e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 25 Sep 2020 12:54:57 +0200 Subject: [PATCH 35/91] Hound --- pype/modules/ftrack/lib/avalon_sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 5a5d489714..40b14a02a8 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -1141,8 +1141,8 @@ class SyncEntitiesFactory: tasks = {} for tt in task_types: tasks[tt["name"]] = { - "short_name": get_task_short_name(tt["name"]) - } + "short_name": get_task_short_name(tt["name"]) + } self.entities_dict[id]["final_entity"]["config"] = { "tasks": tasks, "apps": proj_apps From 0ac12df93decc0b618f2088d9e520083bcb362d1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 25 Sep 2020 15:46:13 +0200 Subject: [PATCH 36/91] feat(nuke): loader plugin for alembic camera --- pype/plugins/nuke/load/load_camera_abc.py | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 pype/plugins/nuke/load/load_camera_abc.py diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py new file mode 100644 index 0000000000..51810c45f9 --- /dev/null +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -0,0 +1,64 @@ +from avalon import api +import nuke +from pprint import pformat + +class AlembicCameraLoader(api.Loader): + """ + This will load alembic camera into script. + """ + + families = ["camera"] + representations = ["abc"] + + label = "Load Alembic Camera" + icon = "camera" + color = "orange" + + def load(self, context, name, namespace, data): + + # import dependencies + from avalon.nuke import containerise + + # get main variables + version = context['version'] + version_data = version.get("data", {}) + vname = version.get("name", None) + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + fps = version_data.get("fps") or nuke.root()["fps"].getValue() + namespace = namespace or context['asset']['name'] + object_name = "{}_{}".format(name, namespace) + + # prepare data for imprinting + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["frameStart", "frameEnd", "source", "author", "fps"] + + data_imprint = {"frameStart": first, + "frameEnd": last, + "version": vname, + "objectName": object_name} + + for k in add_keys: + data_imprint.update({k: version_data[k]}) + + # getting file path + file = self.fname.replace("\\", "/") + + camera_node = nuke.createNode( + "Camera2", + "file {} read_from_file True".format(file), + inpanel=False + ) + camera_node.forceValidate() + # camera_node["read_from_file"].setValue(True) + # camera_node["file"].setValue(file) + camera_node["frame_rate"].setValue(float(fps)) + camera_node["tile_color"].setValue(int("0x3469ffff", 16)) + + return containerise( + node=camera_node, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) From 984f815f1d9d63f9d78ba75c257c4d430cf3d5dc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 25 Sep 2020 16:12:46 +0200 Subject: [PATCH 37/91] feat(nuke): remove placeholder file --- pype/plugins/nuke/create/create_camera | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pype/plugins/nuke/create/create_camera diff --git a/pype/plugins/nuke/create/create_camera b/pype/plugins/nuke/create/create_camera deleted file mode 100644 index 0d542b8ad7..0000000000 --- a/pype/plugins/nuke/create/create_camera +++ /dev/null @@ -1,3 +0,0 @@ -# create vanilla camera if no camera is selected -# if camera is selected then it will convert it into containerized object -# it is major versioned in publish From 2df5999ceb09e037e0617cd92309646b9674563c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 25 Sep 2020 16:13:03 +0200 Subject: [PATCH 38/91] feat(nuke): create camera wip --- pype/plugins/nuke/create/create_camera.py | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 pype/plugins/nuke/create/create_camera.py diff --git a/pype/plugins/nuke/create/create_camera.py b/pype/plugins/nuke/create/create_camera.py new file mode 100644 index 0000000000..594c736c82 --- /dev/null +++ b/pype/plugins/nuke/create/create_camera.py @@ -0,0 +1,52 @@ +import avalon.nuke +from avalon.nuke import lib as anlib +import nuke + + +class CreateCamera(avalon.nuke.Creator): + """Add Publishable Backdrop""" + + name = "camera" + label = "Create 3d Camera" + family = "camera" + icon = "camera" + defaults = ["Main"] + + def __init__(self, *args, **kwargs): + super(CreateCamera, self).__init__(*args, **kwargs) + self.nodes = nuke.selectedNodes() + self.node_color = "0xdfea5dff" + return + + def process(self): + nodes = list() + if (self.options or {}).get("useSelection"): + nodes = self.nodes + + if len(nodes) >= 1: + anlib.select_nodes(nodes) + # camera_node = autoBackdrop() + # camera_node["name"].setValue("{}_BDN".format(self.name)) + # camera_node["tile_color"].setValue(int(self.node_color, 16)) + # camera_node["note_font_size"].setValue(24) + # camera_node["label"].setValue("[{}]".format(self.name)) + # # add avalon knobs + # instance = anlib.imprint(camera_node, self.data) + # + # return instance + else: + msg = str("Please select nodes you " + "wish to add to a container") + self.log.error(msg) + nuke.message(msg) + return + else: + camera_node = autoBackdrop() + camera_node["name"].setValue("{}_BDN".format(self.name)) + camera_node["tile_color"].setValue(int(self.node_color, 16)) + camera_node["note_font_size"].setValue(24) + camera_node["label"].setValue("[{}]".format(self.name)) + # add avalon knobs + instance = anlib.imprint(camera_node, self.data) + + return instance From 81694986a272d9a91ceb1fceeaa139d0473867eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 25 Sep 2020 16:29:36 +0200 Subject: [PATCH 39/91] feat(nuke): 3d camera creator --- pype/plugins/nuke/create/create_camera.py | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/pype/plugins/nuke/create/create_camera.py b/pype/plugins/nuke/create/create_camera.py index 594c736c82..a3b78b0d0e 100644 --- a/pype/plugins/nuke/create/create_camera.py +++ b/pype/plugins/nuke/create/create_camera.py @@ -15,7 +15,7 @@ class CreateCamera(avalon.nuke.Creator): def __init__(self, *args, **kwargs): super(CreateCamera, self).__init__(*args, **kwargs) self.nodes = nuke.selectedNodes() - self.node_color = "0xdfea5dff" + self.node_color = "0xff9100ff" return def process(self): @@ -24,16 +24,13 @@ class CreateCamera(avalon.nuke.Creator): nodes = self.nodes if len(nodes) >= 1: - anlib.select_nodes(nodes) - # camera_node = autoBackdrop() - # camera_node["name"].setValue("{}_BDN".format(self.name)) - # camera_node["tile_color"].setValue(int(self.node_color, 16)) - # camera_node["note_font_size"].setValue(24) - # camera_node["label"].setValue("[{}]".format(self.name)) - # # add avalon knobs - # instance = anlib.imprint(camera_node, self.data) - # - # return instance + for n in nodes: + data = self.data.copy() + subset = self.family + n["name"].value().capitalize() + data["subset"] = subset + n["tile_color"].setValue(int(self.node_color, 16)) + # add avalon knobs + anlib.imprint(n, data) else: msg = str("Please select nodes you " "wish to add to a container") @@ -41,12 +38,8 @@ class CreateCamera(avalon.nuke.Creator): nuke.message(msg) return else: - camera_node = autoBackdrop() - camera_node["name"].setValue("{}_BDN".format(self.name)) + camera_node = nuke.createNode("Camera2") camera_node["tile_color"].setValue(int(self.node_color, 16)) - camera_node["note_font_size"].setValue(24) - camera_node["label"].setValue("[{}]".format(self.name)) # add avalon knobs instance = anlib.imprint(camera_node, self.data) - return instance From 7c71db88fc278bffe1c8da1da3788b3e4b22c000 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 25 Sep 2020 17:14:17 +0200 Subject: [PATCH 40/91] feat(nuke): create camera plugin final --- pype/plugins/nuke/create/create_camera.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nuke/create/create_camera.py b/pype/plugins/nuke/create/create_camera.py index a3b78b0d0e..4c668925ad 100644 --- a/pype/plugins/nuke/create/create_camera.py +++ b/pype/plugins/nuke/create/create_camera.py @@ -24,13 +24,20 @@ class CreateCamera(avalon.nuke.Creator): nodes = self.nodes if len(nodes) >= 1: + # loop selected nodes for n in nodes: data = self.data.copy() - subset = self.family + n["name"].value().capitalize() - data["subset"] = subset + if len(nodes) > 1: + # rename subset name only if more + # then one node are selected + subset = self.family + n["name"].value().capitalize() + data["subset"] = subset + + # change node color n["tile_color"].setValue(int(self.node_color, 16)) # add avalon knobs anlib.imprint(n, data) + return True else: msg = str("Please select nodes you " "wish to add to a container") @@ -38,6 +45,7 @@ class CreateCamera(avalon.nuke.Creator): nuke.message(msg) return else: + # if selected is off then create one node camera_node = nuke.createNode("Camera2") camera_node["tile_color"].setValue(int(self.node_color, 16)) # add avalon knobs From 89eb5e1347592a9b42e93a59d08fc7c1788cbf7d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 29 Sep 2020 12:26:37 +0200 Subject: [PATCH 41/91] Added back print of comments Revert unwanted merge --- pype/hosts/nukestudio/tags.py | 4 ++-- pype/plugins/nukestudio/publish/collect_shots.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pype/hosts/nukestudio/tags.py b/pype/hosts/nukestudio/tags.py index 36edb16da6..c8af0cabc1 100644 --- a/pype/hosts/nukestudio/tags.py +++ b/pype/hosts/nukestudio/tags.py @@ -71,8 +71,8 @@ def add_tags_from_presets(): # Get project task types. tasks = io.find_one({"type": "project"})["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} - for task_name, _ in tasks.items(): - nks_pres_tags["[Tasks]"][task_name] = { + for task_type in tasks.keys(): + nks_pres_tags["[Tasks]"][task_type] = { "editable": "1", "note": "", "icon": { diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/nukestudio/publish/collect_shots.py index 42b1ea160d..a33e1fad49 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/nukestudio/publish/collect_shots.py @@ -40,11 +40,12 @@ class CollectShots(api.InstancePlugin): data["name"] = data["subset"] + "_" + data["asset"] data["label"] = ( - "{} - {} - tasks:{} - assetbuilds:{}".format( + "{} - {} - tasks:{} - assetbuilds:{} - comments:{}".format( data["asset"], data["subset"], data["tasks"].keys(), - [x["name"] for x in data.get("assetbuilds", [])] + [x["name"] for x in data.get("assetbuilds", [])], + len(data["comments"]) ) ) From 62ac602985d73000e77233520f122c7ddd964f8a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 29 Sep 2020 13:32:59 +0200 Subject: [PATCH 42/91] add look assigner to pype menu even if scriptsmenu is N/A --- pype/hosts/maya/menu.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/hosts/maya/menu.py b/pype/hosts/maya/menu.py index 70ad8d31ca..98406719c7 100644 --- a/pype/hosts/maya/menu.py +++ b/pype/hosts/maya/menu.py @@ -32,6 +32,15 @@ def deferred(): command=lambda *args: BuildWorkfile().process() ) + def add_look_assigner_item(): + import mayalookassigner + cmds.menuItem(divider=True, parent=pipeline._menu) + cmds.menuItem( + "Maya Look assigner", + parent=pipeline._menu, + command=lambda *args: mayalookassigner.show() + ) + log.info("Attempting to install scripts menu..") try: @@ -43,6 +52,7 @@ def deferred(): "'scriptsmenu' module seems unavailable." ) add_build_workfiles_item() + add_look_assigner_item() return # load configuration of custom menu From 9be103974ec433d2c725174225bd630f3a4270e2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Sep 2020 10:27:26 +0200 Subject: [PATCH 43/91] feat(nuke): exctractor plugin for camera abs (presets can do fbx) --- .../plugins/nuke/publish/collect_instances.py | 1 - pype/plugins/nuke/publish/extract_camera.py | 185 ++++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 pype/plugins/nuke/publish/extract_camera.py diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 9085e12bd8..d2031266bd 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -60,7 +60,6 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): families.append(family) - # except disabled nodes but exclude backdrops in test if ("nukenodes" not in family) and (node["disable"].value()): continue diff --git a/pype/plugins/nuke/publish/extract_camera.py b/pype/plugins/nuke/publish/extract_camera.py new file mode 100644 index 0000000000..9a1efba1df --- /dev/null +++ b/pype/plugins/nuke/publish/extract_camera.py @@ -0,0 +1,185 @@ +import nuke +import os +import math +import pyblish.api +import pype.api +from avalon.nuke import lib as anlib +from pprint import pformat + + +class ExtractCamera(pype.api.Extractor): + """ 3D camera exctractor + """ + label = 'Exctract Camera' + order = pyblish.api.ExtractorOrder + families = ["camera"] + hosts = ["nuke"] + + # presets + write_geo_knobs = [ + ("file_type", "abc"), + ("storageFormat", "Ogawa"), + ("writeGeometries", False), + ("writePointClouds", False), + ("writeAxes", False) + ] + + def process(self, instance): + handle_start = instance.context.data["handleStart"] + handle_end = instance.context.data["handleEnd"] + first_frame = int(nuke.root()["first_frame"].getValue()) + last_frame = int(nuke.root()["last_frame"].getValue()) + step = 1 + output_range = str(nuke.FrameRange(first_frame, last_frame, step)) + + self.log.info("instance.data: `{}`".format( + pformat(instance.data))) + + rm_nodes = list() + self.log.info("Crating additional nodes") + subset = instance.data["subset"] + staging_dir = self.staging_dir(instance) + + # get extension form preset + extension = next((k[1] for k in self.write_geo_knobs + if k[0] == "file_type"), None) + if not extension: + raise RuntimeError( + "Bad config for extension in presets. " + "Talk to your supervisor or pipeline admin") + + # create file name and path + filename = subset + ".{}".format(extension) + file_path = os.path.join(staging_dir, filename).replace("\\", "/") + + with anlib.maintained_selection(): + # bake camera with axeses onto word coordinate XYZ + rm_n = bakeCameraWithAxeses( + nuke.toNode(instance.data["name"]), output_range) + rm_nodes.append(rm_n) + + # create scene node + rm_n = nuke.createNode("Scene") + rm_nodes.append(rm_n) + + # create write geo node + wg_n = nuke.createNode("WriteGeo") + wg_n["file"].setValue(file_path) + # add path to write to + for k, v in self.write_geo_knobs: + wg_n[k].setValue(v) + rm_nodes.append(wg_n) + + # write out camera + nuke.execute( + wg_n, + int(first_frame), + int(last_frame) + ) + # erase additional nodes + for n in rm_nodes: + nuke.delete(n) + + self.log.info(file_path) + + # create representation data + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': extension, + 'ext': extension, + 'files': filename, + "stagingDir": staging_dir, + "frameStart": first_frame, + "frameEnd": last_frame + } + instance.data["representations"].append(representation) + + instance.data.update({ + "path": file_path, + "outputDir": staging_dir, + "ext": extension, + "handleStart": handle_start, + "handleEnd": handle_end, + "frameStart": first_frame + handle_start, + "frameEnd": last_frame - handle_end, + "frameStartHandle": first_frame, + "frameEndHandle": last_frame, + }) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, file_path)) + + +def bakeCameraWithAxeses(camera_node, output_range): + """ Baking all perent hiearchy of axeses into camera + with transposition onto word XYZ coordinance + """ + bakeFocal = False + bakeHaperture = False + bakeVaperture = False + + camera_matrix = camera_node['world_matrix'] + + new_cam_n = nuke.createNode("Camera2") + new_cam_n.setInput(0, None) + new_cam_n['rotate'].setAnimated() + new_cam_n['translate'].setAnimated() + + old_focal = camera_node['focal'] + if old_focal.isAnimated() and not (old_focal.animation(0).constant()): + new_cam_n['focal'].setAnimated() + bakeFocal = True + else: + new_cam_n['focal'].setValue(old_focal.value()) + + old_haperture = camera_node['haperture'] + if old_haperture.isAnimated() and not ( + old_haperture.animation(0).constant()): + new_cam_n['haperture'].setAnimated() + bakeHaperture = True + else: + new_cam_n['haperture'].setValue(old_haperture.value()) + + old_vaperture = camera_node['vaperture'] + if old_vaperture.isAnimated() and not ( + old_vaperture.animation(0).constant()): + new_cam_n['vaperture'].setAnimated() + bakeVaperture = True + else: + new_cam_n['vaperture'].setValue(old_vaperture.value()) + + new_cam_n['win_translate'].setValue(camera_node['win_translate'].value()) + new_cam_n['win_scale'].setValue(camera_node['win_scale'].value()) + + for x in nuke.FrameRange(output_range): + math_matrix = nuke.math.Matrix4() + for y in range(camera_matrix.height()): + for z in range(camera_matrix.width()): + matrix_pointer = z + (y * camera_matrix.width()) + math_matrix[matrix_pointer] = camera_matrix.getValueAt( + x, (y + (z * camera_matrix.width()))) + + rot_matrix = nuke.math.Matrix4(math_matrix) + rot_matrix.rotationOnly() + rot = rot_matrix.rotationsZXY() + + new_cam_n['rotate'].setValueAt(math.degrees(rot[0]), x, 0) + new_cam_n['rotate'].setValueAt(math.degrees(rot[1]), x, 1) + new_cam_n['rotate'].setValueAt(math.degrees(rot[2]), x, 2) + new_cam_n['translate'].setValueAt( + camera_matrix.getValueAt(x, 3), x, 0) + new_cam_n['translate'].setValueAt( + camera_matrix.getValueAt(x, 7), x, 1) + new_cam_n['translate'].setValueAt( + camera_matrix.getValueAt(x, 11), x, 2) + + if bakeFocal: + new_cam_n['focal'].setValueAt(old_focal.getValueAt(x), x) + if bakeHaperture: + new_cam_n['haperture'].setValueAt(old_haperture.getValueAt(x), x) + if bakeVaperture: + new_cam_n['vaperture'].setValueAt(old_vaperture.getValueAt(x), x) + + return new_cam_n From dce6ab49422e681b1f742624785289c5bae3a0cc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Sep 2020 10:28:38 +0200 Subject: [PATCH 44/91] fix(nuke): loader was too strict for properties frame start/and maya is not adding them to version and it would be crashing --- pype/plugins/nuke/load/load_camera_abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py index 51810c45f9..95ebb65005 100644 --- a/pype/plugins/nuke/load/load_camera_abc.py +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -31,7 +31,7 @@ class AlembicCameraLoader(api.Loader): # prepare data for imprinting # add additional metadata from the version to imprint to Avalon knob - add_keys = ["frameStart", "frameEnd", "source", "author", "fps"] + add_keys = ["source", "author", "fps"] data_imprint = {"frameStart": first, "frameEnd": last, @@ -55,6 +55,8 @@ class AlembicCameraLoader(api.Loader): camera_node["frame_rate"].setValue(float(fps)) camera_node["tile_color"].setValue(int("0x3469ffff", 16)) + camera_node["reload"].execute() + return containerise( node=camera_node, name=name, From 11cf4056a7aeaa384ab867bf7460e0c61f34222a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Oct 2020 14:53:03 +0200 Subject: [PATCH 45/91] replace files in current representation --- .../global/publish/extract_scanline_exr.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py index 2f68a2af0d..1c3873b021 100644 --- a/pype/plugins/global/publish/extract_scanline_exr.py +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Convert exrs in representation to tiled exrs usin oiio tools.""" import os -import copy +import shutil import pyblish.api import pype.api @@ -47,40 +47,44 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): oiio_tool_path = os.getenv("PYPE_OIIO_PATH", "") - new_files = [] for file in input_files: + original_name = os.path.join(stagingdir, file) + temp_name = os.path.join(stagingdir, "__{}".format(file)) + # move original render to temp location + shutil.move(original_name, temp_name) oiio_cmd = [] oiio_cmd.append(oiio_tool_path) oiio_cmd.append( - os.path.join(stagingdir, file) + os.path.join(stagingdir, temp_name) ) oiio_cmd.append("--scanline") oiio_cmd.append("-o") - new_file = f"_scanline_{file}" - new_files.append(new_file) - oiio_cmd.append(os.path.join(stagingdir, new_file)) + oiio_cmd.append(os.path.join(stagingdir, original_name)) subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") pype.api.subprocess(subprocess_exr) # raise error if there is no ouptput - if not os.path.exists(os.path.join(stagingdir, new_file)): + if not os.path.exists(os.path.join(stagingdir, original_name)): self.log.error( - f"File {new_file} was not produced by oiio tool!") + ("File {} was not converted " + "by oiio tool!").format(original_name)) raise AssertionError("OIIO tool conversion failed") + else: + try: + shutil.remove(temp_name) + except OSError as e: + self.log.warning("Unable to delete temp file") + self.log.warning(e) - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = copy.deepcopy(repre) - - representation['name'] = 'scanline_exr' - representation['files'] = new_files if len(new_files) > 1 else new_files[0] # noqa: E501 - representation['tags'] = [] - - self.log.debug("Adding: {}".format(representation)) - representations_new.append(representation) + repre['name'] = 'exr' + repre['outputName'] = "scanline exr" + try: + repre['tags'].remove('toScanline') + except ValueError: + # no `toScanline` tag present + pass instance.data["representations"] += representations_new From 3f628d12672e7f86ea1aa603ff04c8ea9ba48546 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Oct 2020 16:19:37 +0200 Subject: [PATCH 46/91] minor fixes --- pype/plugins/global/publish/extract_scanline_exr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py index 1c3873b021..d829f198b6 100644 --- a/pype/plugins/global/publish/extract_scanline_exr.py +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -28,7 +28,7 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): "Processing representation {}".format(repre.get("name"))) tags = repre.get("tags", []) if "toScanline" not in tags: - self.log.info("- missing toScanline tag") + self.log.info(" - missing toScanline tag") continue # run only on exrs @@ -38,10 +38,10 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): if not isinstance(repre['files'], (list, tuple)): input_files = [repre['files']] - self.log.info("We have a sequence.") + self.log.info("We have a single frame") else: input_files = repre['files'] - self.log.info("We have a single frame") + self.log.info("We have a sequence") stagingdir = os.path.normpath(repre.get("stagingDir")) @@ -74,7 +74,7 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): raise AssertionError("OIIO tool conversion failed") else: try: - shutil.remove(temp_name) + os.remove(temp_name) except OSError as e: self.log.warning("Unable to delete temp file") self.log.warning(e) From 2e9be7332681097fa05998dbd6d41567c0d96d48 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Oct 2020 19:20:00 +0200 Subject: [PATCH 47/91] Removed unload + reload to keep changes to untouched shaders --- pype/plugins/maya/load/load_look.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pype/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py index cb5b6fa2e8..c5b58c9bd5 100644 --- a/pype/plugins/maya/load/load_look.py +++ b/pype/plugins/maya/load/load_look.py @@ -117,10 +117,7 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): # highlight failed edits to user if failed_edits: # clean references - removes failed reference edits - cmds.file(unloadReference=reference_node) cmds.file(cr=reference_node) # cleanReference - # reload reference, now it shouldn't fail - self._load_reference(file_type, node, path, reference_node) # reapply shading groups from json representation on orig nodes pype.hosts.maya.lib.apply_shaders(relationships, From a3f7fa777bbc2a86c32659836deaad8af4bbee26 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Oct 2020 10:09:42 +0200 Subject: [PATCH 48/91] make asset dependencies configurable --- .../maya/publish/submit_maya_deadline.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index e4048592a7..7509b5875a 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -262,6 +262,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): use_published = True tile_assembler_plugin = "PypeTileAssembler" + asset_dependencies = False def process(self, instance): """Plugin entry point.""" @@ -417,9 +418,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Adding file dependencies. dependencies = instance.context.data["fileDependencies"] dependencies.append(filepath) - for dependency in dependencies: - key = "AssetDependency" + str(dependencies.index(dependency)) - payload_skeleton["JobInfo"][key] = dependency + if self.assembly_files: + for dependency in dependencies: + key = "AssetDependency" + str(dependencies.index(dependency)) + payload_skeleton["JobInfo"][key] = dependency # Handle environments ----------------------------------------------- # We need those to pass them to pype for it to set correct context @@ -731,10 +733,14 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): def _get_maya_payload(self, data): payload = copy.deepcopy(payload_skeleton) - job_info_ext = { - # Asset dependency to wait for at least the scene file to sync. - "AssetDependency0": data["filepath"], - } + if not self.asset_dependencies: + job_info_ext = {} + + else: + job_info_ext = { + # Asset dependency to wait for at least the scene file to sync. + "AssetDependency0": data["filepath"], + } plugin_info = { "SceneFile": data["filepath"], From ef240a387944c72c8ccad0890e9e0eb3c2e9ba3a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Oct 2020 11:04:29 +0200 Subject: [PATCH 49/91] Fix schemas for correct creation from empty DB --- schema/config-1.1.json | 22 +++++++++++++--------- schema/inventory-1.1.json | 2 +- schema/project-2.1.json | 6 +++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/schema/config-1.1.json b/schema/config-1.1.json index 5f4fe4b2fb..ea5ab0ff27 100644 --- a/schema/config-1.1.json +++ b/schema/config-1.1.json @@ -1,14 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "avalon-core:config-1.1", + "title": "pype:config-1.1", "description": "A project configuration.", "type": "object", "additionalProperties": false, "required": [ - "template", "tasks", "apps" ], @@ -29,13 +28,18 @@ }, "tasks": { "type": "object", - "properties": { - "short_name": {"type": "string"}, - "icon": {"type": "string"}, - "group": {"type": "string"}, - "label": {"type": "string"} - }, - "required": ["short_name"] + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "group": {"type": "string"}, + "label": {"type": "string"} + }, + "required": [ + "short_name" + ] + } }, "apps": { "type": "array", diff --git a/schema/inventory-1.1.json b/schema/inventory-1.1.json index f46df6973d..1b572b7d23 100644 --- a/schema/inventory-1.1.json +++ b/schema/inventory-1.1.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "avalon-core:config-1.1", + "title": "pype:config-1.1", "description": "A project configuration.", "type": "object", diff --git a/schema/project-2.1.json b/schema/project-2.1.json index 22327b2f06..40e3bdb638 100644 --- a/schema/project-2.1.json +++ b/schema/project-2.1.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "avalon-core:project-2.1", + "title": "pype:project-2.1", "description": "A unit of data", "type": "object", @@ -20,7 +20,7 @@ "schema": { "description": "Schema identifier for payload", "type": "string", - "enum": ["avalon-core:project-2.1"], + "enum": ["avalon-core:project-2.1", "pype:project-2.1"], "example": "avalon-core:project-2.1" }, "type": { @@ -52,7 +52,7 @@ "type": "object", "description": "Document metadata", "example": { - "schema": "avalon-core:config-1.1", + "schema": "pype:config-1.1", "apps": [ { "name": "maya2016", From 48cc3cab3d17a06272fe9073258ae322cfc32c1c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 2 Oct 2020 14:14:45 +0200 Subject: [PATCH 50/91] remove obsolete task variable --- pype/plugins/maya/publish/collect_yeti_cache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/maya/publish/collect_yeti_cache.py b/pype/plugins/maya/publish/collect_yeti_cache.py index e24517951b..26c3f601f6 100644 --- a/pype/plugins/maya/publish/collect_yeti_cache.py +++ b/pype/plugins/maya/publish/collect_yeti_cache.py @@ -30,7 +30,6 @@ class CollectYetiCache(pyblish.api.InstancePlugin): label = "Collect Yeti Cache" families = ["yetiRig", "yeticache"] hosts = ["maya"] - tasks = {"animation": {"type": "Animation"}, "fx": {"type": "FX"}} def process(self, instance): From 48b6278fa8ff85702a3157a069325ebd58e4cee0 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 2 Oct 2020 14:20:02 +0200 Subject: [PATCH 51/91] return lost changes ;) --- pype/plugins/nukestudio/publish/collect_shots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/nukestudio/publish/collect_shots.py index a33e1fad49..03fc7ab282 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/nukestudio/publish/collect_shots.py @@ -45,7 +45,7 @@ class CollectShots(api.InstancePlugin): data["subset"], data["tasks"].keys(), [x["name"] for x in data.get("assetbuilds", [])], - len(data["comments"]) + len(data.get("comments", [])) ) ) From d965a2a2a21c50303bb9f794572bc9d3ee393c5f Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 2 Oct 2020 14:34:09 +0200 Subject: [PATCH 52/91] rename look assigner --- pype/hosts/maya/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/maya/menu.py b/pype/hosts/maya/menu.py index 98406719c7..6d610b2645 100644 --- a/pype/hosts/maya/menu.py +++ b/pype/hosts/maya/menu.py @@ -36,7 +36,7 @@ def deferred(): import mayalookassigner cmds.menuItem(divider=True, parent=pipeline._menu) cmds.menuItem( - "Maya Look assigner", + "Look assigner", parent=pipeline._menu, command=lambda *args: mayalookassigner.show() ) From 826f437dbac71716293f400843e18e4f89d4b997 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 2 Oct 2020 14:42:31 +0200 Subject: [PATCH 53/91] add custom menu at all times --- pype/hosts/maya/menu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/hosts/maya/menu.py b/pype/hosts/maya/menu.py index 6d610b2645..9dadd8d1f5 100644 --- a/pype/hosts/maya/menu.py +++ b/pype/hosts/maya/menu.py @@ -34,7 +34,6 @@ def deferred(): def add_look_assigner_item(): import mayalookassigner - cmds.menuItem(divider=True, parent=pipeline._menu) cmds.menuItem( "Look assigner", parent=pipeline._menu, @@ -43,6 +42,9 @@ def deferred(): log.info("Attempting to install scripts menu..") + add_build_workfiles_item() + add_look_assigner_item() + try: import scriptsmenu.launchformaya as launchformaya import scriptsmenu.scriptsmenu as scriptsmenu @@ -51,8 +53,6 @@ def deferred(): "Skipping studio.menu install, because " "'scriptsmenu' module seems unavailable." ) - add_build_workfiles_item() - add_look_assigner_item() return # load configuration of custom menu From add97d828c252860d17e6fa542a652abba2ace1b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 2 Oct 2020 15:53:09 +0200 Subject: [PATCH 54/91] fixed bad intent values in burnins --- pype/plugins/global/publish/extract_burnin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index b81cfbc050..5649c9aef2 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -314,14 +314,15 @@ class ExtractBurnin(pype.api.Extractor): "comment": context.data.get("comment") or "" }) - intent_label = context.data.get("intent") + intent_label = context.data.get("intent") or "" if intent_label and isinstance(intent_label, dict): value = intent_label.get("value") if value: - intent_label = intent_label.get("label") + intent_label = intent_label["label"] + else: + intent_label = "" - if intent_label: - burnin_data["intent"] = intent_label + burnin_data["intent"] = intent_label temp_data = { "frame_start": frame_start, From f5a911aed932534fa2b3e231f080075377a47630 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:46:52 +0200 Subject: [PATCH 55/91] fixed super calls --- pype/tools/pyblish_pype/widgets.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pype/tools/pyblish_pype/widgets.py b/pype/tools/pyblish_pype/widgets.py index 880d4755ad..54198a8cfe 100644 --- a/pype/tools/pyblish_pype/widgets.py +++ b/pype/tools/pyblish_pype/widgets.py @@ -6,7 +6,7 @@ from .constants import PluginStates, InstanceStates, Roles class EllidableLabel(QtWidgets.QLabel): def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(EllidableLabel, self).__init__(*args, **kwargs) self.setObjectName("EllidableLabel") def paintEvent(self, event): @@ -21,7 +21,7 @@ class EllidableLabel(QtWidgets.QLabel): class PerspectiveLabel(QtWidgets.QTextEdit): def __init__(self, parent=None): - super(self.__class__, self).__init__(parent) + super(PerspectiveLabel, self).__init__(parent) self.setObjectName("PerspectiveLabel") size_policy = self.sizePolicy() @@ -50,7 +50,7 @@ class PerspectiveLabel(QtWidgets.QTextEdit): return margins.top() + document.size().height() + margins.bottom() def sizeHint(self): - width = super(self.__class__, self).sizeHint().width() + width = super(PerspectiveLabel, self).sizeHint().width() return QtCore.QSize(width, self.heightForWidth(width)) @@ -407,7 +407,7 @@ class ExpandableWidget(QtWidgets.QWidget): self.content_widget.setVisible(checked) def resizeEvent(self, event): - super(self.__class__, self).resizeEvent(event) + super(ExpandableWidget, self).resizeEvent(event) self.content.updateGeometry() def set_content(self, in_widget): @@ -481,7 +481,7 @@ class CommentBox(QtWidgets.QLineEdit): class TerminalDetail(QtWidgets.QTextEdit): def __init__(self, text, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(TerminalDetail, self).__init__(*args, **kwargs) self.setReadOnly(True) self.setHtml(text) @@ -504,7 +504,7 @@ class FilterButton(QtWidgets.QPushButton): def __init__(self, name, *args, **kwargs): self.filter_name = name - super(self.__class__, self).__init__(*args, **kwargs) + super(FilterButton, self).__init__(*args, **kwargs) self.toggled.connect(self.on_toggle) @@ -522,8 +522,8 @@ class FilterButton(QtWidgets.QPushButton): class TerminalFilterWidget(QtWidgets.QWidget): # timer.timeout.connect(lambda: self._update(self.parent_widget)) def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(TerminalFilterWidget, self).__init__(*args, **kwargs) self.filter_changed = QtCore.Signal() info_icon = awesome.tags["info"] From 83e356102ccb24a45020150d4ee139cc51d01d20 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:47:11 +0200 Subject: [PATCH 56/91] added parenting for terminal fiter widget --- pype/tools/pyblish_pype/widgets.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pype/tools/pyblish_pype/widgets.py b/pype/tools/pyblish_pype/widgets.py index 54198a8cfe..ebdb6d6dce 100644 --- a/pype/tools/pyblish_pype/widgets.py +++ b/pype/tools/pyblish_pype/widgets.py @@ -531,16 +531,16 @@ class TerminalFilterWidget(QtWidgets.QWidget): error_icon = awesome.tags["exclamation-triangle"] filter_buttons = ( - FilterButton("info", info_icon), - FilterButton("log_debug", log_icon), - FilterButton("log_info", log_icon), - FilterButton("log_warning", log_icon), - FilterButton("log_error", log_icon), - FilterButton("log_critical", log_icon), - FilterButton("error", error_icon) + FilterButton("info", info_icon, self), + FilterButton("log_debug", log_icon, self), + FilterButton("log_info", log_icon, self), + FilterButton("log_warning", log_icon, self), + FilterButton("log_error", log_icon, self), + FilterButton("log_critical", log_icon, self), + FilterButton("error", error_icon, self) ) - layout = QtWidgets.QHBoxLayout() + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) # Add spacers layout.addWidget(QtWidgets.QWidget(), 1) From 7fbeef6c8eb1dd6de3dc4eceb27064dbde301714 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:47:36 +0200 Subject: [PATCH 57/91] TerminalFilterWidget has object name and stylesheets for that --- pype/tools/pyblish_pype/app.css | 33 ++++++++++++++++++++++++++++-- pype/tools/pyblish_pype/widgets.py | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pype/tools/pyblish_pype/app.css b/pype/tools/pyblish_pype/app.css index 3a2c05c1f3..c51126e89f 100644 --- a/pype/tools/pyblish_pype/app.css +++ b/pype/tools/pyblish_pype/app.css @@ -459,7 +459,7 @@ QToolButton { color: #fff; } -#TerminalFilerBtn { +#TerminalFilterWidget QPushButton { /* font: %(font_size_pt)spt; */ font-family: "FontAwesome"; text-align: center; @@ -468,29 +468,58 @@ QToolButton { border-color: #777777; border-style: none; padding: 0px; - border-radius: 3px; + border-radius: 8px; +} +#TerminalFilterWidget QPushButton:hover { + background: #5f5f5f; + border-style: none; +} +#TerminalFilterWidget QPushButton:pressed { + background: #606060; + border-style: none; +} +#TerminalFilterWidget QPushButton:pressed:hover { + background: #626262; + border-style: none; } #TerminalFilerBtn[type="info"]:checked {color: rgb(255, 255, 255);} +#TerminalFilerBtn[type="info"]:hover:pressed {color: rgba(255, 255, 255, 163);} #TerminalFilerBtn[type="info"] {color: rgba(255, 255, 255, 63);} #TerminalFilerBtn[type="error"]:checked {color: rgb(255, 74, 74);} +#TerminalFilerBtn[type="error"]:hover:pressed {color: rgba(255, 74, 74, 163);} #TerminalFilerBtn[type="error"] {color: rgba(255, 74, 74, 63);} #TerminalFilerBtn[type="log_debug"]:checked {color: rgb(255, 102, 232);} #TerminalFilerBtn[type="log_debug"] {color: rgba(255, 102, 232, 63);} +#TerminalFilerBtn[type="log_debug"]:hover:pressed { + color: rgba(255, 102, 232, 163); +} #TerminalFilerBtn[type="log_info"]:checked {color: rgb(102, 171, 255);} #TerminalFilerBtn[type="log_info"] {color: rgba(102, 171, 255, 63);} +#TerminalFilerBtn[type="log_info"]:hover:pressed { + color: rgba(102, 171, 255, 163); +} #TerminalFilerBtn[type="log_warning"]:checked {color: rgb(255, 186, 102);} #TerminalFilerBtn[type="log_warning"] {color: rgba(255, 186, 102, 63);} +#TerminalFilerBtn[type="log_warning"]:hover:pressed { + color: rgba(255, 186, 102, 163); +} #TerminalFilerBtn[type="log_error"]:checked {color: rgb(255, 77, 88);} #TerminalFilerBtn[type="log_error"] {color: rgba(255, 77, 88, 63);} +#TerminalFilerBtn[type="log_error"]:hover:pressed { + color: rgba(255, 77, 88, 163); +} #TerminalFilerBtn[type="log_critical"]:checked {color: rgb(255, 79, 117);} #TerminalFilerBtn[type="log_critical"] {color: rgba(255, 79, 117, 63);} +#TerminalFilerBtn[type="log_critical"]:hover:pressed { + color: rgba(255, 79, 117, 163); +} #SuspendLogsBtn { background: #444; diff --git a/pype/tools/pyblish_pype/widgets.py b/pype/tools/pyblish_pype/widgets.py index ebdb6d6dce..2a9b66a5a2 100644 --- a/pype/tools/pyblish_pype/widgets.py +++ b/pype/tools/pyblish_pype/widgets.py @@ -522,8 +522,8 @@ class FilterButton(QtWidgets.QPushButton): class TerminalFilterWidget(QtWidgets.QWidget): # timer.timeout.connect(lambda: self._update(self.parent_widget)) def __init__(self, *args, **kwargs): - super(TerminalFilterWidget, self).__init__(*args, **kwargs) + self.setObjectName("TerminalFilterWidget") self.filter_changed = QtCore.Signal() info_icon = awesome.tags["info"] From 76a7437fd04153a008e81b2e3986e49c951f95e4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:47:50 +0200 Subject: [PATCH 58/91] mover resize of window --- pype/tools/pyblish_pype/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/app.py b/pype/tools/pyblish_pype/app.py index 0f662d5b3e..9879c63030 100644 --- a/pype/tools/pyblish_pype/app.py +++ b/pype/tools/pyblish_pype/app.py @@ -92,7 +92,6 @@ def show(parent=None): self._window.show() self._window.activateWindow() - self._window.resize(*settings.WindowSize) self._window.setWindowTitle(settings.WindowTitle) font = QtGui.QFont("Open Sans", 8, QtGui.QFont.Normal) @@ -100,5 +99,6 @@ def show(parent=None): self._window.setStyleSheet(css) self._window.reset() + self._window.resize(*settings.WindowSize) return self._window From 79aa29cb7a577e4452fe3436b9dd767f3873b0b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:47:59 +0200 Subject: [PATCH 59/91] fixed toggle --- pype/tools/pyblish_pype/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/widgets.py b/pype/tools/pyblish_pype/widgets.py index 2a9b66a5a2..4da759899e 100644 --- a/pype/tools/pyblish_pype/widgets.py +++ b/pype/tools/pyblish_pype/widgets.py @@ -328,7 +328,7 @@ class PerspectiveWidget(QtWidgets.QWidget): self.records.toggle_content(len_records > 0) def toggle_me(self): - self.parent_widget.toggle_perspective_widget() + self.parent_widget.parent().toggle_perspective_widget() class ClickableWidget(QtWidgets.QLabel): From b547dffa41bce29ffad251ece873a54418302e40 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:48:20 +0200 Subject: [PATCH 60/91] views can respond to animated mouse clicks --- pype/tools/pyblish_pype/view.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 477303eae8..542d95bf05 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -83,6 +83,7 @@ class OverviewView(QtWidgets.QTreeView): self.setHeaderHidden(True) self.setRootIsDecorated(False) self.setIndentation(0) + self.setAnimated(True) def event(self, event): if not event.type() == QtCore.QEvent.KeyPress: @@ -201,6 +202,34 @@ class InstanceView(OverviewView): model.setData(index, new_state, QtCore.Qt.CheckStateRole) self.toggled.emit(index, new_state) + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + pos_index = self.indexAt(event.pos()) + if ( + pos_index.isValid() + and pos_index.data(Roles.TypeRole) == model.InstanceType + ): + if event.pos().x() < 20: + indexes = self.selectionModel().selectedIndexes() + if pos_index in indexes: + any_checked = False + for index in indexes: + if index.data(QtCore.Qt.CheckStateRole): + any_checked = True + break + + for index in indexes: + self.toggled.emit(index, not any_checked) + return + + else: + self.toggled.emit(pos_index, not any_checked) + + elif event.pos().x() > self.width() - 20: + self.show_perspective.emit(pos_index) + + return super(InstanceView, self).mousePressEvent(event) + def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.LeftButton: indexes = self.selectionModel().selectedIndexes() From 424ca4181dacc345f328b8faaed98b8f642fba4c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:48:57 +0200 Subject: [PATCH 61/91] Implemented animated pages --- pype/tools/pyblish_pype/window.py | 273 +++++++++++++++++++++++++++--- 1 file changed, 251 insertions(+), 22 deletions(-) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 2a037ba4bc..0365962dc2 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -39,6 +39,7 @@ Todo: the first time to understand how to actually to it! """ +import sys from functools import partial from . import delegate, model, settings, util, view, widgets @@ -48,6 +49,10 @@ from Qt import QtCore, QtGui, QtWidgets from .constants import ( PluginStates, PluginActionStates, InstanceStates, GroupStates, Roles ) +if sys.version_info[0] == 3: + from queue import Queue +else: + from Queue import Queue class Window(QtWidgets.QDialog): @@ -267,6 +272,7 @@ class Window(QtWidgets.QDialog): layout.addWidget(footer_button_play, 0) footer_layout = QtWidgets.QVBoxLayout(footer_widget) + footer_layout.addWidget(terminal_filters_widget) footer_layout.addWidget(comment_intent_widget) footer_layout.addLayout(layout) @@ -281,16 +287,21 @@ class Window(QtWidgets.QDialog): ) closing_placeholder.hide() - perspective_widget = widgets.PerspectiveWidget(self) + perspective_widget = widgets.PerspectiveWidget(main_widget) perspective_widget.hide() + pages_widget = QtWidgets.QWidget(main_widget) + layout = QtWidgets.QVBoxLayout(pages_widget) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(header_widget, 0) + layout.addWidget(body_widget, 1) + # Main layout layout = QtWidgets.QVBoxLayout(main_widget) - layout.addWidget(header_widget, 0) - layout.addWidget(body_widget, 3) + layout.addWidget(pages_widget, 3) layout.addWidget(perspective_widget, 3) layout.addWidget(closing_placeholder, 1) - layout.addWidget(terminal_filters_widget, 0) layout.addWidget(footer_widget, 0) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) @@ -382,6 +393,7 @@ class Window(QtWidgets.QDialog): # Enable CSS on plain QWidget objects for _widget in ( + pages_widget, header_widget, body_widget, artist_page, @@ -457,6 +469,7 @@ class Window(QtWidgets.QDialog): self.main_widget = main_widget + self.pages_widget = pages_widget self.header_widget = header_widget self.body_widget = body_widget @@ -498,13 +511,20 @@ class Window(QtWidgets.QDialog): "overview": header_tab_overview, "terminal": header_tab_terminal } - self.pages = { - "artist": artist_page, - "overview": overview_page, - "terminal": terminal_page - } + self.pages = ( + ("artist", artist_page), + ("overview", overview_page), + ("terminal", terminal_page) + ) current_page = settings.InitialTab or "artist" + self.comment_main_widget.setVisible( + not current_page == "terminal" + ) + self.terminal_filters_widget.setVisible( + current_page == "terminal" + ) + self.state = { "is_closing": False, "current_page": current_page @@ -548,11 +568,9 @@ class Window(QtWidgets.QDialog): show = True self.perspective_widget.set_context(index) - self.body_widget.setVisible(not show) - self.header_widget.setVisible(not show) - + self.pages_widget.setVisible(not show) self.perspective_widget.setVisible(show) - self.terminal_filters_widget.setVisible(show) + self.footer_items_visibility() def change_toggleability(self, enable_value): for plugin_item in self.plugin_model.plugin_items.values(): @@ -603,25 +621,235 @@ class Window(QtWidgets.QDialog): self.update_compatibility() def on_tab_changed(self, target): - self.comment_main_widget.setVisible(not target == "terminal") - self.terminal_filters_widget.setVisible(target == "terminal") - - for name, page in self.pages.items(): - if name != target: - page.hide() - - self.pages[target].show() + previous_page = None + target_page = None + direction = None + for name, page in self.pages: + if name == target: + target_page = page + if direction is None: + direction = -1 + elif name == self.state["current_page"]: + previous_page = page + if direction is None: + direction = 1 + else: + page.setVisible(False) self.state["current_page"] = target + self.slide_page(previous_page, target_page, direction) + + def slide_page(self, previous_page, target_page, direction): + if previous_page is None: + for name, page in self.pages: + for _name, _page in self.pages: + if name != _name: + _page.hide() + page.show() + page.hide() + + if ( + previous_page == target_page + or previous_page is None + ): + if not target_page.isVisible(): + target_page.show() + return + + width = previous_page.frameGeometry().width() + offset = QtCore.QPoint(direction * width, 0) + + previous_rect = ( + previous_page.frameGeometry().x(), + previous_page.frameGeometry().y(), + width, + previous_page.frameGeometry().height() + ) + curr_pos = previous_page.pos() + + previous_page.hide() + target_page.show() + target_page.update() + target_rect = ( + target_page.frameGeometry().x(), + target_page.frameGeometry().y(), + target_page.frameGeometry().width(), + target_page.frameGeometry().height() + ) + previous_page.show() + + target_page.raise_() + previous_page.setGeometry(*previous_rect) + target_page.setGeometry(*target_rect) + + target_page.move(curr_pos + offset) + + duration = 450 + + anim_old = QtCore.QPropertyAnimation( + previous_page, b"pos", self + ) + anim_old.setDuration(duration) + anim_old.setStartValue(curr_pos) + anim_old.setEndValue(curr_pos - offset) + anim_old.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim_new = QtCore.QPropertyAnimation( + target_page, b"pos", self + ) + anim_new.setDuration(duration) + anim_new.setStartValue(curr_pos + offset) + anim_new.setEndValue(curr_pos) + anim_new.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim_group = QtCore.QParallelAnimationGroup(self) + anim_group.addAnimation(anim_old) + anim_group.addAnimation(anim_new) + + def slide_finished(): + previous_page.hide() + self.footer_items_visibility() + + anim_group.finished.connect(slide_finished) + anim_group.start() + + def footer_items_visibility( + self, + comment_visible=None, + terminal_filters_visibile=None + ): + target = self.state["current_page"] + comment_visibility = ( + not target == "terminal" + and self.comment_box.isEnabled() + ) + terminal_filters_visibility = target == "terminal" + + if comment_visible is not None and comment_visibility: + comment_visibility = comment_visible + + if ( + terminal_filters_visibile is not None + and terminal_filters_visibility + ): + terminal_filters_visibility = terminal_filters_visibile + + duration = 150 + + hiding_widgets = [] + showing_widgets = [] + if (comment_visibility != ( + self.comment_main_widget.isVisible() + )): + if self.comment_main_widget.isVisible(): + hiding_widgets.append(self.comment_main_widget) + else: + showing_widgets.append(self.comment_main_widget) + + if (terminal_filters_visibility != ( + self.terminal_filters_widget.isVisible() + )): + if self.terminal_filters_widget.isVisible(): + hiding_widgets.append(self.terminal_filters_widget) + else: + showing_widgets.append(self.terminal_filters_widget) + + if not hiding_widgets and not showing_widgets: + return + + hiding_widgets_queue = Queue() + showing_widgets_queue = Queue() + widgets_by_pos_y = {} + for widget in hiding_widgets: + key = widget.mapToGlobal(widget.rect().topLeft()).x() + widgets_by_pos_y[key] = widget + + for key in sorted(widgets_by_pos_y.keys()): + widget = widgets_by_pos_y[key] + hiding_widgets_queue.put((widget, )) + + for widget in hiding_widgets: + widget.hide() + + for widget in showing_widgets: + widget.show() + + self.footer_widget.updateGeometry() + widgets_by_pos_y = {} + for widget in showing_widgets: + key = widget.mapToGlobal(widget.rect().topLeft()).x() + widgets_by_pos_y[key] = widget + + for key in reversed(sorted(widgets_by_pos_y.keys())): + widget = widgets_by_pos_y[key] + showing_widgets_queue.put(widget) + + for widget in showing_widgets: + widget.hide() + + for widget in hiding_widgets: + widget.show() + + def process_showing(): + if showing_widgets_queue.empty(): + return + + widget = showing_widgets_queue.get() + widget.show() + + widget_rect = widget.frameGeometry() + second_rect = QtCore.QRect(widget_rect) + second_rect.setTopLeft(second_rect.bottomLeft()) + + animation = QtCore.QPropertyAnimation( + widget, b"geometry", self + ) + animation.setDuration(duration) + animation.setStartValue(second_rect) + animation.setEndValue(widget_rect) + animation.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + animation.finished.connect(process_showing) + animation.start() + + def process_hiding(): + if hiding_widgets_queue.empty(): + return process_showing() + + item = hiding_widgets_queue.get() + if isinstance(item, tuple): + widget = item[0] + hiding_widgets_queue.put(widget) + widget_rect = widget.frameGeometry() + second_rect = QtCore.QRect(widget_rect) + second_rect.setTopLeft(second_rect.bottomLeft()) + + anim = QtCore.QPropertyAnimation( + widget, b"geometry", self + ) + anim.setDuration(duration) + anim.setStartValue(widget_rect) + anim.setEndValue(second_rect) + anim.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim.finished.connect(process_hiding) + anim.start() + else: + item.hide() + return process_hiding() + + process_hiding() def on_validate_clicked(self): self.comment_box.setEnabled(False) + self.footer_items_visibility() self.intent_box.setEnabled(False) self.validate() def on_play_clicked(self): self.comment_box.setEnabled(False) + self.footer_items_visibility() self.intent_box.setEnabled(False) self.publish() @@ -644,7 +872,7 @@ class Window(QtWidgets.QDialog): def apply_log_suspend_value(self, value): self._suspend_logs = value if self.state["current_page"] == "terminal": - self.on_tab_changed("overview") + self.tabs["overview"].setChecked(True) self.tabs["terminal"].setVisible(not self._suspend_logs) @@ -753,6 +981,7 @@ class Window(QtWidgets.QDialog): comment = self.controller.context.data.get("comment") self.comment_box.setText(comment or None) self.comment_box.setEnabled(True) + self.footer_items_visibility() if self.intent_model.has_items: self.on_intent_changed() From a7f7efa470896c618b17861bbf6d9363e1954ba3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:57:51 +0200 Subject: [PATCH 62/91] fixed terminal visibility for perspective widget --- pype/tools/pyblish_pype/window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 0365962dc2..9c22e41c43 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -720,10 +720,14 @@ class Window(QtWidgets.QDialog): ): target = self.state["current_page"] comment_visibility = ( - not target == "terminal" + not self.perspective_widget.isVisible() + and not target == "terminal" and self.comment_box.isEnabled() ) - terminal_filters_visibility = target == "terminal" + terminal_filters_visibility = ( + target == "terminal" + or self.perspective_widget.isVisible() + ) if comment_visible is not None and comment_visibility: comment_visibility = comment_visible From 91dde033a9c56353d160bacc59c4b55d7fd2de05 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 4 Oct 2020 09:57:59 +0200 Subject: [PATCH 63/91] lowered animation duration --- pype/tools/pyblish_pype/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 9c22e41c43..88e3a75af1 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -684,7 +684,7 @@ class Window(QtWidgets.QDialog): target_page.move(curr_pos + offset) - duration = 450 + duration = 250 anim_old = QtCore.QPropertyAnimation( previous_page, b"pos", self From e435b96b1f94f5ec036e5c309649a4152658e110 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 Oct 2020 11:12:40 +0200 Subject: [PATCH 64/91] fix(nuke): nukes bug workaround animation keys --- pype/plugins/nuke/load/load_camera_abc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py index 95ebb65005..12042ce215 100644 --- a/pype/plugins/nuke/load/load_camera_abc.py +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -50,12 +50,15 @@ class AlembicCameraLoader(api.Loader): inpanel=False ) camera_node.forceValidate() - # camera_node["read_from_file"].setValue(True) - # camera_node["file"].setValue(file) camera_node["frame_rate"].setValue(float(fps)) camera_node["tile_color"].setValue(int("0x3469ffff", 16)) - camera_node["reload"].execute() + # workaround because nuke's bug is not adding animation keys properly + nuke.nodeCopy("%clipboard%") + camera_node_name = camera_node["name"].value() + nuke.delete(camera_node) + nuke.nodePaste("%clipboard%") + camera_node = nuke.toNode(camera_node_name) return containerise( node=camera_node, From 73a8581825df437859789f05c8f63eedd095d355 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 12:30:37 +0200 Subject: [PATCH 65/91] fixed animations in instance view --- pype/tools/pyblish_pype/view.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 542d95bf05..b4a7d1fe5b 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -160,6 +160,7 @@ class InstanceView(OverviewView): def __init__(self, parent=None): super(InstanceView, self).__init__(parent) self.viewport().setMouseTracking(True) + self.clicked.connect(self.item_expand) def mouseMoveEvent(self, event): index = self.indexAt(event.pos()) @@ -244,11 +245,11 @@ class InstanceView(OverviewView): elif event.pos().x() > self.width() - 20: self.show_perspective.emit(index) else: - if event.pos().x() < EXPANDER_WIDTH: - self.item_expand(index) - else: + if event.pos().x() >= EXPANDER_WIDTH: self.group_toggle(index) self.item_expand(index, True) + event.accept() + return True return super(InstanceView, self).mouseReleaseEvent(event) From 922ec77ea6ce84ee4803cdf9d07ad87c0e55e454 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 Oct 2020 13:27:43 +0200 Subject: [PATCH 66/91] fix(nuke): repair workaround node position and name --- pype/plugins/nuke/load/load_camera_abc.py | 46 +++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py index 12042ce215..58a4d24b36 100644 --- a/pype/plugins/nuke/load/load_camera_abc.py +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -1,6 +1,8 @@ -from avalon import api +from avalon import api, io +from avalon.nuke import lib as anlib +from avalon.nuke import containerise, update_container import nuke -from pprint import pformat + class AlembicCameraLoader(api.Loader): """ @@ -13,12 +15,9 @@ class AlembicCameraLoader(api.Loader): label = "Load Alembic Camera" icon = "camera" color = "orange" + node_color = "0x3469ffff" def load(self, context, name, namespace, data): - - # import dependencies - from avalon.nuke import containerise - # get main variables version = context['version'] version_data = version.get("data", {}) @@ -44,21 +43,28 @@ class AlembicCameraLoader(api.Loader): # getting file path file = self.fname.replace("\\", "/") - camera_node = nuke.createNode( - "Camera2", - "file {} read_from_file True".format(file), - inpanel=False - ) - camera_node.forceValidate() - camera_node["frame_rate"].setValue(float(fps)) - camera_node["tile_color"].setValue(int("0x3469ffff", 16)) + with anlib.maintained_selection(): + camera_node = nuke.createNode( + "Camera2", + "name {} file {} read_from_file True".format( + object_name, file), + inpanel=False + ) + camera_node.forceValidate() + camera_node["frame_rate"].setValue(float(fps)) - # workaround because nuke's bug is not adding animation keys properly - nuke.nodeCopy("%clipboard%") - camera_node_name = camera_node["name"].value() - nuke.delete(camera_node) - nuke.nodePaste("%clipboard%") - camera_node = nuke.toNode(camera_node_name) + # workaround because nuke's bug is not adding + # animation keys properly + xpos = camera_node.xpos() + ypos = camera_node.ypos() + nuke.nodeCopy("%clipboard%") + nuke.delete(camera_node) + nuke.nodePaste("%clipboard%") + camera_node = nuke.toNode(object_name) + camera_node.setXYpos(xpos, ypos) + + # color node by correct color by actual version + self.node_version_color(version, camera_node) return containerise( node=camera_node, From b6388d37843cd77bf38b9e7800e3e4bef3d11902 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 Oct 2020 13:28:04 +0200 Subject: [PATCH 67/91] feat(nuke): adding update and remove actions --- pype/plugins/nuke/load/load_camera_abc.py | 111 ++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py index 58a4d24b36..d8ca125f7f 100644 --- a/pype/plugins/nuke/load/load_camera_abc.py +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -73,3 +73,114 @@ class AlembicCameraLoader(api.Loader): context=context, loader=self.__class__.__name__, data=data_imprint) + + def update(self, container, representation): + """ + Called by Scene Inventory when look should be updated to current + version. + If any reference edits cannot be applied, eg. shader renamed and + material not present, reference is unloaded and cleaned. + All failed edits are highlighted to the user via message box. + + Args: + container: object that has look to be updated + representation: (dict): relationship data to get proper + representation from DB and persisted + data in .json + Returns: + None + """ + # Get version from io + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + object_name = container['objectName'] + # get corresponding node + camera_node = nuke.toNode(object_name) + + # get main variables + version_data = version.get("data", {}) + vname = version.get("name", None) + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + fps = version_data.get("fps") or nuke.root()["fps"].getValue() + + # prepare data for imprinting + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["source", "author", "fps"] + + data_imprint = {"frameStart": first, + "frameEnd": last, + "version": vname, + "objectName": object_name} + + for k in add_keys: + data_imprint.update({k: version_data[k]}) + + # getting file path + file = api.get_representation_path(representation).replace("\\", "/") + + with anlib.maintained_selection(): + camera_node = nuke.toNode(object_name) + camera_node['selected'].setValue(True) + + # collect input output dependencies + dependencies = camera_node.dependencies() + dependent = camera_node.dependent() + + camera_node["frame_rate"].setValue(float(fps)) + camera_node["file"].setValue(file) + + # workaround because nuke's bug is + # not adding animation keys properly + xpos = camera_node.xpos() + ypos = camera_node.ypos() + nuke.nodeCopy("%clipboard%") + nuke.delete(camera_node) + nuke.nodePaste("%clipboard%") + camera_node = nuke.toNode(object_name) + camera_node.setXYpos(xpos, ypos) + + # link to original input nodes + for i, input in enumerate(dependencies): + camera_node.setInput(i, input) + # link to original output nodes + for d in dependent: + index = next((i for i, dpcy in enumerate( + d.dependencies()) + if camera_node is dpcy), 0) + d.setInput(index, camera_node) + + # color node by correct color by actual version + self.node_version_color(version, camera_node) + + self.log.info("udated to version: {}".format(version.get("name"))) + + return update_container(camera_node, data_imprint) + + def node_version_color(self, version, node): + """ Coloring a node by correct color by actual version + """ + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # change color of node + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd88467ff", 16)) + else: + node["tile_color"].setValue(int(self.node_color, 16)) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from avalon.nuke import viewer_update_and_undo_stop + node = nuke.toNode(container['objectName']) + with viewer_update_and_undo_stop(): + nuke.delete(node) From dc81f5dfac4530cc9222ac742aac9cbd2338f70f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 Oct 2020 13:48:48 +0200 Subject: [PATCH 68/91] fix(nuke): missing arguments to data imprint --- pype/plugins/nuke/load/load_camera_abc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py index d8ca125f7f..377d60e84b 100644 --- a/pype/plugins/nuke/load/load_camera_abc.py +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -110,7 +110,8 @@ class AlembicCameraLoader(api.Loader): # add additional metadata from the version to imprint to Avalon knob add_keys = ["source", "author", "fps"] - data_imprint = {"frameStart": first, + data_imprint = {"representation": str(representation["_id"]), + "frameStart": first, "frameEnd": last, "version": vname, "objectName": object_name} From ac0ef2a94a9e615106e60705640e5ab1f759097c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 Oct 2020 13:49:04 +0200 Subject: [PATCH 69/91] clean(nuke): spaces --- pype/plugins/nuke/load/load_backdrop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/nuke/load/load_backdrop.py b/pype/plugins/nuke/load/load_backdrop.py index 66f9a8e1c1..7d18893965 100644 --- a/pype/plugins/nuke/load/load_backdrop.py +++ b/pype/plugins/nuke/load/load_backdrop.py @@ -240,7 +240,6 @@ class LoadBackdropNodes(api.Loader): return update_container(GN, data_imprint) - def switch(self, container, representation): self.update(container, representation) From 496a29ba992cd2071612b649e1b4204e14e3503f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 5 Oct 2020 13:58:57 +0200 Subject: [PATCH 70/91] fixing harmony js function signatures --- pype/hosts/harmony/__init__.py | 32 ++++++++++++------- pype/plugins/harmony/create/create_render.py | 7 ++-- pype/plugins/harmony/load/load_audio.py | 8 ++--- pype/plugins/harmony/load/load_background.py | 8 ++--- .../harmony/load/load_imagesequence.py | 7 ++-- .../harmony/load/load_template_workfile.py | 7 ++-- .../harmony/publish/collect_current_file.py | 7 ++-- .../harmony/publish/collect_palettes.py | 7 ++-- .../harmony/publish/extract_palette.py | 7 ++-- .../plugins/harmony/publish/extract_render.py | 14 ++++---- .../harmony/publish/extract_template.py | 22 +++++++------ .../harmony/publish/extract_workfile.py | 6 ++-- .../plugins/harmony/publish/validate_audio.py | 13 +++++--- .../publish/validate_scene_settings.py | 11 ++++--- 14 files changed, 89 insertions(+), 67 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index a6a3310374..6721939c7e 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -14,7 +14,9 @@ signature = str(uuid4()) def set_scene_settings(settings): - func = """function %s_func(args) + + signature = harmony.signature("set_scene_settings") + func = """function %s(args) { if (args[0]["fps"]) { @@ -41,7 +43,7 @@ def set_scene_settings(settings): ) } } - %s_func + %s """ % (signature, signature) harmony.send({"function": func, "args": [settings]}) @@ -62,7 +64,7 @@ def get_asset_settings(): "resolutionHeight": resolution_height } - harmony_config = config.get_presets().["harmony"]["general"] + harmony_config = config.get_presets()["harmony"]["general"] skip_resolution_check = harmony_config.get(["skip_resolution_check"], []) if os.getenv('AVALON_TASK') in skip_resolution_check: @@ -121,15 +123,17 @@ def check_inventory(): outdated_containers.append(container) # Colour nodes. - func = """function %s_func(args){ + sig = harmony.signature("set_color") + func = """function %s(args){ + for( var i =0; i <= args[0].length - 1; ++i) { var red_color = new ColorRGBA(255, 0, 0, 255); node.setColor(args[0][i], red_color); } } - %s_func - """ % (signature, signature) + %s + """ % (sig, sig) outdated_nodes = [] for container in outdated_containers: if container["loader"] == "ImageSequenceLoader": @@ -158,7 +162,9 @@ def application_launch(): def export_template(backdrops, nodes, filepath): - func = """function %s_func(args) + + sig = harmony.signature("set_color") + func = """function %s(args) { var temp_node = node.add("Top", "temp_note", "NOTE", 0, 0, 0); @@ -193,8 +199,8 @@ def export_template(backdrops, nodes, filepath): Action.perform("onActionUpToParent()", "Node View"); node.deleteNode(template_group, true, true); } - %s_func - """ % (signature, signature) + %s + """ % (sig, sig) harmony.send({ "function": func, "args": [ @@ -235,12 +241,14 @@ def install(): def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle node enabling on instance toggles.""" - func = """function %s_func(args) + + sig = harmony.signature("enable_node") + func = """function %s(args) { node.setEnable(args[0], args[1]) } - %s_func - """ % (signature, signature) + %s + """ % (sig, sig) try: harmony.send( {"function": func, "args": [instance[0], new_value]} diff --git a/pype/plugins/harmony/create/create_render.py b/pype/plugins/harmony/create/create_render.py index 493c585a09..a94e395241 100644 --- a/pype/plugins/harmony/create/create_render.py +++ b/pype/plugins/harmony/create/create_render.py @@ -13,13 +13,14 @@ class CreateRender(harmony.Creator): super(CreateRender, self).__init__(*args, **kwargs) def setup_node(self, node): - func = """function func(args) + sig = harmony.signature() + func = """function %s(args) { node.setTextAttr(args[0], "DRAWING_TYPE", 1, "PNG4"); node.setTextAttr(args[0], "DRAWING_NAME", 1, args[1]); node.setTextAttr(args[0], "MOVIE_PATH", 1, args[1]); } - func - """ + %s + """ % (sig, sig) path = "{0}/{0}".format(node.split("/")[-1]) harmony.send({"function": func, "args": [node, path]}) diff --git a/pype/plugins/harmony/load/load_audio.py b/pype/plugins/harmony/load/load_audio.py index 600791e61a..71ce5b30e0 100644 --- a/pype/plugins/harmony/load/load_audio.py +++ b/pype/plugins/harmony/load/load_audio.py @@ -1,6 +1,6 @@ from avalon import api, harmony - +sig = harmony.signature() func = """ function getUniqueColumnName( column_prefix ) { @@ -18,14 +18,14 @@ function getUniqueColumnName( column_prefix ) return column_name; } -function func(args) +function %s(args) { var uniqueColumnName = getUniqueColumnName(args[0]); column.add(uniqueColumnName , "SOUND"); column.importSound(uniqueColumnName, 1, args[1]); } -func -""" +%s +""" % (sig, sig) class ImportAudioLoader(api.Loader): diff --git a/pype/plugins/harmony/load/load_background.py b/pype/plugins/harmony/load/load_background.py index f96fc275be..b1be9389e3 100644 --- a/pype/plugins/harmony/load/load_background.py +++ b/pype/plugins/harmony/load/load_background.py @@ -324,9 +324,9 @@ class BackgroundLoader(api.Loader): )["result"] container['nodes'].append(read_node) - # Colour node. - func = """function func(args){ + sig = harmony.signature("set_color") + func = """function %s(args){ for( var i =0; i <= args[0].length - 1; ++i) { var red_color = new ColorRGBA(255, 0, 0, 255); @@ -339,8 +339,8 @@ class BackgroundLoader(api.Loader): } } } - func - """ + %s + """ % (sig, sig) if pype.lib.is_latest(representation): harmony.send({"function": func, "args": [node, "green"]}) else: diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index c5f50a7d23..056d5554ad 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -301,7 +301,8 @@ class ImageSequenceLoader(api.Loader): ) # Colour node. - func = """function func(args){ + sig = harmony.signature("copyFile") + func = """function %s(args){ for( var i =0; i <= args[0].length - 1; ++i) { var red_color = new ColorRGBA(255, 0, 0, 255); @@ -314,8 +315,8 @@ class ImageSequenceLoader(api.Loader): } } } - func - """ + %s + """ % (sig, sig) if pype.lib.is_latest(representation): harmony.send({"function": func, "args": [node, "green"]}) else: diff --git a/pype/plugins/harmony/load/load_template_workfile.py b/pype/plugins/harmony/load/load_template_workfile.py index 3e79cc1903..1d1fd7f7be 100644 --- a/pype/plugins/harmony/load/load_template_workfile.py +++ b/pype/plugins/harmony/load/load_template_workfile.py @@ -21,15 +21,16 @@ class ImportTemplateLoader(api.Loader): with zipfile.ZipFile(zip_file, "r") as zip_ref: zip_ref.extractall(template_path) - func = """function func(args) + sig = harmony.signature("paste") + func = """function %s(args) { var template_path = args[0]; var drag_object = copyPaste.pasteTemplateIntoGroup( template_path, "Top", 1 ); } - func - """ + %s + """ % (sig, sig) harmony.send({"function": func, "args": [template_path]}) diff --git a/pype/plugins/harmony/publish/collect_current_file.py b/pype/plugins/harmony/publish/collect_current_file.py index aab66c2b62..40c154e847 100644 --- a/pype/plugins/harmony/publish/collect_current_file.py +++ b/pype/plugins/harmony/publish/collect_current_file.py @@ -13,15 +13,16 @@ class CollectCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" - func = """function func() + sig = harmony.signature() + func = """function %s() { return ( scene.currentProjectPath() + "/" + scene.currentVersionName() + ".xstage" ); } - func - """ + %s + """ % (sig, sig) current_file = harmony.send({"function": func})["result"] context.data["currentFile"] = os.path.normpath(current_file) diff --git a/pype/plugins/harmony/publish/collect_palettes.py b/pype/plugins/harmony/publish/collect_palettes.py index 2a2c1066c0..dc573c381f 100644 --- a/pype/plugins/harmony/publish/collect_palettes.py +++ b/pype/plugins/harmony/publish/collect_palettes.py @@ -13,7 +13,8 @@ class CollectPalettes(pyblish.api.ContextPlugin): hosts = ["harmony"] def process(self, context): - func = """function func() + sig = harmony.signature() + func = """function %s() { var palette_list = PaletteObjectManager.getScenePaletteList(); @@ -26,8 +27,8 @@ class CollectPalettes(pyblish.api.ContextPlugin): return palettes; } - func - """ + %s + """ % (sig, sig) palettes = harmony.send({"function": func})["result"] for name, id in palettes.items(): diff --git a/pype/plugins/harmony/publish/extract_palette.py b/pype/plugins/harmony/publish/extract_palette.py index 9bca005278..9b5f1f5dc9 100644 --- a/pype/plugins/harmony/publish/extract_palette.py +++ b/pype/plugins/harmony/publish/extract_palette.py @@ -13,14 +13,15 @@ class ExtractPalette(pype.api.Extractor): families = ["harmony.palette"] def process(self, instance): - func = """function func(args) + sig = harmony.signature() + func = """function %s(args) { var palette_list = PaletteObjectManager.getScenePaletteList(); var palette = palette_list.getPaletteById(args[0]); return (palette.getPath() + "/" + palette.getName() + ".plt"); } - func - """ + %s + """ % (sig, sig) palette_file = harmony.send( {"function": func, "args": [instance.data["id"]]} )["result"] diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 70dceb9ca2..10e6b05bea 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -21,7 +21,8 @@ class ExtractRender(pyblish.api.InstancePlugin): def process(self, instance): # Collect scene data. - func = """function func(write_node) + sig = harmony.signature() + func = """function %s(write_node) { return [ about.getApplicationPath(), @@ -33,8 +34,8 @@ class ExtractRender(pyblish.api.InstancePlugin): sound.getSoundtrackAll().path() ] } - func - """ + %s + """ % (sig, sig) result = harmony.send( {"function": func, "args": [instance[0]]} )["result"] @@ -50,12 +51,13 @@ class ExtractRender(pyblish.api.InstancePlugin): # Set output path to temp folder. path = tempfile.mkdtemp() - func = """function func(args) + sig = harmony.signature() + func = """function %s(args) { node.setTextAttr(args[0], "DRAWING_NAME", 1, args[1]); } - func - """ + %s + """ % (sig, sig) result = harmony.send( { "function": func, diff --git a/pype/plugins/harmony/publish/extract_template.py b/pype/plugins/harmony/publish/extract_template.py index 1ba0befc54..d6851e4027 100644 --- a/pype/plugins/harmony/publish/extract_template.py +++ b/pype/plugins/harmony/publish/extract_template.py @@ -2,7 +2,7 @@ import os import shutil import pype.api -import avalon.harmony +from avalon import harmony import pype.hosts.harmony @@ -30,7 +30,7 @@ class ExtractTemplate(pype.api.Extractor): unique_backdrops = [backdrops[x] for x in set(backdrops.keys())] # Get non-connected nodes within backdrops. - all_nodes = avalon.harmony.send( + all_nodes = harmony.send( {"function": "node.subNodes", "args": ["Top"]} )["result"] for node in [x for x in all_nodes if x not in dependencies]: @@ -66,7 +66,8 @@ class ExtractTemplate(pype.api.Extractor): instance.data["representations"] = [representation] def get_backdrops(self, node): - func = """function func(probe_node) + sig = harmony.signature() + func = """function %s(probe_node) { var backdrops = Backdrop.backdrops("Top"); var valid_backdrops = []; @@ -92,14 +93,15 @@ class ExtractTemplate(pype.api.Extractor): } return valid_backdrops; } - func - """ - return avalon.harmony.send( + %s + """ % (sig, sig) + return harmony.send( {"function": func, "args": [node]} )["result"] def get_dependencies(self, node, dependencies): - func = """function func(args) + sig = harmony.signature() + func = """function %s(args) { var target_node = args[0]; var numInput = node.numberOfInputPorts(target_node); @@ -110,10 +112,10 @@ class ExtractTemplate(pype.api.Extractor): } return dependencies; } - func - """ + %s + """ % (sig, sig) - current_dependencies = avalon.harmony.send( + current_dependencies = harmony.send( {"function": func, "args": [node]} )["result"] diff --git a/pype/plugins/harmony/publish/extract_workfile.py b/pype/plugins/harmony/publish/extract_workfile.py index 304b70e293..3c5af11021 100644 --- a/pype/plugins/harmony/publish/extract_workfile.py +++ b/pype/plugins/harmony/publish/extract_workfile.py @@ -2,7 +2,7 @@ import os import shutil import pype.api -import avalon.harmony +from avalon import harmony import pype.hosts.harmony @@ -15,10 +15,10 @@ class ExtractWorkfile(pype.api.Extractor): def process(self, instance): # Export template. - backdrops = avalon.harmony.send( + backdrops = harmony.send( {"function": "Backdrop.backdrops", "args": ["Top"]} )["result"] - nodes = avalon.harmony.send( + nodes = harmony.send( {"function": "node.subNodes", "args": ["Top"]} )["result"] staging_dir = self.staging_dir(instance) diff --git a/pype/plugins/harmony/publish/validate_audio.py b/pype/plugins/harmony/publish/validate_audio.py index ba113e7610..cc8d2cdc35 100644 --- a/pype/plugins/harmony/publish/validate_audio.py +++ b/pype/plugins/harmony/publish/validate_audio.py @@ -1,14 +1,17 @@ -import json import os import pyblish.api -import avalon.harmony -import pype.hosts.harmony +from avalon import harmony class ValidateAudio(pyblish.api.InstancePlugin): - """Ensures that there is an audio file in the scene. If you are sure that you want to send render without audio, you can disable this validator before clicking on "publish" """ + """Ensures that there is an audio file in the scene. + + If you are sure that you want to send render without audio, you can + disable this validator before clicking on "publish" + + """ order = pyblish.api.ValidatorOrder label = "Validate Audio" @@ -26,7 +29,7 @@ class ValidateAudio(pyblish.api.InstancePlugin): } func """ - result = avalon.harmony.send( + result = harmony.send( {"function": func, "args": [instance[0]]} )["result"] diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index d7895804bd..fbeedeab77 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -2,7 +2,7 @@ import json import pyblish.api -import avalon.harmony +from avalon import harmony import pype.hosts.harmony @@ -46,7 +46,8 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): for string in self.frame_check_filter): expected_settings.pop("frameEnd") - func = """function func() + sig = harmony.signature() + func = """function %s() { return { "fps": scene.getFrameRate(), @@ -56,9 +57,9 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): "resolutionHeight": scene.defaultResolutionY() }; } - func - """ - current_settings = avalon.harmony.send({"function": func})["result"] + %s + """ % (sig, sig) + current_settings = harmony.send({"function": func})["result"] invalid_settings = [] for key, value in expected_settings.items(): From 05eeca0307adbb61010cdc76660891a39351df85 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 19:11:40 +0200 Subject: [PATCH 71/91] validate index --- pype/tools/pyblish_pype/view.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index b4a7d1fe5b..11a6217712 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -178,6 +178,8 @@ class InstanceView(OverviewView): self.collapse(index) def group_toggle(self, index): + if not index.isValid(): + return model = index.model() chilren_indexes_checked = [] From b84ad5449f342997301d8ac2569cced51aaba0d9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 19:11:51 +0200 Subject: [PATCH 72/91] moved variable definition --- pype/tools/pyblish_pype/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 11a6217712..56c396d027 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -214,8 +214,8 @@ class InstanceView(OverviewView): ): if event.pos().x() < 20: indexes = self.selectionModel().selectedIndexes() + any_checked = False if pos_index in indexes: - any_checked = False for index in indexes: if index.data(QtCore.Qt.CheckStateRole): any_checked = True From 81a0aab10bc80c70080f2aaae7f5fec06c2b8c30 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 19:12:05 +0200 Subject: [PATCH 73/91] clicked signal not used --- pype/tools/pyblish_pype/view.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 56c396d027..b8fc41b0b2 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -160,7 +160,6 @@ class InstanceView(OverviewView): def __init__(self, parent=None): super(InstanceView, self).__init__(parent) self.viewport().setMouseTracking(True) - self.clicked.connect(self.item_expand) def mouseMoveEvent(self, event): index = self.indexAt(event.pos()) From 939b03d74c0de0b65bfcb305f2639701799e959b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 19:54:38 +0200 Subject: [PATCH 74/91] fixed mouse press/release in instance view --- pype/tools/pyblish_pype/view.py | 125 +++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index b8fc41b0b2..6ecc4dfdc7 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -160,6 +160,8 @@ class InstanceView(OverviewView): def __init__(self, parent=None): super(InstanceView, self).__init__(parent) self.viewport().setMouseTracking(True) + self._pressed_group_index = None + self._pressed_expander = None def mouseMoveEvent(self, event): index = self.indexAt(event.pos()) @@ -204,53 +206,90 @@ class InstanceView(OverviewView): model.setData(index, new_state, QtCore.Qt.CheckStateRole) self.toggled.emit(index, new_state) + def _mouse_press(self, event): + if event.button() != QtCore.Qt.LeftButton: + return + + self._pressed_group_index = None + self._pressed_expander = None + + pos_index = self.indexAt(event.pos()) + if not pos_index.isValid(): + return + + if pos_index.data(Roles.TypeRole) != model.InstanceType: + self._pressed_group_index = pos_index + if event.pos().x() < 20: + self._pressed_expander = True + else: + self._pressed_expander = False + + elif event.pos().x() < 20: + indexes = self.selectionModel().selectedIndexes() + any_checked = False + if pos_index in indexes: + for index in indexes: + if index.data(QtCore.Qt.CheckStateRole): + any_checked = True + break + + for index in indexes: + self.toggled.emit(index, not any_checked) + return True + self.toggled.emit(pos_index, not any_checked) + + elif event.pos().x() > self.width() - 20: + self.show_perspective.emit(pos_index) + def mousePressEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - pos_index = self.indexAt(event.pos()) - if ( - pos_index.isValid() - and pos_index.data(Roles.TypeRole) == model.InstanceType - ): - if event.pos().x() < 20: - indexes = self.selectionModel().selectedIndexes() - any_checked = False - if pos_index in indexes: - for index in indexes: - if index.data(QtCore.Qt.CheckStateRole): - any_checked = True - break - - for index in indexes: - self.toggled.emit(index, not any_checked) - return - - else: - self.toggled.emit(pos_index, not any_checked) - - elif event.pos().x() > self.width() - 20: - self.show_perspective.emit(pos_index) - + if self._mouse_press(event): + return return super(InstanceView, self).mousePressEvent(event) - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: + def _mouse_release(self, event, pressed_expander, pressed_index): + if event.button() != QtCore.Qt.LeftButton: + return + + pos_index = self.indexAt(event.pos()) + if not pos_index.isValid(): + return + + if pos_index.data(Roles.TypeRole) == model.InstanceType: indexes = self.selectionModel().selectedIndexes() - if len(indexes) == 1: - index = indexes[0] - pos_index = self.indexAt(event.pos()) - if index == pos_index: - # If instance or Plugin - if index.data(Roles.TypeRole) == model.InstanceType: - if event.pos().x() < 20: - self.toggled.emit(index, None) - elif event.pos().x() > self.width() - 20: - self.show_perspective.emit(index) - else: - if event.pos().x() >= EXPANDER_WIDTH: - self.group_toggle(index) - self.item_expand(index, True) - event.accept() - return True + if len(indexes) == 1 and indexes[0] == pos_index: + if event.pos().x() < 20: + self.toggled.emit(indexes[0], None) + elif event.pos().x() > self.width() - 20: + self.show_perspective.emit(indexes[0]) + return True + return + + if pressed_index != pos_index: + return + + if self.state() == QtWidgets.QTreeView.State.DragSelectingState: + indexes = self.selectionModel().selectedIndexes() + if len(indexes) != 1 or indexes[0] != pos_index: + return + + if event.pos().x() < EXPANDER_WIDTH: + if pressed_expander is True: + self.item_expand(pos_index) + return True + else: + if pressed_expander is False: + self.group_toggle(pos_index) + self.item_expand(pos_index, True) + return True + + def mouseReleaseEvent(self, event): + pressed_index = self._pressed_group_index + pressed_expander = self._pressed_expander is True + self._pressed_group_index = None + self._pressed_expander = None + result = self._mouse_release(event, pressed_expander, pressed_index) + if result: + return return super(InstanceView, self).mouseReleaseEvent(event) From 6a6071eb87c1c415073a64d8e4b881e6934606c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 20:11:38 +0200 Subject: [PATCH 75/91] added application icon to standalone publisher tool --- pype/tools/standalonepublish/__main__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py index aba8e6c0a4..85a574f8dc 100644 --- a/pype/tools/standalonepublish/__main__.py +++ b/pype/tools/standalonepublish/__main__.py @@ -1,15 +1,26 @@ import os import sys import app +import ctypes import signal -from Qt import QtWidgets +from Qt import QtWidgets, QtGui from avalon import style +from pype.api import resources if __name__ == "__main__": + + # Allow to change icon of running process in windows taskbar + if os.name == "nt": + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + u"standalonepublish" + ) + qt_app = QtWidgets.QApplication([]) # app.setQuitOnLastWindowClosed(False) qt_app.setStyleSheet(style.load_stylesheet()) + icon = QtGui.QIcon(resources.pype_icon_filepath()) + qt_app.setWindowIcon(icon) def signal_handler(sig, frame): print("You pressed Ctrl+C. Process ended.") From 855f363ed90e13198016d136cbf25e66cbb52843 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 21:57:40 +0200 Subject: [PATCH 76/91] colect current pype user makes sure user is filled --- pype/plugins/global/publish/collect_current_pype_user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/collect_current_pype_user.py b/pype/plugins/global/publish/collect_current_pype_user.py index 359e6b852c..a8947dd8fb 100644 --- a/pype/plugins/global/publish/collect_current_pype_user.py +++ b/pype/plugins/global/publish/collect_current_pype_user.py @@ -13,7 +13,7 @@ class CollectCurrentUserPype(pyblish.api.ContextPlugin): def process(self, context): user = os.getenv("PYPE_USERNAME", "").strip() if not user: - return + user = context.data.get("user", getpass.getuser()) context.data["user"] = user - self.log.debug("Pype user is \"{}\"".format(user)) + self.log.debug("Colected user \"{}\"".format(user)) From 1b8fba5d8a28e620211229e048717ee809e0dec8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 21:58:07 +0200 Subject: [PATCH 77/91] add `PYPE_USERNAME` to deadline environments so it is filled on farm during publish --- pype/plugins/global/publish/submit_publish_job.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 99f0ae7cb6..d26ec2bf14 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -174,7 +174,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER", "PYPE_METADATA_FILE", "AVALON_PROJECT", - "PYPE_LOG_NO_COLORS" + "PYPE_LOG_NO_COLORS", + "PYPE_USERNAME" ] # custom deadline atributes @@ -297,6 +298,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): environment["PYPE_METADATA_FILE"] = roothless_metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] environment["PYPE_LOG_NO_COLORS"] = "1" + environment["PYPE_USERNAME"] = instance.context.data["user"] try: environment["PYPE_PYTHON_EXE"] = os.environ["PYPE_PYTHON_EXE"] except KeyError: From e0ff9cc04f47a46899f4d03381db80f8dbc5f567 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 5 Oct 2020 23:15:31 +0200 Subject: [PATCH 78/91] fix QApp utilization and minor style changes --- pype/hosts/harmony/__init__.py | 28 +++++++++---------- pype/plugins/harmony/load/load_background.py | 16 ++++++----- .../plugins/harmony/publish/extract_render.py | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 6721939c7e..fbf5ca6f12 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -1,6 +1,5 @@ import os import sys -from uuid import uuid4 from avalon import api, io, harmony from avalon.vendor import Qt @@ -10,9 +9,6 @@ from pype import lib from pype.api import config -signature = str(uuid4()) - - def set_scene_settings(settings): signature = harmony.signature("set_scene_settings") @@ -64,9 +60,12 @@ def get_asset_settings(): "resolutionHeight": resolution_height } - harmony_config = config.get_presets()["harmony"]["general"] + try: + skip_resolution_check = \ + config.get_presets()["harmony"]["general"]["skip_resolution_check"] + except KeyError: + skip_resolution_check = [] - skip_resolution_check = harmony_config.get(["skip_resolution_check"], []) if os.getenv('AVALON_TASK') in skip_resolution_check: scene_data.pop("resolutionWidth") scene_data.pop("resolutionHeight") @@ -86,21 +85,20 @@ def ensure_scene_settings(): valid_settings[key] = value # Warn about missing attributes. - print("Starting new QApplication..") - app = Qt.QtWidgets.QApplication(sys.argv) - - message_box = Qt.QtWidgets.QMessageBox() - message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning) - msg = "Missing attributes:" if invalid_settings: + print("Starting new QApplication..") + app = Qt.QtWidgets.QApplication.instance() + if not app: + app = Qt.QtWidgets.QApplication(sys.argv) + + message_box = Qt.QtWidgets.QMessageBox() + message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning) + msg = "Missing attributes:" for item in invalid_settings: msg += f"\n{item}" message_box.setText(msg) message_box.exec_() - # Garbage collect QApplication. - del app - set_scene_settings(valid_settings) diff --git a/pype/plugins/harmony/load/load_background.py b/pype/plugins/harmony/load/load_background.py index b1be9389e3..5ef4535576 100644 --- a/pype/plugins/harmony/load/load_background.py +++ b/pype/plugins/harmony/load/load_background.py @@ -1,11 +1,9 @@ import os -import uuid - -import clique +import json from avalon import api, harmony import pype.lib -import json + copy_files = """function copyFile(srcFilename, dstFilename) { @@ -256,7 +254,9 @@ class BackgroundLoader(api.Loader): container_nodes = [] for layer in sorted(layers): - file_to_import = [os.path.join(bg_folder, layer).replace("\\", "/")] + file_to_import = [ + os.path.join(bg_folder, layer).replace("\\", "/") + ] read_node = harmony.send( { @@ -301,8 +301,10 @@ class BackgroundLoader(api.Loader): print(container) for layer in sorted(layers): - file_to_import = [os.path.join(bg_folder, layer).replace("\\", "/")] - print(20*"#") + file_to_import = [ + os.path.join(bg_folder, layer).replace("\\", "/") + ] + print(20 * "#") print(f"FILE TO REPLACE: {file_to_import}") print(f"LAYER: {layer}") node = harmony.find_node_by_name(layer, "READ") diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 10e6b05bea..4fd61efbbf 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -91,7 +91,7 @@ class ExtractRender(pyblish.api.InstancePlugin): if len(collections) > 1: for col in collections: if len(list(col)) > 1: - collection = col + collection = col else: collection = collections[0] From 9772bf76607106b3f8a563aa0cf1966595993007 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 6 Oct 2020 14:07:52 +0200 Subject: [PATCH 79/91] add audio to instance.data only if audio file exists --- pype/plugins/harmony/publish/extract_render.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 4fd61efbbf..9d3ae33e23 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -45,8 +45,7 @@ class ExtractRender(pyblish.api.InstancePlugin): frame_start = result[4] frame_end = result[5] audio_path = result[6] - if audio_path: - instance.data["audio"] = [{"filename": audio_path}] + instance.data["fps"] = frame_rate # Set output path to temp folder. @@ -139,6 +138,9 @@ class ExtractRender(pyblish.api.InstancePlugin): } instance.data["representations"] = [representation, thumbnail] + if audio_path and os.path.exists(audio_path): + instance.data["audio"] = [{"filename": audio_path}] + # Required for extract_review plugin (L222 onwards). instance.data["frameStart"] = frame_start instance.data["frameEnd"] = frame_end From f04301bf5e05d5f808b628afd4cd34379801b3e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 6 Oct 2020 14:33:44 +0200 Subject: [PATCH 80/91] fixed instance toggle --- pype/tools/pyblish_pype/view.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 6ecc4dfdc7..248c1fbbf9 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -227,6 +227,9 @@ class InstanceView(OverviewView): elif event.pos().x() < 20: indexes = self.selectionModel().selectedIndexes() any_checked = False + if len(indexes) <= 1: + return + if pos_index in indexes: for index in indexes: if index.data(QtCore.Qt.CheckStateRole): @@ -238,9 +241,6 @@ class InstanceView(OverviewView): return True self.toggled.emit(pos_index, not any_checked) - elif event.pos().x() > self.width() - 20: - self.show_perspective.emit(pos_index) - def mousePressEvent(self, event): if self._mouse_press(event): return From b08395579fcdc43873489057ab0524de9fd044c9 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 6 Oct 2020 15:04:30 +0200 Subject: [PATCH 81/91] user pype user in deadline submission --- pype/plugins/maya/publish/submit_maya_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 7509b5875a..0ae19cbb81 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -348,7 +348,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): comment = context.data.get("comment", "") dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty - deadline_user = context.data.get("deadlineUser", getpass.getuser()) + deadline_user = context.data.get("user", getpass.getuser()) jobname = "%s - %s" % (filename, instance.name) # Get the variables depending on the renderer @@ -418,7 +418,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Adding file dependencies. dependencies = instance.context.data["fileDependencies"] dependencies.append(filepath) - if self.assembly_files: + if self.asset_dependencies: for dependency in dependencies: key = "AssetDependency" + str(dependencies.index(dependency)) payload_skeleton["JobInfo"][key] = dependency From 04ea72f0a6daff2ebb471abeac412d227532f200 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 16:29:39 +0200 Subject: [PATCH 82/91] Revert "Merge branch 'draft/pyblish_pype_keep_gui_responsive' into feature/451-Fusion_basic_integration" This reverts commit bcb7201ffb7a1d71daa47226b484fa9a71f4c28b. --- pype/tools/pyblish_pype/control.py | 36 ++---------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index d73c7efad7..0162848f2b 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -9,10 +9,8 @@ import os import sys import traceback import inspect -import threading -import six -from Qt import QtCore, QtWidgets +from Qt import QtCore import pyblish.api import pyblish.util @@ -30,27 +28,6 @@ class IterationBreak(Exception): pass -class ProcessThread(threading.Thread): - def __init__(self, plugin, context, instance): - super(ProcessThread, self).__init__() - - self.result = None - self.exception = None - - self.plugin = plugin - self.context = context - self.instance = instance - - def run(self): - try: - result = pyblish.plugin.process( - self.plugin, self.context, self.instance - ) - self.result = result - except Exception: - self.exception = sys.exc_info() - - class Controller(QtCore.QObject): # Emitted when the GUI is about to start processing; # e.g. resetting, validating or publishing. @@ -254,16 +231,7 @@ class Controller(QtCore.QObject): self.processing["nextOrder"] = plugin.order try: - thread = ProcessThread(plugin, self.context, instance) - thread.start() - while thread.isAlive(): - QtWidgets.QApplication.processEvents() - - thread.join() - if thread.exception: - six.reraise(*thread.exception) - - result = thread.result + result = pyblish.plugin.process(plugin, self.context, instance) # Make note of the order at which the # potential error error occured. if result["error"] is not None: From 9819ae08932e4213ad2d7eb3001056a1d80a2cd1 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 6 Oct 2020 16:17:37 +0200 Subject: [PATCH 83/91] fix typo --- pype/hosts/fusion/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py index 668472105e..251b3a8b4f 100644 --- a/pype/hosts/fusion/menu.py +++ b/pype/hosts/fusion/menu.py @@ -75,7 +75,7 @@ class PypeMenu(QtWidgets.QWidget): "Duplicate with input connections", self ) reset_resolution_btn = QtWidgets.QPushButton( - "Reset Resolution from peresets", self + "Reset Resolution from project", self ) layout = QtWidgets.QVBoxLayout(self) From 0eaf09101ec02eb7d5b906c0f849c62c43d0208c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Oct 2020 17:38:19 +0200 Subject: [PATCH 84/91] feat(hiero): renaming host name references --- pype/hosts/{nukestudio => hiero}/__init__.py | 24 +++++++++--------- pype/hosts/{nukestudio => hiero}/events.py | 2 +- pype/hosts/{nukestudio => hiero}/lib.py | 10 ++++---- pype/hosts/{nukestudio => hiero}/menu.py | 4 +-- pype/hosts/{nukestudio => hiero}/tags.json | 0 pype/hosts/{nukestudio => hiero}/tags.py | 4 +-- pype/hosts/{nukestudio => hiero}/workio.py | 4 +-- .../_unused/collect_metadata.py | 2 +- .../_unused/collect_submission.py | 0 .../_unused/collect_timecodes.py | 2 +- .../_unused/collect_workfile_version.py | 0 .../_unused/extract_plates_waiting.py | 2 +- .../_unused/extract_tasks.py | 2 +- .../_unused/subset-representations_logic.txt | 0 .../_unused/validate_projectroot.py | 2 +- .../_unused/validate_resolved_paths.py | 2 +- .../_unused/validate_task.py | 4 +-- .../_unused/validate_track_item.py | 2 +- .../_unused/validate_viewer_lut.py | 10 ++++---- ...load_sequences_to_timeline_asset_origin.py | 2 +- .../publish/collect_active_project.py | 0 .../publish/collect_assetbuilds.py | 2 +- .../publish/collect_audio.py | 2 +- .../publish/collect_calculate_retime.py | 2 +- .../publish/collect_clip_resolution.py | 2 +- .../publish/collect_clips.py | 2 +- .../publish/collect_colorspace.py | 0 .../publish/collect_current_file.py | 0 .../publish/collect_effects.py | 0 .../publish/collect_frame_ranges.py | 2 +- .../publish/collect_framerate.py | 2 +- .../publish/collect_handles.py | 2 +- .../publish/collect_hierarchy_context.py | 2 +- .../publish/collect_host.py | 0 .../publish/collect_host_version.py | 0 .../publish/collect_instance_version.py | 0 .../publish/collect_leader_clip.py | 2 +- .../publish/collect_plates.py | 4 +-- .../publish/collect_remove_clip_instances.py | 2 +- .../publish/collect_reviews.py | 2 +- .../publish/collect_selection.py | 0 .../publish/collect_sequence.py | 2 +- .../publish/collect_shots.py | 2 +- .../publish/collect_tag_comments.py | 2 +- .../publish/collect_tag_framestart.py | 2 +- .../publish/collect_tag_handles.py | 2 +- .../publish/collect_tag_resolution.py | 2 +- .../publish/collect_tag_retime.py | 2 +- .../publish/collect_tag_subsets.py | 2 +- .../publish/collect_tag_tasks.py | 2 +- .../publish/collect_tags.py | 2 +- .../publish/extract_audio.py | 2 +- .../publish/extract_effects.py | 0 .../publish/extract_review_cutup_video.py | 2 +- .../publish/validate_hierarchy.py | 2 +- .../publish/validate_names.py | 2 +- .../publish/version_up_workfile.py | 2 +- .../HieroPlayer/PlayerPresets.hrox | 0 .../Icons/1_add_handles_end.png | Bin .../hiero_plugin_path/Icons/2_add_handles.png | Bin .../hiero_plugin_path/Icons/3D.png | Bin .../Icons/3_add_handles_start.png | Bin .../hiero_plugin_path/Icons/4_2D.png | Bin .../hiero_plugin_path/Icons/edit.png | Bin .../hiero_plugin_path/Icons/fusion.png | Bin .../hiero_plugin_path/Icons/hierarchy.png | Bin .../hiero_plugin_path/Icons/houdini.png | Bin .../hiero_plugin_path/Icons/layers.psd | Bin .../hiero_plugin_path/Icons/lense.png | Bin .../hiero_plugin_path/Icons/lense1.png | Bin .../hiero_plugin_path/Icons/maya.png | Bin .../hiero_plugin_path/Icons/nuke.png | Bin .../hiero_plugin_path/Icons/resolution.png | Bin .../hiero_plugin_path/Icons/resolution.psd | Bin .../hiero_plugin_path/Icons/retiming.png | Bin .../hiero_plugin_path/Icons/retiming.psd | Bin .../hiero_plugin_path/Icons/review.png | Bin .../hiero_plugin_path/Icons/review.psd | Bin .../hiero_plugin_path/Icons/volume.png | Bin .../hiero_plugin_path/Icons/z_layer_bg.png | Bin .../hiero_plugin_path/Icons/z_layer_fg.png | Bin .../hiero_plugin_path/Icons/z_layer_main.png | Bin .../Python/Startup/SpreadsheetExport.py | 0 .../Python/Startup/Startup.py | 19 ++++++++++++++ .../Startup/otioexporter/OTIOExportTask.py | 0 .../Startup/otioexporter/OTIOExportUI.py | 0 .../Python/Startup/otioexporter/__init__.py | 0 .../Python/Startup/project_helpers.py | 0 .../Python/Startup/selection_tracker.py | 0 .../Python/Startup/setFrameRate.py | 0 .../Python/Startup/version_everywhere.py | 0 .../Python/StartupUI/PimpMySpreadsheet.py | 0 .../Python/StartupUI/Purge.py | 0 .../StartupUI/nukeStyleKeyboardShortcuts.py | 0 .../StartupUI/otioimporter/OTIOImport.py | 0 .../Python/StartupUI/otioimporter/__init__.py | 0 .../Python/StartupUI/setPosterFrame.py | 0 .../pipeline.xml | 2 +- .../pipeline.xml | 2 +- .../pipeline.xml | 2 +- .../Python/Startup/Startup.py | 19 -------------- 101 files changed, 90 insertions(+), 90 deletions(-) rename pype/hosts/{nukestudio => hiero}/__init__.py (79%) rename pype/hosts/{nukestudio => hiero}/events.py (98%) rename pype/hosts/{nukestudio => hiero}/lib.py (98%) rename pype/hosts/{nukestudio => hiero}/menu.py (97%) rename pype/hosts/{nukestudio => hiero}/tags.json (100%) rename pype/hosts/{nukestudio => hiero}/tags.py (98%) rename pype/hosts/{nukestudio => hiero}/workio.py (95%) rename pype/plugins/{nukestudio => hiero}/_unused/collect_metadata.py (97%) rename pype/plugins/{nukestudio => hiero}/_unused/collect_submission.py (100%) rename pype/plugins/{nukestudio => hiero}/_unused/collect_timecodes.py (99%) rename pype/plugins/{nukestudio => hiero}/_unused/collect_workfile_version.py (100%) rename pype/plugins/{nukestudio => hiero}/_unused/extract_plates_waiting.py (96%) rename pype/plugins/{nukestudio => hiero}/_unused/extract_tasks.py (99%) rename pype/plugins/{nukestudio => hiero}/_unused/subset-representations_logic.txt (100%) rename pype/plugins/{nukestudio => hiero}/_unused/validate_projectroot.py (97%) rename pype/plugins/{nukestudio => hiero}/_unused/validate_resolved_paths.py (96%) rename pype/plugins/{nukestudio => hiero}/_unused/validate_task.py (97%) rename pype/plugins/{nukestudio => hiero}/_unused/validate_track_item.py (98%) rename pype/plugins/{nukestudio => hiero}/_unused/validate_viewer_lut.py (53%) rename pype/plugins/{nukestudio => hiero}/load/load_sequences_to_timeline_asset_origin.py (97%) rename pype/plugins/{nukestudio => hiero}/publish/collect_active_project.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_assetbuilds.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_audio.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_calculate_retime.py (99%) rename pype/plugins/{nukestudio => hiero}/publish/collect_clip_resolution.py (96%) rename pype/plugins/{nukestudio => hiero}/publish/collect_clips.py (99%) rename pype/plugins/{nukestudio => hiero}/publish/collect_colorspace.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_current_file.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_effects.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_frame_ranges.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_framerate.py (95%) rename pype/plugins/{nukestudio => hiero}/publish/collect_handles.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_hierarchy_context.py (99%) rename pype/plugins/{nukestudio => hiero}/publish/collect_host.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_host_version.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_instance_version.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_leader_clip.py (97%) rename pype/plugins/{nukestudio => hiero}/publish/collect_plates.py (99%) rename pype/plugins/{nukestudio => hiero}/publish/collect_remove_clip_instances.py (94%) rename pype/plugins/{nukestudio => hiero}/publish/collect_reviews.py (99%) rename pype/plugins/{nukestudio => hiero}/publish/collect_selection.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/collect_sequence.py (91%) rename pype/plugins/{nukestudio => hiero}/publish/collect_shots.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_comments.py (97%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_framestart.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_handles.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_resolution.py (95%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_retime.py (97%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_subsets.py (97%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tag_tasks.py (96%) rename pype/plugins/{nukestudio => hiero}/publish/collect_tags.py (96%) rename pype/plugins/{nukestudio => hiero}/publish/extract_audio.py (98%) rename pype/plugins/{nukestudio => hiero}/publish/extract_effects.py (100%) rename pype/plugins/{nukestudio => hiero}/publish/extract_review_cutup_video.py (99%) rename pype/plugins/{nukestudio => hiero}/publish/validate_hierarchy.py (96%) rename pype/plugins/{nukestudio => hiero}/publish/validate_names.py (97%) rename pype/plugins/{nukestudio => hiero}/publish/version_up_workfile.py (95%) rename setup/{nukestudio => hiero}/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/1_add_handles_end.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/2_add_handles.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/3D.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/3_add_handles_start.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/4_2D.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/edit.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/fusion.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/hierarchy.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/houdini.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/layers.psd (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/lense.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/lense1.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/maya.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/nuke.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/resolution.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/resolution.psd (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/retiming.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/retiming.psd (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/review.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/review.psd (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/volume.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/z_layer_bg.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/z_layer_fg.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Icons/z_layer_main.png (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/SpreadsheetExport.py (100%) create mode 100644 setup/hiero/hiero_plugin_path/Python/Startup/Startup.py rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/otioexporter/__init__.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/project_helpers.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/selection_tracker.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/setFrameRate.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/Startup/version_everywhere.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/StartupUI/Purge.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py (100%) rename setup/{nukestudio => hiero}/hiero_plugin_path/Python/StartupUI/setPosterFrame.py (100%) rename setup/{nukestudio/hiero_plugin_path/TaskPresets/11.2 => hiero/hiero_plugin_path/TaskPresets/10.5/Processors}/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml (99%) rename setup/{nukestudio/hiero_plugin_path/TaskPresets/10.5 => hiero/hiero_plugin_path/TaskPresets/11.1}/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml (99%) rename setup/{nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors => hiero/hiero_plugin_path/TaskPresets/11.2}/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml (99%) delete mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py diff --git a/pype/hosts/nukestudio/__init__.py b/pype/hosts/hiero/__init__.py similarity index 79% rename from pype/hosts/nukestudio/__init__.py rename to pype/hosts/hiero/__init__.py index c84b288f4a..50fd39440b 100644 --- a/pype/hosts/nukestudio/__init__.py +++ b/pype/hosts/hiero/__init__.py @@ -31,17 +31,17 @@ __all__ = [ ] # get logger -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") ''' Creating all important host related variables ''' AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") # plugin root path -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "inventory") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "hiero", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "hiero", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "hiero", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "hiero", "inventory") # registering particular pyblish gui but `lite` is recomended!! if os.getenv("PYBLISH_GUI", None): @@ -50,7 +50,7 @@ if os.getenv("PYBLISH_GUI", None): def install(): """ - Installing Nukestudio integration for avalon + Installing Hiero integration for avalon Args: config (obj): avalon config module `pype` in our case, it is not @@ -61,8 +61,8 @@ def install(): # adding all events _register_events() - log.info("Registering NukeStudio plug-ins..") - pyblish.register_host("nukestudio") + log.info("Registering Hiero plug-ins..") + pyblish.register_host("hiero") pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -87,11 +87,11 @@ def install(): def uninstall(): """ - Uninstalling Nukestudio integration for avalon + Uninstalling Hiero integration for avalon """ - log.info("Deregistering NukeStudio plug-ins..") - pyblish.deregister_host("nukestudio") + log.info("Deregistering Hiero plug-ins..") + pyblish.deregister_host("hiero") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) @@ -102,7 +102,7 @@ def _register_events(): Adding all callbacks. """ - # if task changed then change notext of nukestudio + # if task changed then change notext of hiero avalon.on("taskChanged", _update_menu_task_label) log.info("Installed event callback for 'taskChanged'..") diff --git a/pype/hosts/nukestudio/events.py b/pype/hosts/hiero/events.py similarity index 98% rename from pype/hosts/nukestudio/events.py rename to pype/hosts/hiero/events.py index 509319f717..d78f8d54d4 100644 --- a/pype/hosts/nukestudio/events.py +++ b/pype/hosts/hiero/events.py @@ -4,7 +4,7 @@ from pype.api import Logger from .lib import sync_avalon_data_to_workfile, launch_workfiles_app from .tags import add_tags_from_presets -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") def startupCompleted(event): diff --git a/pype/hosts/nukestudio/lib.py b/pype/hosts/hiero/lib.py similarity index 98% rename from pype/hosts/nukestudio/lib.py rename to pype/hosts/hiero/lib.py index 6f2d9ad357..db7199a190 100644 --- a/pype/hosts/nukestudio/lib.py +++ b/pype/hosts/hiero/lib.py @@ -8,7 +8,7 @@ from avalon.vendor.Qt import (QtWidgets, QtGui) import pype.api as pype from pype.api import Logger, Anatomy -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") cached_process = None @@ -82,7 +82,7 @@ def sync_avalon_data_to_workfile(): def launch_workfiles_app(event): """ - Event for launching workfiles after nukestudio start + Event for launching workfiles after hiero start Args: event (obj): required but unused @@ -109,9 +109,9 @@ def reload_config(): "pypeapp", "{}.api".format(AVALON_CONFIG), "{}.templates".format(AVALON_CONFIG), - "{}.hosts.nukestudio.lib".format(AVALON_CONFIG), - "{}.hosts.nukestudio.menu".format(AVALON_CONFIG), - "{}.hosts.nukestudio.tags".format(AVALON_CONFIG) + "{}.hosts.hiero.lib".format(AVALON_CONFIG), + "{}.hosts.hiero.menu".format(AVALON_CONFIG), + "{}.hosts.hiero.tags".format(AVALON_CONFIG) ): log.info("Reloading module: {}...".format(module)) try: diff --git a/pype/hosts/nukestudio/menu.py b/pype/hosts/hiero/menu.py similarity index 97% rename from pype/hosts/nukestudio/menu.py rename to pype/hosts/hiero/menu.py index 35adcfc16c..697381f3cb 100644 --- a/pype/hosts/nukestudio/menu.py +++ b/pype/hosts/hiero/menu.py @@ -12,7 +12,7 @@ from .lib import ( set_workfiles ) -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") self = sys.modules[__name__] self._change_context_menu = None @@ -38,7 +38,7 @@ def _update_menu_task_label(*args): def install(): """ - Installing menu into Nukestudio + Installing menu into Hiero """ diff --git a/pype/hosts/nukestudio/tags.json b/pype/hosts/hiero/tags.json similarity index 100% rename from pype/hosts/nukestudio/tags.json rename to pype/hosts/hiero/tags.json diff --git a/pype/hosts/nukestudio/tags.py b/pype/hosts/hiero/tags.py similarity index 98% rename from pype/hosts/nukestudio/tags.py rename to pype/hosts/hiero/tags.py index c8af0cabc1..152d054a8a 100644 --- a/pype/hosts/nukestudio/tags.py +++ b/pype/hosts/hiero/tags.py @@ -6,7 +6,7 @@ import hiero from pype.api import Logger from avalon import io -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") def tag_data(): @@ -65,7 +65,7 @@ def add_tags_from_presets(): log.debug("Setting default tags on project: {}".format(project.name())) - # get nukestudio tags.json + # get hiero tags.json nks_pres_tags = tag_data() # Get project task types. diff --git a/pype/hosts/nukestudio/workio.py b/pype/hosts/hiero/workio.py similarity index 95% rename from pype/hosts/nukestudio/workio.py rename to pype/hosts/hiero/workio.py index 2cf898aa33..f11a34c9a8 100644 --- a/pype/hosts/nukestudio/workio.py +++ b/pype/hosts/hiero/workio.py @@ -4,11 +4,11 @@ from avalon import api from pype.api import Logger -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") def file_extensions(): - return api.HOST_WORKFILE_EXTENSIONS["nukestudio"] + return api.HOST_WORKFILE_EXTENSIONS["hiero"] def has_unsaved_changes(): diff --git a/pype/plugins/nukestudio/_unused/collect_metadata.py b/pype/plugins/hiero/_unused/collect_metadata.py similarity index 97% rename from pype/plugins/nukestudio/_unused/collect_metadata.py rename to pype/plugins/hiero/_unused/collect_metadata.py index 23d36ba4a2..c85cb4e898 100644 --- a/pype/plugins/nukestudio/_unused/collect_metadata.py +++ b/pype/plugins/hiero/_unused/collect_metadata.py @@ -6,7 +6,7 @@ class CollectClipMetadata(api.InstancePlugin): order = api.CollectorOrder + 0.01 label = "Collect Metadata" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): item = instance.data["item"] diff --git a/pype/plugins/nukestudio/_unused/collect_submission.py b/pype/plugins/hiero/_unused/collect_submission.py similarity index 100% rename from pype/plugins/nukestudio/_unused/collect_submission.py rename to pype/plugins/hiero/_unused/collect_submission.py diff --git a/pype/plugins/nukestudio/_unused/collect_timecodes.py b/pype/plugins/hiero/_unused/collect_timecodes.py similarity index 99% rename from pype/plugins/nukestudio/_unused/collect_timecodes.py rename to pype/plugins/hiero/_unused/collect_timecodes.py index 5ac07314a4..e79ee27a15 100644 --- a/pype/plugins/nukestudio/_unused/collect_timecodes.py +++ b/pype/plugins/hiero/_unused/collect_timecodes.py @@ -10,7 +10,7 @@ class CollectClipTimecodes(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.101 label = "Collect Timecodes" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): diff --git a/pype/plugins/nukestudio/_unused/collect_workfile_version.py b/pype/plugins/hiero/_unused/collect_workfile_version.py similarity index 100% rename from pype/plugins/nukestudio/_unused/collect_workfile_version.py rename to pype/plugins/hiero/_unused/collect_workfile_version.py diff --git a/pype/plugins/nukestudio/_unused/extract_plates_waiting.py b/pype/plugins/hiero/_unused/extract_plates_waiting.py similarity index 96% rename from pype/plugins/nukestudio/_unused/extract_plates_waiting.py rename to pype/plugins/hiero/_unused/extract_plates_waiting.py index 9a4d883917..f385f74a3e 100644 --- a/pype/plugins/nukestudio/_unused/extract_plates_waiting.py +++ b/pype/plugins/hiero/_unused/extract_plates_waiting.py @@ -8,7 +8,7 @@ class ExtractPlateCheck(api.ContextPlugin): order = api.ExtractorOrder + 0.01 label = "Plates Export Waiting" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["encode"] def process(self, context): diff --git a/pype/plugins/nukestudio/_unused/extract_tasks.py b/pype/plugins/hiero/_unused/extract_tasks.py similarity index 99% rename from pype/plugins/nukestudio/_unused/extract_tasks.py rename to pype/plugins/hiero/_unused/extract_tasks.py index 3e6ef9b71c..4f75728468 100644 --- a/pype/plugins/nukestudio/_unused/extract_tasks.py +++ b/pype/plugins/hiero/_unused/extract_tasks.py @@ -6,7 +6,7 @@ class ExtractTasks(api.InstancePlugin): order = api.ExtractorOrder label = "Tasks" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] optional = True diff --git a/pype/plugins/nukestudio/_unused/subset-representations_logic.txt b/pype/plugins/hiero/_unused/subset-representations_logic.txt similarity index 100% rename from pype/plugins/nukestudio/_unused/subset-representations_logic.txt rename to pype/plugins/hiero/_unused/subset-representations_logic.txt diff --git a/pype/plugins/nukestudio/_unused/validate_projectroot.py b/pype/plugins/hiero/_unused/validate_projectroot.py similarity index 97% rename from pype/plugins/nukestudio/_unused/validate_projectroot.py rename to pype/plugins/hiero/_unused/validate_projectroot.py index 94315014c6..51e5082250 100644 --- a/pype/plugins/nukestudio/_unused/validate_projectroot.py +++ b/pype/plugins/hiero/_unused/validate_projectroot.py @@ -22,7 +22,7 @@ class ValidateProjectRoot(api.ContextPlugin): order = api.ValidatorOrder label = "Project Root" - hosts = ["nukestudio"] + hosts = ["hiero"] actions = [RepairProjectRoot] def process(self, context): diff --git a/pype/plugins/nukestudio/_unused/validate_resolved_paths.py b/pype/plugins/hiero/_unused/validate_resolved_paths.py similarity index 96% rename from pype/plugins/nukestudio/_unused/validate_resolved_paths.py rename to pype/plugins/hiero/_unused/validate_resolved_paths.py index f1f0b7bbc8..21883aa1d3 100644 --- a/pype/plugins/nukestudio/_unused/validate_resolved_paths.py +++ b/pype/plugins/hiero/_unused/validate_resolved_paths.py @@ -5,7 +5,7 @@ class ValidateResolvedPaths(api.ContextPlugin): order = api.ValidatorOrder label = "Resolved Paths" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): import os diff --git a/pype/plugins/nukestudio/_unused/validate_task.py b/pype/plugins/hiero/_unused/validate_task.py similarity index 97% rename from pype/plugins/nukestudio/_unused/validate_task.py rename to pype/plugins/hiero/_unused/validate_task.py index ff8fa6b6e1..3af94273bb 100644 --- a/pype/plugins/nukestudio/_unused/validate_task.py +++ b/pype/plugins/hiero/_unused/validate_task.py @@ -13,7 +13,7 @@ class ValidateOutputRange(api.InstancePlugin): order = api.ValidatorOrder families = ["trackItem.task"] label = "Output Range" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, instance): @@ -43,7 +43,7 @@ class ValidateImageSequence(api.InstancePlugin): families = ["trackItem.task", "img"] match = api.Subset label = "Image Sequence" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, instance): diff --git a/pype/plugins/nukestudio/_unused/validate_track_item.py b/pype/plugins/hiero/_unused/validate_track_item.py similarity index 98% rename from pype/plugins/nukestudio/_unused/validate_track_item.py rename to pype/plugins/hiero/_unused/validate_track_item.py index 48f63b5608..f29e1d5d75 100644 --- a/pype/plugins/nukestudio/_unused/validate_track_item.py +++ b/pype/plugins/hiero/_unused/validate_track_item.py @@ -10,7 +10,7 @@ class ValidateClip(api.InstancePlugin): families = ["clip"] # match = api.Exact label = "Validate Track Item" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, instance): diff --git a/pype/plugins/nukestudio/_unused/validate_viewer_lut.py b/pype/plugins/hiero/_unused/validate_viewer_lut.py similarity index 53% rename from pype/plugins/nukestudio/_unused/validate_viewer_lut.py rename to pype/plugins/hiero/_unused/validate_viewer_lut.py index 08c084880d..4d307b5a71 100644 --- a/pype/plugins/nukestudio/_unused/validate_viewer_lut.py +++ b/pype/plugins/hiero/_unused/validate_viewer_lut.py @@ -2,11 +2,11 @@ from pyblish import api class ValidateViewerLut(api.ContextPlugin): - """Validate viewer lut in NukeStudio is the same as in Nuke.""" + """Validate viewer lut in Hiero is the same as in Nuke.""" order = api.ValidatorOrder label = "Viewer LUT" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, context): @@ -14,8 +14,8 @@ class ValidateViewerLut(api.ContextPlugin): import hiero # nuke_lut = nuke.ViewerProcess.node()["current"].value() - nukestudio_lut = context.data["activeProject"].lutSettingViewer() - self.log.info("__ nukestudio_lut: {}".format(nukestudio_lut)) + hiero_lut = context.data["activeProject"].lutSettingViewer() + self.log.info("__ hiero_lut: {}".format(hiero_lut)) msg = "Viewer LUT can only be RGB" - assert "RGB" in nukestudio_lut, msg + assert "RGB" in hiero_lut, msg diff --git a/pype/plugins/nukestudio/load/load_sequences_to_timeline_asset_origin.py b/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py similarity index 97% rename from pype/plugins/nukestudio/load/load_sequences_to_timeline_asset_origin.py rename to pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py index c56dcbcaaa..3c58a0a90d 100644 --- a/pype/plugins/nukestudio/load/load_sequences_to_timeline_asset_origin.py +++ b/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py @@ -1,6 +1,6 @@ from avalon import api import hiero -from pype.hosts.nukestudio import lib +from pype.hosts.hiero import lib reload(lib) diff --git a/pype/plugins/nukestudio/publish/collect_active_project.py b/pype/plugins/hiero/publish/collect_active_project.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_active_project.py rename to pype/plugins/hiero/publish/collect_active_project.py diff --git a/pype/plugins/nukestudio/publish/collect_assetbuilds.py b/pype/plugins/hiero/publish/collect_assetbuilds.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_assetbuilds.py rename to pype/plugins/hiero/publish/collect_assetbuilds.py index 76326c320b..f20acaf3da 100644 --- a/pype/plugins/nukestudio/publish/collect_assetbuilds.py +++ b/pype/plugins/hiero/publish/collect_assetbuilds.py @@ -14,7 +14,7 @@ class CollectAssetBuilds(api.ContextPlugin): # Run just after CollectClip order = api.CollectorOrder + 0.02 label = "Collect AssetBuilds" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): asset_builds = {} diff --git a/pype/plugins/nukestudio/publish/collect_audio.py b/pype/plugins/hiero/publish/collect_audio.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_audio.py rename to pype/plugins/hiero/publish/collect_audio.py index 727d7da795..a63a18be05 100644 --- a/pype/plugins/nukestudio/publish/collect_audio.py +++ b/pype/plugins/hiero/publish/collect_audio.py @@ -14,7 +14,7 @@ class CollectAudio(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1021 label = "Collect Audio" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_calculate_retime.py b/pype/plugins/hiero/publish/collect_calculate_retime.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_calculate_retime.py rename to pype/plugins/hiero/publish/collect_calculate_retime.py index a97b43a4ce..1b2f047da2 100644 --- a/pype/plugins/nukestudio/publish/collect_calculate_retime.py +++ b/pype/plugins/hiero/publish/collect_calculate_retime.py @@ -8,7 +8,7 @@ class CollectCalculateRetime(api.InstancePlugin): order = api.CollectorOrder + 0.02 label = "Collect Calculate Retiming" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['retime'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_clip_resolution.py b/pype/plugins/hiero/publish/collect_clip_resolution.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_clip_resolution.py rename to pype/plugins/hiero/publish/collect_clip_resolution.py index b70f8f2f95..8d441959ba 100644 --- a/pype/plugins/nukestudio/publish/collect_clip_resolution.py +++ b/pype/plugins/hiero/publish/collect_clip_resolution.py @@ -6,7 +6,7 @@ class CollectClipResolution(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.101 label = "Collect Clip Resoluton" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): sequence = instance.context.data['activeSequence'] diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/hiero/publish/collect_clips.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_clips.py rename to pype/plugins/hiero/publish/collect_clips.py index d39e25bfc6..e11ad93883 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/hiero/publish/collect_clips.py @@ -9,7 +9,7 @@ class CollectClips(api.ContextPlugin): order = api.CollectorOrder + 0.01 label = "Collect Clips" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): # create asset_names conversion table diff --git a/pype/plugins/nukestudio/publish/collect_colorspace.py b/pype/plugins/hiero/publish/collect_colorspace.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_colorspace.py rename to pype/plugins/hiero/publish/collect_colorspace.py diff --git a/pype/plugins/nukestudio/publish/collect_current_file.py b/pype/plugins/hiero/publish/collect_current_file.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_current_file.py rename to pype/plugins/hiero/publish/collect_current_file.py diff --git a/pype/plugins/nukestudio/publish/collect_effects.py b/pype/plugins/hiero/publish/collect_effects.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_effects.py rename to pype/plugins/hiero/publish/collect_effects.py diff --git a/pype/plugins/nukestudio/publish/collect_frame_ranges.py b/pype/plugins/hiero/publish/collect_frame_ranges.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_frame_ranges.py rename to pype/plugins/hiero/publish/collect_frame_ranges.py index 1cb5e5dd1e..19a46d80b1 100644 --- a/pype/plugins/nukestudio/publish/collect_frame_ranges.py +++ b/pype/plugins/hiero/publish/collect_frame_ranges.py @@ -6,7 +6,7 @@ class CollectClipFrameRanges(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.101 label = "Collect Frame Ranges" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_framerate.py b/pype/plugins/hiero/publish/collect_framerate.py similarity index 95% rename from pype/plugins/nukestudio/publish/collect_framerate.py rename to pype/plugins/hiero/publish/collect_framerate.py index 694052f802..6d2d2eef2b 100644 --- a/pype/plugins/nukestudio/publish/collect_framerate.py +++ b/pype/plugins/hiero/publish/collect_framerate.py @@ -6,7 +6,7 @@ class CollectFramerate(api.ContextPlugin): order = api.CollectorOrder + 0.01 label = "Collect Framerate" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): sequence = context.data["activeSequence"] diff --git a/pype/plugins/nukestudio/publish/collect_handles.py b/pype/plugins/hiero/publish/collect_handles.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_handles.py rename to pype/plugins/hiero/publish/collect_handles.py index c16f1a5803..1f278a6b4c 100644 --- a/pype/plugins/nukestudio/publish/collect_handles.py +++ b/pype/plugins/hiero/publish/collect_handles.py @@ -6,7 +6,7 @@ class CollectClipHandles(api.ContextPlugin): order = api.CollectorOrder + 0.0121 label = "Collect Handles" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): assets_shared = context.data.get("assetsShared") diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/hiero/publish/collect_hierarchy_context.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_hierarchy_context.py rename to pype/plugins/hiero/publish/collect_hierarchy_context.py index 930efd618e..249194003b 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/hiero/publish/collect_hierarchy_context.py @@ -46,7 +46,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): clip_out = instance.data["clipOut"] fps = context.data["fps"] - # build data for inner nukestudio project property + # build data for inner hiero project property data = { "sequence": ( context.data['activeSequence'].name().replace(' ', '_') diff --git a/pype/plugins/nukestudio/publish/collect_host.py b/pype/plugins/hiero/publish/collect_host.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_host.py rename to pype/plugins/hiero/publish/collect_host.py diff --git a/pype/plugins/nukestudio/publish/collect_host_version.py b/pype/plugins/hiero/publish/collect_host_version.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_host_version.py rename to pype/plugins/hiero/publish/collect_host_version.py diff --git a/pype/plugins/nukestudio/publish/collect_instance_version.py b/pype/plugins/hiero/publish/collect_instance_version.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_instance_version.py rename to pype/plugins/hiero/publish/collect_instance_version.py diff --git a/pype/plugins/nukestudio/publish/collect_leader_clip.py b/pype/plugins/hiero/publish/collect_leader_clip.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_leader_clip.py rename to pype/plugins/hiero/publish/collect_leader_clip.py index 62ef420316..bba85cf411 100644 --- a/pype/plugins/nukestudio/publish/collect_leader_clip.py +++ b/pype/plugins/hiero/publish/collect_leader_clip.py @@ -6,7 +6,7 @@ class CollectLeaderClip(api.InstancePlugin): order = api.CollectorOrder + 0.0111 label = "Collect Leader Clip" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/hiero/publish/collect_plates.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_plates.py rename to pype/plugins/hiero/publish/collect_plates.py index 770cef7e3f..0c08b94148 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/hiero/publish/collect_plates.py @@ -16,7 +16,7 @@ class CollectPlates(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1021 label = "Collect Plates" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): @@ -85,7 +85,7 @@ class CollectPlatesData(api.InstancePlugin): order = api.CollectorOrder + 0.48 label = "Collect Plates Data" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["plate"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_remove_clip_instances.py b/pype/plugins/hiero/publish/collect_remove_clip_instances.py similarity index 94% rename from pype/plugins/nukestudio/publish/collect_remove_clip_instances.py rename to pype/plugins/hiero/publish/collect_remove_clip_instances.py index d41dc50ab1..c42bd1345b 100644 --- a/pype/plugins/nukestudio/publish/collect_remove_clip_instances.py +++ b/pype/plugins/hiero/publish/collect_remove_clip_instances.py @@ -5,7 +5,7 @@ class CollectClipSubsets(api.InstancePlugin): order = api.CollectorOrder + 0.103 label = "Collect Remove Clip Instaces" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/hiero/publish/collect_reviews.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_reviews.py rename to pype/plugins/hiero/publish/collect_reviews.py index 3167c66170..0ef0842e82 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/hiero/publish/collect_reviews.py @@ -15,7 +15,7 @@ class CollectReviews(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1022 label = "Collect Reviews" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["plate"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_selection.py b/pype/plugins/hiero/publish/collect_selection.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_selection.py rename to pype/plugins/hiero/publish/collect_selection.py diff --git a/pype/plugins/nukestudio/publish/collect_sequence.py b/pype/plugins/hiero/publish/collect_sequence.py similarity index 91% rename from pype/plugins/nukestudio/publish/collect_sequence.py rename to pype/plugins/hiero/publish/collect_sequence.py index 162d1ebdfe..4247c7c4cb 100644 --- a/pype/plugins/nukestudio/publish/collect_sequence.py +++ b/pype/plugins/hiero/publish/collect_sequence.py @@ -7,7 +7,7 @@ class CollectSequence(api.ContextPlugin): order = api.CollectorOrder - 0.01 label = "Collect Sequence" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): context.data['activeSequence'] = hiero.ui.activeSequence() diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/hiero/publish/collect_shots.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_shots.py rename to pype/plugins/hiero/publish/collect_shots.py index 03fc7ab282..22f23e5742 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/hiero/publish/collect_shots.py @@ -7,7 +7,7 @@ class CollectShots(api.InstancePlugin): # Run just before CollectClipSubsets order = api.CollectorOrder + 0.1021 label = "Collect Shots" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_comments.py b/pype/plugins/hiero/publish/collect_tag_comments.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_tag_comments.py rename to pype/plugins/hiero/publish/collect_tag_comments.py index e14e53d439..76d7b6a67c 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_comments.py +++ b/pype/plugins/hiero/publish/collect_tag_comments.py @@ -6,7 +6,7 @@ class CollectClipTagComments(api.InstancePlugin): order = api.CollectorOrder + 0.013 label = "Collect Comments" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_framestart.py b/pype/plugins/hiero/publish/collect_tag_framestart.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_tag_framestart.py rename to pype/plugins/hiero/publish/collect_tag_framestart.py index 993aa99a3e..0d14271aa5 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_framestart.py +++ b/pype/plugins/hiero/publish/collect_tag_framestart.py @@ -6,7 +6,7 @@ class CollectClipTagFrameStart(api.InstancePlugin): order = api.CollectorOrder + 0.013 label = "Collect Frame Start" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_handles.py b/pype/plugins/hiero/publish/collect_tag_handles.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_tag_handles.py rename to pype/plugins/hiero/publish/collect_tag_handles.py index a6a63faea9..7e51efd8e1 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_handles.py +++ b/pype/plugins/hiero/publish/collect_tag_handles.py @@ -7,7 +7,7 @@ class CollectClipTagHandles(api.ContextPlugin): order = api.CollectorOrder + 0.012 label = "Collect Tag Handles" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, context): diff --git a/pype/plugins/nukestudio/publish/collect_tag_resolution.py b/pype/plugins/hiero/publish/collect_tag_resolution.py similarity index 95% rename from pype/plugins/nukestudio/publish/collect_tag_resolution.py rename to pype/plugins/hiero/publish/collect_tag_resolution.py index 24c13d2b4a..ef46d9d594 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_resolution.py +++ b/pype/plugins/hiero/publish/collect_tag_resolution.py @@ -6,7 +6,7 @@ class CollectClipTagResolution(api.InstancePlugin): order = api.CollectorOrder + 0.013 label = "Collect Source Resolution" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_retime.py b/pype/plugins/hiero/publish/collect_tag_retime.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_tag_retime.py rename to pype/plugins/hiero/publish/collect_tag_retime.py index 32e49e1b2a..0634130976 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_retime.py +++ b/pype/plugins/hiero/publish/collect_tag_retime.py @@ -6,7 +6,7 @@ class CollectTagRetime(api.InstancePlugin): order = api.CollectorOrder + 0.014 label = "Collect Retiming Tag" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_subsets.py b/pype/plugins/hiero/publish/collect_tag_subsets.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_tag_subsets.py rename to pype/plugins/hiero/publish/collect_tag_subsets.py index 0d42000896..d3d247727b 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_subsets.py +++ b/pype/plugins/hiero/publish/collect_tag_subsets.py @@ -6,7 +6,7 @@ class CollectClipSubsetsTags(api.InstancePlugin): order = api.CollectorOrder + 0.012 label = "Collect Tags Subsets" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_tasks.py b/pype/plugins/hiero/publish/collect_tag_tasks.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_tag_tasks.py rename to pype/plugins/hiero/publish/collect_tag_tasks.py index ed2f3009d3..b83e208b58 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_tasks.py +++ b/pype/plugins/hiero/publish/collect_tag_tasks.py @@ -6,7 +6,7 @@ class CollectClipTagTasks(api.InstancePlugin): order = api.CollectorOrder + 0.012 label = "Collect Tag Tasks" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tags.py b/pype/plugins/hiero/publish/collect_tags.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_tags.py rename to pype/plugins/hiero/publish/collect_tags.py index 49005f4b22..fde6f7a26b 100644 --- a/pype/plugins/nukestudio/publish/collect_tags.py +++ b/pype/plugins/hiero/publish/collect_tags.py @@ -6,7 +6,7 @@ class CollectClipTags(api.InstancePlugin): order = api.CollectorOrder + 0.011 label = "Collect Tags" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/extract_audio.py b/pype/plugins/hiero/publish/extract_audio.py similarity index 98% rename from pype/plugins/nukestudio/publish/extract_audio.py rename to pype/plugins/hiero/publish/extract_audio.py index 2c4afc8412..34fdcdfe9d 100644 --- a/pype/plugins/nukestudio/publish/extract_audio.py +++ b/pype/plugins/hiero/publish/extract_audio.py @@ -7,7 +7,7 @@ class ExtractAudioFile(pype.api.Extractor): order = api.ExtractorOrder label = "Extract Subset Audio" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip", "audio"] match = api.Intersection diff --git a/pype/plugins/nukestudio/publish/extract_effects.py b/pype/plugins/hiero/publish/extract_effects.py similarity index 100% rename from pype/plugins/nukestudio/publish/extract_effects.py rename to pype/plugins/hiero/publish/extract_effects.py diff --git a/pype/plugins/nukestudio/publish/extract_review_cutup_video.py b/pype/plugins/hiero/publish/extract_review_cutup_video.py similarity index 99% rename from pype/plugins/nukestudio/publish/extract_review_cutup_video.py rename to pype/plugins/hiero/publish/extract_review_cutup_video.py index d1ce3675b1..d1953b5aa2 100644 --- a/pype/plugins/nukestudio/publish/extract_review_cutup_video.py +++ b/pype/plugins/hiero/publish/extract_review_cutup_video.py @@ -9,7 +9,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): order = api.ExtractorOrder # order = api.CollectorOrder + 0.1023 label = "Extract Review CutUp Video" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["review"] # presets diff --git a/pype/plugins/nukestudio/publish/validate_hierarchy.py b/pype/plugins/hiero/publish/validate_hierarchy.py similarity index 96% rename from pype/plugins/nukestudio/publish/validate_hierarchy.py rename to pype/plugins/hiero/publish/validate_hierarchy.py index 8013a98efd..d43f7fd562 100644 --- a/pype/plugins/nukestudio/publish/validate_hierarchy.py +++ b/pype/plugins/hiero/publish/validate_hierarchy.py @@ -9,7 +9,7 @@ class ValidateHierarchy(api.InstancePlugin): order = api.ValidatorOrder families = ["clip", "shot"] label = "Validate Hierarchy" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): asset_name = instance.data.get("asset", None) diff --git a/pype/plugins/nukestudio/publish/validate_names.py b/pype/plugins/hiero/publish/validate_names.py similarity index 97% rename from pype/plugins/nukestudio/publish/validate_names.py rename to pype/plugins/hiero/publish/validate_names.py index 8f7436cca0..52e4bf8ecc 100644 --- a/pype/plugins/nukestudio/publish/validate_names.py +++ b/pype/plugins/hiero/publish/validate_names.py @@ -13,7 +13,7 @@ class ValidateNames(api.InstancePlugin): families = ["clip"] match = api.Exact label = "Names" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/version_up_workfile.py b/pype/plugins/hiero/publish/version_up_workfile.py similarity index 95% rename from pype/plugins/nukestudio/publish/version_up_workfile.py rename to pype/plugins/hiero/publish/version_up_workfile.py index 195099dd09..893d3789eb 100644 --- a/pype/plugins/nukestudio/publish/version_up_workfile.py +++ b/pype/plugins/hiero/publish/version_up_workfile.py @@ -7,7 +7,7 @@ class VersionUpWorkfile(api.ContextPlugin): order = api.IntegratorOrder + 10.1 label = "Version-up Workfile" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True active = True diff --git a/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox b/setup/hiero/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox similarity index 100% rename from setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox rename to setup/hiero/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox diff --git a/setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png b/setup/hiero/hiero_plugin_path/Icons/1_add_handles_end.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png rename to setup/hiero/hiero_plugin_path/Icons/1_add_handles_end.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png b/setup/hiero/hiero_plugin_path/Icons/2_add_handles.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png rename to setup/hiero/hiero_plugin_path/Icons/2_add_handles.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/3D.png b/setup/hiero/hiero_plugin_path/Icons/3D.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/3D.png rename to setup/hiero/hiero_plugin_path/Icons/3D.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png b/setup/hiero/hiero_plugin_path/Icons/3_add_handles_start.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png rename to setup/hiero/hiero_plugin_path/Icons/3_add_handles_start.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/4_2D.png b/setup/hiero/hiero_plugin_path/Icons/4_2D.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/4_2D.png rename to setup/hiero/hiero_plugin_path/Icons/4_2D.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/edit.png b/setup/hiero/hiero_plugin_path/Icons/edit.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/edit.png rename to setup/hiero/hiero_plugin_path/Icons/edit.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/fusion.png b/setup/hiero/hiero_plugin_path/Icons/fusion.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/fusion.png rename to setup/hiero/hiero_plugin_path/Icons/fusion.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png b/setup/hiero/hiero_plugin_path/Icons/hierarchy.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png rename to setup/hiero/hiero_plugin_path/Icons/hierarchy.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/houdini.png b/setup/hiero/hiero_plugin_path/Icons/houdini.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/houdini.png rename to setup/hiero/hiero_plugin_path/Icons/houdini.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/layers.psd b/setup/hiero/hiero_plugin_path/Icons/layers.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/layers.psd rename to setup/hiero/hiero_plugin_path/Icons/layers.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/lense.png b/setup/hiero/hiero_plugin_path/Icons/lense.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/lense.png rename to setup/hiero/hiero_plugin_path/Icons/lense.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/lense1.png b/setup/hiero/hiero_plugin_path/Icons/lense1.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/lense1.png rename to setup/hiero/hiero_plugin_path/Icons/lense1.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/maya.png b/setup/hiero/hiero_plugin_path/Icons/maya.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/maya.png rename to setup/hiero/hiero_plugin_path/Icons/maya.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/nuke.png b/setup/hiero/hiero_plugin_path/Icons/nuke.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/nuke.png rename to setup/hiero/hiero_plugin_path/Icons/nuke.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/resolution.png b/setup/hiero/hiero_plugin_path/Icons/resolution.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/resolution.png rename to setup/hiero/hiero_plugin_path/Icons/resolution.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/resolution.psd b/setup/hiero/hiero_plugin_path/Icons/resolution.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/resolution.psd rename to setup/hiero/hiero_plugin_path/Icons/resolution.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.png b/setup/hiero/hiero_plugin_path/Icons/retiming.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/retiming.png rename to setup/hiero/hiero_plugin_path/Icons/retiming.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.psd b/setup/hiero/hiero_plugin_path/Icons/retiming.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/retiming.psd rename to setup/hiero/hiero_plugin_path/Icons/retiming.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/review.png b/setup/hiero/hiero_plugin_path/Icons/review.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/review.png rename to setup/hiero/hiero_plugin_path/Icons/review.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/review.psd b/setup/hiero/hiero_plugin_path/Icons/review.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/review.psd rename to setup/hiero/hiero_plugin_path/Icons/review.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/volume.png b/setup/hiero/hiero_plugin_path/Icons/volume.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/volume.png rename to setup/hiero/hiero_plugin_path/Icons/volume.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png b/setup/hiero/hiero_plugin_path/Icons/z_layer_bg.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png rename to setup/hiero/hiero_plugin_path/Icons/z_layer_bg.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png b/setup/hiero/hiero_plugin_path/Icons/z_layer_fg.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png rename to setup/hiero/hiero_plugin_path/Icons/z_layer_fg.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png b/setup/hiero/hiero_plugin_path/Icons/z_layer_main.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png rename to setup/hiero/hiero_plugin_path/Icons/z_layer_main.png diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py b/setup/hiero/hiero_plugin_path/Python/Startup/SpreadsheetExport.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py rename to setup/hiero/hiero_plugin_path/Python/Startup/SpreadsheetExport.py diff --git a/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py b/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py new file mode 100644 index 0000000000..003d7959c5 --- /dev/null +++ b/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py @@ -0,0 +1,19 @@ +import traceback + +# activate hiero from pype +import avalon.api +import pype.hosts.hiero +avalon.api.install(pype.hosts.hiero) + +try: + __import__("pype.hosts.hiero") + __import__("pyblish") + +except ImportError as e: + print traceback.format_exc() + print("pyblish: Could not load integration: %s " % e) + +else: + # Setup integration + import pype.hosts.hiero.lib + pype.hosts.hiero.lib.setup() diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py b/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py rename to setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py b/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py rename to setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py b/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/__init__.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py rename to setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/__init__.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/project_helpers.py b/setup/hiero/hiero_plugin_path/Python/Startup/project_helpers.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/project_helpers.py rename to setup/hiero/hiero_plugin_path/Python/Startup/project_helpers.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py b/setup/hiero/hiero_plugin_path/Python/Startup/selection_tracker.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py rename to setup/hiero/hiero_plugin_path/Python/Startup/selection_tracker.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py b/setup/hiero/hiero_plugin_path/Python/Startup/setFrameRate.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py rename to setup/hiero/hiero_plugin_path/Python/Startup/setFrameRate.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py b/setup/hiero/hiero_plugin_path/Python/Startup/version_everywhere.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py rename to setup/hiero/hiero_plugin_path/Python/Startup/version_everywhere.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/Purge.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/Purge.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/setPosterFrame.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/setPosterFrame.py diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 99% rename from setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml index e24a4dbe4e..690820c788 100644 --- a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml +++ b/setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -1,6 +1,6 @@ 991 - //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + //10.11.0.184/171001_ftrack/tgbvfx/editorial/hiero/workspace/ 1 True 3 diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 99% rename from setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml index e24a4dbe4e..690820c788 100644 --- a/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml +++ b/setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -1,6 +1,6 @@ 991 - //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + //10.11.0.184/171001_ftrack/tgbvfx/editorial/hiero/workspace/ 1 True 3 diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 99% rename from setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml index e24a4dbe4e..690820c788 100644 --- a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml +++ b/setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -1,6 +1,6 @@ 991 - //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + //10.11.0.184/171001_ftrack/tgbvfx/editorial/hiero/workspace/ 1 True 3 diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py deleted file mode 100644 index e5c5729e2c..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py +++ /dev/null @@ -1,19 +0,0 @@ -import traceback - -# activate nukestudio from pype -import avalon.api -import pype.hosts.nukestudio -avalon.api.install(pype.hosts.nukestudio) - -try: - __import__("pype.hosts.nukestudio") - __import__("pyblish") - -except ImportError as e: - print traceback.format_exc() - print("pyblish: Could not load integration: %s " % e) - -else: - # Setup integration - import pype.hosts.nukestudio.lib - pype.hosts.nukestudio.lib.setup() From 5368632c859466b18ec879ab3c393d557aafc0a3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 6 Oct 2020 17:45:38 +0200 Subject: [PATCH 85/91] removed repre output name --- pype/plugins/global/publish/extract_scanline_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py index d829f198b6..ca62476ab2 100644 --- a/pype/plugins/global/publish/extract_scanline_exr.py +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -80,7 +80,6 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): self.log.warning(e) repre['name'] = 'exr' - repre['outputName'] = "scanline exr" try: repre['tags'].remove('toScanline') except ValueError: From 8dba1b95570e0dc7e7bc9b269cfeac612ba3c560 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Oct 2020 13:00:01 +0200 Subject: [PATCH 86/91] pass instance version to metadata.json if it's not 1 --- pype/plugins/global/publish/submit_publish_job.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 227a48221b..fd109cf881 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -740,6 +740,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) + # skip locking version if we are creating v01 + instance_version = instance.data.get("version") + if instance_version != 1: + instance_skeleton_data["version"] = instance_version + # transfer specific families from original instance to new render for item in self.families_transfer: if item in instance.data.get("families", []): From 065b5d03408f77e9ddf06767a701180adae5c1f5 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Oct 2020 13:00:23 +0200 Subject: [PATCH 87/91] add setting for sync workfile version with maya render --- pype/plugins/maya/publish/collect_render.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 093827809c..2848a55152 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -59,6 +59,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.01 hosts = ["maya"] label = "Collect Render Layers" + sync_workfile_version = False def process(self, context): """Entry point to collector.""" @@ -250,6 +251,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "convertToScanline": render_instance.data.get("convertToScanline") or False # noqa: E501 } + if self.sync_workfile_version: + data["version"] = context.data["version"] + # Apply each user defined attribute as data for attr in cmds.listAttr(layer, userDefined=True) or list(): try: From dc77711d510910240fdb3404aae5f1352e0bd9e1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Oct 2020 17:02:05 +0200 Subject: [PATCH 88/91] fix(hiero): loading Tasks correctly --- pype/hosts/hiero/tags.py | 8 ++++++-- .../ftrack/publish/integrate_hierarchy_ftrack.py | 12 +++++++----- pype/plugins/global/publish/extract_burnin.py | 2 +- .../global/publish/extract_hierarchy_avalon.py | 13 +++++++------ pype/plugins/global/publish/extract_review.py | 2 +- pype/plugins/global/publish/integrate_new.py | 9 ++++----- .../publish/validate_custom_ftrack_attributes.py | 2 +- .../hiero/publish/collect_hierarchy_context.py | 2 +- pype/plugins/hiero/publish/collect_shots.py | 2 +- pype/plugins/hiero/publish/collect_tag_tasks.py | 5 +++-- .../hiero/publish/extract_review_cutup_video.py | 2 +- 11 files changed, 33 insertions(+), 26 deletions(-) diff --git a/pype/hosts/hiero/tags.py b/pype/hosts/hiero/tags.py index 152d054a8a..551dc1698d 100644 --- a/pype/hosts/hiero/tags.py +++ b/pype/hosts/hiero/tags.py @@ -3,6 +3,8 @@ import os import json import hiero +from pprint import pformat + from pype.api import Logger from avalon import io @@ -71,15 +73,17 @@ def add_tags_from_presets(): # Get project task types. tasks = io.find_one({"type": "project"})["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} + log.debug("__ tasks: {}".format(pformat(tasks))) for task_type in tasks.keys(): - nks_pres_tags["[Tasks]"][task_type] = { + nks_pres_tags["[Tasks]"][task_type.lower()] = { "editable": "1", "note": "", "icon": { "path": "icons:TagGood.png" }, "metadata": { - "family": "task" + "family": "task", + "type": task_type } } diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 7d4e0333d6..2ee0898711 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -143,15 +143,17 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # existing_tasks.append(child['type']['name']) for task in tasks: - if task.lower() in existing_tasks: + task_name = next(iter(task)) + task_type = task[task_name]["type"] + if task_name.lower() in existing_tasks: print("Task {} already exists".format(task)) continue - tasks_to_create.append(task) + tasks_to_create.append((task_name, task_type)) - for task in tasks_to_create: + for task_name, task_type in tasks_to_create: self.create_task( - name=task, - task_type=task, + name=task_name, + task_type=task_type, parent=entity ) try: diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index a613e39113..353f2f27f0 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -23,7 +23,7 @@ class ExtractBurnin(pype.api.Extractor): "nuke", "maya", "shell", - "nukestudio", + "hiero", "premiere", "standalonepublisher", "harmony" diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 2d82eca6b2..64df672709 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -100,12 +100,13 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): # Do not override data, only update cur_entity_data = entity.get("data") or {} new_tasks = data.pop("tasks", {}) - if "tasks" in cur_entity_data and new_tasks: - for task_name in new_tasks.keys(): - if task_name \ - not in cur_entity_data["tasks"].keys(): - cur_entity_data["tasks"][task_name] = \ - new_tasks[task_name] + if "tasks" not in cur_entity_data and not new_tasks: + continue + for task in new_tasks: + task_name = next(iter(task)) + if task_name in cur_entity_data["tasks"].keys(): + continue + cur_entity_data["tasks"][task_name] = task[task_name] cur_entity_data.update(data) data = cur_entity_data else: diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 0b1c0f0745..f4a39a7c31 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -26,7 +26,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "nuke", "maya", "shell", - "nukestudio", + "hiero", "premiere", "harmony", "standalonepublisher", diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 68549e9186..4dc6006076 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -521,8 +521,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # get 'files' info for representation and all attached resources self.log.debug("Preparing files information ...") representation["files"] = self.get_files_info( - instance, - self.integrated_file_sizes) + instance, + self.integrated_file_sizes) self.log.debug("__ representation: {}".format(representation)) destination_list.append(dst) @@ -543,10 +543,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre_ids_to_remove.append(repre["_id"]) io.delete_many({"_id": {"$in": repre_ids_to_remove}}) - self.log.debug("__ representations: {}".format(representations)) for rep in instance.data["representations"]: - self.log.debug("__ represNAME: {}".format(rep['name'])) - self.log.debug("__ represPATH: {}".format(rep['published_path'])) + self.log.debug("__ rep: {}".format(rep)) + io.insert_many(representations) instance.data["published_representations"] = ( published_representations diff --git a/pype/plugins/global/publish/validate_custom_ftrack_attributes.py b/pype/plugins/global/publish/validate_custom_ftrack_attributes.py index 633deaa6d1..4bddcd2e03 100644 --- a/pype/plugins/global/publish/validate_custom_ftrack_attributes.py +++ b/pype/plugins/global/publish/validate_custom_ftrack_attributes.py @@ -46,7 +46,7 @@ class ValidateFtrackAttributes(pyblish.api.InstancePlugin): "houdini", "maya", "nuke", - "nukestudio", + "hiero", "photoshop", "premiere", "resolve", diff --git a/pype/plugins/hiero/publish/collect_hierarchy_context.py b/pype/plugins/hiero/publish/collect_hierarchy_context.py index 249194003b..f18783cd37 100644 --- a/pype/plugins/hiero/publish/collect_hierarchy_context.py +++ b/pype/plugins/hiero/publish/collect_hierarchy_context.py @@ -13,7 +13,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): """ label = "Collect Hierarchy Clip" - order = pyblish.api.CollectorOrder + 0.101 + order = pyblish.api.CollectorOrder + 0.102 families = ["clip"] def convert_to_entity(self, key, value): diff --git a/pype/plugins/hiero/publish/collect_shots.py b/pype/plugins/hiero/publish/collect_shots.py index 22f23e5742..6f83e08fbe 100644 --- a/pype/plugins/hiero/publish/collect_shots.py +++ b/pype/plugins/hiero/publish/collect_shots.py @@ -43,7 +43,7 @@ class CollectShots(api.InstancePlugin): "{} - {} - tasks:{} - assetbuilds:{} - comments:{}".format( data["asset"], data["subset"], - data["tasks"].keys(), + [task.keys()[0] for task in data["tasks"]], [x["name"] for x in data.get("assetbuilds", [])], len(data.get("comments", [])) ) diff --git a/pype/plugins/hiero/publish/collect_tag_tasks.py b/pype/plugins/hiero/publish/collect_tag_tasks.py index b83e208b58..dbcf5e5260 100644 --- a/pype/plugins/hiero/publish/collect_tag_tasks.py +++ b/pype/plugins/hiero/publish/collect_tag_tasks.py @@ -20,8 +20,9 @@ class CollectClipTagTasks(api.InstancePlugin): # gets only task family tags and collect labels if "task" in t_family: - t_task = t_metadata.get("tag.label", "") - tasks.append(t_task) + t_task_name = t_metadata.get("tag.label", "") + t_task_type = t_metadata.get("tag.type", "") + tasks.append({t_task_name: {"type": t_task_type}}) instance.data["tasks"] = tasks diff --git a/pype/plugins/hiero/publish/extract_review_cutup_video.py b/pype/plugins/hiero/publish/extract_review_cutup_video.py index d1953b5aa2..868d450fd6 100644 --- a/pype/plugins/hiero/publish/extract_review_cutup_video.py +++ b/pype/plugins/hiero/publish/extract_review_cutup_video.py @@ -227,7 +227,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): "step": 1, "fps": fps, "name": "cut_up_preview", - "tags": ["review", "delete"] + self.tags_addition, + "tags": ["review"] + self.tags_addition, "ext": ext, "anatomy_template": "publish" } From 79275d9ff5f43d14aefa070300bd53b49f5904dd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Oct 2020 17:26:23 +0200 Subject: [PATCH 89/91] fix(hiero): thumbnail was rewriting in cycle --- pype/plugins/hiero/publish/collect_plates.py | 9 +++++---- pype/plugins/hiero/publish/collect_reviews.py | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pype/plugins/hiero/publish/collect_plates.py b/pype/plugins/hiero/publish/collect_plates.py index 0c08b94148..4d1cc36a92 100644 --- a/pype/plugins/hiero/publish/collect_plates.py +++ b/pype/plugins/hiero/publish/collect_plates.py @@ -192,16 +192,17 @@ class CollectPlatesData(api.InstancePlugin): instance.data["representations"].append( plates_mov_representation) - thumb_file = head + ".png" + thumb_frame = instance.data["clipInH"] + ( + (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) - thumb_frame = instance.data["sourceIn"] + ((instance.data["sourceOut"] - instance.data["sourceIn"])/2) thumbnail = item.thumbnail(thumb_frame).save( thumb_path, format='png' ) - self.log.debug("__ sourceIn: `{}`".format(instance.data["sourceIn"])) - self.log.debug("__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) + self.log.debug("__ thumbnail: `{}`, frame: `{}`".format( + thumbnail, thumb_frame)) thumb_representation = { 'files': thumb_file, diff --git a/pype/plugins/hiero/publish/collect_reviews.py b/pype/plugins/hiero/publish/collect_reviews.py index 0ef0842e82..edb81aed8a 100644 --- a/pype/plugins/hiero/publish/collect_reviews.py +++ b/pype/plugins/hiero/publish/collect_reviews.py @@ -142,12 +142,12 @@ class CollectReviews(api.InstancePlugin): staging_dir = os.path.dirname( source_path) - thumb_file = head + ".png" + thumb_frame = instance.data["clipInH"] + ( + (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) self.log.debug("__ thumb_path: {}".format(thumb_path)) - thumb_frame = instance.data["sourceIn"] + ( - (instance.data["sourceOut"] - instance.data["sourceIn"]) / 2) self.log.debug("__ thumb_frame: {}".format(thumb_frame)) thumbnail = item.thumbnail(thumb_frame).save( thumb_path, From 333c764c12b79958e2bab222b11a6655a6309634 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Oct 2020 17:28:15 +0200 Subject: [PATCH 90/91] clean(hiero): hound suggestion --- setup/hiero/hiero_plugin_path/Python/Startup/Startup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py b/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py index 003d7959c5..cdf85c524f 100644 --- a/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py +++ b/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py @@ -10,7 +10,7 @@ try: __import__("pyblish") except ImportError as e: - print traceback.format_exc() + print(traceback.format_exc()) print("pyblish: Could not load integration: %s " % e) else: From ef648f61d0d783c40bdbc8daffae862a2474c85c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 12 Oct 2020 22:44:08 +0200 Subject: [PATCH 91/91] Add .circleci/config.yml --- .circleci/config.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..a5452f329f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,24 @@ +version: 2.1 + +jobs: + deploy-website: + docker: + - image: circleci/node:10.16 + + steps: + - checkout + - run: + name: Deploying to GitHub Pages + command: | + git config --global user.email "mkolar@users.noreply.github.com" + git config --global user.name "Website Deployment Script" + echo "machine github.com login mkolar password $GITHUB_TOKEN" > ~/.netrc + cd website && yarn install && GIT_USER=mkolar yarn run publish-gh-pages + +workflows: + build_and_deploy: + jobs: + - deploy-website: + filters: + branches: + only: feature/move_documentation