From 415c234d1bd355dc6e59e4e1370e0523835e4862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 17 Jan 2017 16:32:49 +0100 Subject: [PATCH] io_blend_utils: ship BAM as wheel file, running BAM-pack from that Previously we had a copy of BAM's blendfile_pack.py shipped with Blender. Updating that was cumbersome, as changes in one copy would have to be manually ported to the other. This is now resolved by not bundling blendfile_pack.py at all, but to include BAM as wheel file. This wheel file can be produced by running "python3 setup.py bdist_wheel" in the BAM source directory, and can be bundled with Blender without further modification. Blender looks for a file "blender_bam-*.whl" in the io_blend_utils directory, and loads the (alphabetically) last version. Even though there should be only one wheel file bundled, things won't break when there happen to be more, and Blender should pick up on the latest version (given simple versioning schemes). --- io_blend_utils/README.md | 25 + io_blend_utils/__init__.py | 52 +- io_blend_utils/bl_utils/subprocess_helper.py | 17 + .../blender_bam-1.1.1-py3-none-any.whl | Bin 0 -> 49044 bytes io_blend_utils/blendfile_pack.py | 612 ------------------ 5 files changed, 83 insertions(+), 623 deletions(-) create mode 100644 io_blend_utils/README.md create mode 100644 io_blend_utils/blender_bam-1.1.1-py3-none-any.whl delete mode 100755 io_blend_utils/blendfile_pack.py diff --git a/io_blend_utils/README.md b/io_blend_utils/README.md new file mode 100644 index 000000000..459a5f4df --- /dev/null +++ b/io_blend_utils/README.md @@ -0,0 +1,25 @@ + +Bundling BAM with Blender +------------------------- + +Blender is bundled with a version of [BAM](https://pypi.python.org/pypi/blender-bam/). +To update this version, first build a new `wheel `_ file in +BAM itself: + + python3 setup.py bdist_wheel + +Then copy this wheel to Blender: + + cp dist/blender_bam-xxx.whl /path/to/blender/release/scripts/addons/io_blend_utils/ + +Remove old wheels that are still in `/path/to/blender/release/scripts/addons/io_blend_utils/` +before committing. + + +Running bam-pack from the wheel +------------------------------- + +This is the way that Blender runs bam-pack: + + PYTHONPATH=./path/to/blender_bam-xxx.whl python3 -m bam.pack + diff --git a/io_blend_utils/__init__.py b/io_blend_utils/__init__.py index adbd2d041..d6f3c269d 100644 --- a/io_blend_utils/__init__.py +++ b/io_blend_utils/__init__.py @@ -18,8 +18,8 @@ bl_info = { "name": "Blend File Utils", - "author": "Campbell Barton", - "version": (0, 1), + "author": "Campbell Barton and Sybren A. Stüvel", + "version": (1, 0), "blender": (2, 76, 0), "location": "File > External Data > Blend Utils", "description": "Utility for packing blend files", @@ -27,8 +27,9 @@ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/BlendFile_Utils", "support": 'OFFICIAL', "category": "Import-Export", - } +} +import logging import bpy from bpy.types import Operator @@ -41,6 +42,7 @@ class ExportBlendPack(Operator, ExportHelper, SubprocessHelper): """Packs a blend file and all its dependencies into an archive for easy redistribution""" bl_idname = "export_blend.pack" bl_label = "Pack Blend to Archive" + log = logging.getLogger('%s.ExportBlendPack' % __name__) # ExportHelper filename_ext = ".zip" @@ -55,22 +57,26 @@ def poll(cls, context): return bpy.data.is_saved def process_pre(self): - import os import tempfile self.temp_dir = tempfile.TemporaryDirectory() - filepath_blend = bpy.data.filepath - + self.environ = {'PYTHONPATH': pythonpath()} + self.outfname = bpy.path.ensure_ext(self.filepath, ".zip") self.command = ( bpy.app.binary_path_python, - os.path.join(os.path.dirname(__file__), "blendfile_pack.py"), + '-m', 'bam.pack', # file to pack - "--input", filepath_blend, + "--input", bpy.data.filepath, # file to write - "--output", bpy.path.ensure_ext(self.filepath, ".zip"), + "--output", self.outfname, "--temp", self.temp_dir.name, - ) + ) + + if self.log.isEnabledFor(logging.INFO): + import shlex + cmd_to_log = ' '.join(shlex.quote(s) for s in self.command) + self.log.info('Executing %s', cmd_to_log) def process_post(self, returncode): if self.temp_dir is not None: @@ -79,6 +85,7 @@ def process_post(self, returncode): except: import traceback traceback.print_exc() + self.log.info('Written to %s', self.outfname) def menu_func(self, context): @@ -89,7 +96,30 @@ def menu_func(self, context): classes = ( ExportBlendPack, - ) +) + + +def pythonpath() -> str: + """Returns the value of a PYTHONPATH environment variable needed to run BAM from its wheel file. + """ + + import os.path + import glob + + log = logging.getLogger('%s.pythonpath' % __name__) + + # Find the wheel to run. + my_dir = os.path.abspath(os.path.dirname(__file__)) + wheelpaths = glob.glob(os.path.join(my_dir, 'blender_bam-*.whl')) + wheelpath = sorted(wheelpaths)[-1] # use the last version we find, should be only one. + log.info('Using wheel file %s to run BAM-Pack', wheelpath) + + # Update the PYTHONPATH to include that wheel. + existing_pypath = os.environ.get('PYTHONPATH', '') + if existing_pypath: + return os.pathsep.join((existing_pypath, wheelpath)) + + return wheelpath def register(): diff --git a/io_blend_utils/bl_utils/subprocess_helper.py b/io_blend_utils/bl_utils/subprocess_helper.py index 024f0da93..ef9c602ab 100644 --- a/io_blend_utils/bl_utils/subprocess_helper.py +++ b/io_blend_utils/bl_utils/subprocess_helper.py @@ -22,6 +22,7 @@ Defines an operator mix-in to use for non-blocking command line access. """ + class SubprocessHelper: """ Mix-in class for operators to run commands in a non-blocking way. @@ -39,8 +40,18 @@ class SubprocessHelper: ``process_post(returncode)``: Callback that runs when the process has ende. returncode is -1 if the process was terminated. + + Subclass may define: + ``environment``: + Dict of environment variables exposed to the subprocess. + Contrary to the subprocess.Popen(env=...) parameter, this + dict is and not used to replace the existing environment + entirely, but is just used to update it. """ + environ = {} + command = () + @staticmethod def _non_blocking_readlines(f, chunk=64): """ @@ -138,14 +149,20 @@ def modal(self, context, event): def execute(self, context): import subprocess + import os + import copy self.process_pre() + env = copy.deepcopy(os.environ) + env.update(self.environ) + try: p = subprocess.Popen( self.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=env, ) except FileNotFoundError as ex: # Command not found diff --git a/io_blend_utils/blender_bam-1.1.1-py3-none-any.whl b/io_blend_utils/blender_bam-1.1.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..e422f63ae249fdbec902e71bdbe47f74eb64c056 GIT binary patch literal 49044 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

|lw(tA`Oc+)dVB#e-9 zy_{KCP93imr#jcI4YrWn^{S#w>8+w_Ik@E|pivLGtffwtgC9DN)Ai2kTksjyd~Jh@ z_!qLi_4=+ldYadAHG)XCb)CqZv=@OoJV6I>le>5T1CV@JU zEFz>7qZtj0eUcbwK?S(XE2{8b_XF$M9fQHSi?^ZYIPNZO=XD=G9IgR8qvO+PA8(+2 zV(v|TbFQA$T4TiY=dEcW8O9PjPi(UsvHTAxAwHh=3+Q^5`SFwjNrFg-M%iU3p>8%L zQyi&!lw8-?6qVnxG7<$Q?trd2qKPzTp7ic3e+#d4VyFubbRv}1-$2$y1fdi_(yH?g zBuYlp>$h?$8&nh`0ZNtZtJ1#g$OkjH7jY9Dph1#saPehDu1+G2 zriV%h$yM7H=#O*+xZxzS#}#3lrdi0M?oJg8>NJ(0HEirF-EkA;Gp5bx&b+oIU0NDQUi%Y;P#~-zEIQ^PPYRjF#Jq81V*SH_S3@GH zYds7pVhY1HYuc{E+gN3}(KI_bOMCfQa8o^0wHo-qRA<%f|6skqMx8y9YYx^26_o-s zL{s50XPj-xitz$V&1wGWDlG)hl2%pI5a)or3qWKY|AvwXx)_6nQmY|BcB;^B%3C91 zO3eU6Hw|t5RQaU%G{k||ybQ9oj@<^;6%DbXs+oP&1x%}KpM`}AK zP(q}&Bq02UPmdj^=(-iKE=e@L!X+XA_xbYc9*gA}J)QFbhbz3(PX;pR5eD%x)1?9p zw+lrmqboG|Xf@X{9)JmR*-*DgFhu79VaY~YH|+*kkmC0IuNz=Z!#Qk>a);aKw)k1( zw!d*BHsGWlU)=eRe8p&r^?FbIp(FD7^`kEJ1@joxN@816{*{9Iw|$z8YZXGodZ%pG1)qVwebOuJx%sglUWg z1w>g`A}z0oA09|Ci7r?87HA$&ahA)CP@?baEmBnU1!}l4Htg<2A~7`m%{c*He#*q3 zdha|{Qz*b7(Ycd_U%cEc)Y-cEalV$zTj}t|CK>WSynI!?xY^9DcGZ9{idT(mtHfpp zV%rd88(rgURoCsJvoFW`?)(N&?-m0?OU%2XS!{LM1wZZGCWJLBX4#k zKG~ha`)dsi*7kAZwE0~6^>K4#*la6mWdXAz+giLHL#W=*LRl}UT;A^?dQ8ZcHYr{I zSP-yt+Z(toT%XSwGI4$58EdQ7wB;U1xg+S*EV1kNm)o&iFTB|*cqpMY!2e@x#0;eKU4ksXtN+;8(A?$0XT(IKzTMHN*1$Nj@L zri#Rr=0C4pMLRT`=Rt;#=@LY@Hyx)tV>rIhhi_(L8puDqGLL&tA77q3psoP)lPC36YX@5O6ScZ`ZT!D*46W>Nb6VrU2rFeve% zY*I8(LxLCvnm$CR6LlsuNwB_DjTzJo(r{r_sbHCF>6_!gTMseRX@kBE5~3i+i1#wi zDP|Vgz2~Iyh7Aos>>&nfgaO!*UYUqYT>x|rx&v9%vA1_}^ zSPzV1+c89#cDw1!DyH(P!`s_R?tnyqGFkX|r~@U9j@XMNzhWCH?)FFZ4Q3MY;9e=C zIi3vF5`vnV*!gs%Y#o?=Fo3!}y}i6P_<6k_li}f;49^uaPgj%OZPWB!o3GN#7G9^xo35nC;(jW4O?L?dq z$&*HdCMii;)G{o3t<*n`K0hHHf&p{5=7@}=(yUQwB)CpMRluyJgCvHSaQK*=tJmU; zMtU~3m?3(>sr0!CR*li?*LYON?p{TC)=G~Lh?r>*T!%AgrR>7ljIKIn9&6MtR z_*NH4UvrdDn8ArPAihFi?cWm)Q%O)Wejw65KgK{A19zZgT0+pv#I+zpqvE7!GE6hK z9!OLy0j@(_$m@lt>JcjiLrzoeB7Fv#_0T!TzT_=R;0H!r$kBK1eX=F zNZ?tG2}e`f`1~1hW7q_u3h_QLp>1X$B(vA0%{Htb;ub|{8L#$pnVlQwS6&8;#~X9cfA|Wz zJFSRp)JC+!$>3RBi^=TyIB> zr(lUvC1kVVgawzk2Q<5bw=Nk`CT1dPca8KWGPSo!ZG&$w_Fpd&3!XB}pY;2SnjZPG z{9qM6=i_Gs27qSkfzD6GdQ9cvmYH0wX}cfX82gI7Y)q3LE9G+LajZt{s~JI0o)%`J zqA?ro#+r-0k?kRI?i{$Iplzj7rI5w`yM&6e9LnjQ9+-8nD)v+Z=;=HU2||I_jr~Kp z=xQGOl`;;^dDFrt$mlZf<@uU9mb6UU)3jKqgdP(dX+!!#+$_;~vuJ-CYImj#MDOk;R z>I>q>h_I0?{X;3zoIWCt5lREte85V7BolTq9(5v=14wgz5%np}& z0r+Yj>Ke?f9Ac+&lz;rufb9K(JE+{m?vu*i`*9PAq&XGi9Wb|(4k9Wt4jxk`K)nK1 z=?@gUdR7loNM{}h)cyi@oi6%l<1vaE)ZL~4W zyaW3W>uPLNUmdQrBCDAzxjs|NtE7rx)+yI1_GS>qUr z1^2@WYj*0hP)rMQ`u@k1d)tIwQd?Xq0}BrTpezIcfat%f-oL?yyP=J>snh>%byzxY za-{8kQzLYEk(QW}%H2Lqb&jql+L9`^)V1VDmTgg@kqWYnqErx4aPQP!+3&vM0U!|w zMmcSbs=!vkXL9TYy6*#S`MnsCCZQ)2cnuVBxB5ufADng7)Y!h@;JY#SaC4d3&&IFc zpE-DN;WY*yHVxsMp0BVib#+66Xp`N$9?)Za<&ftvBhD^*^Y8Q5hkFc{`u#=XgBHmG zNmiZEOkm>A>WM(M=!-BP_yW|g`GYC1;WeZ=ksNAppY!A4{gKymj41z=kXuJ)$Y44; zkRl!=Y>huM;(ib|yLJ?a)jfb2vkbGAjq&lkND${|z!UHgvVAYozk7x1QUIpN`=XIk zAD4gdFN96spoxC+(J#b5t^Jala0)P^fuPvNeveep5eNs^`$+(^9@3P$_o%uSj8{PS z$LqE63jm9~>P$aDF_BUqFWdB=wG)&I=lCiXkuxpnM>H4lIMi#eu1Z(;JKy z96W!2_CP%IGkx~p!hzSsZjNr#KLd`QUNrPN8Vv3B^K==_Fo24v-nz{A#4{P+)};{>DI$FMDJ%+>8bK@GOfUq>~fM-(Vm|)N#Z3 zU$0OmX{2RIsOPOo0a1;lYiBO>DJbr`0$-Q4-Y2>t|z#FK)7AOb<@V6KQ)$T&;xKinEBB? zv|#b4kqmO7r-CJN?$JAfl-==fJ*Tdvg=YO)-7H%sQuzQBDjJ8^qc1v@EVKyV6;oA4scun)vACj`yIOnzu4HztgiZe+ay zT?G^`Z%*vkHZf$7_yJL|Mc8RVV9`%u&*i(hqS##hggqGvcswSeD?Y!;K%=cdqW!R% z7yrEh;)4w~Wxr)Z$b?|xwhz3M6X|H{1_~aK;P`La zW8Zys&_)EZ4DUM~TF9q*W-m%tAuKK}W!7YUmK-MXjHU zv{YLVgMVQsKsK)h1-I}{u6B6CB{SshykuP2T&via<&2ESaL_c_Dyju;0YOLJQ-I#D zCgLj2avM58%Sd_6^|M2ECj?h?AO$R%o=L}tkEGE_&2$z3t?{bQq}O&DI2OZLBSbPV zutXcS2(zj@Y+T=2qRChpPvPhh(v#Bb zCDU7Je6;WI*{4+4+*ap{j4DyoabI?xByrd#sTy{jufL0kz?)X-J>O^@;UEx?tGqU7 z@evUQ&XjJjZmDyzL&1Yq!1%Uc%(iR;Dpq-hvi4)@MD=Wwt7gaSKU?4HTp-An93%sl zPV?xlr$%Cbya34nBy?AP1DS4^lG7I7#=|1Z^9tn8Pdb{JVc!GRIM4)F@BAk2FmCGD za`0af23;`5$Pp}xQK`}h3si1Odxz-~0C;0M4QFCdKm_;TGW&wvvzc1qgo=vUE~W;@ zaY~a_hxDX)Gav2ogMu~}ft$9kceto|kG0IC{vrYVmnJ=uW{?+*4*Yj2evwqo{&cL& zQ{bE0O5(m=lKTO@dt11CK6ZzkUyKW6FK7Dg^t<1|alohJ1b5vBtL{(pXGMFCWkQa~ z-KuA)tKY4}`V2B}WfwBO!k=w<7QtH}#9|RiMPpcHTKl}e$XmvMfC7z)J03dXFbu&GaT_?B zaAg=Hgt`JjB+_S4IvO9}yu{!}f^q^?KMDX9YIx&`O=EWja=2y4fC$EPbuNs3-M0Cy z*l+Y}>X@L(Kxz=AiS3KmALC*bV7n18ltJUsPc#RR2>0a(E2DR2lDS6*#W40{7{!f< zna3D|%1^!Y0}4PO*c!gyIg=_vBwMh1>wn?ba0;7h8Q9(-{eaT0u9-t~XsuZ8C8=Oi zLZca|kN@w&Z!+!=CYsd@%5ZA{O3$p@dSgcVWGSF?UWTUMd? zvcrH21esBC(4xHr=U;k>`#6}OOq=8;yWYTqhbEVLpzd(0R6UqEvs=L(?Ta6MYHhV|*!Qsl z_x3Pp^3>7$wF~bv`WxFfC%Tx4=FStklRPv`0pe4u&S?a~H{4P*S*os)d3;iXPFq!# zp^JIb2gX^!d-N%8xlFus6;9P~TpeLjH3gIH|J4DBGtOonyq zleJ%)1!0k>s#fFPH0j&{-gc1WsZhV&A*~cqj`6Ft6ad`C>BTBV-`gmr9yVDijdU`h zw~8QWTm-l$ZugmG};Z(U_dvv-#6WFy<8I z(bDmM9T+x^`y^mMnFFbBC(+gIL-@*pY*8SuW*R#raD_a9Fl?waNx)beZ;q~@cz>Q2 z<$2k9_7}A~+OpfMsa39eecoXa_+bE6NslA3e+e@G{k147rWQO{QyVOal290Fdf-dD z^(|nv(5DCopt^%wS!mudMOy}{jW8Am-i&b0Z*Ck%@hC|8H?Gn>)m+PB(vX4kQU z;f-HcVe`y(Ed9_)`;#$YRY4-QcI$n(KY};^9cURK5F=N{Y5zGy) zDZ0FZ_>%Hv7zs^!r5N^$w!({6W9Ea;;M%V05S{s4!X4S_bMXScai-;r8!HS{{Grf5 zz3ej2NfSnxh5>PN`4Vwym$CUZ1JKY*egLpQvaf@2tm5M~Oz-X$6Bnz=-jze>%w3fs zI=KIqb*IQUP1g4p!`5x)vrK;qG**Y;G|As!Qbdi*mM!#`YzNsKC@ol|BZ2`k!z?9Y zSx>H8VDI;wI`Qrs34DDLeqh1#TGyoh3(LpD%|LZ&225IUFgCM|+i3!50mZ2PaVOt$ zNPm}k`7wK19Iqd}IXqGut62lh@@tokpb3&95F4Rojef#UM!aOS~lWET_@2jVnKc zOvPyKBJ6>D>CFF=EX5SE1o>}*N0$69l)o!j{nHF8a*Ap|OS(9;La>!SO)n0I;kSTd)y39%?Gk&XM zM~t@p!0l0-_V+Z9t*iK=W|3I-Byg4oNC?J~si>$o3w0zJWcb7i6`{GN`MriQV7sY+ zi0;~xeHd9%2INnF^Dz{F>F5Iy zG@EeU*)Idr^iz5T<+h4Er(xzVN8`#i=kwxOufAFM`OH%~X0mE_jE2bVdY6gtodN&l>% znkXH~5fV{DLQg*xAmP1a zp+8%c4*HiNLp37e7*V5GS%1{5sb5=7uIS`F*Wgyn3V1A}WK?UsnABIMBU`wCXw#m& zhVD@{#dKEBt({I{&3qE*H;LQhTlUj_vL%NgjB@$Z73`p$%@s zMd_(P@wF1#tR*>{%S)|qiDsB5hlHHP`AqAP@W@X6sV9jcwwELtO3LRmB3Y00Wj@49+mw#s#$DF%x$B1M?! z7^V_G<>bA!XJWX&o%znhF9jCAiWhxz#M zj{-aRTIy{64pa#9G8IbpQEz zLG3l<)WoE@{%R4GNyB8G+Y(sEV&6>571hvgYqE2cC6s&0D-JZ@bQH*o0xS)~r6{0E z4xFl0u8@e%Jm%)M92T!b&C8=Pc`)e1gkZE7bnB@wB_fw2GgEUNYd6&=nngk8Mgh4J z)ocUnGf#8JpK?I2caHn=y&#H(w1p5b4ber3hAW6~pTSzMm9bTIZ6Ps*F!-2>Uig~j zBQ94Ac9pD4o(!)Mx12S%G534IQAdKpW|Efb1$s%ZfG;{Er!~Nf@{4*Fu+0VF77P9_ zn6iDK=Z8T}tBzC!6-C$J6^C9-6IVr94C0b~giwKY6%B4KS)dC>O;l>0R|#biK4+}Z z3y5(IT|3O^L=gTi7{RD{b&<#aD(oG=pkc9%(Z7G$*g;=%(G|qUe8|pzAIV>)Qx$}!p^=Mo8ObRCB0N6YP74Y zltA&c8AH}}Z|5IZ_r?J{kS6apt01@C_ZlfIVK<-6T^mnCD6Xt(8YEHYil|egSVmIc zxhji`y%kj&6yPd4j-MCIg7&1uZWtV{p@K_}G)dNuHu+d+$suG(i@%IGTTmK{+bmVJ zuqRy)4_B3@t>~qdO65mfhh+0J;9c)FH;PU(&-OqLWkVP$4^futlZ8fvz%ly~_yrR1oSET}q~ML||ibUhr&S>585-+0+qxNw!iCilbgAQcEU zf0o(xY;0Ke!lq@9O^KX=$Jjrf7@`K~dbC?#3{i_bL+=_GUZB{J!ormEg5&Pg>EPma zP;im;L~KrRT4ifpX8WGFqL!aVk1KDDI*vK37~Ln)$GPl>L$G1nYBTA+1v`-n8i3_- z&Z-y2;#pKiQpAl2*Y@m0h$-s|QmB3=+ZGtvle}wS(JAvrq~fpFELc}D-@G!$Qk^nZ zwu6kGSf;g3S8@nU7H(fmfo!RsrEzS)}yquTi)cjDztYGaCyvO(e4LCG=NR$Q- zu*EV*hdG2mFummFdrzA-B&1sC-O|u?Tu(Qc$k{ch4i>-C*9Vpn&9F;wD)yBy)hg#m z(J02JcmV4tk5SBQD}t=)n-%&hM`PVh>Kmh3luT!CW0-p~C9TUsm>&A7%Y?DZitHgJ z&nVP!{deVu(a_By(=nb^nQ6ax1IvM`-Y}n^+5jbLpZi!E4#e?$( z?Rc7=4=Js&E3c1o5sDmt#&DVF{fg64lwI387}uFLsraMnWHY`zi{?A@Q;A;>qrmqF zh+adyHfhl@8sSb6_})sm@&gy&Xm-CSmN<8qswiv z_^f*=5M+787f@kZFq`x9{#uTTi4~@C9Y93!Q?onhySTKV?>sG*n5R3<0tO{N43D4% z(YmF645flbEF7J7{9YUhFJ+(->gH~t5-7Q;w(gswq`3QHDU>f98Q`U4InhxuDe1X^ zZF>l^UO3fOdtY3k$WUmKw{sY9Ecj)xX-Qsu77Mu3<3p9Hb1gBc~L#T zOi>9DO+^f9twjs7FRmi(w9Ck(<@ibFz6o*XS+1I|Di@f~J5kvOf}~Ov;d^EMNpoXacA7;{e=tzX*~aV;`^5IEKsF5;Zi%>2+8d|r ze4ohfH+2z6##Zyzw~)85_bL_G-=Git!lyEbj;@`>TlS?b6h4O0tGAM|7OOP~(9Pt477+qGuzR9Z(1kr36JWR{ ze;9TlacywF@3_4zDzU-@K=zyto&f7f)R@hAlOe@#s?meQ8b2q+pJ&;Pw3zK{KA)B^ z!D3)Md{LBq)<}B<#?y`REz}yPaggNaYk$`D&%(Nar6DNW_zRDx0%qU~VQ>mhTs^|O z^(LTDxD5?*4owo5S9sm;@kGq5rRVf%+0`xcS*LU@-}Y*UYe=nB0L8WL)oOR*y5_yd z!^mC`NL4XUG0&2j%$1|OMRv?2+Y71Z?s`kC7el`IK^;sBv*RwddSfn(V%bqj*9qzE zUaLF4Eax>Hsz01fDO)4c^w{AKnYSRZr;&woy|qkF(8G=97YO&K=lMSl{N(#&=Zi-D zD&ph$sSfahK(=)X54xnG%=iefZ_+{O&kDN@UJusc9A4)y-5*RAf#xS;BcF`76KLw49k}V-kIr?4odW() zUzs+G!bQ`EX;FwEFE2%~Fky=~*=&Yj)o(6~V5xmTUKKW5broQ{xh;zJBV+si0_9tk zl-)}OXIh~Pr(!9aFvmdGM``a= z!l;(jkx1We6XN|Tb@CP%yVZ#0dHVVK*f;mA z+{UZfE<6GBtZq_?pQ`0sI)o!cs8681fo9OVs?oZOFEVkX{f9UM%Ch@mwTS(ylcBT# z&f0~tq^y=xrytv>?Y6NJ^K4wbp%)Ru)}wS9zK27MB}clAc47El3g+!lg0Eq4+Zv*j zedZxJVr(i8hnxxhNpY7$)4q3ZLLaSK?wyWYfbS8G)hT1{}qQELK-ZB(xO`piW zA0L|cjr^oP!XB*5@2748&n%9<>L*)2gw6LIXu)5; z7}-oZjIPcBG%yf*`_z3nzaag=5etI)Fh~&h=Fy3~6ARYr{`XB+|K{ji>v*Cdkl#A|7CPekCmz|LQ;o*V~_z<04t{I!sm@YL==-YR$zj0Oju$_(6 z*Tp;p=XyUlpJtz8q5}u+ahi3dr<3)?!MUrwnZxA3#72hJT4r(KAj#$jZ3on zpNxF~8|6FMVCWTSJNjwNFS5Y}03U4b$4E78dOAwPGrv8H(|wRi>_icx875o#b%aj_ z;v%|9Pguojz0N%FVA=+)h=AgzqAui>b7Wh0^v50VA`Qg7 zt#g71gUQQjp#VU~>&bkyeO&lM#8Rt4zjhlB7uPE9>-Ahm#L`Qefp|>(O6Kwdz)&5)N>A4kGae{3D0ckGHQ|4b$kMyqgr<4lDz z#|a8BIeal-h4CYJ)q$f9YUc$s&X9L{#A{$AxZ}fOa6PLaR4njSef56v$CL#sVDQ3~ z+{I>uuuer?W8$emqaMprg{p=9TxzeCz>Lg~P+dea=n?B%Y1GMshQ=i^BR#nP(eE^C z5g7t(P$E9!D~v7|4V%wMdSKmAH`UX?bdoAIOmEg5s4|HJ<9FI83AWH1X1Z8!+a<(Z zm%4o|0F{+%!VZE{NE4?jJ37fzuB<;Hy~>0!iTxw(P)aYum2 zh^m8$vOK$j@N0AGZn!FXr*C|-g!H=&d z3BE$%uXvh=1j)?0;rU7(xQWA(p}X~`&GW4M#J)iQc!Bn(r9%+RzY5h%1E)YDh|A`k zvVB=I7*L2QI>gD0JsnvPLTmDYfssrg!B0tN%JanW#RX&J#}6!Sa+UM;XYh?SM!Em? z!D9`kAoXy_b{4oX8{DW5NE+7I)fa5z(umV01ZpxBC$PPU~1GIgperPk;Jr z2pgBe^_Z)iPXg}ImB`>!lCCqATy9=4ie%8KQA8ZwQ>k~0l;DhF3>3{I^~aQ+avZ#+ zS|;QIAYpMeSVK$kSWvUL=7A>Ch;;DL4Qg-TR={wooh@v3d9He#s?TmEGW`z)Z63SH zTQ#8Zye&O7OUrvtQojIVg1+{!>Cmi?SyqD{x<=desWD{b>@=_hm;_%UT6{!&dJ($g z+37y1;&*c_qE1C}Z5pK5!~s5P0&Cb8xl~;PFLt2QzeO#KzIQDw5uo zBZ>1W(;U9gPu%!_{FE#rTpfhD3L#hEqF21p+$-_Z3;_|8NI1Vk8o|mg$cA=FkP+eI zGtF8h_kMy`pmiu6Wp@W_;i+Y7R~F0!0pC;>1S z?|7whi5T*sf+wWSd3oEcib|C1O4s-gmMsL3#yVr*e7HY`!p4$4;jgse2rE(1KaU!^udqQt(W`nQp96ytv{Y~dymk{v~xBaZ{X=@WoUkqI+e+d-%BCvM=C=D zHC3ZR`yRv8LFWJU=<@b-`!O9Czb0p@A3vG?W4J)wTC9I#_sa3At40S6TAMDX0D;CM zZ9qpO;yg`>S=UIZDXHG7c>!UV6AG_uP^n<2rWjBO$?7y7)G};J*R8cVCiFbUC)`-| z;caXy28ixrCWw$wUpWCVH=@w%$pO)6O1d~OQkyvrdJj(6OqRvP$MUVYR)Bhtl4#vk*a0Oy{nUdMWe^UZ-$jx+l9LaJN) zSYdon@C%L~{JcEv*%bnh;;x7E0U1rnjDop843tSIY%_*|lQdJa+$~*~t3v+s(cz}e z?G&_3bB)A^EmWvfe=31c1tne*rX5JV9^?mhBLPY&W=v2-7DfdJQFYzz@FMx^tno^E zH?Te#ggmI(oWd@fe*f}7S&>OsL)ec~gv0ctGfQls;mjFp*%(TIjz$4>#;Q=$XjjX1 zbB6cQ06s7JR#lr)%9MuZbN*$JEy{fEj~Zr-sE}4tS66fxS5rcp0N6G@`CuE;cq!KG z?Jo_qO$Y=ea&d2h4)N061l$896cT#UN^;I*2lKX|!OzK8fqRch*(nf@8zLm{lM$K`S{xX<$V2*s6n+hummCT-ID zf^D`ceqza0g-TC`DiA587?%SL?_#8_{vMzPH9ST+upD+YGdKD5<`X-=A4@O-s#fmr8Dj3vTZ0`^OBCD(XU$T-PiX5S;G z*tbs-J5Z9g`S_1P%#rC3s@e9iLiB?))3CVn~5h$C-J8WL0{*X>cCBwzA>s za4--+l`EP}a-+wm%=$W7kA?7E#*{TVlMhgYy<(ewKyX6f1@x%r$hA+wYi>_CvE-$^ ze7k_DHSV{cYcT_b*NC#4WA?PO|X2=LWove(;yxDHc_(Wk|A_`6B(cgpG$Fy zVvxv?ASm#b!BQQ^%ifg?t3l;Jj`}Ep#v@@QKwD`jD|0o#Bv8=^Zj_o+a(Em9Wyq(@ z76^$!zV;BjdDKT%ldfQ#5IHNXK<==FGfmV4aZSWDiNNu~Vr#H+`DLMeGJ5^sHP~_E z$Qpl>V=N3ORXL8MGVaWOpVu$fXXk{=m!KLo`{K!Hrz!(xEjk^6xUaw?N_oxF3vVh) z&rq?$tRLgo3By?eeY(UGIL6Nn*_Du9GzxGG=*y-t)xD+$cQ_EI8T}jrltkH2$!VU{ zEo7AQ2@fn#7BRqI6+|o!riZ}R9^sIfzLRI1w-Ac7R#%3t^dnChP{qd<@#gm52lZhsmX3IVrTcJQ(QB1|8+2@R5!^Pk*^FV9J^H08s zuE+)zTw^y4)8$RUpz0IZTG~urdMOGfcdCN%s6y0@348rarIxJ={AbJ_MP6Iw>J_ZT z(k#TnBmo8(OOmAX%^*_xR-7L*)pBG(nzxE+J|gyc#p)R)Qzsm?H!Afwb*+rEzM@5E z!hFYvG`4InG|ohs#NWphf(j6#oY>pAQ_&Lq!2d9e5U#Bl&sYi$yJ?!-i4-A+`SI1W zdoc_L4iFB7l$0M|3++oS1ihZf=wCVW*vZIra7WL4c!7o>18gUd3RD7E#Z9OsLL?g+ zeDJIr4R$7sch>iN3N|sUGA&TwKp|SzVi*J}QPtgUuF8n9h(^!=s+S!uT+jU83T3nY zzI*SDkNwp4dbsW};ZAiU+!PYRtbYC&OsHK5(wJt$)&_g!k}t3R^YUA6Tzd(8Kk7^^CXXZsMf_7CS_IdqC;wdO4kT(z!z zl#|8ut+MRJHF#P8%TBy`zSO)d#nBjPB*o{ko%nuH{AgYHrtC$CMw6Jb+?K?3pJpAY zIavHp4k5GHb`Qo7`1VGb#OV2iN37xWoELhs1zHmM6Y@HlSt!L>9pSxoW&AS)9Oq53 z7UK^>5%Nk9kjt&Pr$XdgHL3x~bC-j@C}4B@vS#N-O=57P!8(flMHOmogHf=hEH|ki zVfw*5qtG-}pY(&fgiI5Atm!ti-RQ8Y*h7WvBMqrtM|jP7c`Q_JoEDN`tj*YMQ*yGYQ}86f+frkYIJ=lKGj%>2GwaP zcOADx`lXC79`$D@H@FG-8gHJO;H<0ZO3U5FIJK_=g`*frY4#)=q?ja7#6RRhKU{#9 z!A2`aw-SOqNVnBhtDO9xVOy0GVN9XxG}PAAP7iN^D)yfuIrciBx-<_W+d zkD@_LnOA_6o?Amb{ou6XU5e1GkGYszcu?f48exl>Or@o=X42-@s=tvbL5l0%UrDp5FmLj*3k=~B9*Xdd!xz)}FH04w)A z=Q$^$IEKH}ixuKU?Hm{3xWezD?mSK5J{0T9AH>-|9!|}yfPAKi1%wmkt~P#HzSYocurxUF)*#kfx3wth zf=lXZuO$3<&;4klWty&O{bdMXhAEHaVl*_rgw^J8oUfp|hclMrcC2;* z0CX-c*vX>9k$R*bZ4TiriZb)VtEmL54!9q1pYNIzE3uCdZc5nbx>G-$zwS!x57gSm z>@9ia!TFfqfP817X_Q-&4vM&c7H|oc?E(3yR9QTFgTtn%TQlXQ7R8sPakoh;RmLT? zhk`~*Sbvu1krfOIfR7d^Q#foh+o{ZiFJDj;=x$U)Y%VGR58=jp6cKfh9uQqEpP3;3 z{51!rc%Pr2V`r<3!2|Z$-tqQ%ofmkC=mSj5#n|n=UwPl02alhEFZg+`V;C~ey<-zw zfSSH&;8w-Gf=!#_NbStz5$%}Rk8CTU=WANB$>loBJ3d29UQ(khz0=Q);|uGwyMxZ& z#N4X_axS^gFkWomW>HQ;RwaV&cgEru?u?Jtu24yd7gMH@)UU*d8a#wi+r^B#AX!qh zilS^@6quL>oHD55EwI>xBuP0vxNjzxB$Vljk9bcfl==1b-^_dD5Xr%wxi@SZoN|-8 z&RAJ)NbhuZtZD-e9bI$Bmm7hOpN3zz|Il0Rr>mE^>_Q>b=&m0dJ3#PO)A+N zB-nc_l7*2OXaI(6K45Z%-$8*|(@Gd~NwhkAQ{l8!uXU9_(RU|E?QB21Wuyw_K4Rdh zRh}$eq=36UGdR1qxnQHJ$*gXxTY-NoAYa2h+o840E8p6PYVDh#RdncX=wsdh^Gh;E zk0*XRZ-)kjC)Z81GQ~U8XfS8RtwvQovzwDqNjh#cq^)_*Qzqnl;=t^nq$y}s;iD<` zGO0EJ9AUNMp({zJfs$3ztaM9TY_0AG=B%N>hh{ULHeI=+Yiw5&VX*5{(4CbRS$>+Q z5OH6~uSv}PY-9DIn60mVy)_d1Ht$eK{>;_%v*Wfg@W8P~tkNnHpL&yZu9l`&6kZ;2 zAIvi4#9N5bmHKh2T^3C=TkLd@ae5j(fF~^+TYu?neM7KxVLnc#C1YbYxq<1PgI3!rT+)Mj+TC0X*H3&n53Leb1bRx`oL|lg+4>O?Am(`Sl zjp1;8>WR=s>P29UH1z4kH7Pm~HhhaT>rB~!h_0FSFty2Lc5TUG^k{_4DW$kB> z&${0?d}dPXkKK~OWm&?DP3!eVNUesqx1RBEpK@YR}zy8u~1+n+r>($#WMQa6*ho zm+@;j=8M#60a>J#);1H^dIep#G`_+yZSbFrD!v6ZT@c^&%%vNEH~qxf zqUYd}W(QxOPMo@fdiS&@>zW@06unVTAI)Yf?muLa7sI17V`INd%45DerZu8&p7A{+ zYrL+;BHK!%)*08_Iv>(BX=rO)2>F!Fe2z5>LkpskiQ>iGEN5r;ZnW`3{7QHDWEIgS zxm;>y1y%zDQ>iBO%hp%?{`oaL1#NxxHx#Q>za;1?+_^=eW6VId%Q$v;5nZ$c>B5II zH3p>d`yjYOYpPlQykM{hXBz!G#-_2luNg%m!$!k+;E7OW?I0Q#m-Q(cagEk%{nqwi z0Joc^+KR~3DG^g#5>aS`@|qUb0WPQ9OKTcEmk}n|1B8(YMc_2PDyQ3c;`W&9eR52z zFzP&D#$LRG9m=#u*$fVgBDh$uuRrP!k2Y^jO2&0c;OL*=jRS@3dzvQS2vg=g%MAhMto)M@D+L82{$ESE{r6N(rx6Iz=GZDNM@=nSE9HvZAD9xCY&}7HJ zLsL;c6DDC*XmXtrVE_w-W9v+^)5yemLDL2aU{JpZ^l->_7Wqds21YL5iuH*d2U?!$ zR_4%okV)QNWO!f37|8?{A?|@iI3v33i>y16@~X3m;?6&>N|X3^!@(9zY)&C2E2UTfLOJ2^_-$UaZ}57Nnv zpkoZ_YvDL7fJ=AU%E(FFw|okkTVL<0FKVq!oSl;g1K-GO#=cZ?`&k5A7bXO^;Ah2@ zWK%6Pl4xJka=+D<325YLKDdqr%-j!RT^V{Vf2iPaB4c&OiqP1d*%t@wtnxJH#T_re zGlTssp`CnOIY$FZURK%z7kA>LPL;8o}kTZPb z#QAmv{Cag`9me(y*};TK{>s*TKWN2FuT`0c6d4>TO`}z8BfP2Dk@Ca#wYqZuj`?I` zq<79w(d@<-n}V=A?ubTH=X0Yk6wu^|Ycvw*WRr7YhO2X0YS+v{7^q=Rije0^4-v}) z*G@#@?j*TjneYWKg4i)Q+A8K9DZvDYlzU*7$8~OWoAR%E*Ne-?9})R+HLUT;^Pry}VZ!27V2B zO@_JChx@L2#poW+r%kTc@A1>CqBC&Q2>8I5lno5*xG#QXZ&N0_;$YxpN+W4CYGKe5GrD8<6`UK&r^ zx+HobPoc8KA%#G>fsx>_MtXy0CP zoZ;z7`Z78;}>3=W;?-K}0+P4ZzUFFlLex`&o8J%QK zh&f0sbX3A?xDWwe^SEqbKys=mDCSP2Vr=jjbPa$qq)B1mvh6X8#*!izR9AtY3WM*7 z<`ONYrA`E9dHhWh?ibC|GsiZg#ybZ9^t&P$^1s)toE*(89q1g~92||T-nT!Fsr+lt z|GAEx1&`{mCXLLplSLws%znPe3~?|nim(bsy<@GaWUh$3U3~6OPf>~aupN|_Pe36h zJ8KSf-#IhybvL+dIogvoyZXjRG-by7Rc07|5&N~#xp;WAaVHCF@{{(-*Mgd2N(g-> zoV6gm!hnkLpOBBnuq`0b*~Rj;&^5aZizdzr_W2q}uxI9jhirLy z0ysV~+hkT_NrV(choO03{X%SUo1%jVf1u0RZV^Hv8$w{>pYvRidcM(AFgLDzkiF3O zWOXhJE=(9&o_jojcan$>uQas{`$9^zmKJYs$buPpV92uH2 z##!Na2XRYtt{j0kCmI-CV~5VH?5(XhYu#gOXJdDcr$ACu7)IR>Un~iT%Y{AzKxV@S zN^hT#af=8?wgH6BQI6$2*B-TDzI3(Q9T{rW(*<|Yu4xE(An*jJ?f{D+3i0~KEwj`| zY55K8&XWzjAQZfSwB#p9I=xhEpS(POpmeB$5KNwM036)co~?JG_O3LbRcX-zQKQVL zsvqXHujOfbZ2-Y*TC`Z+ZLYz%d|RXjocQ`Us+ioDMu=On*=8wUR*$x)HAgPq*7kgZ zPrfOmHKOM%z4D!eqn?P_gq1dl#+g6-xYX`8<}bYQ-^G-8KJjpfJ^Q02?T&~|P`JNm zGtKLc&D%KG#X%=ZUoCLuMjKh+zSSf;VjaP~3uP`f$Y~llu{?uGx-8wDSmR6fu_&t$ zH6)=a`m?MC{_Bs%&H09aH7!^mq$+o6-KPD-3^FF6eBUC_vwKv^7k<^_}UJS1Up0 z2bE@Pn6=fuUZ+Fcd=53lyT-_frgTSy_GVG(bz=aPTZR*aN;&Ze-8;vG46-mBQF(r_gkEI}8G11BX*!e};N>(_CoBwzc zh`1cUtAB)~Rh74P&u0F!gr$)V-E(18Sr7Re_cUQb5Q%x&OtP|#dHeTkm756}|6~Pq|ZQH3Vj5 zgW91MMY8n^Jw0Cj(lmj&rtUgB^>ahab%hd!zBB20D_fK{AS0`&Y!iM$D!8RJgCk7( zqVLMfG+DEuCak@0{$GDMuZuixs-9)RZ={Ad@Cm^@A|(`xC1RS=6+EesOgE$5h-R|i zvK7U_>tkUv7UuajG-%-}U4}<{+7NR=i>4enrdQMBhQ{0#BQfrAeejcGa}~HF%6EO% ze0Ce%9b(TT^cZeXk!Ew`=fo5iKtD&u6Yif-Q7uMx2m-+}2d4ZQrvmahnPI3t`ndN9 z{yBbQHf60yT@zkG;ZBBkVQXaL`TX2;In`R=QR+4|lv3^Gq%>cw1%OJ$X*eD@8LAu7<1GEoe%_1*<6K*`CqNB2%pS$82mBUic1I$DJHUO@p7`l%H+_ z>(79b)+(r30?=?qKxmh7!oL>K({xwb#^O4C&{v~C8hJS% zPhE4lTfG6ax}QIxJ)1)lA+7{O=!hSwtiT?LGPiv5@ZQki!b>f`x!tY-GjvXj+UvLX z@W0L%ep)vy97YF9N^9A_E#V8%;&KqTCL&#K86@tcipf~t2QJR{M)hj|`z6X~WUupHd_luN z`|Ds_2`_Ivy+AvY~8?Qi89zubQjX9y>Jcl)bR{~zv+tR3y$ zbZl+RtQ{R_9bFy6WX30`#wWxh=wxVzsmUoK_axpUs#XHTDmRCuxM^zs;j2D`!-@aB zz=RMK0HF9kd|4Se>KW=e>d~4z*jNY3YuO+(AiPo~W~onUOPJyZgA%@F3sFi7sbBe& z&g@%zFkCP7-)mthJzbnGtDqo2uJxEL)a_iIa*ap`LLSTIz}pkH4@sWNX)NT1=wlK% z^GlI#M|?t4IGg_%74V(0?RuUVGDAxhxxBw4{YziMq6LG#F<~l}1;d$nV4*-itV=DZ z-)b?Gv>3$Ao|II^O=HoA(kh#aJ{GLo?7mzLGo=u8M_#mfXjsIyPfgmZw>v6dM_I|# zgNK>*n_&@Gq^OLEyK7>pEw-$7a;z|2Knjnt#evaQni}_i2Ja&FoR<^HDXxhYC@Es> zf;3pPGAs*dIcvv_+2iIx$4w1dWl(D7l9$`abE})7?MJChGLrDb81cu9Fh5a4I2x^> zNo9Co!&jCEaohTf9$`I8tA7?bcNSunn#bTj;u2#*EKOGaNgY!yo>__rJe3v6%e=a? zq8jR(__POBZyl%}=5RTcN^~^^a>l5rOs%9v)&E>BEFk*Ly?KOH5tl5-Ft1`Xe4!hotXndcU+f_~mTC;R(z`m4{ov^@^JPZS4B)%SPFVVOnPhzR+5Htd~~8t zo_>~T(_VH=l174hh^|&%Ok#+dHiQnML~fFPlBsru28c!F-~j(Q%JoJL}7NUBzz zoPtt%4@z9>n>=|j^ZeMzgyfv`XxZ-epP_xy5tjJ=E|yCJ0ss)dhxfm}TBL;)`Gxou z`4irm;%4|@`ZqR;E=Q!JL5J`6iAh_~hV!k|ls^yGgjI-iUUsvubSOx3lo;VO$5M_m$`%~Ph(!H?g_*!1N_4|!n4~*4fEWmV`V!H!s>W{^|=D#;`k4M zva@TqmovBM1zb5F(?u-XE$=Rbl}@3|Bz6*nFRWB+ka##I)V?>44B6>akZ+UDMkjOd zL0i1U_okIjKOAA^>f^?s6O)!KpQw*{3ITOfoi}lf57!9B&NSr@6mhsOVYNZs*=KZp zWrr)l^Kf92j#gx?4Dsb~;~l`kY&I5sv~4m-014kmJ#k1)1P`f2U}JGFPZcMpi0QoZbP+M0 zoP)cE<;c8e4UBH^T>393=Zm%>VHoY6 z{Mb;ZyTy=R-&9`9;=b@xYhe74a6(}WaZ9a34jF<}xA(m?u|~WRO~a|TE(5|K;(YKbIUz=*^$$LcD zafY6h1uKBtY`W`+_pa5o6UM=);)IM8Z_+;^9&VG;jKcgS8^srJ)Q^g3rkF)CMq^Sd!=+;t!YF)P;4$r3cK z_IG5sb@$aaaeBa~pqE}z(6TM>7R{`ATl~+;yyEty(DL6*2nfskurVbMB(4p&{l_u8 zhCHQ)Bgs&sH1nxM;(7PhyOzb`xIuf__`+am-f~?M;-jSt+&Nai`7I{4~^dH1#dSE@nz4w2V+V4uZ!Qu z#xP4Y+%7G03yWjm%;y&-RR^K=tkK=U!TRN@w#xb6xKGs=v$tSre%_POrXvRSw==Ib zg~F6_<04;$>(C{MK}&sIOi@3{ZlMGP2Dv>6=Ma>`$~mmSS>C7G1Z|rmaKpI~x82s8 zppDM|7UR}5F=tq<;Tdax(kVu-hKwOjf3kpBeAZ2wR(tFQo~>1!fSPssNtI2(OZ*mO zHunv$)1zd;+D!-%sq0s)jao_uV z`5BE9)9>A)@V!C*W%>SC@4NMH<9`_c{2lkbe`l|K0OHY0&>|yzy`2-=_Kozgz#42>ajGINbko{jYNC z|9AgyN%r3{{z>HGZw!FyKVtkPdGROEp9JZD1HrzZ(O>VwAHnGS2VMG~aDS3<{ss5u zduj6D;QoV}^LN01;yL>Zu-?EeG#KhnuRVgJkr{)Q#5{oeuq zM|SWh?4Oyz->^>q2kigL6aIwzb2k4s975gyj`FX${hx?`&Jh1bglqWUi2s@|ewGCL Tl|}*3-fzP1&)A!P{q+9= 2: - if path_src[0] != b'/'[0] and path_src[1] == b':'[0]: - pass - else: - raise Exception("Internal error 'path_src' -> %r must be absolute" % path_src) - - path_src = os.path.normpath(path_src) - if os.name != "nt": - path_dst = os.path.relpath(path_src, base_dir_src) - else: - # exception for windows, we need to support mapping between drives - try: - path_dst = os.path.relpath(path_src, base_dir_src) - except ValueError: - # include the absolute path when the file is on a different drive. - path_dst = os.path.relpath( - os.path.join(base_dir_src, b'__' + path_src.replace(b':', b'\\')), - base_dir_src, - ) - - if blendfile_src_dir_fakeroot is None: - # /foo/../bar.png --> /foo/__/bar.png - path_dst = path_dst.replace(b'..', b'__') - path_dst = os.path.normpath(path_dst) - else: - if b'..' in path_dst: - # remap, relative to project root - - # paths - path_dst = os.path.join(blendfile_src_dir_fakeroot, path_dst) - path_dst = os.path.normpath(path_dst) - # if there are paths outside the root still... - # This means they are outside the project directory, We dont support this, - # so name accordingly - if b'..' in path_dst: - # SHOULD NEVER HAPPEN - path_dst = path_dst.replace(b'..', b'__nonproject__') - path_dst = b'_' + path_dst - - # _dbg(b"FINAL A: " + path_dst) - path_dst_final = os.path.join(os.path.relpath(base_dir_src, fp_basedir), path_dst) - path_dst_final = os.path.normpath(path_dst_final) - # _dbg(b"FINAL B: " + path_dst_final) - - return path_dst, path_dst_final - - -def pack( - # store the blendfile relative to this directory, can be: - # os.path.dirname(blendfile_src) - # but in some cases we wan't to use a path higher up. - # base_dir_src, - blendfile_src, blendfile_dst, - mode='ZIP', - # optionally pass in the temp dir - base_dir_dst_temp=None, - paths_remap_relbase=None, - deps_remap=None, paths_remap=None, paths_uuid=None, - # load every libs dep, not just used deps. - all_deps=False, - compress_level=-1, - # yield reports - report=None, - - # The project path, eg: - # /home/me/myproject/mysession/path/to/blend/file.blend - # the path would be: b'path/to/blend' - # - # This is needed so we can choose to store paths - # relative to project or relative to the current file. - # - # When None, map _all_ paths are relative to the current blend. - # converting: '../../bar' --> '__/__/bar' - # so all paths are nested and not moved outside the session path. - blendfile_src_dir_fakeroot=None, - - # Read variations from json files. - use_variations=False, - - # do _everything_ except to write the paths. - # useful if we want to calculate deps to remap but postpone applying them. - readonly=False, - # dict of binary_edits: - # {file: [(ofs, bytes), ...], ...} - # ... where the file is the relative 'packed' location. - binary_edits=None, - - # Filename filter, allow to exclude files from the pack, - # function takes a string returns True if the files should be included. - filename_filter=None, - ): - """ - :param deps_remap: Store path deps_remap info as follows. - {"file.blend": {"path_new": "path_old", ...}, ...} - - :type deps_remap: dict or None - """ - - # Internal details: - # - we copy to a temp path before operating on the blend file - # so we can modify in-place. - # - temp files are only created once, (if we never touched them before), - # this way, for linked libraries - a single blend file may be used - # multiple times, each access will apply new edits on top of the old ones. - # - we track which libs we have touched (using 'lib_visit' arg), - # this means that the same libs wont be touched many times to modify the same data - # also prevents cyclic loops from crashing. - - import os - import sys - - if sys.stdout.isatty(): - from utils.system import colorize - else: - from utils.system import colorize_dummy as colorize - - # in case this is directly from the command line or user-input - blendfile_src = os.path.normpath(os.path.abspath(blendfile_src)) - blendfile_dst = os.path.normpath(os.path.abspath(blendfile_dst)) - - # first check args are OK - # fakeroot _cant_ start with a separator, since we prepend chars to it. - assert((blendfile_src_dir_fakeroot is None) or - (not blendfile_src_dir_fakeroot.startswith(os.sep.encode('ascii')))) - - path_temp_files = set() - path_copy_files = set() - - # path_temp_files --> original-location - path_temp_files_orig = {} - - TEMP_SUFFIX = b'@' - - if report is None: - def report(msg): - return msg - - yield report("%s: %r...\n" % (colorize("\nscanning deps", color='bright_green'), blendfile_src)) - - if TIMEIT: - import time - t = time.time() - - base_dir_src = os.path.dirname(blendfile_src) - base_dir_dst = os.path.dirname(blendfile_dst) - # _dbg(blendfile_src) - # _dbg(blendfile_dst) - - if base_dir_dst_temp is None: - base_dir_dst_temp = base_dir_dst - - if mode == 'ZIP': - base_dir_dst_temp = os.path.join(base_dir_dst_temp, b'__blendfile_temp__') - else: - base_dir_dst_temp = os.path.join(base_dir_dst_temp, b'__blendfile_pack__') - - def temp_remap_cb(filepath, rootdir): - """ - Create temp files in the destination path. - """ - filepath = blendfile_path_walker.utils.compatpath(filepath) - - if use_variations: - if blendfile_levels_dict_curr: - filepath = blendfile_levels_dict_curr.get(filepath, filepath) - - # ... - - # first remap this blend file to the location it will end up (so we can get images relative to _that_) - # TODO(cam) cache the results - fp_basedir_conv = _relpath_remap(os.path.join(rootdir, b'dummy'), base_dir_src, base_dir_src, blendfile_src_dir_fakeroot)[0] - fp_basedir_conv = os.path.join(base_dir_src, os.path.dirname(fp_basedir_conv)) - - # then get the file relative to the new location - filepath_tmp = _relpath_remap(filepath, base_dir_src, fp_basedir_conv, blendfile_src_dir_fakeroot)[0] - filepath_tmp = os.path.normpath(os.path.join(base_dir_dst_temp, filepath_tmp)) + TEMP_SUFFIX - - # only overwrite once (so we can write into a path already containing files) - if filepath_tmp not in path_temp_files: - if mode != 'NONE': - import shutil - os.makedirs(os.path.dirname(filepath_tmp), exist_ok=True) - shutil.copy(filepath, filepath_tmp) - path_temp_files.add(filepath_tmp) - path_temp_files_orig[filepath_tmp] = filepath - if mode != 'NONE': - return filepath_tmp - else: - return filepath - - # ----------------- - # Variation Support - # - # Use a json file to allow recursive-remapping of variations. - # - # file_a.blend - # file_a.json '{"variations": ["tree.blue.blend", ...]}' - # file_a.blend -> file_b.blend - # file_b.blend --> tree.blend - # - # the variation of `file_a.blend` causes `file_b.blend` - # to link in `tree.blue.blend` - - if use_variations: - blendfile_levels = [] - blendfile_levels_dict = [] - blendfile_levels_dict_curr = {} - - def blendfile_levels_rebuild(): - # after changing blend file configurations, - # re-create current variation lookup table - blendfile_levels_dict_curr.clear() - for d in blendfile_levels_dict: - if d is not None: - blendfile_levels_dict_curr.update(d) - - # use variations! - def blendfile_level_cb_enter(filepath): - import json - - filepath_json = os.path.splitext(filepath)[0] + b".json" - if os.path.exists(filepath_json): - with open(filepath_json, encoding='utf-8') as f_handle: - variations = [f.encode("utf-8") for f in json.load(f_handle).get("variations")] - # convert to absolute paths - basepath = os.path.dirname(filepath) - variations = { - # Reverse lookup, from non-variation to variation we specify in this file. - # {"/abs/path/foo.png": "/abs/path/foo.variation.png", ...} - # .. where the input _is_ the variation, - # we just make it absolute and use the non-variation as - # the key to the variation value. - b".".join(f.rsplit(b".", 2)[0::2]): f for f_ in variations - for f in (os.path.normpath(os.path.join(basepath, f_)),) - } - else: - variations = None - - blendfile_levels.append(filepath) - blendfile_levels_dict.append(variations) - - if variations: - blendfile_levels_rebuild() - - def blendfile_level_cb_exit(filepath): - blendfile_levels.pop() - blendfile_levels_dict.pop() - - if blendfile_levels_dict_curr: - blendfile_levels_rebuild() - else: - blendfile_level_cb_enter = blendfile_level_cb_exit = None - blendfile_levels_dict_curr = None - - lib_visit = {} - fp_blend_basename_last = b'' - - for fp, (rootdir, fp_blend_basename) in blendfile_path_walker.FilePath.visit_from_blend( - blendfile_src, - readonly=readonly, - temp_remap_cb=temp_remap_cb, - recursive=True, - recursive_all=all_deps, - lib_visit=lib_visit, - blendfile_level_cb=( - blendfile_level_cb_enter, - blendfile_level_cb_exit, - ) - ): - - # we could pass this in! - fp_blend = os.path.join(fp.basedir, fp_blend_basename) - - if fp_blend_basename_last != fp_blend_basename: - yield report(" %s: %s\n" % (colorize("blend", color='blue'), fp_blend)) - fp_blend_basename_last = fp_blend_basename - - if binary_edits is not None: - # TODO, temp_remap_cb makes paths, this isn't ideal, - # in this case we only want to remap! - if mode == 'NONE': - tmp = temp_remap_cb(fp_blend, base_dir_src) - tmp = os.path.relpath(tmp, base_dir_src) - else: - tmp = temp_remap_cb(fp_blend, base_dir_src) - tmp = os.path.relpath(tmp[:-len(TEMP_SUFFIX)], base_dir_dst_temp) - binary_edits_curr = binary_edits.setdefault(tmp, []) - del tmp - - # assume the path might be relative - path_src_orig = fp.filepath - path_rel = blendfile_path_walker.utils.compatpath(path_src_orig) - path_src = blendfile_path_walker.utils.abspath(path_rel, fp.basedir) - path_src = os.path.normpath(path_src) - - if filename_filter and not filename_filter(path_src): - yield report(" %s: %r\n" % (colorize("exclude", color='yellow'), path_src)) - continue - - # apply variation (if available) - if use_variations: - if blendfile_levels_dict_curr: - path_src_variation = blendfile_levels_dict_curr.get(path_src) - if path_src_variation is not None: - path_src = path_src_variation - path_rel = os.path.join(os.path.dirname(path_rel), os.path.basename(path_src)) - del path_src_variation - - # destination path realtive to the root - # assert(b'..' not in path_src) - assert(b'..' not in base_dir_src) - - # first remap this blend file to the location it will end up (so we can get images relative to _that_) - # TODO(cam) cache the results - fp_basedir_conv = _relpath_remap(fp_blend, base_dir_src, base_dir_src, blendfile_src_dir_fakeroot)[0] - fp_basedir_conv = os.path.join(base_dir_src, os.path.dirname(fp_basedir_conv)) - - # then get the file relative to the new location - path_dst, path_dst_final = _relpath_remap(path_src, base_dir_src, fp_basedir_conv, blendfile_src_dir_fakeroot) - - path_dst = os.path.join(base_dir_dst, path_dst) - - path_dst_final = b'//' + path_dst_final - - # Assign direct or add to edit-list (to apply later) - if not readonly: - fp.filepath = path_dst_final - if binary_edits is not None: - fp.filepath_assign_edits(path_dst_final, binary_edits_curr) - - # add to copy-list - # never copy libs (handled separately) - if not isinstance(fp, blendfile_path_walker.FPElem_block_path) or fp.userdata[0].code != b'LI': - path_copy_files.add((path_src, path_dst)) - - for file_list in ( - blendfile_path_walker.utils.find_sequence_paths(path_src) if fp.is_sequence else (), - fp.files_siblings(), - ): - - _src_dir = os.path.dirname(path_src) - _dst_dir = os.path.dirname(path_dst) - path_copy_files.update( - {(os.path.join(_src_dir, f), os.path.join(_dst_dir, f)) - for f in file_list - }) - del _src_dir, _dst_dir - - if deps_remap is not None: - # this needs to become JSON later... ugh, need to use strings - deps_remap.setdefault( - fp_blend_basename.decode('utf-8'), - {})[path_dst_final.decode('utf-8')] = path_src_orig.decode('utf-8') - - del lib_visit, fp_blend_basename_last - - if TIMEIT: - print(" Time: %.4f\n" % (time.time() - t)) - - yield report(("%s: %d files\n") % - (colorize("\narchiving", color='bright_green'), len(path_copy_files) + 1)) - - # handle deps_remap and file renaming - if deps_remap is not None: - blendfile_src_basename = os.path.basename(blendfile_src).decode('utf-8') - blendfile_dst_basename = os.path.basename(blendfile_dst).decode('utf-8') - - if blendfile_src_basename != blendfile_dst_basename: - if mode == 'FILE': - deps_remap[blendfile_dst_basename] = deps_remap[blendfile_src_basename] - del deps_remap[blendfile_src_basename] - del blendfile_src_basename, blendfile_dst_basename - - # store path mapping {dst: src} - if paths_remap is not None: - - if paths_remap_relbase is not None: - def relbase(fn): - return os.path.relpath(fn, paths_remap_relbase) - else: - def relbase(fn): - return fn - - for src, dst in path_copy_files: - # TODO. relative to project-basepath - paths_remap[os.path.relpath(dst, base_dir_dst).decode('utf-8')] = relbase(src).decode('utf-8') - # main file XXX, should have better way! - paths_remap[os.path.basename(blendfile_src).decode('utf-8')] = relbase(blendfile_src).decode('utf-8') - - # blend libs - for dst in path_temp_files: - src = path_temp_files_orig[dst] - k = os.path.relpath(dst[:-len(TEMP_SUFFIX)], base_dir_dst_temp).decode('utf-8') - paths_remap[k] = relbase(src).decode('utf-8') - del k - - del relbase - - if paths_uuid is not None: - from utils.system import uuid_from_file - - for src, dst in path_copy_files: - # reports are handled again, later on. - if os.path.exists(src): - paths_uuid[os.path.relpath(dst, base_dir_dst).decode('utf-8')] = uuid_from_file(src) - # XXX, better way to store temp target - blendfile_dst_tmp = temp_remap_cb(blendfile_src, base_dir_src) - paths_uuid[os.path.basename(blendfile_src).decode('utf-8')] = uuid_from_file(blendfile_dst_tmp) - - # blend libs - for dst in path_temp_files: - k = os.path.relpath(dst[:-len(TEMP_SUFFIX)], base_dir_dst_temp).decode('utf-8') - if k not in paths_uuid: - if mode == 'NONE': - dst = path_temp_files_orig[dst] - paths_uuid[k] = uuid_from_file(dst) - del k - - del blendfile_dst_tmp - del uuid_from_file - - # -------------------- - # Handle File Copy/Zip - - if mode == 'FILE': - import shutil - blendfile_dst_tmp = temp_remap_cb(blendfile_src, base_dir_src) - - shutil.move(blendfile_dst_tmp, blendfile_dst) - path_temp_files.remove(blendfile_dst_tmp) - - # strip TEMP_SUFFIX - for fn in path_temp_files: - shutil.move(fn, fn[:-len(TEMP_SUFFIX)]) - - for src, dst in path_copy_files: - assert(b'.blend' not in dst) - - # in rare cases a filepath could point to a directory - if (not os.path.exists(src)) or os.path.isdir(src): - yield report(" %s: %r\n" % (colorize("source missing", color='red'), src)) - else: - yield report(" %s: %r -> %r\n" % (colorize("copying", color='blue'), src, dst)) - shutil.copy(src, dst) - - yield report(" %s: %r\n" % (colorize("written", color='green'), blendfile_dst)) - - elif mode == 'ZIP': - import shutil - import zipfile - - # not awesome! - import zlib - assert(compress_level in range(-1, 10)) - _compress_level_orig = zlib.Z_DEFAULT_COMPRESSION - zlib.Z_DEFAULT_COMPRESSION = compress_level - _compress_mode = zipfile.ZIP_STORED if (compress_level == 0) else zipfile.ZIP_DEFLATED - if _compress_mode == zipfile.ZIP_STORED: - def is_compressed_filetype(fn): - return False - else: - from utils.system import is_compressed_filetype - - with zipfile.ZipFile(blendfile_dst.decode('utf-8'), 'w', _compress_mode) as zip_handle: - for fn in path_temp_files: - yield report(" %s: %r -> \n" % (colorize("copying", color='blue'), fn)) - zip_handle.write( - fn.decode('utf-8'), - arcname=os.path.relpath(fn[:-1], base_dir_dst_temp).decode('utf-8'), - ) - os.remove(fn) - - shutil.rmtree(base_dir_dst_temp) - - for src, dst in path_copy_files: - assert(not dst.endswith(b'.blend')) - - # in rare cases a filepath could point to a directory - if (not os.path.exists(src)) or os.path.isdir(src): - yield report(" %s: %r\n" % (colorize("source missing", color='red'), src)) - else: - yield report(" %s: %r -> \n" % (colorize("copying", color='blue'), src)) - zip_handle.write( - src.decode('utf-8'), - arcname=os.path.relpath(dst, base_dir_dst).decode('utf-8'), - compress_type=zipfile.ZIP_STORED if is_compressed_filetype(dst) else _compress_mode, - ) - - zlib.Z_DEFAULT_COMPRESSION = _compress_level_orig - del _compress_level_orig, _compress_mode - - yield report(" %s: %r\n" % (colorize("written", color='green'), blendfile_dst)) - elif mode == 'NONE': - pass - else: - raise Exception("%s not a known mode" % mode) - - -def create_argparse(): - import os - import argparse - - usage_text = ( - "Run this script to extract blend-files(s) to a destination path: " + - os.path.basename(__file__) + - " --input=FILE --output=FILE [options]") - - parser = argparse.ArgumentParser(description=usage_text) - - # for main_render() only, but validate args. - parser.add_argument( - "-i", "--input", dest="path_src", metavar='FILE', required=True, - help="Input blend file", - ) - parser.add_argument( - "-o", "--output", dest="path_dst", metavar='DIR', required=True, - help="Output file", - ) - parser.add_argument( - "-m", "--mode", dest="mode", metavar='MODE', required=False, - choices=('FILE', 'ZIP'), default='ZIP', - help="Type of archive to write into", - ) - parser.add_argument( - "-q", "--quiet", dest="use_quiet", action='store_true', required=False, - help="Suppress status output", - ) - parser.add_argument( - "-t", "--temp", dest="temp_path", metavar='DIR', required=False, - help="Override the default temp directory", - ) - - return parser - - -def main(): - import sys - - parser = create_argparse() - args = parser.parse_args(sys.argv[1:]) - - if args.use_quiet: - def report(msg): - pass - else: - def report(msg): - sys.stdout.write(msg) - sys.stdout.flush() - - for msg in pack( - args.path_src.encode('utf-8'), - args.path_dst.encode('utf-8'), - mode=args.mode, - base_dir_dst_temp=( - args.temp_path.encode('utf-8') - if args.temp_path else None), - ): - report(msg) - - -if __name__ == "__main__": - main()