From 6ee7780972d2c37ccbf0279e5b7c21471afdba97 Mon Sep 17 00:00:00 2001 From: skacmazbelhaine Date: Tue, 6 Jun 2023 15:51:20 +0200 Subject: [PATCH 01/71] Update local settings --- website/docs/admin_settings_local.md | 27 +++++++++++++++++- .../assets/settings/settings_local_02.png | Bin 0 -> 11181 bytes 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 website/docs/assets/settings/settings_local_02.png diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index b254beb53b..214615f442 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -11,18 +11,43 @@ OpenPype stores some of it's settings and configuration in local file system. Th **Local Settings** GUI can be started from the tray menu. -![Local Settings](assets/settings/settings_local.png) +![Local Settings](assets/settings/settings_local_02.png) ## Categories + + ### OpenPype Mongo URL +The **Mongo URL** is the database URL given by your Studio. More details [here](https://openpype.io/docs/artist_getting_started#mongodb). + +
### General +**OpenPype Username** : enter your username (it can also take by default the computer session username). It signs your actions on **OpenPype**. + +
+ +**Admin permissions** : on checked, no need to enter a password (if defined) to access to the **Admin** section. + +
### Experimental tools +Futur version of existing tools or new ones. + +
+ +### Environments +**Environments** data of each software and there extra in-house needed to be loaded correctly. More details [here](https://openpype.io/docs/pype2/admin_config/#environments). + +
### Applications +Location of the softwares and there versions. More details [here](https://openpype.io/docs/admin_settings_system/#applications). + +
### Project Settings +The **Project Settings** allows to determine the root folder. More details [here](https://openpype.io/docs/module_site_sync/#project-settings). + diff --git a/website/docs/assets/settings/settings_local_02.png b/website/docs/assets/settings/settings_local_02.png new file mode 100644 index 0000000000000000000000000000000000000000..725c3327472cae5dc6271586aa4a1de2495104ca GIT binary patch literal 11181 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 T+AsLMj#fcN6 Date: Tue, 6 Jun 2023 15:51:32 +0200 Subject: [PATCH 02/71] Update local settings --- website/docs/admin_settings_local.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index 214615f442..222ee41515 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -36,7 +36,7 @@ Futur version of existing tools or new ones.
-### Environments +### Environments **Environments** data of each software and there extra in-house needed to be loaded correctly. More details [here](https://openpype.io/docs/pype2/admin_config/#environments).
@@ -48,6 +48,3 @@ Location of the softwares and there versions. More details [here](https://openpy ### Project Settings The **Project Settings** allows to determine the root folder. More details [here](https://openpype.io/docs/module_site_sync/#project-settings). - - - From ead57a2cafe2d451e99189d7d1aa94678a032033 Mon Sep 17 00:00:00 2001 From: skacmazbelhaine Date: Tue, 6 Jun 2023 16:30:11 +0200 Subject: [PATCH 03/71] Update local settings --- website/docs/admin_settings_local.md | 2 +- .../docs/assets/settings/settings_local.png | Bin 7212 -> 11181 bytes .../assets/settings/settings_local_02.png | Bin 11181 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 website/docs/assets/settings/settings_local_02.png diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index 222ee41515..177c32451b 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -11,7 +11,7 @@ OpenPype stores some of it's settings and configuration in local file system. Th **Local Settings** GUI can be started from the tray menu. -![Local Settings](assets/settings/settings_local_02.png) +![Local Settings](assets/settings/settings_local.png) ## Categories diff --git a/website/docs/assets/settings/settings_local.png b/website/docs/assets/settings/settings_local.png index d2cf1c920d07e98bb53570f6b76ef039e642e741..725c3327472cae5dc6271586aa4a1de2495104ca 100644 GIT binary patch literal 11181 zcma)i1yodR8!d{6A|W9iI&?P-ImXb9gmkCG&^@RqAtfLkBOoB%ozgubBF#`kNOufz zkDved-@oo!m$l9r=AAQV&N=V%>}T)&ykY99as+tPcvx6i1Pby{O)RV%GQcKu=O!>B zWLx6|yxfJ!>$zcJ-6Q?I-M~soqXY(V-4#@1a93~N#upa|n=mil`^of@D)Yh!e3xm~k-d)tl&8PBr z6neQx9v;(GT98|i_Jk!)*u{ZW(u9;G>~^Y7R1x)V5^5{;$iZc6@q?MkWy(#atorhb3tlHuXQy5g)JU3 z+2Fx>qb;}XD-YJ=j2osM3uY8p4)-5r%@;qoySE+_8>$WxpxGq7tV}1c;K5u_zcl58 z-=4S=>tR*oYynb93Gah%=uHso)2%PuFLh=ds zWxUOt5)}VgBmFcr3AKNC4^omIxp4#^i}q4>aM+AflTQBdVcuPw*QDiQ&TprFcAKV9 zF-P-qnHBN~lwhU8GTIl%3c+kvrW!UsPVb3k2)w>LaWQMS5fc+5;kkK-fWS)cc>Zfp zb#=9Px>HAqqlbrwzrQ3gRb+Rx^!eIt`w9J+vWcOqq3biDW$VbtNRRmsNSs^eO48m`_J|Q8&&zzHl zl=NhKmWG;|+V=A4vuD8nsYyw7{4_Qoalhlu&dyuJedv9fLb-I}`qTO{@tj(p#lc28 z8`Q>{6E(8%+_hsVk*pDCt8xGh%6jwWO;Qpi2oJvrn{?-Mdwcu0Z{Jcq1*k!&^hc!!di&fwUwf`{5=Ri=APl&CSi@#WSl10ch>G4xfv?o`Hb@ zA4y|f-SLrZIU}BN#%Rfk0L=03?(W>&oY$PsDmu}G1=KieY+`afEU{BmWI2$)4Y^n# z(&l)OI3NYwF^zLmxdYDC_RdvQC?TD=``Wih(LI%?XNj#BXvFmyqV;&{rAVsdar>R*sUG@8Mc{D+jTLpP!$xF+Fi)WF#{a(~N&L5^{Y}!QP6Q z-Ek5aJV6Cs=ReQ88Bm02QdbdyExEbY z$P6jS^%>Gbd=_%G1W{E}11^|E!^mjXZ>RaBpR;sxt{Jlw4PnW`#wCgDWPc{8~qxEVdA_6~Os~_zO#KvdMbq~%~S-Iwl3RmE~r7h>cEC!sR6N_~=PESu? zW8=kkCG@7?*x4pn|y~c6+qar%hxkZeR@6(8FK|w)6aoYfI z>%{@$`U1FUDVN2!P>|k5T1G}oRYp!K3WYK;G`#p#SW1l_+=e!t)ZF&Z9tY-ce0IKWPoxOkf3P#8>664&gIsIxW@b29^Vhs}$#H+JR3W@$zrhd657zvaK1TN z`T8C@QrlcuoYB1fCIZ=v;)mH4Sb9+t3kOQK5v|OgOpLX%rA9^>q^-6MOL_eIVN_2* z{%vZ?ckNpyE`vV|2IJ%4;1Ct9&CjV8=NXU@#OR$>k z@9zf!BGK5FU0C>NccFuJnBzqF_U+s7!)yj15hTx+tKzJElYz?PNcx}(*k8hX& zj}^e?wY4=0WA8_NOnw{1wKv)I!_Uvpd+7ZlIu?XPMN7-~tuD8Kq@V@EUWH1Z&Id(9 zu3qG5N}bHONz#`V7jI3JTks@uii@8wgfPHYl5Kqb{D6GE%*x6F?t7iOu*z#^&N*Pi zfNLOSXoU!JQP$D|N8?r1)JX5PVV?k(bOL}}7ZQS*M79(R2L}fi6%{dD%;SdF5+!h> zdFJf#+&e`omU^ObOFrXw7@nlS34q#IZ!)uZHH?xj12>!6xIUbKR?Q!KibNO%wmPO< z6Dg^w1@3iGnD}i~8@Muxg2AUbiN+~ONvC6_aE}2_)TuD?+IeA-{(>rl#DS?Y$C;YTa z0S&{!{Izao#p%?`RHmbC*Us1vkyx)Jv9b2?vBrB2T%MLwV11MR>us+f7Vz1hJ*>89 z|FWR-ZBko^-4?Cq{kCA?*2O+1o(()jusEA3B(ZkfVt|c=p z^s{CkBTDiy6qqTAjOpE}=BldY{`Zc77fbx9v%_dtgx_)B&z}Qy=~B_s`|94_b;woW zWG)pI?gqVQrb>|0U5mKBh*2^pB}`gM3Kx0!PvL=pG41}OwW#6k;i&Dkq_y<1c+uAH z+}w_g9og%4?Ck7eq9&=R9bNWPcn+6NPLn~Y%J&TdiWL2zH$k48MV&r(y|Mzx!#Xi1 z57JJTNmGYf684FS_j|dChlUax8Ze=~8wc?TWc`+!IRfGmoEG->;!o%x#UmMoj=N|6 z{39AxR?L~8g$Ny8zZQOFCUKalny#0#zG6ksMse}Wq~v4*Ek7$o3xVWO!SA0J7Eq_% z@K)r>$X?vV!>iE{q9gZ{5G7_>!jOTb{?7}uQUQs)Uc?$Yp0ZA!EQtej)bx%{EmcBQ zvP*_gDAe0KY0@DwQhRv#7b9csed5Fc?rEJO)zG7}i>)oAfPkR)ch35?HQAOWymVaa zXGUW7($WS+=Ps7o+n?%K`#PzzKtdq`0`SayD{kjBPjEUmcE`=vpT6)#Gj#Uk-}N%T z;35e>-kGlL))80cV#(5LezcqY#Mv;7jV$P<`^=}_%*_1V)0X1TW!c$<+x`|h*7^=b_Unqi&C=N;Uo^XK;p_poYAd9R zN=c7T&PYjd&#!iKH7{^5C^zIW@QAN*Z?7&iQfNk~Xb zj%mnc{0e(2)1H*XmN<$&@`l;Bchf)w$A2{*9}#eIc`GX3V_bHP>*KnAi?v{BY|llK zUY8~Q)|?WA5Bzy_=$8$Frrzmd1m;BQF|na`cCRr(Ky)trtlQNk%LLh_t4Cs}JC`b-Q9sswMXZ2a(1xdjjR?s3;-I zmGg7lH$iVkN1vO2)DWbet%34VnkVc3K3oGPD ztZ%QkZkN0df8N!V-$cK&F6d#BCz{i4G*mfjZQ>U=ZC*}1LN4=GHIEha?x`wNCsl=( z1q9*=QCePrQLcoayfCq~AF=m#IHGzISk-3DL44&?V#TiFOhptd0GzY|`|Fo^F9F?L+F2}Fuh?)3m z?_)}X9nc(S?CSIOc_|A5&`v&wPf{jRlNnyT_hTM1O<337d-gQccCTc5aBy?^hn9<3 ziyxeRm3byia(b(#Iz)_5C_R0QV%ZtJ=iRC9Hc;Z|GYG|NZ{^%}fF)Sj zR0+~rpY6W2nyjy{?|kbWH1{DPfi~$&az^G&4jzuO7mf=Hv0uL~bnRDG`j++>=<7=g zNapJFRB%a11sqAuZu@xY+LS6%Rc03~PLxMulWG@G+IfSCRg_iF{o!e)FwP!iP*BH9 zBDc0=w^d<%M<-PsC(i`gLMg8rIR9O!gQx1s@^XC42!Cmh-?=*-0*R`$(baIdG7oPi zx3E~c`J}94J#CBz`?)eprMR@}gF=Uw8=zRl&Cd*%)fgBi^yc{$9bO+i~Hm}>p> z@zv3FYYNum!$SgsNV3G`o`R1621dw^7AUoPm&a31>F7In)wR^kQ1Tc%sK`g~_9E#h zDYrKm47oPl>s1l?O9yp1IXGGB3-wdJ;av??HNoB1E=gZL@$v3jD7ZZF@UZgmU`9ks zSe6CfaPowoRVaAP`@9kpOJ?rEk&9|VAE4fdgqS6c(o}P$`k7 zYGbb1p4Jz^*U{__amZFsQqf@TG`6)*Pi>S?qaeg1ESX5@TH0S(Q9dFV0_V)X97o-L zH9dmrKH3fJ(wlXx^B8TcHRt~- zr~fFpKy3xeF0h~BuD!g}_Lpw0V>2m^x`#(tp;x?$G)WuVaPE0SM!X+{d-BuY)7l;n zX@RxuL!kLQu6iojqxoh-L4l++Pfehysnakm-2=PI$+FQKBG3#O(B4%54y*K;Wn@f( zBJ;1)iQ$rrgoFX;4-|wfDI-2TCN;ee?HUp+mzv5e(`$uGxVipoTSZCX*t@yfgFsNQ z5~N`}cI+zicE-GVOjL*pzZ=(6H+3)p7WRUT4DNohJe(fSVI@RNKp17{(DK&eZrS!; zr99VEt25#Sm%h@%?k)@jke)?XFc5Z+?+>E)$?(o7Zm zmf*o4qM07F04<@Y`ow~JMv+)o1sNIH$JjJVs`_^w3-@Ex+1wjvz-g}}OQZjSM+rWC z9S4nwXqB`gvF6pUM&YgJr=1F=J#x^4d-r1b`2WVkN5N*4AV9gVZ?5U-XAZ2J>w&T& z4#}#;w>vCYLzA5077Rg*cgyCkYG$1b{F1`to%wzdbZ2EXeXRQ2#d98^BL`hyx690I zKlRpD=LmacFR4CzgsPK7#_2VlB%N1!{|2_JP5?jYMn1)&?iV zX9pdwer|q`1udoZSqf|%bq9BcmzVSV9nT>W5(aNm&pe=}=B18w8ylN0^!ym|_D;g3 zr>*S|hZ-I2g!&ntkkK&gyN{vDf2E)|{lhk?8%XS1j{|A`LF$0O2O|D|g#Wyg9vXwp zw(!oN$C~;K(y+rgg z1_zVL`RtO+H$`4#JpU^5R`jd%rQZ)v+ zzqaS=?7se^amOK8L%Bq3@oWSy;GfJjod0 z6Wz&xz)YPD4E%I-8vNib#l?coFB4ucZLe*w+wQPr6>Ub$Y%234N(f0#&G@rRNH7Wu z_t_?ehd)mqN)$l=3Grm4nwqw8Qa?p`( zZdTLp$L^L(BU+oA3rn3p44`>Z_t7Qy2#Q-uHF@I1j7qw@t%b(R9(5x@<+j%?0p{hl zTa{!Ks}PUVZ(p(f(s*>I(Ze5?KgTZKtT*&rcv^^DWzG5`FXL#GJ!W3W#%o2H3q&k( zH9+5O%Bj}5l&g8_XlQtT@AUGrL+Pb82ajbf%`=PBYc)y5;Dh-& z8VK&;F`=@u@t#jJ^vCEZA|>`?dOE+jc;<*LHV#XkkofH8+Jv`{Uf4r0n1dsBfsaTU z#*jDw1J69B3K{jOSRosT1wC!4+Rt3jDAutlpPF$x`B5dyMc&iZbsHB~Mn;ZA!^{l+ zDM^`n`v4RArLl1w?TSPq@4b7P2k=E-a&q#++l<70mK8sKFoi+e(XOZd($PqR$uSds zX>sxIrb!zc3;;6$jG!>EHcOwmje%nWKp z7>QYxm&s;dj>@s(;q7y9C{hKFjzeC>5pBB$PCL~Wiw;7W5(h{~e}vt&56*0An)z`5 z$+IOHK8b|9g5q2fxdO?0uq74E;;+$M2ZCza*UETiA-#3dQwll7-t}@r{92M-$ zJ{5~lM&S4u;M{y-VIfE?EMzQ1y;@LMtGmTUbeDK$B6$5j5VNANwkDt%`^m+p_xqY= zp=FhYA;_hj2@QS`kv;7(9&Xg=sHl*TreQ2Yp(a2e_^DNXUmP1gu@kDr5jBSaQZb;} zHf^ECwBleOQKg{+iS2(ptp*UOsv6_&uUIb<3C<*g*0AwHbi7WQ6~Yw>!HB_*nDZ4&PR(u&~ajf8*`{KEodf z-W7)Vp}ZLH-};^Si_vmXEn5d?B@()>nonI^zA~VsY7*LOgS0qe`Yah4Ex-^{!BsI~ zVZFk1y&cX^xFNE+c~#KmlTvnsZ%}z--j36|x;NG%G`Nc}YrLf(bBi$8NT3L0J4*E4u)=sDju2e_?8MeZjT{ssm%b3eJt zVQ84v?@;3pZxC?}2MIDpnkDVP zSJ#(%US?&HiHYeM((4u`pI!VAOXDxg|9{}*O-OfFzOUbTW8-;jED5NVO;)ZuJDc(B ziGRF6@Blh0DvCE5*_W3!BUESxbCd;v@&`>km0rIZo%2k>6 zZw37o2??HCgUV)CI)ou{$?*vvzDNf&c-q1~SD2?he)up87S_4oo?hq}5&9u7j|DmW zi<>-EHSc;YTTO-B?a7l2SqZma2W}QiabTnQmr`)TwJo}&q^89K-%^orosG4LIxUVq zB$7BdI3g;N;sLs=z+6i)r_VAuRggV?B>X@CKLGx#Xw3dO8|c=X`I3^ke0G($AdnQd z&)AfMjs!6QL9~6fc=*_gXJcNjuz5L{hereqW@F)a--F(sstz&>9YD)7Ek0(Roo(hx z^n=5V*~rAxc}mAC%gR29QijQwiZXp!dePdt<;to`G2!4-GrhU7?QBp?Jv6u&oq5xZFq5$Q}$|{Act3X`bk6&-Sw+06Z9Ex=$0ADq} z-?se4$F$g8?b;b~$qZON6F^TJona)S&)Ko!XL)-e-#arv^I~xpK zT!YiijlSVe_q4mteR1EMJ^={v)7N~=>@KLeS-?h7Qc7J7e}vuE-D$a+$ea3h^~TK^ z(p|~NSckR$!p{Eb#r=b@{ZTF33EC~!lH$2v;Wr**{*oNO@g#ju5FlwVwKf1g{y%)Y zi9w$^4ecv{h8SrYmX*{4IUTUuMh`0Z!ZsUlJuEyNACtjC7JNU3N%`W(!m}6y5G>+` zkFk-xeFY#2#<}o2ItxCkh6ozd`B@Z#`CY7Rz1_K)ao)^VRAi}Veh5ud1$;0>mwgX^ za4FGTgJL*z@e+fl>c8JNh5AKL_kfD)j#JY&Lt`CKd~mAr3gA=$NS~j-e#_&cO4hHW z<_QY{#2et$l^n$@v>df{F?yYiywkszbW!#ng02(oZ!e0+`p8QypkrV=^K<=xb{ zJPrkC6YQP&E5wSJv2I=6#CE89;`B`Q3i;bgKka{IIS4b~;yUMMQXaX0;Z9DzT&%C{Bco6!)Rb9n2Q!_pu53&z zd408zk}_K9`^-o0E|8Ov_ca`iEJoiYbk&pnw}Vw@UcE|V<9@!lcr)0{)ve-1g0z&6 zJ{c+NbrPZ;{t{%Xqdh+$_?Pd+SYpcXYAmh9(2hpB#z3A8M&pIpw1;rzEYQa{? zUwV5N7Rnc5_Ku^R5J=v5MSxy=ZcSxs$>QSd`S@py*)xK|C(X;J5%3uPm#S;wnD23!bXlE=$-ESHlHJbM_=*=n6OpI0RaItP%VSoU>kq1G> zm`gd=K!%v#+{RJx+dBm{i9J0%IB;#Pk2^3h;IJL}J~LI+(GibEhHJ>GMW~e&y|>Z> z{G7BjQ-S2JtZ$hy8h}HF-}m|Z=g)5oiH&yeYrGMchEguNHryl?9u?Gj+$DoUUdSzJ zC;$dG{VK@K%K^+#fRk*+PeU^y3-igqf&z-)YXLG@;;2}09rllZ>$xp6mMrh;e$zA> z21HuQcYK1#DaWX{r*)6PVH2BagMEFRjM)~i+jUa2Ki7S8s;sT{DA6}~W>Gl`^nln@ zlr=E{SHmUr($TFJa$OS>&`)hebDu_rEv@tNm{@s+bH-`@)2OL-1&mb8Rr<8zy%4$X zTw(n*Dj2W%AJQ_5#0Gwmi=7ATy3CEDFfx&OdolmDSyagNfYm~Kcv6|--(xd4c#f<5Dty$8Ae5 zHjaeeAEs-9dN@!qUJPNB8d`Vvm7Mb^9{XNzuC4g`@jMJ1wl*S11goE!)WcT2SI2tV zG?!|R0Ajq$e8e|V94z$RH(yHJUdct{%H!S!LHBlsGy++h5H6#qr}0I-Zcb_Ndp%zG zK8W$bYh<%jkbW_t&E1(s?+p%alCYBxOS(;|O?sTH-!U;m2U5(6vcth|7Yx zlB(y=`26*`IUpvi49JK*Lz*))ePl&xDDpVid4L-JFl)cUyjmRowGy_j*|WRLmN>x4 z%NeCx*sG>OVc_aoK^ZLO4cqxqm7BYwY^wJk@UxX-qSH4gb;3JrRsI4kkZ;Js%{3_D zx?CK)crkZ%Bim&gxD0-Ip$g4VJbxQuYmS%fCVclvRpR|3d#nw+;rf@ zX`j^wEkkL66bhU@N z$Rh_LmXnd8j_l5uyL$YZ&xMUxZ;(8EtOlH@num-I1591pmm8*&+8pKjj^S>5K9ays zq{%Mrvx*ufr)4CMD0FlznaAIJ0)xJd2-Vl;@o8?UIwS*CyeM)y@i%r-HMusGW2LsK z6FqRKogAM=ZEw+0Qqv134~A!(=Z!vr;&;HngJVzep8+05;Ccj0+}V%(slv)SL!cMw zrRSI^v9hv)hgY*bS-An&OgcJQ5n=iR;=JDEp`pxyrn4`CfsSqnPg$%}EvADAGCZ3S zpBhKSLY^+>N6$(+V{z%&I9oHvM;g;*FH9N1=?q|E;m9BpO97z4Dg;K2R1+s1j4uA2 z1kE3u;rnJA@*w+%dwb=9#u5+E5MX;k$3T&%pT;T0ZS{3&YkS>>QkIOmp&~ZN%_qRP z6~#bG7oV2qy%qj$gij8NgHyl0&TbwP8rt5`puKPM}|#+wt79*?mi`91a8{YP zxaY6y$?$ya?1%@>|Dal=Y-3TZm7B-Op_Nr`-{*zw?4|J+$aTA+`ET;F`oB8k^UuzF zK#Q(o!1-_<-E171gJn|LkT~4>V~Kg40Iqa>GPBHIt~91JU?*~E(k5ebd%e*;pU#G8 z<}dGgT783pBJX{)Hqasw(NAKIE$i&`F80)+f3&n$cRPp}c;W|_Thqm#Obg|i9PAvY zZ5zXmDiy-+8q}_E4f48s9w{pue$B@Q+~PLJP3I*HB~~i#KK{R%W`7V67-}mn>%aDk zin1#+`Cr}IKVSN{uL5uWO%nbSPyhF^4myQRt?$j)w8-c|_zSSQTDyFp35j+W+q6#T zdh|+VLquksdQXoTnVGe#9Ru6qf zm^$GY`MqFop(y1_qO#hMPMu-n-b_uO1+{9psBm~w!e zNdC-y@WI#B+aZmw=5GM~>gMhA^)c#NiRdA8W^bY)F#pDus zy~ofhWiFxzEYLE(*l4{73G`)_7V)(ER?>K9J7I0YDh|3>W|jUnU4jES_Vd8I3D*lX zJ~mo7PLttxoNscy; zvC5sM8wOHA2HM%#X{qX&i`?1S*f1d%s+@G)IxWio-SmcBnLuKda%}98US21F*8+6C zdHq+ZKzMhF6`5j{iW3pui|PRZ7H&$M#LTQLy*Zko#l15i|Y(B*@(tIP4$zTv^s z?hXv*99Q~mxiTr~3!Qd+5ki|I{;Ams`Zx+8g2o2Ah}6{CZxc)eL?+}_2BW|ATj|B& zs?57HK*sBq3c0zpdT$-&Sjn=oY)_Y8wqEaIAeiy-(4_0rEsa_~`jGpk>(fy+NZ{3Ep7G@gYH(;s>b&;}2+(||{_r}jEY0qSs3@h54&B>Z;zsAy6wN<= z(m$Yjz(Da*AT=lmXV0Y^%5?wM!P$kn*_V!OFnrMxrvBj}*xTE^%HM#Un4dO^O-mkn z?;d>^6MhJ3%P2d&lv@mWsAy*fg{&sI`*B#+lbz5QE|t3H>IsO=fOTHRA$MfDy!$vWhY#q2@J^~Dk{z;&g( zu1c@=L#)F+fMrMOE3SU4&oCz!(}k^gOip^=fYp}O6kq?6m1I=K4HgDpMpYO1I;=e! zv#SK8Pp|o24PpD-p7`N&j0y0vo|JB7z{L_NpQg^yUaC~gFj5@{(!6#w?4N0_hrX)m zaAE86Z@Q?K)fN~XSx+W^gru>GB T+AsLMj#fcN6V{?TXm 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 Tu$%%u=7ApE)zK(Xvkdw#D_=3L diff --git a/website/docs/assets/settings/settings_local_02.png b/website/docs/assets/settings/settings_local_02.png deleted file mode 100644 index 725c3327472cae5dc6271586aa4a1de2495104ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11181 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 T+AsLMj#fcN6 Date: Wed, 7 Jun 2023 10:57:27 +0200 Subject: [PATCH 04/71] update after comments - local settings --- website/docs/admin_settings_local.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index 177c32451b..99e27731d0 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -18,7 +18,7 @@ OpenPype stores some of it's settings and configuration in local file system. Th ### OpenPype Mongo URL -The **Mongo URL** is the database URL given by your Studio. More details [here](https://openpype.io/docs/artist_getting_started#mongodb). +The **Mongo URL** is the database URL given by your Studio. More details [here](artist_getting_started#mongodb).
@@ -37,14 +37,14 @@ Futur version of existing tools or new ones.
### Environments -**Environments** data of each software and there extra in-house needed to be loaded correctly. More details [here](https://openpype.io/docs/pype2/admin_config/#environments). +Local replacement of the environment data of each software and additional internal data necessary to be loaded correctly.
### Applications -Location of the softwares and there versions. More details [here](https://openpype.io/docs/admin_settings_system/#applications). +Local override of software paths and their versions. More details [here](admin_settings_system/#applications).
### Project Settings -The **Project Settings** allows to determine the root folder. More details [here](https://openpype.io/docs/module_site_sync/#project-settings). +The **Project Settings** allows to determine the root folder. More details [here](module_site_sync/#local-settings). From 85af2013726edb61a76faff9122a28cd1f1749dc Mon Sep 17 00:00:00 2001 From: skacmazbelhaine Date: Thu, 8 Jun 2023 10:16:04 +0200 Subject: [PATCH 05/71] fix link path --- website/docs/admin_settings_local.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index 99e27731d0..33e0e374ce 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -18,7 +18,7 @@ OpenPype stores some of it's settings and configuration in local file system. Th ### OpenPype Mongo URL -The **Mongo URL** is the database URL given by your Studio. More details [here](artist_getting_started#mongodb). +The **Mongo URL** is the database URL given by your Studio. More details [here](artist_getting_started.md#mongodb).
@@ -42,9 +42,9 @@ Local replacement of the environment data of each software and additional intern
### Applications -Local override of software paths and their versions. More details [here](admin_settings_system/#applications). +Local override of software paths and their versions. More details [here](admin_settings_system.md#applications).
### Project Settings -The **Project Settings** allows to determine the root folder. More details [here](module_site_sync/#local-settings). +The **Project Settings** allows to determine the root folder. More details [here](module_site_sync.md#local-settings). From 54bdd152f6b3d67e240d487b264f1a674a75f876 Mon Sep 17 00:00:00 2001 From: skacmazbelhaine Date: Thu, 8 Jun 2023 11:03:19 +0200 Subject: [PATCH 06/71] Correction of line breaks --- website/docs/admin_settings_local.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index 33e0e374ce..142238f14f 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -15,36 +15,22 @@ OpenPype stores some of it's settings and configuration in local file system. Th ## Categories - - ### OpenPype Mongo URL The **Mongo URL** is the database URL given by your Studio. More details [here](artist_getting_started.md#mongodb). -
- ### General **OpenPype Username** : enter your username (it can also take by default the computer session username). It signs your actions on **OpenPype**. -
- **Admin permissions** : on checked, no need to enter a password (if defined) to access to the **Admin** section. -
- ### Experimental tools Futur version of existing tools or new ones. -
- ### Environments Local replacement of the environment data of each software and additional internal data necessary to be loaded correctly. -
- ### Applications Local override of software paths and their versions. More details [here](admin_settings_system.md#applications). -
- ### Project Settings The **Project Settings** allows to determine the root folder. More details [here](module_site_sync.md#local-settings). From aea1adab65ed3707a8963543313e0e6976082046 Mon Sep 17 00:00:00 2001 From: skacmazbelhaine Date: Tue, 13 Jun 2023 17:03:51 +0200 Subject: [PATCH 07/71] update local settings for global settings PR --- website/docs/admin_settings_local.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/website/docs/admin_settings_local.md b/website/docs/admin_settings_local.md index 142238f14f..8935b29fb5 100644 --- a/website/docs/admin_settings_local.md +++ b/website/docs/admin_settings_local.md @@ -19,18 +19,16 @@ OpenPype stores some of it's settings and configuration in local file system. Th The **Mongo URL** is the database URL given by your Studio. More details [here](artist_getting_started.md#mongodb). ### General -**OpenPype Username** : enter your username (it can also take by default the computer session username). It signs your actions on **OpenPype**. - -**Admin permissions** : on checked, no need to enter a password (if defined) to access to the **Admin** section. +**OpenPype Username** : enter your username (if not provided, it uses computer session username by default). This username is used to sign your actions on **OpenPype**, for example the "author" on a publish. +**Admin permissions** : When enabled you do not need to enter a password (if defined in Studio Settings) to access to the **Admin** section. ### Experimental tools -Futur version of existing tools or new ones. - +Future version of existing tools or new ones. ### Environments Local replacement of the environment data of each software and additional internal data necessary to be loaded correctly. ### Applications -Local override of software paths and their versions. More details [here](admin_settings_system.md#applications). +Local override of software executable paths for each version. More details [here](admin_settings_system.md#applications). ### Project Settings The **Project Settings** allows to determine the root folder. More details [here](module_site_sync.md#local-settings). From 5c44f12b37374b48c8f905834ff55e1bec99dee4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 00:51:48 +0200 Subject: [PATCH 08/71] Cleanup code - Refactor `get_file_node_files` because popping from `paths` by index should have been done in reversed order anyway. It's now changed to not need popping at all. - Removed unused `RENDERER_NODE_TYPES` and if-branch which collected `node_attrs` list which was unused + collected members which was also done outside of the if branch and thus generated no extra data. - Collected all materials from look set attributes at once instead of multiple queries - Collected all file nodes in history from a single query instead of per type - Restructured assignment of `instance.data["resources"]` to be more readable - Cached `PXR_NODES` only ones (Note: plugin load is checked on discovery of the collect look plugin) instead of querying plugin load and its nodes per file node per attribute - Removed some debug logs or combined some messages --- .../maya/plugins/publish/collect_look.py | 192 ++++++------------ 1 file changed, 64 insertions(+), 128 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 287ddc228b..6832411190 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -17,11 +17,6 @@ SHAPE_ATTRS = ["castsShadows", "visibleInRefractions", "doubleSided", "opposite"] - -RENDERER_NODE_TYPES = [ - # redshift - "RedshiftMeshParameters" -] SHAPE_ATTRS = set(SHAPE_ATTRS) @@ -36,12 +31,13 @@ def get_pxr_multitexture_file_attrs(node): FILE_NODES = { + # maya "file": "fileTextureName", - + # arnold (mtoa) "aiImage": "filename", - + # redshift "RedshiftNormalMap": "tex0", - + # renderman "PxrBump": "filename", "PxrNormalMap": "filename", "PxrMultiTexture": get_pxr_multitexture_file_attrs, @@ -49,6 +45,15 @@ FILE_NODES = { "PxrTexture": "filename" } +# Cache pixar dependency node types so we can perform a type lookup against it +PXR_NODES = set() +if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): + PXR_NODES = set( + cmds.pluginInfo("RenderMan_for_Maya", + query=True, + dependNode=True) + ) + def get_attributes(dictionary, attr, node=None): # type: (dict, str, str) -> list @@ -232,20 +237,17 @@ def get_file_node_files(node): """ paths = get_file_node_paths(node) - sequences = [] - replaces = [] + + # For sequences get all files and filter to only existing files + result = [] for index, path in enumerate(paths): if node_uses_image_sequence(node, path): glob_pattern = seq_to_glob(path) - sequences.extend(glob.glob(glob_pattern)) - replaces.append(index) + result.extend(glob.glob(glob_pattern)) + elif os.path.exists(path): + result.append(path) - for index in replaces: - paths.pop(index) - - paths.extend(sequences) - - return [p for p in paths if os.path.exists(p)] + return result class CollectLook(pyblish.api.InstancePlugin): @@ -260,7 +262,7 @@ class CollectLook(pyblish.api.InstancePlugin): membership relations. Collects: - lookAttribtutes (list): Nodes in instance with their altered attributes + lookAttributes (list): Nodes in instance with their altered attributes lookSetRelations (list): Sets and their memberships lookSets (list): List of set names included in the look @@ -285,76 +287,31 @@ class CollectLook(pyblish.api.InstancePlugin): instance: Instance to collect. """ - self.log.info("Looking for look associations " - "for %s" % instance.data['name']) - - # Discover related object sets - self.log.info("Gathering sets ...") - sets = self.collect_sets(instance) + self.log.debug("Looking for look associations " + "for %s" % instance.data['name']) # Lookup set (optimization) instance_lookup = set(cmds.ls(instance, long=True)) - self.log.info("Gathering set relations ...") - # Ensure iteration happen in a list so we can remove keys from the + # Discover related object sets + self.log.debug("Gathering sets ...") + sets = self.collect_sets(instance) + + # Ensure iteration happen in a list to allow removing keys from the # dict within the loop - - # skipped types of attribute on render specific nodes - disabled_types = ["message", "TdataCompound"] - + self.log.info("Gathering set relations ...") for obj_set in list(sets): self.log.debug("From {}".format(obj_set)) - - # if node is specified as renderer node type, it will be - # serialized with its attributes. - if cmds.nodeType(obj_set) in RENDERER_NODE_TYPES: - self.log.info("- {} is {}".format( - obj_set, cmds.nodeType(obj_set))) - - node_attrs = [] - - # serialize its attributes so they can be recreated on look - # load. - for attr in cmds.listAttr(obj_set): - # skip publishedNodeInfo attributes as they break - # getAttr() and we don't need them anyway - if attr.startswith("publishedNodeInfo"): - continue - - # skip attributes types defined in 'disabled_type' list - if cmds.getAttr("{}.{}".format(obj_set, attr), type=True) in disabled_types: # noqa - continue - - node_attrs.append(( - attr, - cmds.getAttr("{}.{}".format(obj_set, attr)), - cmds.getAttr( - "{}.{}".format(obj_set, attr), type=True) - )) - - for member in cmds.ls( - cmds.sets(obj_set, query=True), long=True): - member_data = self.collect_member_data(member, - instance_lookup) - if not member_data: - continue - - # Add information of the node to the members list - sets[obj_set]["members"].append(member_data) - # Get all nodes of the current objectSet (shadingEngine) for member in cmds.ls(cmds.sets(obj_set, query=True), long=True): member_data = self.collect_member_data(member, instance_lookup) - if not member_data: - continue - - # Add information of the node to the members list - sets[obj_set]["members"].append(member_data) + if member_data: + # Add information of the node to the members list + sets[obj_set]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end # Thus the data will be limited to only what we need. - self.log.info("obj_set {}".format(sets[obj_set])) if not sets[obj_set]["members"]: self.log.info( "Removing redundant set information: {}".format(obj_set)) @@ -382,35 +339,26 @@ class CollectLook(pyblish.api.InstancePlugin): "rman__displacement" ] if look_sets: - materials = [] + self.log.debug("Found look sets:\n{}".format(look_sets)) + # Get all material attrs for all look sets to retrieve their inputs + existing_attrs = [] for look in look_sets: - for at in shader_attrs: - try: - con = cmds.listConnections("{}.{}".format(look, at)) - except ValueError: - # skip attributes that are invalid in current - # context. For example in the case where - # Arnold is not enabled. - continue - if con: - materials.extend(con) + for attr in shader_attrs: + if cmds.attributeQuery(attr, node=look_sets, exists=True): + existing_attrs.append("{}.{}".format(look, attr)) + materials = cmds.listConnections(existing_attrs, + source=True, + destination=False) or [] + self.log.debug("Found materials:\n{}".format(materials)) - self.log.info("Found materials:\n{}".format(materials)) - - self.log.info("Found the following sets:\n{}".format(look_sets)) # Get the entire node chain of the look sets - # history = cmds.listHistory(look_sets) - history = [] - for material in materials: - history.extend(cmds.listHistory(material, ac=True)) - - # handle VrayPluginNodeMtl node - see #1397 - vray_plugin_nodes = cmds.ls( - history, type="VRayPluginNodeMtl", long=True) - for vray_node in vray_plugin_nodes: - history.extend(cmds.listHistory(vray_node, ac=True)) + # history = cmds.listHistory(look_sets, allConnections=True) + history = cmds.listHistory(materials, allConnections=True) + # Since we retrieved history only of the connected materials + # connected to the look sets above we now add direct history + # for some of the look sets directly # handling render attribute sets render_set_types = [ "VRayDisplacement", @@ -428,20 +376,17 @@ class CollectLook(pyblish.api.InstancePlugin): or [] ) - all_supported_nodes = FILE_NODES.keys() - files = [] - for node_type in all_supported_nodes: - files.extend(cmds.ls(history, type=node_type, long=True)) + files = cmds.ls(history, + type=list(FILE_NODES.keys()), + long=True) - self.log.info("Collected file nodes:\n{}".format(files)) + self.log.info("Collected file nodes:{}".format(files)) # Collect textures if any file nodes are found - instance.data["resources"] = [] - for n in files: - for res in self.collect_resources(n): - instance.data["resources"].append(res) - - self.log.info("Collected resources: {}".format( - instance.data["resources"])) + resources = [] + for node in files: + resources.extend(self.collect_resources(node)) + instance.data["resources"] = resources + self.log.debug("Collected resources: {}".format(resources)) # Log warning when no relevant sets were retrieved for the look. if ( @@ -456,7 +401,7 @@ class CollectLook(pyblish.api.InstancePlugin): instance.extend(shader for shader in look_sets if shader not in instance_lookup) - self.log.info("Collected look for %s" % instance) + self.log.debug("Collected look for %s" % instance) def collect_sets(self, instance): """Collect all objectSets which are of importance for publishing @@ -536,14 +481,14 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect changes to "custom" attributes node_attrs = get_look_attrs(node) - self.log.info( - "Node \"{0}\" attributes: {1}".format(node, node_attrs) - ) - # Only include if there are any properties we care about if not node_attrs: continue + self.log.debug( + "Node \"{0}\" attributes: {1}".format(node, node_attrs) + ) + node_attributes = {} for attr in node_attrs: if not cmds.attributeQuery(attr, node=node, exists=True): @@ -574,14 +519,12 @@ class CollectLook(pyblish.api.InstancePlugin): Returns: dict """ - self.log.debug("processing: {}".format(node)) - all_supported_nodes = FILE_NODES.keys() - if cmds.nodeType(node) not in all_supported_nodes: + if cmds.nodeType(node) not in FILE_NODES: self.log.error( "Unsupported file node: {}".format(cmds.nodeType(node))) raise AssertionError("Unsupported file node") - self.log.debug(" - got {}".format(cmds.nodeType(node))) + self.log.debug("processing: {} ({})".format(node, cmds.nodeType(node))) attributes = get_attributes(FILE_NODES, cmds.nodeType(node), node) for attribute in attributes: @@ -613,14 +556,7 @@ class CollectLook(pyblish.api.InstancePlugin): # renderman allows nodes to have filename attribute empty while # you can have another incoming connection from different node. - pxr_nodes = set() - if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): - pxr_nodes = set( - cmds.pluginInfo("RenderMan_for_Maya", - query=True, - dependNode=True) - ) - if not source and cmds.nodeType(node) in pxr_nodes: + if not source and cmds.nodeType(node) in PXR_NODES: self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray From fab6c0d9c603e1edfcdd86cc007cd7d0776a7ef5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 01:24:00 +0200 Subject: [PATCH 09/71] Fix typo --- openpype/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 6832411190..e08bf88872 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -345,7 +345,7 @@ class CollectLook(pyblish.api.InstancePlugin): existing_attrs = [] for look in look_sets: for attr in shader_attrs: - if cmds.attributeQuery(attr, node=look_sets, exists=True): + if cmds.attributeQuery(attr, node=look, exists=True): existing_attrs.append("{}.{}".format(look, attr)) materials = cmds.listConnections(existing_attrs, source=True, From 40dc747f9ae1ba867e8afc14735c1e0d3a40ea0e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 01:24:33 +0200 Subject: [PATCH 10/71] Fix file nodes filtering --- openpype/hosts/maya/plugins/publish/collect_look.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index e08bf88872..21e3d83d13 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -45,6 +45,13 @@ FILE_NODES = { "PxrTexture": "filename" } +# Keep only node types that actually exist +all_node_types = set(cmds.allNodeTypes()) +for node_type in list(FILE_NODES.keys()): + if node_type not in all_node_types: + FILE_NODES.pop(node_type) +del all_node_types + # Cache pixar dependency node types so we can perform a type lookup against it PXR_NODES = set() if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): @@ -377,10 +384,13 @@ class CollectLook(pyblish.api.InstancePlugin): ) files = cmds.ls(history, + # It's important only node types are passed that + # exist (e.g. for loaded plugins) because otherwise + # the result will turn back empty type=list(FILE_NODES.keys()), long=True) - self.log.info("Collected file nodes:{}".format(files)) + self.log.info("Collected file nodes: {}".format(files)) # Collect textures if any file nodes are found resources = [] for node in files: From 38ab207e06b63eec371e8ef1cf83c90b73d6630d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 01:30:09 +0200 Subject: [PATCH 11/71] Cosmetics: remove new line in log --- openpype/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 21e3d83d13..0104c4e1dd 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -357,7 +357,7 @@ class CollectLook(pyblish.api.InstancePlugin): materials = cmds.listConnections(existing_attrs, source=True, destination=False) or [] - self.log.debug("Found materials:\n{}".format(materials)) + self.log.debug("Found materials: {}".format(materials)) # Get the entire node chain of the look sets # history = cmds.listHistory(look_sets, allConnections=True) From ae3364a833efc39ce82d073a4402d95b2d78735d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 01:30:28 +0200 Subject: [PATCH 12/71] Cosmetics: remove new line in logs --- openpype/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 0104c4e1dd..f8c5ccf4ee 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -346,7 +346,7 @@ class CollectLook(pyblish.api.InstancePlugin): "rman__displacement" ] if look_sets: - self.log.debug("Found look sets:\n{}".format(look_sets)) + self.log.debug("Found look sets: {}".format(look_sets)) # Get all material attrs for all look sets to retrieve their inputs existing_attrs = [] From 58217d88c57700e7e645d5b7dbca2bc6e5f92bf3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 11:11:59 +0200 Subject: [PATCH 13/71] Ensure file nodes are processed only once --- openpype/hosts/maya/plugins/publish/collect_look.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index f8c5ccf4ee..80c4b61de5 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -383,6 +383,9 @@ class CollectLook(pyblish.api.InstancePlugin): or [] ) + # Ensure unique entries only + history = list(set(history)) + files = cmds.ls(history, # It's important only node types are passed that # exist (e.g. for loaded plugins) because otherwise @@ -390,10 +393,13 @@ class CollectLook(pyblish.api.InstancePlugin): type=list(FILE_NODES.keys()), long=True) + # Sort for log readability + files.sort() + self.log.info("Collected file nodes: {}".format(files)) # Collect textures if any file nodes are found resources = [] - for node in files: + for node in files: # sort for log readability resources.extend(self.collect_resources(node)) instance.data["resources"] = resources self.log.debug("Collected resources: {}".format(resources)) From 95e763dee4d592d94180cf9047eb9210fae9246c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 11:12:46 +0200 Subject: [PATCH 14/71] Clarify that computed source is only used for logging by moving logic closer together --- .../maya/plugins/publish/collect_look.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 80c4b61de5..321df3e61c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -548,27 +548,28 @@ class CollectLook(pyblish.api.InstancePlugin): node, attribute )) - computed_attribute = "{}.{}".format(node, attribute) - if attribute == "fileTextureName": - computed_attribute = node + ".computedFileTextureNamePattern" - self.log.info(" - file source: {}".format(source)) color_space_attr = "{}.colorSpace".format(node) try: color_space = cmds.getAttr(color_space_attr) except ValueError: # node doesn't have colorspace attribute color_space = "Raw" + # Compare with the computed file path, e.g. the one with # the pattern in it, to generate some logging information # about this difference - computed_source = cmds.getAttr(computed_attribute) - if source != computed_source: - self.log.debug("Detected computed file pattern difference " - "from original pattern: {0} " - "({1} -> {2})".format(node, - source, - computed_source)) + # Only for file nodes with `fileTextureName` attribute + if attribute == "fileTextureName": + computed_source = cmds.getAttr( + "{}.computedFileTextureNamePattern".format(node) + ) + if source != computed_source: + self.log.debug("Detected computed file pattern difference " + "from original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) # renderman allows nodes to have filename attribute empty while # you can have another incoming connection from different node. From a54cddf0f378eb80bb4d2bd2c7386415ed69f034 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Jul 2023 11:13:12 +0200 Subject: [PATCH 15/71] Clarify in log that it's processing a resource --- openpype/hosts/maya/plugins/publish/collect_look.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 321df3e61c..bbe25c559e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -540,7 +540,9 @@ class CollectLook(pyblish.api.InstancePlugin): "Unsupported file node: {}".format(cmds.nodeType(node))) raise AssertionError("Unsupported file node") - self.log.debug("processing: {} ({})".format(node, cmds.nodeType(node))) + self.log.debug( + "Collecting resource: {} ({})".format(node, cmds.nodeType(node)) + ) attributes = get_attributes(FILE_NODES, cmds.nodeType(node), node) for attribute in attributes: From 58d2bcad88ca7ccd69a04fa6ec1e85dd693ac462 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Aug 2023 12:07:57 +0200 Subject: [PATCH 16/71] Extract active view as thumbnail --- .../publish/extract_active_view_thumbnail.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py diff --git a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py new file mode 100644 index 0000000000..1bf010896a --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py @@ -0,0 +1,51 @@ +import maya.api.OpenMaya as om +import maya.api.OpenMayaUI as omui + +import pyblish.api +import tempfile + + +class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): + """Set instance thumbnail to a screengrab of current active viewport. + + This makes it so that if an instance does not have a thumbnail set yet that + it will get a thumbnail of the currently active view at the time of + publishing as a fallback. + + """ + order = pyblish.api.ExtractorOrder + 0.49 + label = "Active View Thumbnail" + families = ["workfile"] + hosts = ["maya"] + + def process(self, instance): + thumbnail = instance.data.get("thumbnailPath") + if not thumbnail: + view_thumbnail = self.get_view_thumbnail(instance) + if not view_thumbnail: + return + + self.log.debug("Setting instance thumbnail path to: {}".format( + view_thumbnail + )) + instance.data["thumbnailPath"] = view_thumbnail + + def get_view_thumbnail(self, instance): + cache_key = "__maya_view_thumbnail" + context = instance.context + + if cache_key not in context.data: + # Generate only a single thumbnail, even for multiple instances + with tempfile.NamedTemporaryFile(suffix="_thumbnail.jpg", + delete=False) as f: + path = f.name + + view = omui.M3dView.active3dView() + image = om.MImage() + view.readColorBuffer(image, True) + image.writeToFile(path, "jpg") + self.log.debug("Generated thumbnail: {}".format(path)) + + context.data[cache_key] = path + return context.data[cache_key] + From 4049d9acb148e93f11849bc2395b904c0ff13dcf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Aug 2023 12:12:14 +0200 Subject: [PATCH 17/71] Cosmetics - good doggy! --- .../hosts/maya/plugins/publish/extract_active_view_thumbnail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py index 1bf010896a..cb039cbf51 100644 --- a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py @@ -48,4 +48,3 @@ class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): context.data[cache_key] = path return context.data[cache_key] - From 7b74d0b91ddaf0d1d8bda72a84d9cea87ba2f122 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Aug 2023 20:21:25 +0800 Subject: [PATCH 18/71] add ornatrix alembic loader --- openpype/hosts/max/plugins/load/load_model.py | 2 +- .../max/plugins/load/load_model_ornatrix.py | 74 +++++++++++++++++++ .../hosts/max/plugins/load/load_pointcache.py | 2 +- .../plugins/load/load_pointcache_ornatrix.py | 70 ++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/max/plugins/load/load_model_ornatrix.py create mode 100644 openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index cff82a593c..c149f939a2 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -9,7 +9,7 @@ class ModelAbcLoader(load.LoaderPlugin): """Loading model with the Alembic loader.""" families = ["model"] - label = "Load Model(Alembic)" + label = "Load Model(Alembic) with Max" representations = ["abc"] order = -10 icon = "code-fork" diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py new file mode 100644 index 0000000000..d92e92f63b --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_model_ornatrix.py @@ -0,0 +1,74 @@ +import os +from openpype.pipeline import load, get_representation_path +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class ModelAbcLoader(load.LoaderPlugin): + """Loading model with the Ornatrix Alembic loader.""" + + families = ["model"] + label = "Load Model(Alembic) with Ornatrix" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + file_path = os.path.normpath(self.filepath_from_context(context)) + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = True + rt.AlembicImport.CustomAttributes = True + rt.AlembicImport.UVs = True + rt.AlembicImport.VertexColors = True + rt.importFile(file_path) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + abc_container = rt.Container(name=name) + for abc in scene_object_after: + abc.Parent = abc_container + + return containerise( + name, [abc_container], context, loader=self.__class__.__name__ + ) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node_name = container["instance_node"] + instance_name, _ = os.path.splitext(node_name) + container = rt.getNodeByName(instance_name) + for children in container.Children: + rt.Delete(children) + + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = True + rt.AlembicImport.CustomAttributes = True + rt.AlembicImport.UVs = True + rt.AlembicImport.VertexColors = True + rt.importFile(path) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + for scene_object in scene_object_after: + scene_object.Parent = container + + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index 290503e053..e59ad09c9f 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -14,7 +14,7 @@ class AbcLoader(load.LoaderPlugin): """Alembic loader.""" families = ["camera", "animation", "pointcache"] - label = "Load Alembic" + label = "Load Alembic with Max" representations = ["abc"] order = -10 icon = "code-fork" diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py new file mode 100644 index 0000000000..0b0932da6a --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -0,0 +1,70 @@ +import os +from openpype.pipeline import load, get_representation_path +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class ModelAbcLoader(load.LoaderPlugin): + """Ornatrix Alembic loader.""" + + families = ["camera", "animation", "pointcache"] + label = "Load Model(Alembic) with Ornatrix" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + file_path = os.path.normpath(self.filepath_from_context(context)) + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = True + rt.AlembicImport.CustomAttributes = True + rt.importFile(file_path) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + abc_container = rt.Container(name=name) + for abc in scene_object_after: + abc.Parent = abc_container + + return containerise( + name, [abc_container], context, loader=self.__class__.__name__ + ) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node_name = container["instance_node"] + instance_name, _ = os.path.splitext(node_name) + container = rt.getNodeByName(instance_name) + for children in container.Children: + rt.Delete(children) + + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = False + rt.AlembicImport.CustomAttributes = True + rt.importFile(path) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + for scene_object in scene_object_after: + scene_object.Parent = container + + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) From 8a175b06edb6b6ca8039ac6560c19541d923bd0f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Aug 2023 20:29:20 +0800 Subject: [PATCH 19/71] rename the label --- openpype/hosts/max/plugins/load/load_model.py | 2 +- openpype/hosts/max/plugins/load/load_model_ornatrix.py | 2 +- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index c149f939a2..2f402efef8 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -9,7 +9,7 @@ class ModelAbcLoader(load.LoaderPlugin): """Loading model with the Alembic loader.""" families = ["model"] - label = "Load Model(Alembic) with Max" + label = "Load Model with Alembic" representations = ["abc"] order = -10 icon = "code-fork" diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py index d92e92f63b..8dc34278f5 100644 --- a/openpype/hosts/max/plugins/load/load_model_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_model_ornatrix.py @@ -8,7 +8,7 @@ class ModelAbcLoader(load.LoaderPlugin): """Loading model with the Ornatrix Alembic loader.""" families = ["model"] - label = "Load Model(Alembic) with Ornatrix" + label = "Load Model with Ornatrix Alembic" representations = ["abc"] order = -10 icon = "code-fork" diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 0b0932da6a..663c64bc0a 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -8,7 +8,7 @@ class ModelAbcLoader(load.LoaderPlugin): """Ornatrix Alembic loader.""" families = ["camera", "animation", "pointcache"] - label = "Load Model(Alembic) with Ornatrix" + label = "Load Alembic with Ornatrix" representations = ["abc"] order = -10 icon = "code-fork" From 938185c20835b545fa5c3b4b3e7673264509acf5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Aug 2023 20:32:39 +0800 Subject: [PATCH 20/71] rename the class name --- openpype/hosts/max/plugins/load/load_model_ornatrix.py | 2 +- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py index 8dc34278f5..424820e6e6 100644 --- a/openpype/hosts/max/plugins/load/load_model_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_model_ornatrix.py @@ -4,7 +4,7 @@ from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib -class ModelAbcLoader(load.LoaderPlugin): +class ModelOxAbcLoader(load.LoaderPlugin): """Loading model with the Ornatrix Alembic loader.""" families = ["model"] diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 663c64bc0a..0d04dbdef3 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -4,7 +4,7 @@ from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib -class ModelAbcLoader(load.LoaderPlugin): +class OxAbcLoader(load.LoaderPlugin): """Ornatrix Alembic loader.""" families = ["camera", "animation", "pointcache"] From cb48a2268c996ade66744540c2019b3eb8d06e98 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Aug 2023 00:19:52 +0800 Subject: [PATCH 21/71] add the using alembic import back to the importfile --- openpype/hosts/max/plugins/load/load_model.py | 3 +- .../max/plugins/load/load_model_ornatrix.py | 74 ------------------- .../hosts/max/plugins/load/load_pointcache.py | 3 +- .../plugins/load/load_pointcache_ornatrix.py | 70 ------------------ 4 files changed, 4 insertions(+), 146 deletions(-) delete mode 100644 openpype/hosts/max/plugins/load/load_model_ornatrix.py delete mode 100644 openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index 2f402efef8..288fc58454 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -30,7 +30,8 @@ class ModelAbcLoader(load.LoaderPlugin): rt.AlembicImport.CustomAttributes = True rt.AlembicImport.UVs = True rt.AlembicImport.VertexColors = True - rt.importFile(file_path, rt.name("noPrompt")) + rt.importFile( + file_path, rt.name("noPrompt"), using=rt.AlembicImport) abc_after = { c diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py deleted file mode 100644 index 424820e6e6..0000000000 --- a/openpype/hosts/max/plugins/load/load_model_ornatrix.py +++ /dev/null @@ -1,74 +0,0 @@ -import os -from openpype.pipeline import load, get_representation_path -from openpype.hosts.max.api.pipeline import containerise -from openpype.hosts.max.api import lib - - -class ModelOxAbcLoader(load.LoaderPlugin): - """Loading model with the Ornatrix Alembic loader.""" - - families = ["model"] - label = "Load Model with Ornatrix Alembic" - representations = ["abc"] - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - - file_path = os.path.normpath(self.filepath_from_context(context)) - scene_object_before = [obj for obj in rt.rootNode.Children] - rt.AlembicImport.ImportToRoot = True - rt.AlembicImport.CustomAttributes = True - rt.AlembicImport.UVs = True - rt.AlembicImport.VertexColors = True - rt.importFile(file_path) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - - abc_container = rt.Container(name=name) - for abc in scene_object_after: - abc.Parent = abc_container - - return containerise( - name, [abc_container], context, loader=self.__class__.__name__ - ) - - def update(self, container, representation): - from pymxs import runtime as rt - - path = get_representation_path(representation) - node_name = container["instance_node"] - instance_name, _ = os.path.splitext(node_name) - container = rt.getNodeByName(instance_name) - for children in container.Children: - rt.Delete(children) - - scene_object_before = [obj for obj in rt.rootNode.Children] - rt.AlembicImport.ImportToRoot = True - rt.AlembicImport.CustomAttributes = True - rt.AlembicImport.UVs = True - rt.AlembicImport.VertexColors = True - rt.importFile(path) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - - for scene_object in scene_object_after: - scene_object.Parent = container - - lib.imprint( - container["instance_node"], - {"representation": str(representation["_id"])}, - ) - - def switch(self, container, representation): - self.update(container, representation) - - def remove(self, container): - from pymxs import runtime as rt - - node = rt.GetNodeByName(container["instance_node"]) - rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index e59ad09c9f..24bcf58582 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -33,7 +33,8 @@ class AbcLoader(load.LoaderPlugin): } rt.AlembicImport.ImportToRoot = False - rt.importFile(file_path, rt.name("noPrompt")) + rt.importFile( + file_path, rt.name("noPrompt"), using=rt.AlembicImport) abc_after = { c diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py deleted file mode 100644 index 0d04dbdef3..0000000000 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ /dev/null @@ -1,70 +0,0 @@ -import os -from openpype.pipeline import load, get_representation_path -from openpype.hosts.max.api.pipeline import containerise -from openpype.hosts.max.api import lib - - -class OxAbcLoader(load.LoaderPlugin): - """Ornatrix Alembic loader.""" - - families = ["camera", "animation", "pointcache"] - label = "Load Alembic with Ornatrix" - representations = ["abc"] - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - - file_path = os.path.normpath(self.filepath_from_context(context)) - scene_object_before = [obj for obj in rt.rootNode.Children] - rt.AlembicImport.ImportToRoot = True - rt.AlembicImport.CustomAttributes = True - rt.importFile(file_path) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - - abc_container = rt.Container(name=name) - for abc in scene_object_after: - abc.Parent = abc_container - - return containerise( - name, [abc_container], context, loader=self.__class__.__name__ - ) - - def update(self, container, representation): - from pymxs import runtime as rt - - path = get_representation_path(representation) - node_name = container["instance_node"] - instance_name, _ = os.path.splitext(node_name) - container = rt.getNodeByName(instance_name) - for children in container.Children: - rt.Delete(children) - - scene_object_before = [obj for obj in rt.rootNode.Children] - rt.AlembicImport.ImportToRoot = False - rt.AlembicImport.CustomAttributes = True - rt.importFile(path) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - - for scene_object in scene_object_after: - scene_object.Parent = container - - lib.imprint( - container["instance_node"], - {"representation": str(representation["_id"])}, - ) - - def switch(self, container, representation): - self.update(container, representation) - - def remove(self, container): - from pymxs import runtime as rt - - node = rt.GetNodeByName(container["instance_node"]) - rt.Delete(node) From 5cb43a141476fbba18af9a36df747d6082c31036 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Aug 2023 00:30:06 +0800 Subject: [PATCH 22/71] restore the label name --- openpype/hosts/max/plugins/load/load_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index 24bcf58582..f250377cfd 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -14,7 +14,7 @@ class AbcLoader(load.LoaderPlugin): """Alembic loader.""" families = ["camera", "animation", "pointcache"] - label = "Load Alembic with Max" + label = "Load Alembic" representations = ["abc"] order = -10 icon = "code-fork" From 0a69439f2ef257e9800eda5e88d925b258f2ee25 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Aug 2023 01:00:34 +0800 Subject: [PATCH 23/71] supports loading ornatrix alembic --- .../max/plugins/load/load_model_ornatrix.py | 74 +++++++++++++++++++ .../plugins/load/load_pointcache_ornatrix.py | 70 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 openpype/hosts/max/plugins/load/load_model_ornatrix.py create mode 100644 openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py new file mode 100644 index 0000000000..92773ad567 --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_model_ornatrix.py @@ -0,0 +1,74 @@ +import os +from openpype.pipeline import load, get_representation_path +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class OxModelAbcLoader(load.LoaderPlugin): + """Loading model with the Ornatrix Alembic loader.""" + + families = ["model"] + label = "Load Model with Ornatrix Alembic" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + file_path = os.path.normpath(self.filepath_from_context(context)) + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = True + rt.AlembicImport.CustomAttributes = True + rt.AlembicImport.UVs = True + rt.AlembicImport.VertexColors = True + rt.importFile(file_path, rt.name("noPrompt")) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + abc_container = rt.Container(name=name) + for abc in scene_object_after: + abc.Parent = abc_container + + return containerise( + name, [abc_container], context, loader=self.__class__.__name__ + ) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node_name = container["instance_node"] + instance_name, _ = os.path.splitext(node_name) + container = rt.getNodeByName(instance_name) + for children in container.Children: + rt.Delete(children) + + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = True + rt.AlembicImport.CustomAttributes = True + rt.AlembicImport.UVs = True + rt.AlembicImport.VertexColors = True + rt.importFile(path) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + for scene_object in scene_object_after: + scene_object.Parent = container + + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py new file mode 100644 index 0000000000..cd08e9a2ff --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -0,0 +1,70 @@ +import os +from openpype.pipeline import load, get_representation_path +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class OxAbcLoader(load.LoaderPlugin): + """Ornatrix Alembic loader.""" + + families = ["camera", "animation", "pointcache"] + label = "Load Alembic with Ornatrix" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + file_path = os.path.normpath(self.filepath_from_context(context)) + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = True + rt.AlembicImport.CustomAttributes = True + rt.importFile(file_path, rt.name("noPrompt")) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + abc_container = rt.Container(name=name) + for abc in scene_object_after: + abc.Parent = abc_container + + return containerise( + name, [abc_container], context, loader=self.__class__.__name__ + ) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node_name = container["instance_node"] + instance_name, _ = os.path.splitext(node_name) + container = rt.getNodeByName(instance_name) + for children in container.Children: + rt.Delete(children) + + scene_object_before = [obj for obj in rt.rootNode.Children] + rt.AlembicImport.ImportToRoot = False + rt.AlembicImport.CustomAttributes = True + rt.importFile(path) + scene_object_after = [obj for obj in rt.rootNode.Children] + for scene_object in scene_object_before: + scene_object_after = scene_object_after.remove(scene_object) + + for scene_object in scene_object_after: + scene_object.Parent = container + + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) From 17904600a00dbb64875ce06fd07d3d6d220ec8ca Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Aug 2023 14:56:23 +0800 Subject: [PATCH 24/71] add using ornatrix alembic importer into the ornatrix alembic loaders --- openpype/hosts/max/plugins/load/load_model_ornatrix.py | 4 +++- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py index 92773ad567..38c081b91c 100644 --- a/openpype/hosts/max/plugins/load/load_model_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_model_ornatrix.py @@ -23,7 +23,9 @@ class OxModelAbcLoader(load.LoaderPlugin): rt.AlembicImport.CustomAttributes = True rt.AlembicImport.UVs = True rt.AlembicImport.VertexColors = True - rt.importFile(file_path, rt.name("noPrompt")) + rt.importFile( + file_path, rt.name("noPrompt"), + using=rt.Ornatrix_Alembic_Importer) scene_object_after = [obj for obj in rt.rootNode.Children] for scene_object in scene_object_before: scene_object_after = scene_object_after.remove(scene_object) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index cd08e9a2ff..65a8273ab5 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -21,7 +21,9 @@ class OxAbcLoader(load.LoaderPlugin): scene_object_before = [obj for obj in rt.rootNode.Children] rt.AlembicImport.ImportToRoot = True rt.AlembicImport.CustomAttributes = True - rt.importFile(file_path, rt.name("noPrompt")) + rt.importFile( + file_path, rt.name("noPrompt"), + using=rt.Ornatrix_Alembic_Importer) scene_object_after = [obj for obj in rt.rootNode.Children] for scene_object in scene_object_before: scene_object_after = scene_object_after.remove(scene_object) From a3c761c0683c25ab5f4a901d06a3f955ffcd8d10 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Aug 2023 15:36:59 +0800 Subject: [PATCH 25/71] add using ornatrix alembic importer into the ornatrix alembic loaders when updating version --- openpype/hosts/max/plugins/load/load_model_ornatrix.py | 4 +++- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py index 38c081b91c..07633ec55d 100644 --- a/openpype/hosts/max/plugins/load/load_model_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_model_ornatrix.py @@ -53,7 +53,9 @@ class OxModelAbcLoader(load.LoaderPlugin): rt.AlembicImport.CustomAttributes = True rt.AlembicImport.UVs = True rt.AlembicImport.VertexColors = True - rt.importFile(path) + rt.importFile( + path, rt.name("noPrompt"), + using=rt.Ornatrix_Alembic_Importer) scene_object_after = [obj for obj in rt.rootNode.Children] for scene_object in scene_object_before: scene_object_after = scene_object_after.remove(scene_object) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 65a8273ab5..f783583ff1 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -49,7 +49,9 @@ class OxAbcLoader(load.LoaderPlugin): scene_object_before = [obj for obj in rt.rootNode.Children] rt.AlembicImport.ImportToRoot = False rt.AlembicImport.CustomAttributes = True - rt.importFile(path) + rt.importFile( + path, rt.name("noPrompt"), + using=rt.Ornatrix_Alembic_Importer) scene_object_after = [obj for obj in rt.rootNode.Children] for scene_object in scene_object_before: scene_object_after = scene_object_after.remove(scene_object) From f79f0c44da2e486887f0078c7e3d4dcfa2d4e007 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 11 Aug 2023 12:27:09 +0200 Subject: [PATCH 26/71] Add to cleanup files Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/maya/plugins/publish/extract_active_view_thumbnail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py index cb039cbf51..b4e62f8acc 100644 --- a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py @@ -46,5 +46,6 @@ class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): image.writeToFile(path, "jpg") self.log.debug("Generated thumbnail: {}".format(path)) + context.data["cleanupFullPaths"].append(path) context.data[cache_key] = path return context.data[cache_key] From 1157971186c8b364afa03a6081246b8bca41abb9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 28 Aug 2023 18:55:49 +0800 Subject: [PATCH 27/71] ornatrix is a fur modifier so no loading model for ornatrix abc --- .../max/plugins/load/load_model_ornatrix.py | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 openpype/hosts/max/plugins/load/load_model_ornatrix.py diff --git a/openpype/hosts/max/plugins/load/load_model_ornatrix.py b/openpype/hosts/max/plugins/load/load_model_ornatrix.py deleted file mode 100644 index 07633ec55d..0000000000 --- a/openpype/hosts/max/plugins/load/load_model_ornatrix.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -from openpype.pipeline import load, get_representation_path -from openpype.hosts.max.api.pipeline import containerise -from openpype.hosts.max.api import lib - - -class OxModelAbcLoader(load.LoaderPlugin): - """Loading model with the Ornatrix Alembic loader.""" - - families = ["model"] - label = "Load Model with Ornatrix Alembic" - representations = ["abc"] - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - - file_path = os.path.normpath(self.filepath_from_context(context)) - scene_object_before = [obj for obj in rt.rootNode.Children] - rt.AlembicImport.ImportToRoot = True - rt.AlembicImport.CustomAttributes = True - rt.AlembicImport.UVs = True - rt.AlembicImport.VertexColors = True - rt.importFile( - file_path, rt.name("noPrompt"), - using=rt.Ornatrix_Alembic_Importer) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - - abc_container = rt.Container(name=name) - for abc in scene_object_after: - abc.Parent = abc_container - - return containerise( - name, [abc_container], context, loader=self.__class__.__name__ - ) - - def update(self, container, representation): - from pymxs import runtime as rt - - path = get_representation_path(representation) - node_name = container["instance_node"] - instance_name, _ = os.path.splitext(node_name) - container = rt.getNodeByName(instance_name) - for children in container.Children: - rt.Delete(children) - - scene_object_before = [obj for obj in rt.rootNode.Children] - rt.AlembicImport.ImportToRoot = True - rt.AlembicImport.CustomAttributes = True - rt.AlembicImport.UVs = True - rt.AlembicImport.VertexColors = True - rt.importFile( - path, rt.name("noPrompt"), - using=rt.Ornatrix_Alembic_Importer) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - - for scene_object in scene_object_after: - scene_object.Parent = container - - lib.imprint( - container["instance_node"], - {"representation": str(representation["_id"])}, - ) - - def switch(self, container, representation): - self.update(container, representation) - - def remove(self, container): - from pymxs import runtime as rt - - node = rt.GetNodeByName(container["instance_node"]) - rt.Delete(node) From 749e8911f5f1a8ce00ce8e3d024004d3fb13c832 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 28 Aug 2023 23:20:43 +0800 Subject: [PATCH 28/71] make sure ornatrix loader would raise runtime error if the user doesn't have the plugin installed --- .../plugins/load/load_pointcache_ornatrix.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index f783583ff1..1dce9aaa65 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -2,7 +2,7 @@ import os from openpype.pipeline import load, get_representation_path from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib - +from pymxs import runtime as rt class OxAbcLoader(load.LoaderPlugin): """Ornatrix Alembic loader.""" @@ -15,7 +15,10 @@ class OxAbcLoader(load.LoaderPlugin): color = "orange" def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt + plugin_list = get_plugins() + if "ornatrix.dlo" not in plugin_list: + raise RuntimeError("Ornatrix plugin not " + "found/installed in Max yet..") file_path = os.path.normpath(self.filepath_from_context(context)) scene_object_before = [obj for obj in rt.rootNode.Children] @@ -37,8 +40,6 @@ class OxAbcLoader(load.LoaderPlugin): ) def update(self, container, representation): - from pymxs import runtime as rt - path = get_representation_path(representation) node_name = container["instance_node"] instance_name, _ = os.path.splitext(node_name) @@ -68,7 +69,17 @@ class OxAbcLoader(load.LoaderPlugin): self.update(container, representation) def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) rt.Delete(node) + + +def get_plugins() -> list: + """Get plugin list from 3ds max.""" + manager = rt.PluginManager + count = manager.pluginDllCount + plugin_info_list = [] + for p in range(1, count + 1): + plugin_info = manager.pluginDllName(p) + plugin_info_list.append(plugin_info) + + return plugin_info_list From d92f2bac2f0c4115500d48b3223f65eb7c052e7f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 28 Aug 2023 23:21:54 +0800 Subject: [PATCH 29/71] hound --- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 1dce9aaa65..5f7e0a05f8 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -4,6 +4,7 @@ from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib from pymxs import runtime as rt + class OxAbcLoader(load.LoaderPlugin): """Ornatrix Alembic loader.""" From ad6d03cedae5c5b7bf8ed3ea8261383045f9383d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Aug 2023 17:30:15 +0800 Subject: [PATCH 30/71] fix the playbacktype --- openpype/hosts/max/plugins/load/load_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index f250377cfd..3f8b241351 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -52,7 +52,7 @@ class AbcLoader(load.LoaderPlugin): for abc in rt.GetCurrentSelection(): for cam_shape in abc.Children: - cam_shape.playbackType = 2 + cam_shape.playbackType = 0 return containerise( name, [abc_container], context, loader=self.__class__.__name__ From 42d0b19b46d6c30e18b9dbaee00bba87fc7ea5fc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Aug 2023 17:37:12 +0800 Subject: [PATCH 31/71] add correct plugin name --- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 5f7e0a05f8..a3c9d94f83 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -17,7 +17,7 @@ class OxAbcLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): plugin_list = get_plugins() - if "ornatrix.dlo" not in plugin_list: + if "ephere.plugins.autodesk.max.ornatrix.dlo" not in plugin_list: raise RuntimeError("Ornatrix plugin not " "found/installed in Max yet..") From 7be680190f0cf97da06e02747b4a4c8ea9ff46fc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 17:29:53 +0800 Subject: [PATCH 32/71] bug fix on the ornatrix loader --- .../plugins/load/load_pointcache_ornatrix.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index a3c9d94f83..56cac00aeb 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -22,18 +22,20 @@ class OxAbcLoader(load.LoaderPlugin): "found/installed in Max yet..") file_path = os.path.normpath(self.filepath_from_context(context)) - scene_object_before = [obj for obj in rt.rootNode.Children] rt.AlembicImport.ImportToRoot = True rt.AlembicImport.CustomAttributes = True rt.importFile( file_path, rt.name("noPrompt"), using=rt.Ornatrix_Alembic_Importer) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) + + scene_object = [] + for obj in rt.rootNode.Children: + obj_type = rt.ClassOf(obj) + if str(obj_type).startswith("Ox_"): + scene_object.append(obj) abc_container = rt.Container(name=name) - for abc in scene_object_after: + for abc in scene_object: abc.Parent = abc_container return containerise( @@ -48,18 +50,20 @@ class OxAbcLoader(load.LoaderPlugin): for children in container.Children: rt.Delete(children) - scene_object_before = [obj for obj in rt.rootNode.Children] rt.AlembicImport.ImportToRoot = False rt.AlembicImport.CustomAttributes = True rt.importFile( path, rt.name("noPrompt"), using=rt.Ornatrix_Alembic_Importer) - scene_object_after = [obj for obj in rt.rootNode.Children] - for scene_object in scene_object_before: - scene_object_after = scene_object_after.remove(scene_object) - for scene_object in scene_object_after: - scene_object.Parent = container + scene_object = [] + for obj in rt.rootNode.Children: + obj_type = rt.ClassOf(obj) + if str(obj_type).startswith("Ox_"): + scene_object.append(obj) + + for abc in scene_object: + abc.Parent = container lib.imprint( container["instance_node"], From 8440d65949e76dd9aca42206df528a62625ea32b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 4 Sep 2023 20:32:16 +0800 Subject: [PATCH 33/71] update on the ornatrix laoder --- openpype/hosts/max/api/pipeline.py | 51 ------------------- .../plugins/load/load_pointcache_ornatrix.py | 46 ++++++++++++++--- 2 files changed, 38 insertions(+), 59 deletions(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d9a66c60f5..2ce96d16e1 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -15,10 +15,8 @@ from openpype.pipeline import ( ) from openpype.hosts.max.api.menu import OpenPypeMenu from openpype.hosts.max.api import lib -from openpype.hosts.max.api.plugin import MS_CUSTOM_ATTRIB from openpype.hosts.max import MAX_HOST_DIR - from pymxs import runtime as rt # noqa log = logging.getLogger("openpype.hosts.max") @@ -173,52 +171,3 @@ def containerise(name: str, nodes: list, context, if not lib.imprint(container_name, data): print(f"imprinting of {container_name} failed.") return container - - -def load_custom_attribute_data(): - """Re-loading the Openpype/AYON custom parameter built by the creator - - Returns: - attribute: re-loading the custom OP attributes set in Maxscript - """ - return rt.Execute(MS_CUSTOM_ATTRIB) - - -def import_custom_attribute_data(container: str, selections: list): - """Importing the Openpype/AYON custom parameter built by the creator - - Args: - container (str): target container which adds custom attributes - selections (list): nodes to be added into - group in custom attributes - """ - attrs = load_custom_attribute_data() - modifier = rt.EmptyModifier() - rt.addModifier(container, modifier) - container.modifiers[0].name = "OP Data" - rt.custAttributes.add(container.modifiers[0], attrs) - nodes = {} - for i in selections: - nodes = { - str(i): rt.NodeTransformMonitor(node=i), - } - # Setting the property - rt.setProperty( - container.modifiers[0].openPypeData, - "all_handles", nodes.values()) - rt.setProperty( - container.modifiers[0].openPypeData, - "sel_list", nodes.keys()) - - -def update_custom_attribute_data(container: str, selections: list): - """Updating the Openpype/AYON custom parameter built by the creator - - Args: - container (str): target container which adds custom attributes - selections (list): nodes to be added into - group in custom attributes - """ - if container.modifiers[0].name == "OP Data": - rt.deleteModifier(container, container.modifiers[0]) - import_custom_attribute_data(container, selections) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 56cac00aeb..d3b7c61ff8 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -1,6 +1,15 @@ import os from openpype.pipeline import load, get_representation_path -from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api.pipeline import ( + containerise, + import_custom_attribute_data, + update_custom_attribute_data +) +from openpype.hosts.max.api.lib import ( + unique_namespace, + get_namespace, + object_transform_set +) from openpype.hosts.max.api import lib from pymxs import runtime as rt @@ -14,6 +23,7 @@ class OxAbcLoader(load.LoaderPlugin): order = -10 icon = "code-fork" color = "orange" + postfix = "param" def load(self, context, name=None, namespace=None, data=None): plugin_list = get_plugins() @@ -34,21 +44,37 @@ class OxAbcLoader(load.LoaderPlugin): if str(obj_type).startswith("Ox_"): scene_object.append(obj) - abc_container = rt.Container(name=name) + namespace = unique_namespace( + name + "_", + suffix="_", + ) + + abc_container = rt.Container() for abc in scene_object: abc.Parent = abc_container + abc.name = f"{namespace}:{abc.name}" + # rename the abc container with namespace + abc_container_name = f"{namespace}:{name}_{self.postfix}" + abc_container.name = abc_container_name + import_custom_attribute_data( + abc_container, abc_container.Children) return containerise( - name, [abc_container], context, loader=self.__class__.__name__ + name, [abc_container], context, + namespace, loader=self.__class__.__name__ ) def update(self, container, representation): path = get_representation_path(representation) node_name = container["instance_node"] - instance_name, _ = os.path.splitext(node_name) - container = rt.getNodeByName(instance_name) - for children in container.Children: - rt.Delete(children) + namespace, name = get_namespace(node_name) + sub_node_name = f"{namespace}:{name}_{self.postfix}" + inst_container = rt.getNodeByName(sub_node_name) + rt.Select(inst_container.Children) + transform_data = object_transform_set(inst_container.Children) + for prev_obj in rt.selection: + if rt.isValidNode(prev_obj): + rt.Delete(prev_obj) rt.AlembicImport.ImportToRoot = False rt.AlembicImport.CustomAttributes = True @@ -61,9 +87,13 @@ class OxAbcLoader(load.LoaderPlugin): obj_type = rt.ClassOf(obj) if str(obj_type).startswith("Ox_"): scene_object.append(obj) - + update_custom_attribute_data( + inst_container, scene_object.Children) for abc in scene_object: abc.Parent = container + abc.name = f"{namespace}:{abc.name}" + abc.pos = transform_data[f"{abc.name}.transform"] + abc.scale = transform_data[f"{abc.name}.scale"] lib.imprint( container["instance_node"], From 6c1385e2c94e263f9186609c272ab7c0503485d9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 4 Sep 2023 23:49:54 +0200 Subject: [PATCH 34/71] Inherit from correct new style creator --- .../hosts/maya/plugins/create/create_multiverse_usd_over.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py index e1534dd68c..166dbf6515 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py @@ -6,7 +6,7 @@ from openpype.lib import ( ) -class CreateMultiverseUsdOver(plugin.Creator): +class CreateMultiverseUsdOver(plugin.MayaCreator): """Create Multiverse USD Override""" identifier = "io.openpype.creators.maya.mvusdoverride" From 8ceedd7b60c58bf94cc1721f033bff89c0a2b121 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 17:56:09 +0200 Subject: [PATCH 35/71] Draft for implementing a native Maya USD creator next to the Multiverse USD creator --- openpype/hosts/maya/api/plugin.py | 35 ++++ .../maya/plugins/create/create_maya_usd.py | 143 +++++++++++++++ .../plugins/create/create_multiverse_usd.py | 4 + .../hosts/maya/plugins/load/load_maya_usd.py | 100 +++++++++++ .../maya/plugins/publish/extract_maya_usd.py | 168 ++++++++++++++++++ .../plugins/publish/extract_multiverse_usd.py | 2 +- 6 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/maya/plugins/create/create_maya_usd.py create mode 100644 openpype/hosts/maya/plugins/load/load_maya_usd.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_maya_usd.py diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 3f383fafb8..058637c8b5 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -129,12 +129,34 @@ class MayaCreatorBase(object): shared_data["maya_cached_legacy_subsets"] = cache_legacy return shared_data + def get_publish_families(self): + """Return families for the instances of this creator. + + Allow a Creator to define multiple families so that a creator can + e.g. specify `usd` and `usdMaya` and another USD creator can also + specify `usd` but apply different extractors like `usdMultiverse`. + + There is no need to override this method if you only have the + primary family defined by the `family` property as that will always + be set. + + Returns: + list: families for instances of this creator + + """ + return [] + def imprint_instance_node(self, node, data): # We never store the instance_node as value on the node since # it's the node name itself data.pop("instance_node", None) + # Don't store `families` since it's up to the creator itself + # to define the initial publish families - not a stored attribute of + # `families` + data.pop("families", None) + # We store creator attributes at the root level and assume they # will not clash in names with `subset`, `task`, etc. and other # default names. This is just so these attributes in many cases @@ -186,6 +208,11 @@ class MayaCreatorBase(object): # Explicitly re-parse the node name node_data["instance_node"] = node + # If the creator plug-in specifies + families = self.get_publish_families() + if families: + node_data["families"] = families + return node_data def _default_collect_instances(self): @@ -230,6 +257,14 @@ class MayaCreator(NewCreator, MayaCreatorBase): if pre_create_data.get("use_selection"): members = cmds.ls(selection=True) + # Allow a Creator to define multiple families + publish_families = self.get_publish_families() + if publish_families: + families = instance_data.setdefault("families", []) + for family in self.get_publish_families(): + if family not in families: + families.append(family) + with lib.undo_chunk(): instance_node = cmds.sets(members, name=subset_name) instance_data["instance_node"] = instance_node diff --git a/openpype/hosts/maya/plugins/create/create_maya_usd.py b/openpype/hosts/maya/plugins/create/create_maya_usd.py new file mode 100644 index 0000000000..298dc6a24f --- /dev/null +++ b/openpype/hosts/maya/plugins/create/create_maya_usd.py @@ -0,0 +1,143 @@ +from openpype.hosts.maya.api import plugin, lib +from openpype.lib import ( + BoolDef, + NumberDef, + TextDef, + EnumDef +) + + +class CreateMayaUsd(plugin.MayaCreator): + """Create Maya USD Export""" + + identifier = "io.openpype.creators.maya.mayausd" + label = "Maya USD" + family = "usd" + icon = "cubes" + description = "Create Maya USD Export" + + def get_publish_families(self): + return ["usd", "mayaUsd"] + + def get_instance_attr_defs(self): + + defs = lib.collect_animation_defs() + defs.extend([ + EnumDef("defaultUSDFormat", + label="File format", + items={ + "usdc": "Binary", + "usda": "ASCII" + }, + default="usdc"), + BoolDef("stripNamespaces", + label="Strip Namespaces", + default=True), + BoolDef("mergeTransformAndShape", + label="Merge Transform and Shape", + default=True), + # BoolDef("writeAncestors", + # label="Write Ancestors", + # default=True), + # BoolDef("flattenParentXforms", + # label="Flatten Parent Xforms", + # default=False), + # BoolDef("writeSparseOverrides", + # label="Write Sparse Overrides", + # default=False), + # BoolDef("useMetaPrimPath", + # label="Use Meta Prim Path", + # default=False), + # TextDef("customRootPath", + # label="Custom Root Path", + # default=''), + # TextDef("customAttributes", + # label="Custom Attributes", + # tooltip="Comma-separated list of attribute names", + # default=''), + # TextDef("nodeTypesToIgnore", + # label="Node Types to Ignore", + # tooltip="Comma-separated list of node types to be ignored", + # default=''), + # BoolDef("writeMeshes", + # label="Write Meshes", + # default=True), + # BoolDef("writeCurves", + # label="Write Curves", + # default=True), + # BoolDef("writeParticles", + # label="Write Particles", + # default=True), + # BoolDef("writeCameras", + # label="Write Cameras", + # default=False), + # BoolDef("writeLights", + # label="Write Lights", + # default=False), + # BoolDef("writeJoints", + # label="Write Joints", + # default=False), + # BoolDef("writeCollections", + # label="Write Collections", + # default=False), + # BoolDef("writePositions", + # label="Write Positions", + # default=True), + # BoolDef("writeNormals", + # label="Write Normals", + # default=True), + # BoolDef("writeUVs", + # label="Write UVs", + # default=True), + # BoolDef("writeColorSets", + # label="Write Color Sets", + # default=False), + # BoolDef("writeTangents", + # label="Write Tangents", + # default=False), + # BoolDef("writeRefPositions", + # label="Write Ref Positions", + # default=True), + # BoolDef("writeBlendShapes", + # label="Write BlendShapes", + # default=False), + # BoolDef("writeDisplayColor", + # label="Write Display Color", + # default=True), + # BoolDef("writeSkinWeights", + # label="Write Skin Weights", + # default=False), + # BoolDef("writeMaterialAssignment", + # label="Write Material Assignment", + # default=False), + # BoolDef("writeHardwareShader", + # label="Write Hardware Shader", + # default=False), + # BoolDef("writeShadingNetworks", + # label="Write Shading Networks", + # default=False), + # BoolDef("writeTransformMatrix", + # label="Write Transform Matrix", + # default=True), + # BoolDef("writeUsdAttributes", + # label="Write USD Attributes", + # default=True), + # BoolDef("writeInstancesAsReferences", + # label="Write Instances as References", + # default=False), + # BoolDef("timeVaryingTopology", + # label="Time Varying Topology", + # default=False), + # TextDef("customMaterialNamespace", + # label="Custom Material Namespace", + # default=''), + # NumberDef("numTimeSamples", + # label="Num Time Samples", + # default=1), + # NumberDef("timeSamplesSpan", + # label="Time Samples Span", + # default=0.0), + # + ]) + + return defs diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index 0b0ad3bccb..2963d4d5b6 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -14,6 +14,10 @@ class CreateMultiverseUsd(plugin.MayaCreator): label = "Multiverse USD Asset" family = "usd" icon = "cubes" + description = "Create Multiverse USD Asset" + + def get_publish_families(self): + return ["usd", "mvUsd"] def get_instance_attr_defs(self): diff --git a/openpype/hosts/maya/plugins/load/load_maya_usd.py b/openpype/hosts/maya/plugins/load/load_maya_usd.py new file mode 100644 index 0000000000..26c497768d --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_maya_usd.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +import maya.cmds as cmds + +from openpype.pipeline import ( + load, + get_representation_path, +) +from openpype.pipeline.load import get_representation_path_from_context +from openpype.hosts.maya.api.lib import ( + namespaced, + unique_namespace +) +from openpype.hosts.maya.api.pipeline import containerise + + +class MayaUsdLoader(load.LoaderPlugin): + """Read USD data in a Maya USD Proxy""" + + families = ["model", "usd", "pointcache", "animation"] + representations = ["usd", "usda", "usdc", "usdz", "abc"] + + label = "Load USD to Maya Proxy" + order = -1 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, options=None): + asset = context['asset']['name'] + namespace = namespace or unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + # Make sure we can load the plugin + cmds.loadPlugin("mayaUsdPlugin", quiet=True) + + path = get_representation_path_from_context(context) + + # Create the shape + cmds.namespace(addNamespace=namespace) + with namespaced(namespace, new=False): + transform = cmds.createNode("transform", + name=name, + skipSelect=True) + proxy = cmds.createNode('mayaUsdProxyShape', + name="{}Shape".format(name), + parent=transform, + skipSelect=True) + + cmds.connectAttr("time1.outTime", "{}.time".format(proxy)) + cmds.setAttr("{}.filePath".format(proxy), path, type="string") + + nodes = [transform, proxy] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + # type: (dict, dict) -> None + """Update container with specified representation.""" + node = container['objectName'] + assert cmds.objExists(node), "Missing container" + + members = cmds.sets(node, query=True) or [] + shapes = cmds.ls(members, type="mayaUsdProxyShape") + + path = get_representation_path(representation) + for shape in shapes: + cmds.setAttr("{}.filePath".format(shape), path, type="string") + + cmds.setAttr("{}.representation".format(node), + str(representation["_id"]), + type="string") + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + # type: (dict) -> None + """Remove loaded container.""" + # Delete container and its contents + if cmds.objExists(container['objectName']): + members = cmds.sets(container['objectName'], query=True) or [] + cmds.delete([container['objectName']] + members) + + # Remove the namespace, if empty + namespace = container['namespace'] + if cmds.namespace(exists=namespace): + members = cmds.namespaceInfo(namespace, listNamespace=True) + if not members: + cmds.namespace(removeNamespace=namespace) + else: + self.log.warning("Namespace not deleted because it " + "still has members: %s", namespace) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py new file mode 100644 index 0000000000..3b95037d4c --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -0,0 +1,168 @@ +import os +import six + +from maya import cmds + +import pyblish.api +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import maintained_selection + + +class ExtractMayaUsd(publish.Extractor): + """Extractor for Maya USD Asset data. + + Upon publish a .usd (or .usdz) asset file will typically be written. + """ + + label = "Extract Maya USD Asset" + hosts = ["maya"] + families = ["mayaUsd"] + + @property + def options(self): + """Overridable options for Maya USD Export + + Given in the following format + - {NAME: EXPECTED TYPE} + + If the overridden option's type does not match, + the option is not included and a warning is logged. + + """ + + # TODO: Support more `mayaUSDExport` parameters + return { + "stripNamespaces": bool, + "mergeTransformAndShape": bool, + "exportDisplayColor": bool, + "exportColorSets": bool, + "exportInstances": bool, + "exportUVs": bool, + "exportVisibility": bool, + "exportComponentTags": bool, + "exportRefsAsInstanceable": bool, + "eulerFilter": bool, + "renderableOnly": bool, + #"worldspace": bool, + } + + @property + def default_options(self): + """The default options for Maya USD Export.""" + + # TODO: Support more `mayaUSDExport` parameters + return { + "stripNamespaces": False, + "mergeTransformAndShape": False, + "exportDisplayColor": False, + "exportColorSets": True, + "exportInstances": True, + "exportUVs": True, + "exportVisibility": True, + "exportComponentTags": True, + "exportRefsAsInstanceable": False, + "eulerFilter": True, + "renderableOnly": False, + #"worldspace": False + } + + def parse_overrides(self, instance, options): + """Inspect data of instance to determine overridden options""" + + for key in instance.data: + if key not in self.options: + continue + + # Ensure the data is of correct type + value = instance.data[key] + if isinstance(value, six.text_type): + value = str(value) + if not isinstance(value, self.options[key]): + self.log.warning( + "Overridden attribute {key} was of " + "the wrong type: {invalid_type} " + "- should have been {valid_type}".format( + key=key, + invalid_type=type(value).__name__, + valid_type=self.options[key].__name__)) + continue + + options[key] = value + + return options + + def filter_members(self, members): + # Can be overridden by inherited classes + return members + + def process(self, instance): + + # Load plugin first + cmds.loadPlugin("mayaUsdPlugin", quiet=True) + + # Define output file path + staging_dir = self.staging_dir(instance) + file_name = "{0}.usd".format(instance.name) + file_path = os.path.join(staging_dir, file_name) + file_path = file_path.replace('\\', '/') + + # Parse export options + options = self.default_options + options = self.parse_overrides(instance, options) + self.log.info("Export options: {0}".format(options)) + + # Perform extraction + self.log.debug("Performing extraction ...") + + members = instance.data("setMembers") + self.log.debug('Collected objects: {}'.format(members)) + members = self.filter_members(members) + if not members: + self.log.error('No members!') + return + + start = instance.data["frameStartHandle"] + end = instance.data["frameEndHandle"] + + with maintained_selection(): + self.log.debug('Exporting USD: {} / {}'.format(file_path, members)) + cmds.mayaUSDExport(file=file_path, + frameRange=(start, end), + frameStride=instance.data.get("step", 1.0), + exportRoots=members, + **options) + + representation = { + 'name': "usd", + 'ext': "usd", + 'files': file_name, + 'stagingDir': staging_dir + } + instance.data.setdefault("representations", []).append(representation) + + self.log.debug( + "Extracted instance {} to {}".format(instance.name, file_path) + ) + + +class ExtractMayaUsdAnim(ExtractMayaUsd): + """Extractor for Maya USD Animation Sparse Cache data. + + This will extract the sparse cache data from the scene and generate a + USD file with all the animation data. + + Upon publish a .usd sparse cache will be written. + """ + label = "Extract Maya USD Animation Sparse Cache" + families = ["animation", "mayaUsd"] + match = pyblish.api.Subset + + def filter_members(self, members): + out_set = next((i for i in members if i.endswith("out_SET")), None) + + if out_set is None: + self.log.warning("Expecting out_SET") + return None + + members = cmds.ls(cmds.sets(out_set, query=True), long=True) + return members diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 4399eacda1..e0a1369556 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -28,7 +28,7 @@ class ExtractMultiverseUsd(publish.Extractor): label = "Extract Multiverse USD Asset" hosts = ["maya"] - families = ["usd"] + families = ["mvUsd"] scene_type = "usd" file_formats = ["usd", "usda", "usdz"] From 90417a42c38e6197583969d2549de2b3b810568d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 17:56:30 +0200 Subject: [PATCH 36/71] Allow loading USD into Arnold Standin in Maya --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index b5cc4d629b..e1bd1954fa 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -32,8 +32,8 @@ def get_current_session_fps(): class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" - families = ["ass", "animation", "model", "proxyAbc", "pointcache"] - representations = ["ass", "abc"] + families = ["ass", "animation", "model", "proxyAbc", "pointcache", "usd"] + representations = ["ass", "abc", "usda", "usdc", "usd"] label = "Load as Arnold standin" order = -5 From 34b15587c11005ca9c2d7db77f44fa18ebb37f4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 18:00:23 +0200 Subject: [PATCH 37/71] Cosmetics --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index e1bd1954fa..2e1329f201 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -17,6 +17,7 @@ from openpype.hosts.maya.api.lib import ( ) from openpype.hosts.maya.api.pipeline import containerise + def is_sequence(files): sequence = False collections, remainder = clique.assemble(files, minimum_items=1) @@ -29,6 +30,7 @@ def get_current_session_fps(): session_fps = float(legacy_io.Session.get('AVALON_FPS', 25)) return convert_to_maya_fps(session_fps) + class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" From 5c12d9c8621130703d063ec5aba0fef832d479ac Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 18:04:21 +0200 Subject: [PATCH 38/71] Remove commented out attribute definitions --- .../maya/plugins/create/create_maya_usd.py | 106 +----------------- 1 file changed, 1 insertion(+), 105 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_maya_usd.py b/openpype/hosts/maya/plugins/create/create_maya_usd.py index 298dc6a24f..554b8169db 100644 --- a/openpype/hosts/maya/plugins/create/create_maya_usd.py +++ b/openpype/hosts/maya/plugins/create/create_maya_usd.py @@ -1,8 +1,6 @@ from openpype.hosts.maya.api import plugin, lib from openpype.lib import ( BoolDef, - NumberDef, - TextDef, EnumDef ) @@ -35,109 +33,7 @@ class CreateMayaUsd(plugin.MayaCreator): default=True), BoolDef("mergeTransformAndShape", label="Merge Transform and Shape", - default=True), - # BoolDef("writeAncestors", - # label="Write Ancestors", - # default=True), - # BoolDef("flattenParentXforms", - # label="Flatten Parent Xforms", - # default=False), - # BoolDef("writeSparseOverrides", - # label="Write Sparse Overrides", - # default=False), - # BoolDef("useMetaPrimPath", - # label="Use Meta Prim Path", - # default=False), - # TextDef("customRootPath", - # label="Custom Root Path", - # default=''), - # TextDef("customAttributes", - # label="Custom Attributes", - # tooltip="Comma-separated list of attribute names", - # default=''), - # TextDef("nodeTypesToIgnore", - # label="Node Types to Ignore", - # tooltip="Comma-separated list of node types to be ignored", - # default=''), - # BoolDef("writeMeshes", - # label="Write Meshes", - # default=True), - # BoolDef("writeCurves", - # label="Write Curves", - # default=True), - # BoolDef("writeParticles", - # label="Write Particles", - # default=True), - # BoolDef("writeCameras", - # label="Write Cameras", - # default=False), - # BoolDef("writeLights", - # label="Write Lights", - # default=False), - # BoolDef("writeJoints", - # label="Write Joints", - # default=False), - # BoolDef("writeCollections", - # label="Write Collections", - # default=False), - # BoolDef("writePositions", - # label="Write Positions", - # default=True), - # BoolDef("writeNormals", - # label="Write Normals", - # default=True), - # BoolDef("writeUVs", - # label="Write UVs", - # default=True), - # BoolDef("writeColorSets", - # label="Write Color Sets", - # default=False), - # BoolDef("writeTangents", - # label="Write Tangents", - # default=False), - # BoolDef("writeRefPositions", - # label="Write Ref Positions", - # default=True), - # BoolDef("writeBlendShapes", - # label="Write BlendShapes", - # default=False), - # BoolDef("writeDisplayColor", - # label="Write Display Color", - # default=True), - # BoolDef("writeSkinWeights", - # label="Write Skin Weights", - # default=False), - # BoolDef("writeMaterialAssignment", - # label="Write Material Assignment", - # default=False), - # BoolDef("writeHardwareShader", - # label="Write Hardware Shader", - # default=False), - # BoolDef("writeShadingNetworks", - # label="Write Shading Networks", - # default=False), - # BoolDef("writeTransformMatrix", - # label="Write Transform Matrix", - # default=True), - # BoolDef("writeUsdAttributes", - # label="Write USD Attributes", - # default=True), - # BoolDef("writeInstancesAsReferences", - # label="Write Instances as References", - # default=False), - # BoolDef("timeVaryingTopology", - # label="Time Varying Topology", - # default=False), - # TextDef("customMaterialNamespace", - # label="Custom Material Namespace", - # default=''), - # NumberDef("numTimeSamples", - # label="Num Time Samples", - # default=1), - # NumberDef("timeSamplesSpan", - # label="Time Samples Span", - # default=0.0), - # + default=True) ]) return defs From d6f2ace99d87d2cb8069c02de72c6f7244a222bf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 18:04:57 +0200 Subject: [PATCH 39/71] Cosmetics --- openpype/hosts/maya/plugins/publish/extract_maya_usd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py index 3b95037d4c..70508042c0 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -43,7 +43,7 @@ class ExtractMayaUsd(publish.Extractor): "exportRefsAsInstanceable": bool, "eulerFilter": bool, "renderableOnly": bool, - #"worldspace": bool, + # "worldspace": bool, } @property @@ -63,7 +63,7 @@ class ExtractMayaUsd(publish.Extractor): "exportRefsAsInstanceable": False, "eulerFilter": True, "renderableOnly": False, - #"worldspace": False + # "worldspace": False } def parse_overrides(self, instance, options): From 7f78a95559870a79f2b20c456ea4ec8a3419e30d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 22:04:38 +0200 Subject: [PATCH 40/71] Export correct file type (ascii vs binary) based on instance setting --- openpype/hosts/maya/plugins/publish/extract_maya_usd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py index 70508042c0..32730d2963 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -32,6 +32,7 @@ class ExtractMayaUsd(publish.Extractor): # TODO: Support more `mayaUSDExport` parameters return { + "defaultUSDFormat": str, "stripNamespaces": bool, "mergeTransformAndShape": bool, "exportDisplayColor": bool, @@ -52,6 +53,7 @@ class ExtractMayaUsd(publish.Extractor): # TODO: Support more `mayaUSDExport` parameters return { + "defaultUSDFormat": "usdc", "stripNamespaces": False, "mergeTransformAndShape": False, "exportDisplayColor": False, From 93e3e310295c758f236e208dc7ed130eee4a4335 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 22:05:02 +0200 Subject: [PATCH 41/71] Log message as debug --- openpype/hosts/maya/plugins/publish/extract_maya_usd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py index 32730d2963..4a5fcc3366 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -111,7 +111,7 @@ class ExtractMayaUsd(publish.Extractor): # Parse export options options = self.default_options options = self.parse_overrides(instance, options) - self.log.info("Export options: {0}".format(options)) + self.log.debug("Export options: {0}".format(options)) # Perform extraction self.log.debug("Performing extraction ...") From f4a0ab45e4a9f50d60488fb8cfc7ccaae7f016a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 23:10:32 +0200 Subject: [PATCH 42/71] Allow exporting custom attributes with `mayaUSDExport` --- .../maya/plugins/create/create_maya_usd.py | 16 ++- .../maya/plugins/publish/extract_maya_usd.py | 113 +++++++++++++++++- 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_maya_usd.py b/openpype/hosts/maya/plugins/create/create_maya_usd.py index 554b8169db..f05f155dd9 100644 --- a/openpype/hosts/maya/plugins/create/create_maya_usd.py +++ b/openpype/hosts/maya/plugins/create/create_maya_usd.py @@ -1,7 +1,8 @@ from openpype.hosts.maya.api import plugin, lib from openpype.lib import ( BoolDef, - EnumDef + EnumDef, + TextDef ) @@ -33,7 +34,18 @@ class CreateMayaUsd(plugin.MayaCreator): default=True), BoolDef("mergeTransformAndShape", label="Merge Transform and Shape", - default=True) + default=True), + BoolDef("includeUserDefinedAttributes", + label="Include User Defined Attributes", + default=False), + TextDef("attr", + label="Custom Attributes", + default="", + placeholder="attr1, attr2"), + TextDef("attrPrefix", + label="Custom Attributes Prefix", + default="", + placeholder="prefix1, prefix2") ]) return defs diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py index 4a5fcc3366..dfdea6868f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -1,5 +1,7 @@ import os import six +import json +import contextlib from maya import cmds @@ -8,6 +10,88 @@ from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection +@contextlib.contextmanager +def usd_export_attributes(nodes, attrs=None, attr_prefixes=None, mapping=None): + """Define attributes for the given nodes that should be exported. + + MayaUSDExport will export custom attributes if the Maya node has a + string attribute `USD_UserExportedAttributesJson` that provides an + export mapping for the maya attributes. This context manager will try + to autogenerate such an attribute during the export to include attributes + for the export. + + """ + # todo: this might be better done with a custom export chaser + # see `chaser` argument for `mayaUSDExport` + + import maya.api.OpenMaya as om + + if not attrs and not attr_prefixes: + # context manager does nothing + yield + return + + if attrs is None: + attrs = [] + if attr_prefixes is None: + attr_prefixes = [] + if mapping is None: + mapping = {} + + usd_json_attr = "USD_UserExportedAttributesJson" + strings = attrs + ["{}*".format(prefix) for prefix in attr_prefixes] + context_state = {} + for node in set(nodes): + node_attrs = cmds.listAttr(node, st=strings) + if not node_attrs: + # Nothing to do for this node + continue + + node_attr_data = {} + for node_attr in set(node_attrs): + node_attr_data[node_attr] = mapping.get(node_attr, {}) + + if cmds.attributeQuery(usd_json_attr, node=node, exists=True): + existing_node_attr_value = cmds.getAttr( + "{}.{}".format(node, usd_json_attr) + ) + if existing_node_attr_value and existing_node_attr_value != "{}": + # Any existing attribute mappings in an existing + # `USD_UserExportedAttributesJson` attribute always take + # precedence over what this function tries to imprint + existing_node_attr_data = json.loads(existing_node_attr_value) + node_attr_data.update(existing_node_attr_data) + + context_state[node] = json.dumps(node_attr_data) + + sel = om.MSelectionList() + dg_mod = om.MDGModifier() + fn_string = om.MFnStringData() + fn_typed = om.MFnTypedAttribute() + try: + for node, value in context_state.items(): + data = fn_string.create(value) + sel.clear() + if cmds.attributeQuery(usd_json_attr, node=node, exists=True): + # Set the attribute value + sel.add("{}.{}".format(node, usd_json_attr)) + plug = sel.getPlug(0) + dg_mod.newPlugValue(plug, data) + else: + # Create attribute with the value as default value + sel.add(node) + node_obj = sel.getDependNode(0) + attr_obj = fn_typed.create(usd_json_attr, + usd_json_attr, + om.MFnData.kString, + data) + dg_mod.addAttribute(node_obj, attr_obj) + dg_mod.doIt() + yield + finally: + dg_mod.undoIt() + + class ExtractMayaUsd(publish.Extractor): """Extractor for Maya USD Asset data. @@ -126,13 +210,30 @@ class ExtractMayaUsd(publish.Extractor): start = instance.data["frameStartHandle"] end = instance.data["frameEndHandle"] + def parse_attr_str(attr_str): + result = list() + for attr in attr_str.split(","): + attr = attr.strip() + if not attr: + continue + result.append(attr) + return result + + attrs = parse_attr_str(instance.data.get("attr", "")) + attrs += instance.data.get("userDefinedAttributes", []) + attrs += ["cbId"] + attr_prefixes = parse_attr_str(instance.data.get("attrPrefix", "")) + + self.log.debug('Exporting USD: {} / {}'.format(file_path, members)) with maintained_selection(): - self.log.debug('Exporting USD: {} / {}'.format(file_path, members)) - cmds.mayaUSDExport(file=file_path, - frameRange=(start, end), - frameStride=instance.data.get("step", 1.0), - exportRoots=members, - **options) + with usd_export_attributes(instance[:], + attrs=attrs, + attr_prefixes=attr_prefixes): + cmds.mayaUSDExport(file=file_path, + frameRange=(start, end), + frameStride=instance.data.get("step", 1.0), + exportRoots=members, + **options) representation = { 'name': "usd", From 17d494c1a2cb5229b764daeb415010f97fe46ad4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 23:13:39 +0200 Subject: [PATCH 43/71] Add logic to collect user defined attributes and merge logic with pointcache and animation family + optimize the query by doing only one `cmds.listAttr` call --- .../maya/plugins/publish/collect_animation.py | 14 ------- .../plugins/publish/collect_pointcache.py | 15 ------- .../collect_user_defined_attributes.py | 39 +++++++++++++++++++ 3 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 8f523f770b..26a0a01c8b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -58,17 +58,3 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): if instance.data.get("farm"): instance.data["families"].append("publish.farm") - # Collect user defined attributes. - if not instance.data.get("includeUserDefinedAttributes", False): - return - - user_defined_attributes = set() - for node in hierarchy: - attrs = cmds.listAttr(node, userDefined=True) or list() - shapes = cmds.listRelatives(node, shapes=True) or list() - for shape in shapes: - attrs.extend(cmds.listAttr(shape, userDefined=True) or list()) - - user_defined_attributes.update(attrs) - - instance.data["userDefinedAttributes"] = list(user_defined_attributes) diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index bb9065792f..5578a57f31 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -45,18 +45,3 @@ class CollectPointcache(pyblish.api.InstancePlugin): if proxy_set: instance.remove(proxy_set) instance.data["setMembers"].remove(proxy_set) - - # Collect user defined attributes. - if not instance.data.get("includeUserDefinedAttributes", False): - return - - user_defined_attributes = set() - for node in instance: - attrs = cmds.listAttr(node, userDefined=True) or list() - shapes = cmds.listRelatives(node, shapes=True) or list() - for shape in shapes: - attrs.extend(cmds.listAttr(shape, userDefined=True) or list()) - - user_defined_attributes.update(attrs) - - instance.data["userDefinedAttributes"] = list(user_defined_attributes) diff --git a/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py b/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py new file mode 100644 index 0000000000..4d0790ad7c --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py @@ -0,0 +1,39 @@ +from maya import cmds + +import pyblish.api + + +class CollectUserDefinedAttributes(pyblish.api.InstancePlugin): + """Collect user defined attributes for nodes in instance.""" + + order = pyblish.api.CollectorOrder + 0.4 + families = ["pointcache", "animation", "usd"] + label = "Collect User Defined Attributes" + hosts = ["maya"] + + def process(self, instance): + + # Collect user defined attributes. + if not instance.data.get("includeUserDefinedAttributes", False): + return + + if "out_hierarchy" in instance.data: + # animation family + nodes = instance.data["out_hierarchy"] + else: + nodes = instance[:] + if not nodes: + return + + shapes = cmds.listRelatives(nodes, shapes=True, fullPath=True) or [] + nodes = set(nodes).union(shapes) + + attrs = cmds.listAttr(list(nodes), userDefined=True) or [] + user_defined_attributes = list(sorted(set(attrs))) + instance.data["userDefinedAttributes"] = user_defined_attributes + + self.log.debug( + "Collected user defined attributes: {}".format( + ", ".join(user_defined_attributes) + ) + ) From 4a861a6bfcdc06026fe4146e162523a1dc33cb58 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Sep 2023 23:29:46 +0200 Subject: [PATCH 44/71] Improve docstring --- .../maya/plugins/publish/extract_maya_usd.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py index dfdea6868f..09bbf01831 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -20,6 +20,26 @@ def usd_export_attributes(nodes, attrs=None, attr_prefixes=None, mapping=None): to autogenerate such an attribute during the export to include attributes for the export. + Arguments: + nodes (List[str]): Nodes to process. + attrs (Optional[List[str]]): Full name of attributes to include. + attr_prefixes (Optional[List[str]]): Prefixes of attributes to include. + mapping (Optional[Dict[Dict]]): A mapping per attribute name for the + conversion to a USD attribute, including renaming, defining type, + converting attribute precision, etc. This match the usual + `USD_UserExportedAttributesJson` json mapping of `mayaUSDExport`. + When no mapping provided for an attribute it will use `{}` as + value. + + Examples: + >>> with usd_export_attributes( + >>> ["pCube1"], attrs="myDoubleAttributeAsFloat", mapping={ + >>> "myDoubleAttributeAsFloat": { + >>> "usdAttrName": "my:namespace:attrib", + >>> "translateMayaDoubleToUsdSinglePrecision": True, + >>> } + >>> }) + """ # todo: this might be better done with a custom export chaser # see `chaser` argument for `mayaUSDExport` From a61f7ac7998453dad917d01e43fa134320a8e7e5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 00:02:39 +0200 Subject: [PATCH 45/71] Add support for Look Assigner to assign looks in `aiStandin` for USD files based on `cbId` attributes in the USD file. - For this to currently work the transform and shape should *not* be merged into a single Prim inside USD because otherwise the unique `cbId` between Transform and Shape node will be lost. --- .../tools/mayalookassigner/arnold_standin.py | 8 ++++ .../hosts/maya/tools/mayalookassigner/usd.py | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 openpype/hosts/maya/tools/mayalookassigner/usd.py diff --git a/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py b/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py index 0ce2b21dcd..076b0047bb 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py +++ b/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py @@ -10,6 +10,7 @@ from openpype.client import get_last_version_by_subset_name from openpype.hosts.maya import api from . import lib from .alembic import get_alembic_ids_cache +from .usd import is_usd_lib_supported, get_usd_ids_cache log = logging.getLogger(__name__) @@ -74,6 +75,13 @@ def get_nodes_by_id(standin): # Support alembic files directly return get_alembic_ids_cache(path) + elif ( + is_usd_lib_supported and + any(path.endswith(ext) for ext in [".usd", ".usda", ".usdc"]) + ): + # Support usd files directly + return get_usd_ids_cache(path) + json_path = None for f in os.listdir(os.path.dirname(path)): if f.endswith(".json"): diff --git a/openpype/hosts/maya/tools/mayalookassigner/usd.py b/openpype/hosts/maya/tools/mayalookassigner/usd.py new file mode 100644 index 0000000000..beecbd531a --- /dev/null +++ b/openpype/hosts/maya/tools/mayalookassigner/usd.py @@ -0,0 +1,38 @@ +from collections import defaultdict + +try: + from pxr import Usd + is_usd_lib_supported = True +except ImportError: + is_usd_lib_supported = False + + +def get_usd_ids_cache(path): + # type: (str) -> dict + """Build a id to node mapping in a USD file. + + Nodes without IDs are ignored. + + Returns: + dict: Mapping of id to nodes in the USD file. + + """ + if not is_usd_lib_supported: + raise RuntimeError("No pxr.Usd python library available.") + + stage = Usd.Stage.Open(path) + ids = {} + for prim in stage.Traverse(): + attr = prim.GetAttribute("userProperties:cbId") + if not attr.IsValid(): + continue + path = str(prim.GetPath()) + value = attr.Get() + if not value: + continue + ids[path] = value + + cache = defaultdict(list) + for path, value in ids.items(): + cache[value].append(path) + return dict(cache) From b953391f43592fedf18069093c21e33a35136871 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 00:07:39 +0200 Subject: [PATCH 46/71] Only get path if a value is found --- openpype/hosts/maya/tools/mayalookassigner/usd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/tools/mayalookassigner/usd.py b/openpype/hosts/maya/tools/mayalookassigner/usd.py index beecbd531a..6b5cb2f0f5 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/usd.py +++ b/openpype/hosts/maya/tools/mayalookassigner/usd.py @@ -26,10 +26,10 @@ def get_usd_ids_cache(path): attr = prim.GetAttribute("userProperties:cbId") if not attr.IsValid(): continue - path = str(prim.GetPath()) value = attr.Get() if not value: continue + path = str(prim.GetPath()) ids[path] = value cache = defaultdict(list) From 11cd5a874eee0877c16c9cfde8cb8b74f1630364 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 00:13:34 +0200 Subject: [PATCH 47/71] Make sure to run after `CollectPointcache` and `CollectAnimation` --- .../maya/plugins/publish/collect_user_defined_attributes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py b/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py index 4d0790ad7c..16fef2e168 100644 --- a/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py +++ b/openpype/hosts/maya/plugins/publish/collect_user_defined_attributes.py @@ -6,7 +6,7 @@ import pyblish.api class CollectUserDefinedAttributes(pyblish.api.InstancePlugin): """Collect user defined attributes for nodes in instance.""" - order = pyblish.api.CollectorOrder + 0.4 + order = pyblish.api.CollectorOrder + 0.45 families = ["pointcache", "animation", "usd"] label = "Collect User Defined Attributes" hosts = ["maya"] From 8f5b09af2110bf93169bc69f91989981e8f3ad99 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Sep 2023 15:58:09 +0800 Subject: [PATCH 48/71] adding back the missing function after resolving conflict --- openpype/hosts/max/api/pipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index c4167dfe0a..23b89a9ac2 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -15,6 +15,7 @@ from openpype.pipeline import ( ) from openpype.hosts.max.api.menu import OpenPypeMenu from openpype.hosts.max.api import lib +from openpype.hosts.max.api.plugin import MS_CUSTOM_ATTRIB from openpype.hosts.max import MAX_HOST_DIR from pymxs import runtime as rt # noqa From 862907079c7c143854e864270878afb40449060e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 15:23:11 +0200 Subject: [PATCH 49/71] Support special creator attributes in Maya's flattened `creator_attributes` structure that are not convertable to Maya native attribute types (list, tuple, dict), like e.g. `EnumDef` with `multiselection=True` --- openpype/hosts/maya/api/plugin.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 058637c8b5..770767fc7d 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -161,8 +161,17 @@ class MayaCreatorBase(object): # will not clash in names with `subset`, `task`, etc. and other # default names. This is just so these attributes in many cases # are still editable in the maya UI by artists. - # pop to move to end of dict to sort attributes last on the node + # note: pop to move to end of dict to sort attributes last on the node creator_attributes = data.pop("creator_attributes", {}) + + # We only flatten value types which `imprint` function supports + json_creator_attributes = {} + for key, value in dict(creator_attributes).items(): + if isinstance(value, (list, tuple, dict)): + creator_attributes.pop(key) + json_creator_attributes[key] = value + + # Flatten remaining creator attributes to the node itself data.update(creator_attributes) # We know the "publish_attributes" will be complex data of @@ -172,6 +181,10 @@ class MayaCreatorBase(object): data.pop("publish_attributes", {}) ) + # Persist the non-flattened creator attributes (special value types, + # like multiselection EnumDef) + data["creator_attributes"] = json.dumps(json_creator_attributes) + # Since we flattened the data structure for creator attributes we want # to correctly detect which flattened attributes should end back in the # creator attributes when reading the data from the node, so we store @@ -192,15 +205,22 @@ class MayaCreatorBase(object): # being read as 'data' node_data.pop("cbId", None) + # Make sure we convert any creator attributes from the json string + creator_attributes = node_data.get("creator_attributes") + if creator_attributes: + node_data["creator_attributes"] = json.loads(creator_attributes) + else: + node_data["creator_attributes"] = {} + # Move the relevant attributes into "creator_attributes" that # we flattened originally - node_data["creator_attributes"] = {} creator_attribute_keys = node_data.pop("__creator_attributes_keys", "").split(",") for key in creator_attribute_keys: if key in node_data: node_data["creator_attributes"][key] = node_data.pop(key) + # Make sure we convert any publish attributes from the json string publish_attributes = node_data.get("publish_attributes") if publish_attributes: node_data["publish_attributes"] = json.loads(publish_attributes) From 48ab18f5431a42bdd185f25a7bedab5b45770ad9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Sep 2023 21:25:50 +0800 Subject: [PATCH 50/71] Ondrej's comment on changing the raise exception to LoaderError --- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index d3b7c61ff8..d87d6b1bfe 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -1,10 +1,12 @@ import os from openpype.pipeline import load, get_representation_path +from openpype.pipeline.load import LoaderError from openpype.hosts.max.api.pipeline import ( containerise, import_custom_attribute_data, update_custom_attribute_data ) + from openpype.hosts.max.api.lib import ( unique_namespace, get_namespace, @@ -28,8 +30,8 @@ class OxAbcLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): plugin_list = get_plugins() if "ephere.plugins.autodesk.max.ornatrix.dlo" not in plugin_list: - raise RuntimeError("Ornatrix plugin not " - "found/installed in Max yet..") + raise LoaderError("Ornatrix plugin not " + "found/installed in Max yet..") file_path = os.path.normpath(self.filepath_from_context(context)) rt.AlembicImport.ImportToRoot = True From a0f7951ea3927de61722d299fb77239abfd9e033 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 16:02:46 +0200 Subject: [PATCH 51/71] Add job context parameter to USD publisher E.g. You can now directly export with a `Arnold` job context (if it's registered) so that the USD export is Arnold supported and directly renderable with shaders/render attributes by Arnold renderer. --- .../maya/plugins/create/create_maya_usd.py | 25 ++++++++++++++++++- .../maya/plugins/publish/extract_maya_usd.py | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_maya_usd.py b/openpype/hosts/maya/plugins/create/create_maya_usd.py index f05f155dd9..38de218bfb 100644 --- a/openpype/hosts/maya/plugins/create/create_maya_usd.py +++ b/openpype/hosts/maya/plugins/create/create_maya_usd.py @@ -5,6 +5,8 @@ from openpype.lib import ( TextDef ) +from maya import cmds + class CreateMayaUsd(plugin.MayaCreator): """Create Maya USD Export""" @@ -15,11 +17,28 @@ class CreateMayaUsd(plugin.MayaCreator): icon = "cubes" description = "Create Maya USD Export" + cache = {} + def get_publish_families(self): return ["usd", "mayaUsd"] def get_instance_attr_defs(self): + if "jobContextItems" not in self.cache: + # Query once instead of per instance + job_context_items = {} + try: + cmds.loadPlugin("mayaUsdPlugin", quiet=True) + job_context_items = { + cmds.mayaUSDListJobContexts(jobContext=name): name + for name in cmds.mayaUSDListJobContexts(export=True) + } + except RuntimeError: + # Likely `mayaUsdPlugin` plug-in not available + self.log.warning("Unable to retrieve available job " + "contexts for `mayaUsdPlugin` exports") + self.cache["jobContextItems"] = job_context_items + defs = lib.collect_animation_defs() defs.extend([ EnumDef("defaultUSDFormat", @@ -45,7 +64,11 @@ class CreateMayaUsd(plugin.MayaCreator): TextDef("attrPrefix", label="Custom Attributes Prefix", default="", - placeholder="prefix1, prefix2") + placeholder="prefix1, prefix2"), + EnumDef("jobContext", + label="Job Context", + items=self.cache["jobContextItems"], + multiselection=True), ]) return defs diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py index 09bbf01831..8c32ac1e39 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_usd.py @@ -148,6 +148,7 @@ class ExtractMayaUsd(publish.Extractor): "exportRefsAsInstanceable": bool, "eulerFilter": bool, "renderableOnly": bool, + "jobContext": (list, None) # optional list # "worldspace": bool, } @@ -169,6 +170,7 @@ class ExtractMayaUsd(publish.Extractor): "exportRefsAsInstanceable": False, "eulerFilter": True, "renderableOnly": False, + "jobContext": None # "worldspace": False } From 388ae935e1e37d5771816defd4983bb1d6092bd2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 16:09:51 +0200 Subject: [PATCH 52/71] By default load proxy as *not* `Shareable` --- openpype/hosts/maya/plugins/load/load_maya_usd.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/hosts/maya/plugins/load/load_maya_usd.py b/openpype/hosts/maya/plugins/load/load_maya_usd.py index 26c497768d..2fb1a625a5 100644 --- a/openpype/hosts/maya/plugins/load/load_maya_usd.py +++ b/openpype/hosts/maya/plugins/load/load_maya_usd.py @@ -51,6 +51,14 @@ class MayaUsdLoader(load.LoaderPlugin): cmds.connectAttr("time1.outTime", "{}.time".format(proxy)) cmds.setAttr("{}.filePath".format(proxy), path, type="string") + # By default, we force the proxy to not use a shared stage because + # when doing so Maya will quite easily allow to save into the + # loaded usd file. Since we are loading published files we want to + # avoid altering them. Unshared stages also save their edits into + # the workfile as an artist might expect it to do. + cmds.setAttr("{}.shareStage".format(proxy), False) + # cmds.setAttr("{}.shareStage".format(proxy), lock=True) + nodes = [transform, proxy] self[:] = nodes From f1267546d23040eb778b9f8ead135d6de36184d9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 18:02:24 +0200 Subject: [PATCH 53/71] Avoid error if no job contexts are available --- openpype/hosts/maya/plugins/create/create_maya_usd.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_maya_usd.py b/openpype/hosts/maya/plugins/create/create_maya_usd.py index 38de218bfb..77a96dd4cb 100644 --- a/openpype/hosts/maya/plugins/create/create_maya_usd.py +++ b/openpype/hosts/maya/plugins/create/create_maya_usd.py @@ -31,12 +31,17 @@ class CreateMayaUsd(plugin.MayaCreator): cmds.loadPlugin("mayaUsdPlugin", quiet=True) job_context_items = { cmds.mayaUSDListJobContexts(jobContext=name): name - for name in cmds.mayaUSDListJobContexts(export=True) + for name in cmds.mayaUSDListJobContexts(export=True) or [] } except RuntimeError: # Likely `mayaUsdPlugin` plug-in not available self.log.warning("Unable to retrieve available job " "contexts for `mayaUsdPlugin` exports") + + if not job_context_items: + # enumdef multiselection may not be empty + job_context_items = [""] + self.cache["jobContextItems"] = job_context_items defs = lib.collect_animation_defs() From d5823ab556e016c424b7c4bdd12ddac646d745be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 6 Sep 2023 18:08:51 +0200 Subject: [PATCH 54/71] Add a few tooltips --- .../maya/plugins/create/create_maya_usd.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/hosts/maya/plugins/create/create_maya_usd.py b/openpype/hosts/maya/plugins/create/create_maya_usd.py index 77a96dd4cb..cc9a14bd3a 100644 --- a/openpype/hosts/maya/plugins/create/create_maya_usd.py +++ b/openpype/hosts/maya/plugins/create/create_maya_usd.py @@ -55,12 +55,29 @@ class CreateMayaUsd(plugin.MayaCreator): default="usdc"), BoolDef("stripNamespaces", label="Strip Namespaces", + tooltip=( + "Remove namespaces during export. By default, " + "namespaces are exported to the USD file in the " + "following format: nameSpaceExample_pPlatonic1" + ), default=True), BoolDef("mergeTransformAndShape", label="Merge Transform and Shape", + tooltip=( + "Combine Maya transform and shape into a single USD" + "prim that has transform and geometry, for all" + " \"geometric primitives\" (gprims).\n" + "This results in smaller and faster scenes. Gprims " + "will be \"unpacked\" back into transform and shape " + "nodes when imported into Maya from USD." + ), default=True), BoolDef("includeUserDefinedAttributes", label="Include User Defined Attributes", + tooltip=( + "Whether to include all custom maya attributes found " + "on nodes as metadata (userProperties) in USD." + ), default=False), TextDef("attr", label="Custom Attributes", @@ -73,6 +90,12 @@ class CreateMayaUsd(plugin.MayaCreator): EnumDef("jobContext", label="Job Context", items=self.cache["jobContextItems"], + tooltip=( + "Specifies an additional export context to handle.\n" + "These usually contain extra schemas, primitives,\n" + "and materials that are to be exported for a " + "specific\ntask, a target renderer for example." + ), multiselection=True), ]) From 1365bec5188cfb810ee73b442e9393bf918c2ab9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 11 Sep 2023 16:19:15 +0200 Subject: [PATCH 55/71] :recycle: add check for produced file --- openpype/hosts/max/plugins/publish/extract_model_obj.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index e522b1e7a1..a5d9ad6597 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -3,6 +3,7 @@ import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection +from openpype.pipeline.publish import KnownPublishError class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): @@ -27,6 +28,7 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir)) + self.log.info("Performing Extraction ...") with maintained_selection(): # select and export node_list = instance.data["members"] @@ -38,7 +40,10 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): using=rt.ObjExp, ) - self.log.info("Performing Extraction ...") + if not os.path.exists(filepath): + raise KnownPublishError( + "File {} wasn't produced by 3ds max, please check the logs.") + if "representations" not in instance.data: instance.data["representations"] = [] From 86629c85cab0cb59585f0fd0d6120a2c0c27f7fd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 11 Sep 2023 16:21:35 +0200 Subject: [PATCH 56/71] :recycle: make usd plugin validator optional this is temporary hack to allow testing and publishing models without USD plugin in max as the plugin validator will run always, no matter if the USD extractor is enabled or not. This will change when #5602 is implemented. --- .../max/plugins/publish/validate_usd_plugin.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_usd_plugin.py b/openpype/hosts/max/plugins/publish/validate_usd_plugin.py index 9957e62736..36c4291925 100644 --- a/openpype/hosts/max/plugins/publish/validate_usd_plugin.py +++ b/openpype/hosts/max/plugins/publish/validate_usd_plugin.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- """Validator for USD plugin.""" -from openpype.pipeline import PublishValidationError from pyblish.api import InstancePlugin, ValidatorOrder from pymxs import runtime as rt +from openpype.pipeline import ( + OptionalPyblishPluginMixin, + PublishValidationError +) + def get_plugins() -> list: """Get plugin list from 3ds max.""" @@ -17,17 +21,25 @@ def get_plugins() -> list: return plugin_info_list -class ValidateUSDPlugin(InstancePlugin): +class ValidateUSDPlugin(OptionalPyblishPluginMixin, + InstancePlugin): """Validates if USD plugin is installed or loaded in 3ds max.""" order = ValidatorOrder - 0.01 families = ["model"] hosts = ["max"] - label = "USD Plugin" + label = "Validate USD Plugin loaded" + optional = True def process(self, instance): """Plugin entry point.""" + for sc in ValidateUSDPlugin.__subclasses__(): + self.log.info(sc) + + if not self.is_active(instance.data): + return + plugin_info = get_plugins() usd_import = "usdimport.dli" if usd_import not in plugin_info: From c292a11939a7cf3f980d018031e3da86335925c5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Sep 2023 19:10:52 +0800 Subject: [PATCH 57/71] typo for LoadError --- openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index d87d6b1bfe..f8aadb53c0 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -1,6 +1,6 @@ import os from openpype.pipeline import load, get_representation_path -from openpype.pipeline.load import LoaderError +from openpype.pipeline.load import LoadError from openpype.hosts.max.api.pipeline import ( containerise, import_custom_attribute_data, @@ -30,8 +30,8 @@ class OxAbcLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): plugin_list = get_plugins() if "ephere.plugins.autodesk.max.ornatrix.dlo" not in plugin_list: - raise LoaderError("Ornatrix plugin not " - "found/installed in Max yet..") + raise LoadError("Ornatrix plugin not " + "found/installed in Max yet..") file_path = os.path.normpath(self.filepath_from_context(context)) rt.AlembicImport.ImportToRoot = True From 5f2756b95ee0d06b1ac2748b736db93dc47a7f46 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 13 Sep 2023 22:42:33 +0200 Subject: [PATCH 58/71] Skip view capture when Maya is in headless mode Co-authored-by: Toke Jepsen --- .../plugins/publish/extract_active_view_thumbnail.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py index b4e62f8acc..71a0ba877b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py @@ -4,6 +4,9 @@ import maya.api.OpenMayaUI as omui import pyblish.api import tempfile +from openpype.hosts.maya.lib import IS_HEADLESS + + class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): """Set instance thumbnail to a screengrab of current active viewport. @@ -19,6 +22,13 @@ class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): hosts = ["maya"] def process(self, instance): + if IS_HEADLESS: + self.log.debug( + "Skip extraction of active view thumbnail, due to being in" + "headless mode." + ) + return + thumbnail = instance.data.get("thumbnailPath") if not thumbnail: view_thumbnail = self.get_view_thumbnail(instance) From ef843e25ca35b2e0ab6b0be47f214093131e9c7c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 13 Sep 2023 23:01:58 +0200 Subject: [PATCH 59/71] Update openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py --- .../hosts/maya/plugins/publish/extract_active_view_thumbnail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py index 71a0ba877b..f47dd5e084 100644 --- a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py @@ -7,7 +7,6 @@ import tempfile from openpype.hosts.maya.lib import IS_HEADLESS - class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): """Set instance thumbnail to a screengrab of current active viewport. From 5e20dd3f9cb553c053f35baf0fb321e6b46a3f43 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 09:54:32 +0100 Subject: [PATCH 60/71] Update openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py --- .../hosts/maya/plugins/publish/extract_active_view_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py index f47dd5e084..483ae6d9d3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_active_view_thumbnail.py @@ -4,7 +4,7 @@ import maya.api.OpenMayaUI as omui import pyblish.api import tempfile -from openpype.hosts.maya.lib import IS_HEADLESS +from openpype.hosts.maya.api.lib import IS_HEADLESS class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin): From cdc6366662b00a3e0f413cdba8e1eb1784f10e28 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Sep 2023 17:42:35 +0800 Subject: [PATCH 61/71] update the OP data after the merge of #5424 --- .../plugins/load/load_pointcache_ornatrix.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index f8aadb53c0..0f32b7938f 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -50,19 +50,13 @@ class OxAbcLoader(load.LoaderPlugin): name + "_", suffix="_", ) - - abc_container = rt.Container() + abc_container =[] for abc in scene_object: - abc.Parent = abc_container abc.name = f"{namespace}:{abc.name}" - # rename the abc container with namespace - abc_container_name = f"{namespace}:{name}_{self.postfix}" - abc_container.name = abc_container_name - import_custom_attribute_data( - abc_container, abc_container.Children) + abc_container.append(abc) return containerise( - name, [abc_container], context, + name, abc_container, context, namespace, loader=self.__class__.__name__ ) @@ -89,14 +83,17 @@ class OxAbcLoader(load.LoaderPlugin): obj_type = rt.ClassOf(obj) if str(obj_type).startswith("Ox_"): scene_object.append(obj) - update_custom_attribute_data( - inst_container, scene_object.Children) + ox_abc_objects = [] for abc in scene_object: abc.Parent = container abc.name = f"{namespace}:{abc.name}" - abc.pos = transform_data[f"{abc.name}.transform"] - abc.scale = transform_data[f"{abc.name}.scale"] - + ox_abc_objects.append(abc) + ox_transform = f"{abc.name}.transform" + if ox_transform in transform_data.keys(): + abc.pos = transform_data[ox_transform] or 0 + abc.scale = transform_data[f"{abc.name}.scale"] or 0 + update_custom_attribute_data( + inst_container, ox_abc_objects) lib.imprint( container["instance_node"], {"representation": str(representation["_id"])}, From 2bd4d295afda0e5f64434e6119d802180f4ca618 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Sep 2023 17:46:32 +0800 Subject: [PATCH 62/71] update the Data & hound --- .../plugins/load/load_pointcache_ornatrix.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index 0f32b7938f..cbd15ae6b8 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -3,7 +3,7 @@ from openpype.pipeline import load, get_representation_path from openpype.pipeline.load import LoadError from openpype.hosts.max.api.pipeline import ( containerise, - import_custom_attribute_data, + get_previous_loaded_object, update_custom_attribute_data ) @@ -50,7 +50,7 @@ class OxAbcLoader(load.LoaderPlugin): name + "_", suffix="_", ) - abc_container =[] + abc_container = [] for abc in scene_object: abc.name = f"{namespace}:{abc.name}" abc_container.append(abc) @@ -64,11 +64,12 @@ class OxAbcLoader(load.LoaderPlugin): path = get_representation_path(representation) node_name = container["instance_node"] namespace, name = get_namespace(node_name) - sub_node_name = f"{namespace}:{name}_{self.postfix}" - inst_container = rt.getNodeByName(sub_node_name) - rt.Select(inst_container.Children) - transform_data = object_transform_set(inst_container.Children) - for prev_obj in rt.selection: + node = rt.getNodeByName(node_name) + node_list = get_previous_loaded_object(node) + rt.Select(node_list) + selections = rt.getCurrentSelection() + transform_data = object_transform_set(selections) + for prev_obj in selections: if rt.isValidNode(prev_obj): rt.Delete(prev_obj) @@ -92,8 +93,7 @@ class OxAbcLoader(load.LoaderPlugin): if ox_transform in transform_data.keys(): abc.pos = transform_data[ox_transform] or 0 abc.scale = transform_data[f"{abc.name}.scale"] or 0 - update_custom_attribute_data( - inst_container, ox_abc_objects) + update_custom_attribute_data(node, ox_abc_objects) lib.imprint( container["instance_node"], {"representation": str(representation["_id"])}, From 963f3f42ff287742e6aa2004292dc513464bcaa8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Sep 2023 17:48:18 +0800 Subject: [PATCH 63/71] use the get_plugins function from max.api.lib --- .../max/plugins/load/load_pointcache_ornatrix.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py index cbd15ae6b8..96060a6a6f 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -10,7 +10,8 @@ from openpype.hosts.max.api.pipeline import ( from openpype.hosts.max.api.lib import ( unique_namespace, get_namespace, - object_transform_set + object_transform_set, + get_plugins ) from openpype.hosts.max.api import lib from pymxs import runtime as rt @@ -105,15 +106,3 @@ class OxAbcLoader(load.LoaderPlugin): def remove(self, container): node = rt.GetNodeByName(container["instance_node"]) rt.Delete(node) - - -def get_plugins() -> list: - """Get plugin list from 3ds max.""" - manager = rt.PluginManager - count = manager.pluginDllCount - plugin_info_list = [] - for p in range(1, count + 1): - plugin_info = manager.pluginDllName(p) - plugin_info_list.append(plugin_info) - - return plugin_info_list From 10ed783907dbd3df9c511c71cf144b75ccd810f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 16 Sep 2023 03:25:05 +0000 Subject: [PATCH 64/71] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 35564c2bf0..cf3cb8ba1a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.16.7-nightly.2 - 3.16.7-nightly.1 - 3.16.6 - 3.16.6-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.10-nightly.1 - 3.14.9 - 3.14.9-nightly.5 - - 3.14.9-nightly.4 validations: required: true - type: dropdown From 73e928efc99dbcd90b84d09cd1de0397ed3f633b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Sep 2023 10:12:58 +0200 Subject: [PATCH 65/71] Fix - _id key used instead of id (#5626) Just 'id' is not returned because value in fields. --- openpype/client/server/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index 39322627bb..3ee62a3172 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -422,7 +422,7 @@ def get_last_version_by_subset_name( if not subset: return None return get_last_version_by_subset_id( - project_name, subset["id"], fields=fields + project_name, subset["_id"], fields=fields ) From 36a8976c580d822c816992c752181b48a884202d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:55:07 +0200 Subject: [PATCH 66/71] AYON: Mark deprecated settings in Maya (#5627) * Mark color management preferences as deprecated * add migration hint --- server_addon/maya/server/settings/imageio.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server_addon/maya/server/settings/imageio.py b/server_addon/maya/server/settings/imageio.py index 7512bfe253..946a14c866 100644 --- a/server_addon/maya/server/settings/imageio.py +++ b/server_addon/maya/server/settings/imageio.py @@ -39,8 +39,10 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ColorManagementPreferenceV2Model(BaseSettingsModel): - """Color Management Preference v2 (Maya 2022+).""" - _layout = "expanded" + """Color Management Preference v2 (Maya 2022+). + + Please migrate all to 'imageio/workfile' and enable it. + """ enabled: bool = Field(True, title="Use Color Management Preference v2") @@ -51,7 +53,6 @@ class ColorManagementPreferenceV2Model(BaseSettingsModel): class ColorManagementPreferenceModel(BaseSettingsModel): """Color Management Preference (legacy).""" - _layout = "expanded" renderSpace: str = Field(title="Rendering Space") viewTransform: str = Field(title="Viewer Transform ") @@ -89,11 +90,11 @@ class ImageIOSettings(BaseSettingsModel): # Deprecated colorManagementPreference_v2: ColorManagementPreferenceV2Model = Field( default_factory=ColorManagementPreferenceV2Model, - title="Color Management Preference v2 (Maya 2022+)" + title="DEPRECATED: Color Management Preference v2 (Maya 2022+)" ) colorManagementPreference: ColorManagementPreferenceModel = Field( default_factory=ColorManagementPreferenceModel, - title="Color Management Preference (legacy)" + title="DEPRECATED: Color Management Preference (legacy)" ) From 9f040265e7cd965df20137d9da12396fa8338310 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 18 Sep 2023 16:07:54 +0100 Subject: [PATCH 67/71] Remove context prompt. --- openpype/hosts/maya/api/pipeline.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 60495ac652..3647ec0b6b 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -659,17 +659,6 @@ def on_task_changed(): lib.set_context_settings() lib.update_content_on_context_change() - msg = " project: {}\n asset: {}\n task:{}".format( - get_current_project_name(), - get_current_asset_name(), - get_current_task_name() - ) - - lib.show_message( - "Context was changed", - ("Context was changed to:\n{}".format(msg)), - ) - def before_workfile_open(): if handle_workfile_locks(): From b8997c5ae49841ed3dfc82c30d394909a3b88722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:23:45 +0200 Subject: [PATCH 68/71] create symlinks for ssl libs (#5633) build was missing `libssl.1.1.so` and `libcrypto.1.1.so` symlinks needed by the executable itself, because python is now explicitly build with OpenSSL 1.1.1 --- Dockerfile.centos7 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 index 9217140f20..ab1d3f8253 100644 --- a/Dockerfile.centos7 +++ b/Dockerfile.centos7 @@ -109,6 +109,8 @@ RUN source $HOME/.bashrc \ RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.9/lib \ && cp /usr/lib64/openssl11/libssl* ./build/exe.linux-x86_64-3.9/lib \ && cp /usr/lib64/openssl11/libcrypto* ./build/exe.linux-x86_64-3.9/lib \ + && ln -sr ./build/exe.linux-x86_64-3.9/lib/libssl.so ./build/exe.linux-x86_64-3.9/lib/libssl.1.1.so \ + && ln -sr ./build/exe.linux-x86_64-3.9/lib/libcrypto.so ./build/exe.linux-x86_64-3.9/lib/libcrypto.1.1.so \ && cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.9/lib \ && cp /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.9/vendor/python/PySide2/Qt/lib From a8fa44c3fda18d0799fc171cb6461ba80672a36d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:52:51 +0200 Subject: [PATCH 69/71] AYON: Ignore separated modules (#5619) * ayon mode explicitly ignores addons that have own repository * change warning message of missing addon directory to debug * Better log message --- openpype/modules/base.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 84e213288c..a3c21718b9 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -59,6 +59,14 @@ IGNORED_DEFAULT_FILENAMES = ( "example_addons", "default_modules", ) +# Modules that won't be loaded in AYON mode from "./openpype/modules" +# - the same modules are ignored in "./server_addon/create_ayon_addons.py" +IGNORED_FILENAMES_IN_AYON = { + "ftrack", + "shotgrid", + "sync_server", + "slack", +} # Inherit from `object` for Python 2 hosts @@ -392,9 +400,9 @@ def _load_ayon_addons(openpype_modules, modules_key, log): folder_name = "{}_{}".format(addon_name, addon_version) addon_dir = os.path.join(addons_dir, folder_name) if not os.path.exists(addon_dir): - log.warning(( - "Directory for addon {} {} does not exists. Path \"{}\"" - ).format(addon_name, addon_version, addon_dir)) + log.debug(( + "No localized client code found for addon {} {}." + ).format(addon_name, addon_version)) continue sys.path.insert(0, addon_dir) @@ -483,6 +491,10 @@ def _load_modules(): is_in_current_dir = dirpath == current_dir is_in_host_dir = dirpath == hosts_dir + ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES) + if AYON_SERVER_ENABLED: + ignored_current_dir_filenames |= IGNORED_FILENAMES_IN_AYON + for filename in os.listdir(dirpath): # Ignore filenames if filename in IGNORED_FILENAMES: @@ -490,7 +502,7 @@ def _load_modules(): if ( is_in_current_dir - and filename in IGNORED_DEFAULT_FILENAMES + and filename in ignored_current_dir_filenames ): continue From bed1e35d20dfc949a8b8c3864bbb526644fa37df Mon Sep 17 00:00:00 2001 From: 64qam Date: Tue, 19 Sep 2023 16:54:48 +0200 Subject: [PATCH 70/71] Default create a desktop icon is checked (#5636) --- inno_setup.iss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/inno_setup.iss b/inno_setup.iss index 418bedbd4d..d9a41d22ee 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -36,7 +36,7 @@ WizardStyle=modern Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" [InstallDelete] ; clean everything in previous installation folder @@ -53,4 +53,3 @@ Name: "{autodesktop}\{#MyAppName} {#AppVer}"; Filename: "{app}\openpype_gui.exe" [Run] Filename: "{app}\openpype_gui.exe"; Description: "{cm:LaunchProgram,OpenPype}"; Flags: nowait postinstall skipifsilent - From 50af3321ae9a9e0118f6b25eb7845ce6f9ab1762 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:18:46 +0200 Subject: [PATCH 71/71] AYON: Avoid creation of duplicated links (#5593) * create set of output links before creation * find all existing links before creating them * Added small comment --- .../publish/integrate_inputlinks_ayon.py | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/integrate_inputlinks_ayon.py b/openpype/plugins/publish/integrate_inputlinks_ayon.py index 180524cd08..28684aa889 100644 --- a/openpype/plugins/publish/integrate_inputlinks_ayon.py +++ b/openpype/plugins/publish/integrate_inputlinks_ayon.py @@ -1,7 +1,11 @@ import collections import pyblish.api -from ayon_api import create_link, make_sure_link_type_exists +from ayon_api import ( + create_link, + make_sure_link_type_exists, + get_versions_links, +) from openpype import AYON_SERVER_ENABLED @@ -124,6 +128,33 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin): version_entity["_id"], ) + def _get_existing_links(self, project_name, link_type, entity_ids): + """Find all existing links for given version ids. + + Args: + project_name (str): Name of project. + link_type (str): Type of link. + entity_ids (set[str]): Set of version ids. + + Returns: + dict[str, set[str]]: Existing links by version id. + """ + + output = collections.defaultdict(set) + if not entity_ids: + return output + + existing_in_links = get_versions_links( + project_name, entity_ids, [link_type], "output" + ) + + for entity_id, links in existing_in_links.items(): + if not links: + continue + for link in links: + output[entity_id].add(link["entityId"]) + return output + def create_links_on_server(self, context, new_links): """Create new links on server. @@ -144,16 +175,32 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin): # Create link themselves for link_type, items in new_links.items(): + mapping = collections.defaultdict(set) + # Make sure there are no duplicates of src > dst ids for item in items: - input_id, output_id = item - create_link( - project_name, - link_type, - input_id, - "version", - output_id, - "version" - ) + _input_id, _output_id = item + mapping[_input_id].add(_output_id) + + existing_links_by_in_id = self._get_existing_links( + project_name, link_type, set(mapping.keys()) + ) + + for input_id, output_ids in mapping.items(): + existing_links = existing_links_by_in_id[input_id] + for output_id in output_ids: + # Skip creation of link if already exists + # NOTE: AYON server does not support + # to have same links + if output_id in existing_links: + continue + create_link( + project_name, + link_type, + input_id, + "version", + output_id, + "version" + ) if not AYON_SERVER_ENABLED: