From f280aa3a13e72fe9856a2ddd048a48b10eeedb38 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 15 Aug 2021 18:08:12 +1000 Subject: [PATCH 01/72] Demo library now includes a window type. Neat. --- src/blenderbim/demo-library.blend | Bin 0 -> 865972 bytes src/blenderbim/generate_demo_library.py | 66 ++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/blenderbim/demo-library.blend diff --git a/src/blenderbim/demo-library.blend b/src/blenderbim/demo-library.blend new file mode 100644 index 0000000000000000000000000000000000000000..3660fbfc0fa46fb1e9f7149c9fb9dc9839c9638c GIT binary patch literal 865972 zcmeEv2Vfn=wf0qC%aW{KELp{>?s6}H)m#MF*x1IH>MCP_Z5ek+z@iI7sG)`g0ts=* zOMrwB@*uXc2`CT}QYZ<@3+c}LlRQF5FjfEgX6C!vyV|`?a*ftr&Cc$eGiT0s=1key zxs7vXo;G9Vf>B$?O&z~LE}MOF47%+3k9Xk0=Zi7t2NEt^*|NR`H={fj&0M(15{?}; z=l<|{8~=9Uhx2GY5)u+(h7B85K62#9s>H;^I5$}f3k$Qxj2SaLK0Y35>pvVCC@Lx{ z%*@P8cHBPdqn2gG*VfjK7&>%lQCwVHP;AS~%ZqDjYK9*5g*pNP78DfZ%8`BqvW_~- zL4yVjm@r|&*ujGbXNJQB_AzSIsOsY4;=FLu|0ku%$;nBF4IC?F7Fjcaz1ToRJ}W6H zDQ?`jaihi7Q;(Ih9u7G(GBT2-4JbMsdG#ib*w|Pfd_8>rfB^#%qT!XCoSY=KGfu+9 zXy}henv|53_)gwvD7#H+4 z-(!)V^o=WzHo2usl0AO>_%W%esn(&84f6q*7m$3i4h8>XiRb9iqXY1_;^Jeb<_$UV z>WPhCIo#!dPp+t_C=r>4VT^w$^9O&}*s(bO(U*E*{m)Dqki2l{u=8#?VfObfpHTPt zNx6mN`!b(UywE4ZIK8s6vh;Ax*QKSUSr|`7JEjO#reopxiB-c!UpR77`|T&5{9NmZ z37@J;&ni6Ja$`(}e!29`4*Oh$%qgT}T-n>P#o^XjZ}Ll!@wC+MN$nHrzj)KMSx=u{ zR&y5We{Xn3ON%iX>R@(uc1pB7dP`SURyKI}@Zm>gKe)GLv(u+1TGnZ$)o0u^b=G&U zoY?Tt)V!jpl~VZBl$5CERP4N?xyD#b>iwvrVNM44!w#Y;$I(txo|cifdf2$_@bT+M zO}=V)MotN3mUm=@mdbFnGpXs3 zy1^y&$?*xElU=|bHelz$>sX;0(~vc5`PDVwuRCM#hUC=vBtDBKgkCY`!h88>=nn^t zj0XpbjJ1b){MpxKDNY%bbzarz4Y!?m^8QU@rtKV+l@~Cs_sUOv{{XZBUe9qqEh{nY z)YT=|p5I>mOx3tS0qv#Nir;_NLsm+4;k_k3I4BTTf_y z_N>ZL7ZeUmi>UoF?|}UMfnc&!Pe>nsetXq3r?iw@o0^%>qb>2GMZZeg*5Sv}I2-K~ z`Y2vyI+FJX#wWzjEE+nyW6F%jJ0{O~bVkw8St$vLQO%d(9T4Wj!!6Iiw0NuGtb&Wt zCXA`inh}hSYqaq04*jcOX^*yN=$oK_dL-r~ymT=*Ic3mU6{A{jJK^MKw~amFR@8m3 zw2|DGcR&%p11zdesW|J>%5N;(F!bK6LaRsrLE6s*%&{NKV=9bI@cvQyNMZZBNc0|V z+EJOgm7B**y%BA@wDD~vsp);%f0B9EsOCPB2E@fpnVWa+ifd{*C!d;kRzi{;WmyAK7MBlSA^o{MpBjJC z-5A5i$HoR-J}C3Btyhhv{kQ7?^#Zz)eq?D@escPp3raf9+fn`B z6=MgDv8OxMuTeg%L+q<^BuO80N?y^lt0p#l`7_gIJtcGVi!zd|!|pfH|Hn8WnsUjQ zE>V2rNYoEIea)owi5M%Myt4Qz87m&KcRO~OdV7ue0GSu4>FxZLU_9XTvSDZ6JZ<(f zJ0{lOKRLH>Vq%Ez7N8)`8~uqr55_!TBs_);8ImLQ zegyi*q3DLxq*R{$smjMrZ!5bivmhxuRQjX+Ir_G+dyH{I$try}-^8536Lw6fyZ@$X zv!7ltbl5r4$2+R#KxJ+=+v^>WvwT>iC2MqwWp1Q0l6QAW$#HR0Ps=+WW5r3Q}Zvra;2xI;~h}YwGYxiNJ2laFV@V)#mB~sZOT3cbI8*d6y|{eDioE2MBRXdoiJtHd2z@{=8tLDq zq0fUg`Ce(Fxj!g7F@4UOlIzdAyy~g)+CihD;c+BsQWFvrP99P*XXlie-@?20`oX0Q z0~6f8ML&{qhRPdbzpAP#S3f0ok%#pfhw|Osu*n&d@ou?!Me&Z*jD$Y5u~21?=DGCa zE6*NQz52EjoA=4P_8UfL&S1w2FtNR%5ICBj;4i zA)mOQ-~vk=a@2+NAe3(&V;2r9TlU|pPk8kQmyZ2+$GJ71oIKSXcj8~F@qfpOKe9j{ z0N)hJ{PKWK`a;&e=7BznjOSBj zF1JL+cU9=SVs7Vf^!0mFmSIyerr^Ee>}5kfosyp5`rS$>{_{o*p1*U}q%XdHx%Tl5 zE=|FZa>d(0}Czf|vi&}PYgTKUd*Ku7CX2Dmq!TeM~A71eu2o{)KBD7^7JF*Yvd^kF6E zKfPw!&t44T6FdFSE`#J?Er|G$Fak9_f-40D8s zYTXF(@H&tu*5V1yg}__n7%J~Avcxy_SSy^DmzO2&Rhpfb&{WI(cB$;Qb=cP=*yZrL zma$@uj1?c9x3=t-K{<)8`@s!KO)b7+)~MTldF6z^{oJJ=4@`l1&G6w})27#T&73pm zO{wcQ3f|`vcZPQSWxAC3hnyS1c;IlYgOzuZu`*wqf%zO@j`4{2`IJy&Qo%kIHsI7X zzSjUR^b7Ew2=D)nrUPTeY4h@zp?^4GcJ4gczT!-guW5Kj_V6njCfxJJj>)PY@9N_# zbX`BGsp&&!T_@)D&;$P-b;+q8=pOR|;(Ifk&-x-R@1v8%&*Tf<=(|Tir z?|>|{0bcT8eF$t=+B3D6;?dSN!@UoCLVd-1c+_$D7u3z( z^fT}dNY-!#{SFNBArG+=d>@5;i(z}=%6+KP$v2OQr^@>1)niBh=(jgi|Nf2Zsy>#o zIL7Uj=)-q~Ti5Nl^IGutj{(46+W&t``yV!*ST}+-Gq8n7b%DN{=sg?t8@^b^Y6YmX zeKkKTxW>rdJD%?Vo${h@A#Fnn<_#bd?D25-aZ2Qy$7R)H+T@+<)AC*W74lts)unTC zo?m?85I;^MMvv*5I(>TA%(-(t+jZg_DwKZ02lN5O#Y4LOcyrCcytMd;e7f|%2B9tR zS~o&Hqn)+&-73)rOZ%KDN&s&e!X@HcP&F ztUcjoSu^`>Su4M+U|_1h-zszR&o7={rRsjo@L^rEPMi0zy$>G><6zILYHI#L{7cJ9 zyI#Gv>b<66DG|T7#yeFvT?1S8-B8D+y)8ojtFP*~mp*x4>T*>0liuDoNZTY`@6@o` zM$!ih@qMFw^EmU`NsYT?z4U|ig=O{fuDypJl{)!9<3{MYyV~*Nx&+S^!7z>FnT!$s zLi`avy|v=~^QNVr7EErpXYi94S4kU^EByi0cN4rTFisR7pCfwf^Epm8Ih^-;i$D3H zd>C^Gzo9bMFcjC(whe{Jsq)R^$QIeB=*f%6Oua_lwO2A9xA2|2hX0co8T^+;f^o34 z!$*#Mi@Xuuw{qx*Z8Ni0yUF2o4}KrxZ1mY=&A=#pH-R|j+`VWH*Zsc2-!8A%a+35b z&^A=cxFjpwa*oW*E#EkL>NVoypIA~ca@CNOLE*Hsv4a1*#K_>^V;mn0<6zH5j2`_9 z@|;?fVLzp>R`UbkzEouuqJ(oe$gRg_B`nTL9hH8nCO7$(n;cpn;aOPh4a-mzi4 zDD%tNXxF5#Pzaqkb%k#pWxtY>uAEr^z}1r)9-3KHJ~K5jsYf~|9pgnA@#zO{9riC^ zgZ6pi)F~eeo_&V@sM^|}IPm|-4S)3A&~D>fA^3Ii^(E4-r@QstFz1Rf_0ce%Mm@zi zfciWXA^I{HD`33|`m|F1fw=<)rpVrt=iD%L=A&C`r*({t^1JrNVFO3Jc5Tgjz-Y*j zqOK_?o%CU_`W)%A(c{Mb(2oCAi%UKx{ zSnRJv%7J->L)!nuH;?$xBuv zc^scpFk1E{+j;e*#`{k#shX1^qn&VMaHbJ`#GAIET=LIz>qC>~j`IqxZ9h}5(=_-$ z6)DES(vO`q=@~ozSBn3a`u}O>-&djZj|ZYpLBDM0%epP)%K^@4x5ZBn4pq19JiM-@ z-NhI!4MFvO8T-@7_b{_%e(pZO{`!fzgU2Sx+B&cNBDs%pOBq$$ z0KWixbkaY)x%8p7xq9wzh`gVXdEZBa;Th?(ag!$fw+sF|oO1LBPXtxw*A2iM@(!RkyCI;ziUUk1l^#Y0Gps6 zvd{gC{{#O26)DES(vO=w`6)a8TjmVuy6xm;ePm?`J)9`K>eGcFw{fX4R=o`DqiF+6)NZXJrzOoE$ z#2;=N=AZkQ&sov_?+S)zq|e4rp8TX8|J6-{yPn=s_GK^mLftoYdl1@FvE^dv^A?HS zW~0vyy}=$tmnG8wIc(pO3T_I2uRcHC!!#9^O_%Z8t>WXao0eZRUcPJZF>ztuS7(a+ z*hjPIP|k5fSE8dqf4igBKlg9&|8k@l2TMPG%9JPU_|F-i-{Tt~FU%pM8E>n08+yU` z%GB*1@3F`SV*;`7p<*k2_AZUQ8A={8@TuZUGo(!~lQw3s*g?Qt#9(>HDsyy8WG{nz zE*L(3?XZmOu;+?VPo-a(jOHjJ@&b>*KJ@8e~9%P&~v0d5!k~9dOJsq`9-wXsEabslM9SR zZ-d1S^92(z&kVGy{xx8jr^RmtNZWq#6%*=jpI25ruOKx80#PBH$IHHVpK(}6QW zv@e+BfqlBoP0Kjn-~Si?eznZiTsgmbRAW_1X&%OWk=kxBIc1C+2OYr2aMZM2gK%bfwSndK;}{Na8LY=C4M99F__Th z664{S-M$Y$}P4K3x9+9Ec^?xcP!qE;9AO42-|?odhuB_ zA+70$?Hd1*($cO`W5=TYuZk4oVCg4Lo%#(Y{;~$p{|%6QqgXh9UD?jLt)*AXp2)8A zC|>f1x(~l67{tT3NFB`=JpqRTT%(SouEV#6;uGpQ`tMQ)gW3z~9c&KknIQ+-3}A&d z$6o!$Q2D}+&_99=i!UgI9iqO&FMv1tlfii4-Ou9}>$!jUW%1Q`_qQ|{o{>Hio(g}F zs|zRc5&Q$b0ZNoTkY#V1nM<};esRL=oVn5beh%t_>5rwOz5{3I8FMbEBd8PN52Kn- z3#LOn<2(}j3gI5W4)O|4nIdO_=tf*cUdS;kd_BhRpg|h|dqV%jo-ce6+8x+A$_-m^ z^8;q?U%mSQE|X3;;h&M>8!Elj|3`_v4S&~ffU*j#4B7i~>*?#uZY(Hw|8^NVhF?Y7 zCT&^{>k{e!aDYx>3wC|rTJFIQ3_<987D*g+6+Q#`pskQTVK!utvgL{lJ@#}Dmp9rQ z_#-J(HhhEFa4z~Bz#DUif)D1l3V7eGjBa_s&cGLRVheazoeBLRt>C|vbN|3!*8Cm_ zg=eJCgW%r-Bg!w|D~>;7bNO8-%+EVJD>pSo+A+Le&x2kNiam>JvIdGx$J_fer1uKp ze^DQ#et70hTId();X6zpA_o|Y9&1Nw@}#$aFez6qY=9(;_X$rC+h!tS6umMxMHZN#H()pu>2r`LT0m-^=BpM=6Q z(&ujYzkTP(e@UZ+x||EFQ6~6q=*M1Wl&5rLT7~RubA97EMXRIvb{RMeesSP!{B@St zz8b@$o+4lPM_?GOF5#nvxBnfm69>ouJA;paT}ipKVQZ2%+AxeYb7X8DHlE-o@+QJw zphMUUY%N${QtA)-8y3hUco-mrKl*%>ReZjx1ClNxUq}B5Y^BUO;0N9) zv-s02(Z6bwBySNpO}5R}>+9aQw(`Z<6E*%qi?RPRq413Kc>w-0zgQ@145Il42>L6o z&L}wbf|BjCmk((xs!Yulv&27T0!LsL%r`++*goXh_%3uc>-(#)ym_^S_WuaP#|X=pDL7nQ+2K=Hw4blYP-oT(F_+ z`Y{dJbqPsv5!DssfdlVt#a=A5Kd>9rJ?Ihw`}973eK9 zKWWg6bBkI|Z7JTITQVSEeh<%KM`oO+`sRWs#%c-^JHBFL@W~%NLp>Ed(GP&VrHf6Y|1l6{b*tmBJJ^8e1U8`Lhd)7iBv1IF zjBtE}y)3Xl$#c-#caHuS+Gh9wX<$R?at^|^V3G#xfWMSu5c&iNaUwnJ0JsM89k3zb3qFD={9QWenHNGl zL;umOZo_7f2hs=~Y$`*_uEs6)zO+3b;h8>bLivbaTsPu9_RWC5V0l|8JR^NB{{IPk z{Vy#m>$+l5G1h?jzX5{YM3)$sWy8)nKF58O4ZaIL5V}B}fsRlHqyJ=?H%dL>^nwNQ z8#-HLf0K~kV*(H82H1i(>W}Cl7rLYFQJ0A=WPlHK(?9J~_I4QZ`_x>_%isfn4w_updL~w<*{fWVh!<84yB7sY{8_6Xr{#Tx$F&W1f86lQM}Z zB*iY8Fi6e)6aTWILxKM_k>YF8pEqO9oYuNovvy3YufJQ~`8+mu!o=s~+t0t-vB&k$ z@^U}^z=8e~J`p-(I{?`Q=M0fI8Tvq(gXte-FBz6va&k*?+li+a%+JhC83_G>uhc80 ze_((-M5jHlg)Fcuq3Q8m8FUS-AS-PK_6oj&sX9fs=o4bDDg}K!*bHbO7Y~#{=+F+s zkHCk6R@sHvTMFa_otwXa4EFW|$jJbG8K{ltm2GcJ8C)dxiVzvir}%TU)*#~>?wt-ahL zZ-ObKe_Si&fDV*TKs`XYiGeA5YQd1~wA#k(dim|;i$_e!9Y!4x1AFMim3;y5l)O`b zt%Rw-6mkL|_-e=sY@t{3gDjF)4E!vfOC3~gf@!1io)PUF`mkxJpQwYRMF={S8RbHL z$QLq*t@`Z=&k(0v@D;cxHlzFub%9XiQT->oPLU5}hJ05nD%~z~e+qw*v1{^4Cw(mA z{}oNAo_dkg<91b#W$tdQU@YHo{#w4{e^>hPpU_?c!|wjQ6LZFaJx}1jV?psHn1g~$ zIEV%8A7w&+fMrF!G=2f?1IrAbEXC=@Ky%W(N~$w+WxuoY>lP0_Bd2Hp+KPdwW6&v1 z(UGdF@Gr=ZxJv#Q%cZjof?U7`b(gZi4!}?3gZ-%!xI$j=M!f{yxCYiJr{UxGA;3rE zQM3pp-2hV$FyEhuIAjzasmO<=gE2Xu%w4sy0HD83YP z|G-i1`{!o@aqXVBalaoLci>Olby(H)#KmP@8|GA<3%SrH;e<@Y0(yqN?d3v0N6L%u z{-NiA&=JZHJt7WW;e;-MjpCIx&>B7^qqccv@tTn*!s$wg-%Xy|e>rZi2F+z93)d?CLWsE+_u(*hY^%_L9JU zB5iZ4y6|lQ-Z6Y`bvo9JC(fwa(pgy<{8 z;yOJeEphmi+^QMp6`eb#K6^sm;7nqwLf|2Kh8`7Gfq^w~|ZpRJMkDl6HXc z1t?#U-0K!MhLdm!1habEib$F+9g9jz5bN4vsTS0 zpW8IH4Et56{xI@~tr>kPUuW`&i>R2_gmM6XI0C?`(hfwbTy>0!U{m%x?wpmd4y zfgj3>6Evttz!P;z_+t)D>9wRHFJsEw{QCMOgBKLnq!tvH6svhBV8&DM!kk*B=n?Y? zXgkw|uWDO>3uJ}NzzTdNKP5Y4lxOOm$fWKYxzJ`~?16rPnvXX&qUNvBFNDuVTJQpI zlnMEepXgE9jx9eW2g*sEffo4!Yv@VjOTaVePtH`xhd9b|@5-Uq%i6!6{6p$1>MYJT zJF0*3;*RRw-&kMy*^aX-F28JH*}99GOU^ibO40aXWd+%?;59i<>PfE7Q`J4#8swDx zfJr?37Gwkt$P*#bAVl7>C zl0Kg0LcX}q@*u=1WkCI+Z7P{?UvvZdM6nBCtkP0G@BxnGiBRIIj!6AL`RPxU4kQlW zs^&UH2k6_VIWn||@Nwj0)eq1i59k~820tY)#-{aDpDPBShTbsdQ}kD?I^Eb1GjW za;ou$!4B_ll%IuN8+jqKDhFgodZDM>kV)lf^7prilBcq9m7V?LjP`j!80c}Be8lyR@t=q`3;q0vxa154f6A4p5RR^jLeEBvtd~r?r zkgvgB@#HhZBfu{3Ozx|4kq+tXG=`VT$Nr48e2zF|CYH`N27M6IMk-7Z$2>9GPSfU~ z--B_Gzdk}Xw>*IZPCP>h8E{Q~QGSFbt$I!xN$2-7OamU$hTxkF#ngl!eL!}craa1bK-YFT zDFgZ1Lpy)a!CqlwIFT2006ig3qk9!nkJKffAstSk!`LbrC$NAX?Yb1ZP<{z=Qr4`z zl%$$T>7}yHWJK9`wWn*N_3v zAQ#et1}Er^9-w!1O4(HZ9rx{JgN+EivJt}{^950##mO3T_H7$Zm9^`V3Mo)o+ndwR4E$Ip~e;snDgYs*Eys5+ieAF__zT zJMoSJ3H0vpD`+QBPgQ#*x`)44{)d>^>M`n#sx!hnN%FurQq5P1 zj?`Qf=5}ymz7p@oFb|2-O;+B6%$VO39`M;pZ=x4}e;RcLdXi@tV=6mFh|{h!Jg2_x zI!F2KCwL)_kSEd`925_fPwd-YMwT-*GcloLcuLOjX&GgMho+^7uGM^+DKq9MmA!~8 z{=SOHr20a*XXHm2FpsHh$dp4pgB?&lyUfsm#Fb7cyWMW&+E0hHUMF~v&d7rK8dZmY zlihycdmuY_BTw)&_Mz+uw!{;*CC~hQ9j3R-fpUQtPLvZT@IzgpE!jhn3p(=ajCul{ zojtJlf&2s5(f-O1nqR>inM`9sH~<>cfkyHn+1`Dj

Vz1sNu%MTvH(~li=y{>&~b@d7{U(S8qUB@S$qUL~&Ti3xj@mu0l)XTP@R zol_v+V^yW3iVbAv6{ljZOz8o-rz}B2)jt)TsD1|O8c*DF^1wak=}g1CAcy^#osMZt z=q9Jzy#O3R%d*--d!8=&uuKf8E7VJW+3a+7-lj~ha@c90L-MoT_vdN5*MnE6XW%Po z0?*vEgxtcEq{51nl!EdU87HPIzUUhrs#APoM7)t6KHlS0@5bbrSE%R?RauTTzG><5 z?%d@}&q|KZEU*%DhYUzc&+h(~JXTf{CnWk}`p^F?9dHd&f7q{qehu_%pkD+18tB(R zzXtj>(651h4fJcEUjzLb=+{8M2KqJ7uYrCI^lPAB1N|E4*Fe7p`Zdt6fqo72YoK2P z{Tk@kK)(k1HPEktehu_%pkD+18tB(RzXtj>(651h4fJcEUjzLb=+{8M2KqJ7uYrCI z^lPAB1N|E4*Fe7p`Zdt6fqo72YoK2PM^*!~=ge!Ik6*SOblLMC@9_DC$uC^sM=nZB z$JJ*0hWdZ+qIu{~UwK}hTfe&eX9$~rUGy?S|B>!fzYp?K>vxvn-joTeme)>PK4ry} z+DZO2{TKZj=-0r1qz2BM>+{_rzv6D4pBMXr_>D&K7JlavhDOeuduHqUxy#pwku+!$ zw>)nMLQ!8`{0qCEvp)HO_JbZFf0VKL^0J>Oe|duromp5#uYQ2%Pi@_)(v?aAIM9F} zS#lDE5$|~DJ2B9&^<^D0-u$J&`*;7lSmo;st<}#cdSlf~c9*TauKU`5g?n=1SvzP>;>mk%zR6F0h%Uq>`;uQTKhfvAbO!RD z(XgmtzFg4HHF@~yy-U)RAMbeacLDx_{X>eoxV2?l>73=4v~1*^$inR_mZ|>cf&7ur z!(TT4YlI39=IQvFcdpj`(K|M)u;a5jWPg=9ZBHtRkBNyBzok8NjCru;$>##(r*sGM zf2I60!)D_LJ6~I=^ZV<0-&gXOkaP??|M@r7PB6~Uy8Nq(FZ5h0H(9=yjGjA&PEvk( zS8lt?hja`eL`R&l;OS&CNfpknqI;JB(#&?)s@kD+#a+CI5Wbm6(q4ml1)T7v!ne6IOal(+F-x(0nVdW}t36YWDxuwQY0q>s-EBwdKzHq2k1?!{9{RnvJaxPsOMG$4#1_!L9vgf$fN+oNXlAET%_De9`O4%|+Xyb5m_gk@0V)ZRs#= z3q$L9?cW*N`3AdYnc22jhCkbuY|#o%9Q0Al!)faQ`7FD|*!HdleV?|@ctGkN5xXw5 zjo;ni4?S!>$7n2A+Y-Rnkt_7g*_iRB?};SV5TpC=pRd|=%Y+W{h@;IfXdAaW!?X38 zRnhw>*g9?Hpj;F8U_y%ts^iRsq5~o5tkL7CABY}*m(=7x+fk){c!#ex{~8j3fMvJrd%& z*^KKM9x!+^3_%|7*s{flC;58fxo(cPh+q#y`8!st6 zt!-7yCYvfEx}-dIqMPiI5Z4ECbp2$Qf2tloF$_T-@OXeNMm)*a3(t<4zOA2>I~1Oj zi~1v;oWF{^e##_a+m_9~3!<-^P#@*7snU0|J>tCn$rpZ%c|0DwO#SqS2G3n4WIBe{ zgL=NP^L8EbzB44A_<{FH{}IvV6p6`^+Blc?rdI_}!0 z`fHHCUGB471Ld07$ff^4o4#$`ybUcImv3&{IDccCTy1WR-XAu;nWxXb!U0B?eAP^ zcGsda)vJSgvNO5G9tm+>W7=Pa&BjkN3_-3QQwD~(3422Sn0&qbbw_#Mw!f4+l)t82 zl$m&1rP1QKpk@84mW=?kH%&0*QHnKxZnZ~3Tz42e8E!XtG7Lc;@YuXkQTK=^`Fi46 z+_!jA?ofDAF6xhXHkU<<=fag6TQ{iDPPAhkD30>j6?>aK65_hE^#^Kho8cPUMBYDMxN^(dLxbn-HobR^Zn@g!d_Jgu_6#glS}!jp12@$4KOHJ)phuj<`g1oTFEaLwbhya*%Q zwMN%ZhEoil48vgIPa>~V{0H$QUoSj6OZpa1${h+%%0>OLes&Iv7SBa3+c$67*it%w zdF%S<=gBCKUy*WghdmPF+A{T%;TnS{!w}@^ak@!lf3-q9$=3@{>plG*xv$2rlsgoj zl#BW!p4NzH@jPR3>C9Dnx-iPQLa34Q7?rBvPJ1N8^_pxwzr^rvgD1lf+hr`yd=6Lu`r@#cHXeKW7*#)J3l^ZC3NDg^GOl8AC}UD81#S5JEVCXIAVN4lE1 zMqUSfqr8iPu1Lppq+4R>$gex!?6ngmPZ>J$Bb_f1a>n@O zWIECvFm&YCO^5ut>6ngm`KM{UkY6`lUH~1_k*?X$kss+AlR($4Po^W?c0)&gr1P0? zc&JaNBi$}TM}B^~M11?xT|TBG-5Z9E{QPvjG3t`}F&*hFWAEhWr%MD~cYaJqx+#W^ z{76?8r`KgLKc*wy8be2Z-EkreDeu<$cU)rA+J=2q({TK43++OrdPkPEnzO+B$ z$mh`CQxai6iEEgwd$s*B9qA4jJIFV7M8CjvG17@@l!-#DTFR9c7Hc>gV`AgbIJm9J zvA(`cuct9#=L=?i>`N|dRhdbU5amHjL_S=f)bRs1ei==g)hi7;{GuZsBqMp$pjtei zuK>x~uMt{V`^vD#`lJ+D``Re09&x(s0&J+ik7Y6k`g0zdnk$r?PmkR=aU)K|t=ZTC z@zf3FSe)Lt72Euk8*Z9f=X*T;1#SnWK=i-Ak!z#gpvAP$ zVFgWi#2&^;9m98Nfk)T67~cbdw*q3&2iifldu#_gO*_c;k?kMbbFY3i{dm6E@-uSc zJKx5bFDtp++QE)4{T(zz>t)?ub^ge$-3k!hHNtvHKCG9n{Mv77(oFiG9rR5WUeXTw zL+7^yk+p*liQb?;=W)EZgT#rrvHZkS#oM11UDxd6GoKIrmx22c|C^kj$SHo%_jx&S zu)o4}ag6WOj~#zCZug4r@ePR2mm=+8W6TZe^!BHAyMIuqoCi1@j|b{oy*M4#-?yZY zP9Nk}ZWk4~B|G}=ZE7jiZ&3S2v}L|Q(AOZvEob%ua;0CUAEsaK_?#Y}&|hYoo&FpAweshtKcGr(}CdoQRwCb7K$2E|jG0GiGRs*`8XCc<}PTK5?jV<+9s^ zR@zgBuH(wm*ui4O!N!%~2fkkIsiqDbSBB%i!1riAh#PStZk?}c+=!m{O@$2R0ZRWvDqnIz4IL9Ykmvz8hacW#?8=v?Rh2vjhaC*0=lm-6Etv%I;MaS%J@SQm z(ARzBtN%;p>^-qVtM^FSJxgD+>Th`Xgt}ku98xDT)Jgfquk095zoqRDyG4fG2~U3S znX5jXHU092u?-?aLwicw^!M+%Z2GN5pL*hzAC-OcuhWxv7tW8J{&@TzCGU5Vvi5CV zmfToA^!aE1^xFMAzn}j3iQff}>7V)QozHZBEw28J=0EHf9*QTJlzeS% zgYanZGyD&^xcbR&C_USY8}2>WkDV^^>-M@QU!Gl;eA|1wS3h~i?hTjz*KRuoNikoV zX6Jd~-yUzkc*K6dPn|n`a@~~werxyNUi$3rxWE4IZbuT89QC(G77SWuobsmJbxUXG zjqC0-&a##^$bMSWV@;c>)7n>n+c}Yzq5WC(*WI$|+~a+gm95w{Hv${(vlWnoY)BOs zmyn+B!|LX4d1B2fS?m#Z?>z<6F{6<&R(em;PWN_gT|oZFVB|l&{I#o=udH1myX&@0 zn6RpL)s#t-#;>fMFm>gsRV`C%SBzWPvTAb6>WLGlE*-mcVr}hqIY(c(;euljXm64I zd+tg7u=zYye{6BZz{K(F@a?--DjN~h*QNVj{^1W#5-DslX*qOVuU9fbN=b+IlX0Xs zC(`#k;6E|{&v_;PJkvwwdsOotfa@4I-^6tV&UFpQp-cR6t%POb$WpLrQ{p?g?LN;G z^P=7d)r8HzF!Kuk6WzQJ`{Crnez^6f3%|V=I_4+#I^s%f&p#+94mDq~_eXlZf}!%k zTIbz+3HcIbt^%j4E)+97LwhE=gZ_FPiaKz^_O@a>7sg#dr{ZuuiS9cH7#(y;z*aJFY+gD#EH0(H}RBRnxFOg zuC&!R#&vxgBl?>nCthH%K24T54*gO?-iVqwpX1qET{}+h>e{hG_hp+KPr*&8AhP|x zES;K?fZBq?3%5=@6TV*ct(${lbKLv^Zfaj$(_Su>Ho~o$a26QN@Ud_>_x>sf|#C3!Ad| zVs*cWVe?z2U-U2h@K7T48yyK-!}@#`;E_h+BuLzpnM}2o%VxePzEWIBv&rzsTT3+z1Wv^zIkwcLD2V zedRVe*#`psR*B=_J;wE2{Wo~OXj>xNCokN{D>!aQB@=+h(ZPNZ+spobQDoqyNk|iR(r=Z^ZOvVutWF_H{|WiNm@i_cp~!HtUj1*!*YH z4*uPxAHz(7g!I$o!}@OhB|3gVvg*~NQ1v}H^W%425^*AKo&Rg>!Pte;OM7cze|GP- zz<6-w`Ti$PCkTIC(r@HzQk?EK3~sc0uXRb9I&hvZpl)iBj=8_AZ^VhXb^KN1Mm%-F zmNoi(_t@t35HEcW&%#-kgnlWG9m<>NYujJUht~(LOPbXlx-N-05w~XJ zcZsLg@6op|iT&B&xCQoSkEi-ZoQNCi0r6z~NQGYQ8~r5v2Tu9eS8(1xTI1Hp2-hW< z^jw!z^upsk-s64T6kqQw?`IMN*Ch@7=^V8trnl>GX3099W?8%QOqKC22i||5v{g4NTvg zd*tNg;TCVNbFL|}uS*)gW%o34W9YUniO+(C=;x?)NoJgKysk^qU-brAmxNCM;}Y^B zS(g+{ul>)Ce+~4nOY$A{>yq#}f8^_u;FFbKMqk04PCu>f+rLkn`JIW#_i3~LPDk{0 z#%&C6cLeO7;(jPO=65V}b-2A$ht2ot@A;bV58QXenFZu9)5p$7LE~tLN+kv5;Qmqv zj9fj5v(zopF&*i)FVb?7AL%bk3Z!E?((N*I8= z;w^YL9n+ESprIo_()oNRs4B_wF&*icj{F!Oo7hczUAGCl3oX$Qy` z^h{6s?S`IwX?IS0AU)HQeoem4pM06G(=JHQ^rXMr(33Co^`d8b($^H|{K=Q{JMD}4 zGd<~_GW6t2{dv(dJ?ZlY8+$fC&TUT2`i9vzjA475`p&K6 zDjkw>D;m_6)=6t_$Qw{Y@0z zL4VFeQwP3Bai%#I!mBd9{Yy%q8;?_7dE*`?;nQw z5IrZNzZ=57Vd1xfe=_=WmiTyY2ZU}51(+R?mNzAM|<0w|=I+3uQaQ zc7^Rs#~1W?i|r4`%N)0I{Ks(^tG*p@ucI9a<~s}%c6OL{=BD1Y zGvq^m)v?os-`=S+w9IC_Rr-8joCWd-QBEA;f1e6XXl=N)xtg1Nb7j*X?=jTWPQ>~2 z*bUh~AWp=s`9_T!@zf3w)uLbIH<66?#6D5z98RWuph*J5#J-Tzr=ZL|9rf8zv-OU zhLo_^6D4DezhxsKsW;ClGk5b%R+1Tio3Qg{(=WQir5_W93RvIChxNVVRu_KtMS=aG zqo`lxHU2*C`$YyP;?{h-=@%Kh&?bO>(G$i`yaP>1ee}xM6V7Mknb?=2!b1PgTHdaO^-m1-~C~U z6LF(Hh^Nn2+8{Mb>e~UquCwXQ-PO-+ExB>VPlv7Fzy6ep8FR$$Vfz^Kvpy}7I1b#y z!MJ|TdC3oE`FtPD`s?XWos|4~R=xf$ZB=2Viky+~NGwk8`bNH9xM^yY{c-gN^=*mR zD%(x$h$YdG2j+5)=7aM@lu_FQV|pH&F}IMb^x8I_*0!o;(Z-e*w^WhegTB>!db6&M z{UdT>zlrlj>?g5b#Q7tpM~HS}jD*t!I6ZHaq9AnZA9Yw}-pGWVcbfjueJ=eOdnO*& z^w;FWdf#!k3%|XiW@t7?-@Fm_G=u(}$8q03GB^>p=6g;5$k>I}2l_`F_8Hwsvx)e| z{Z3sm&V9`2uNnUb69k0Xe_L|TzOUIkvacC@k(&*Z9DHH-r7pN>YV6-}d>yQB#EH0B zrvAFtyCa|iFWlg(=&#&x)BU64c;3k1MBE4s@$8;AVtwPhk*;scq%YJeC+3J)pVpZE z5$BB<=X2x{eBP)vLGOg=UEex|M=;#FIUu*==Rkc^^G2qs%eT z9x(3*9|~NT>C6Ihh^vhFfW4Lq73JXi^MgjNo(wUA-@V?Q=}6aHZr0B_=$+qHFdgZv z3Qb3T-T7h`uRA}cBi)ipO-FvDyDTY?j_F8u_hsuocz%bJ+vH2STE}-4Oh>w!%QYSO zb<+jr$8@C2-=XQqk90oY+wy5<_wT$j9qG0kI`Si3ZDJrD(~<7(^jB5+$d7cl#0Jta z9qBsjwB39;S|dLm!q>a-{3&-A3 zI2k^qch&>aGd=0o7<%%h{7!ozJ=2r^prI#U(s}8R=}F(QM9WXUl%IT@$%o^Lj{DvCIlhxvs6N>I}_3 zoOs^7K6I{pBZ1R*NoQI~?qeADiV>wRTh!b(69S~1lMwjm-+DC@{o`c^> zJno`j;ji9LwafZY`|qoNQ+{9lo08tmh9MJhn=2>Q?0nyauckhJzmp(N#0@&ZK|ECi z-%0px*{81M&&OSNHGQ;0;|9MK>N|-sroIKk&GueV*7erLd@~lOH*Wa0h5pJ7H%(26 zkDn0rO?@X}{O)h0Xhyw33n}jWC_j?rmz9!xlqKBnB-rk;9qjm`8J}{T%JHljC$V3} z_R+4KF~%SKpC}gVG-?xnrBv+ZL#$-8PJ`h?rXAen)^6EzalfWNwG1ED%g!&k@%upP zB@TWs7}@$&loN+)2jBX>ywm7x@GQ*tq)n3;asQT`|Wz87xHAN?0^+>YOM8pMgXQ6I$9=X*rz)gw~hZc_c1kxQ38 zC+Cc}ety}0Ih(4*?(seV@Az1s=ok6kk8%B6zvsgm4exatwTb&;aC+A_$Qi7@X=;|{ z_zBUzsdXBry*yslX*dz$J1l)4HWU2dtiOMiMjx-!V1J1Hqvi*7|A_r2=R6JPOW04M z^tVc%9CrH;vF%Udr0*%={c&k>*B9gFqpY`0`^WGR6Mx*TKf_$SLx2Cs@L|30{F)2D z_895Y;4uB8y%z+=^+V6UUy$3x#b$s2}gTYhlBQ+4G-r(jtN8{!X>AyL@gMMxJxV7ux`8ITEW4BHtu+}y?^#}jVEiMSCb z;>q}t3cYY+`-b}F?kC?preLya1NRe$k7B<7euoF@+b(e;Odo7N@t1#CQBOLCyL^HB ziEn-VA?*L{+c|yAo;gK(c7N^EQq_n2>gT7`Rc-s$?t<%bReW{fClh}3hjZ$t=ftUa z=5331pL@rm-K$rxeir*&Tq*Pq3eCZ;@`=K(y+L^83w@{1fd7|;Z=Ud~*YxcV>`iGn z`)RA;&9D5U9`xBQoAwMy+q&nnIi-60q6U_I?xkrL{Ce5+Gk^EM_wK)ElG=YC zhyxEM~Fi(<~CvTu* zz2})av)k1=Re$(RJw9!I(q&%Ao{7gb$4{Mx5BuRAPq^@Ff2~Qg;d{7>!1)W0Ja`-B z#G&Sy>TC6U1;cI(%z6~eSKJaiSe@SEUho57uX!eLz?*`I6g((xjLGs>3z=*d{(a~G`Bqi;2M@9^d3K z{=P}yJVob==Xh7cxLhiJ8`?ItZf1#lXwOmf!|Zl}zLq`CZz!9GM*9uroaL9aZQ1M_U0XX&l{LCQcA3cG zUS7C_?PDMOF}~g7@rL=9q~m%^?Zs-X)@`wM<0mg5UGtYVt1+upc+dCkbfoVNV*w1hK~G5=kqOB zm!xAlKivby?hHNY#w9Kf)Qd?+I@&M!k*+xo`E`FI%5f}ZI~zvMzqPrkG}FM6gY{Tml)dh(?mIq_xwOi%i|*J^t5Wxj71 zyJ5LV&-A44H1y=le7)$Ip7guc>-@==@;mK?`7=G~-!Sy#OZ|D#Gd=08Hl08DQf@DL zrYHUOZJM5Zi9hRsQ-4fP`i@I9J^9jqc+oRG>7O$66>rS?G5>|{*jMUe@svM1NUe@LB6E-%Ae^;-}%@p>KkwJ zrC%Tp&it94^lTr=m-NKJNze48=X3I<{N%&uz)#Itn|7Gv-OgRQU1mDs=d@$i3#J<{ zYgTs1SRqGl>=6&HzoGn^Y6IZVt<8T`Z-gVUIa${Km%I zm_l`WkDI8!FN?CEKj#4sn6XBA>($qoI)$}W>(v^0sn(|no%E5$J3D`?^nKt5It)c$ zvtn~B=651Jh#PStZnOj9srkMwc>PO_Z@c3a&xM=k5R;H|pbok^i93F4s#s<}aR$e2 zzSutO{!1>nfjUNJWaH#VZHM@_`cqij3~m`$YTOvMn|fhFhm0YkyI$0I)hz#k@9n0WGJEq;zj>&-bt>Wspw&}0PD#<_NNu90+x)ki z9fO+qmdDuac#E;)JY&Z<>v5}fg|XwYxVmCQ$|HtI75M!{ zPdc|dmcb2ntm?HXzh58urK(@%$MyYn0rh(3%*8WjE}h%3Xy$^G8|I*H8=Wj&w|sNU z#@6L)-E!^QdlQU4z9MX)4}>_V58AK38~m^Jr#~zI>DPxTzh56Sc51(E^kn@zP#<

6w?a%8zU#7OWZnlt17QwAEx|%eN0Q& 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 Y5PZDESauomR_muRwtP$pL{onM0l_2s?EnA( literal 0 HcmV?d00001 diff --git a/src/blenderbim/generate_demo_library.py b/src/blenderbim/generate_demo_library.py index cc4213d0f8..c9a95c0342 100644 --- a/src/blenderbim/generate_demo_library.py +++ b/src/blenderbim/generate_demo_library.py @@ -1,14 +1,26 @@ +import bpy import ifcopenshell import ifcopenshell.api class LibraryGenerator: def generate(self): + ifcopenshell.api.pre_listeners = {} + ifcopenshell.api.post_listeners = {} + self.file = ifcopenshell.api.run("project.create_file") self.project = ifcopenshell.api.run( "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library" ) ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"}) + ifcopenshell.api.run("context.add_context", self.file, context="Model") + self.body = ifcopenshell.api.run( + "context.add_context", self.file, context="Model", subcontext="Body", target_view="MODEL_VIEW" + ) + ifcopenshell.api.run("context.add_context", self.file, context="Plan") + self.annotation = ifcopenshell.api.run( + "context.add_context", self.file, context="Model", subcontext="Annotation", target_view="PLAN_VIEW" + ) self.material = ifcopenshell.api.run("material.add_material", self.file, name="Unknown") @@ -29,9 +41,7 @@ class LibraryGenerator: self.create_layer_type("IfcRampType", "DEMO200", 0.2) - profile = self.file.create_entity( - "IfcCircleProfileDef", ProfileType="AREA", Radius=0.3 - ) + profile = self.file.create_entity("IfcCircleProfileDef", ProfileType="AREA", Radius=0.3) self.create_profile_type("IfcPileType", "DEMO1", profile) self.create_layer_type("IfcSlabType", "DEMO150", 0.2) @@ -80,6 +90,8 @@ class LibraryGenerator: ) self.create_profile_type("IfcBeamType", "DEMO2", profile) + self.create_window_type("IfcWindowType", "DEMO1") + self.file.write("blenderbim-demo-library.ifc") def create_layer_type(self, ifc_class, name, thickness): @@ -101,5 +113,53 @@ class LibraryGenerator: ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile) ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + def create_window_type(self, ifc_class, name): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name) + obj = bpy.data.objects.get("Window") + representation = ifcopenshell.api.run( + "geometry.add_representation", + self.file, + context=self.body, + blender_object=obj, + geometry=obj.data, + total_items=max(1, len(obj.material_slots)), + ) + + ifcopenshell.api.run( + "style.assign_representation_styles", + self.file, + **{ + "shape_representation": representation, + "styles": [ + ifcopenshell.api.run("style.add_style", self.file, **self.get_style_settings(s.material)) + for s in obj.material_slots + ], + }, + ) + ifcopenshell.api.run( + "geometry.assign_representation", self.file, product=element, representation=representation + ) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + + def get_style_settings(self, material): + transparency = material.diffuse_color[3] + diffuse_colour = material.diffuse_color + if ( + material.use_nodes + and hasattr(material.node_tree, "nodes") + and "Principled BSDF" in material.node_tree.nodes + ): + bsdf = material.node_tree.nodes["Principled BSDF"] + transparency = bsdf.inputs["Alpha"].default_value + diffuse_colour = bsdf.inputs["Base Color"].default_value + transparency = 1 - transparency + return { + "name": material.name, + "external_definition": None, + "surface_colour": tuple(material.diffuse_color), + "transparency": transparency, + "diffuse_colour": tuple(diffuse_colour), + } + LibraryGenerator().generate() From a6c11b7e23f9372d5d82e013cc62a8eed28bae08 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 15 Aug 2021 18:32:42 +1000 Subject: [PATCH 02/72] Improved accuracy of boolean modifiers for dynamic voids --- .../bim/module/geometry/operator.py | 2 ++ .../blenderbim/bim/module/void/operator.py | 2 ++ src/blenderbim/demo-library.blend | Bin 865972 -> 868072 bytes 3 files changed, 4 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 3d2be2b4d2..485ef7d77b 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -228,6 +228,8 @@ class SwitchRepresentation(bpy.types.Operator): modifier = self.element_obj.modifiers.new("IfcOpeningElement", "BOOLEAN") modifier.operation = "DIFFERENCE" modifier.object = opening + modifier.solver = "EXACT" + modifier.use_self = True else: for modifier in self.element_obj.modifiers: if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name: diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index d859a87601..0a2d8ee319 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -55,6 +55,8 @@ class AddOpening(bpy.types.Operator): modifier = obj.modifiers.new("IfcOpeningElement", "BOOLEAN") modifier.operation = "DIFFERENCE" modifier.object = opening + modifier.solver = "EXACT" + modifier.use_self = True return {"FINISHED"} diff --git a/src/blenderbim/demo-library.blend b/src/blenderbim/demo-library.blend index 3660fbfc0fa46fb1e9f7149c9fb9dc9839c9638c..06132f34b144548d17ea379b55bcb712336bb542 100644 GIT binary patch delta 38048 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 From d7a76bfa11a22883c16f7814eec466cf4f802875 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sun, 15 Aug 2021 19:19:32 +0200 Subject: [PATCH 03/72] Fix https://github.com/IfcOpenShell/IfcOpenShell/issues/1656 : Operator needs a file to execute --- .../blenderbim/bim/module/project/operator.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index f47e94d8ac..1990025fed 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -82,6 +82,10 @@ class CreateProjectLibrary(bpy.types.Operator): bl_label = "Create Project Library" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return IfcStore.get_file() + def execute(self, context): IfcStore.begin_transaction(self) IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True) @@ -321,6 +325,10 @@ class AppendLibraryElement(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} definition: bpy.props.IntProperty() + @classmethod + def poll(cls, context): + return IfcStore.get_file() + def execute(self, context): return IfcStore.execute_ifc_operator(self, context) @@ -403,6 +411,10 @@ class EnableEditingHeader(bpy.types.Operator): bl_label = "Enable Editing Header" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return IfcStore.get_file() + def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMProjectProperties @@ -435,6 +447,10 @@ class EditHeader(bpy.types.Operator): bl_label = "Edit Header" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return IfcStore.get_file() + def execute(self, context): IfcStore.begin_transaction(self) self.transaction_data = {} From d22c11c2000c5526ef65a4b68a87e1376425ade0 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sun, 15 Aug 2021 19:27:25 +0200 Subject: [PATCH 04/72] Fix : Poll checking for file is not needed --- src/blenderbim/blenderbim/bim/module/project/operator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 1990025fed..416cc9c950 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -82,10 +82,6 @@ class CreateProjectLibrary(bpy.types.Operator): bl_label = "Create Project Library" bl_options = {"REGISTER", "UNDO"} - @classmethod - def poll(cls, context): - return IfcStore.get_file() - def execute(self, context): IfcStore.begin_transaction(self) IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True) From 296bf0387db1abb9417824c3f3d28efa5eb83b73 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 16 Aug 2021 00:16:51 +0200 Subject: [PATCH 05/72] Improve ifc patch arguments User Experience (#1655) * Add description field to Attribute Container * Add helper function to extract IFCPatch arguments * Add Operator to populate Attributes with IFCPatch arguments * Support int, float, bool and improve UX/UI * Move all reflection logic inside helper module * Improve UI * Correctly unpack arguments when Patching IFC * Refactor Attribute class to provide agnostic value access * Take advantage of Attribute agnosticity refactor * Use regular json if no docstring when Patching * centralize docstring extraction in UI module * Revert "centralize docstring extraction in UI module" This reverts commit e7a63d2a0ba709eb5d64552ea76722b58085438c. * Place Patch description in recipe enum field * Minor fixes * Minor fixes * Remove description field from Attribute * Auto-update IFC Patch args on recipe change --- src/blenderbim/blenderbim/bim/helper.py | 43 ++++------ .../blenderbim/bim/module/constraint/ui.py | 10 +-- .../blenderbim/bim/module/cost/ui.py | 54 ++----------- .../blenderbim/bim/module/document/ui.py | 10 +-- .../bim/module/georeference/operator.py | 11 +-- .../blenderbim/bim/module/georeference/ui.py | 25 +----- .../bim/module/material/operator.py | 26 +----- .../blenderbim/bim/module/material/ui.py | 28 +------ .../blenderbim/bim/module/patch/__init__.py | 1 + .../blenderbim/bim/module/patch/helper.py | 80 +++++++++++++++++++ .../blenderbim/bim/module/patch/operator.py | 45 +++++++++-- .../blenderbim/bim/module/patch/prop.py | 12 ++- .../blenderbim/bim/module/patch/ui.py | 21 +++-- .../blenderbim/bim/module/pset/operator.py | 39 +-------- .../blenderbim/bim/module/pset/ui.py | 15 +--- .../blenderbim/bim/module/sequence/ui.py | 38 ++------- .../bim/module/structural/operator.py | 10 +-- .../blenderbim/bim/module/structural/ui.py | 21 +---- src/blenderbim/blenderbim/bim/prop.py | 77 +++++++++++------- src/ifcpatch/ifcpatch/__init__.py | 6 +- 20 files changed, 247 insertions(+), 325 deletions(-) create mode 100644 src/blenderbim/blenderbim/bim/module/patch/helper.py diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index fefb377c45..8eef8750c1 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -11,22 +11,8 @@ from blenderbim.bim.ifc import IfcStore def draw_attributes(props, layout, copy_operator=None): for attribute in props: row = layout.row(align=True) - value = None - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - value = attribute.string_value - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - value = attribute.bool_value - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - value = attribute.int_value - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - value = attribute.float_value - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - value = attribute.enum_value + draw_attribute(attribute, row) + value = attribute.get_value() if attribute.is_optional: row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") if copy_operator: @@ -34,6 +20,17 @@ def draw_attributes(props, layout, copy_operator=None): op.data = json.dumps({"name": attribute.name, "value": value, "is_null": attribute.is_null}) +def draw_attribute(attribute, layout): + if not attribute.get_value_attr(): + layout.label(text=attribute.name) + else: + layout.prop( + attribute, + attribute.get_value_attr(), + text=attribute.name, + ) + + def import_attributes(ifc_class, props, data, callback=None): for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) @@ -70,17 +67,5 @@ def export_attributes(props, callback=None): is_handled_by_callback = callback(attributes, prop) if callback else False if is_handled_by_callback: continue # Our job is done - - if prop.is_null: - attributes[prop.name] = None - elif prop.data_type == "string": - attributes[prop.name] = prop.string_value - elif prop.data_type == "boolean": - attributes[prop.name] = prop.bool_value - elif prop.data_type == "integer": - attributes[prop.name] = prop.int_value - elif prop.data_type == "float": - attributes[prop.name] = prop.float_value - elif prop.data_type == "enum": - attributes[prop.name] = prop.enum_value + attributes[prop.name] = prop.get_value() return attributes diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py index 2a0ae2d6cd..22b09f092a 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py @@ -1,5 +1,6 @@ from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.constraint.data import Data @@ -44,14 +45,7 @@ class BIM_PT_constraints(Panel): self.draw_editable_ui(context) def draw_editable_ui(self, context): - for attribute in self.props.constraint_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.constraint_attributes, self.layout) class BIM_PT_object_constraints(Panel): diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 952dabd44d..779d329d4f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -1,6 +1,7 @@ import blenderbim.bim.module.cost.prop as CostProp from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.cost.data import Data @@ -66,14 +67,7 @@ class BIM_PT_cost_schedules(Panel): self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index") def draw_editable_cost_schedule_ui(self): - for attribute in self.props.cost_schedule_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.cost_schedule_attributes, self.layout) def draw_editable_cost_item_ui(self, cost_schedule_id): row = self.layout.row(align=True) @@ -119,18 +113,7 @@ class BIM_PT_cost_schedules(Panel): self.draw_editable_cost_item_values_ui() def draw_editable_cost_item_attributes_ui(self): - for attribute in self.props.cost_item_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.cost_item_attributes, self.layout) def draw_editable_cost_item_quantities_ui(self): row = self.layout.row(align=True) @@ -165,20 +148,7 @@ class BIM_PT_cost_schedules(Panel): self.draw_editable_cost_item_quantity_ui(box) def draw_editable_cost_item_quantity_ui(self, layout): - for attribute in self.props.quantity_attributes: - row = layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.quantity_attributes, self.layout) def draw_editable_cost_item_values_ui(self): row = self.layout.row(align=True) @@ -253,21 +223,7 @@ class BIM_PT_cost_schedules(Panel): op.cost_value = cost_value_id def draw_editable_cost_value_ui(self, layout, cost_value): - for attribute in self.props.cost_value_attributes: - row = layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") - + draw_attributes(self.props.cost_value_attributes, self.layout) class BIM_PT_cost_item_quantities(Panel): bl_label = "IFC Cost Item Quantities" diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index 0bbafafb77..70e9edcce5 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -1,5 +1,6 @@ from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.document.data import Data @@ -48,14 +49,7 @@ class BIM_PT_documents(Panel): self.draw_editable_ui(context) def draw_editable_ui(self, context): - for attribute in self.props.document_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.document_attributes, self.layout) class BIM_PT_object_documents(Panel): diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 22a883db5b..ff1970152b 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -102,16 +102,7 @@ class EditGeoreferencing(bpy.types.Operator): if data_type == "entity": continue blender_attribute = props.projected_crs.get(attribute.name()) - if blender_attribute.is_null: - projected_crs[attribute.name()] = None - elif blender_attribute.data_type == "string": - projected_crs[attribute.name()] = blender_attribute.string_value - elif blender_attribute.data_type == "float": - projected_crs[attribute.name()] = blender_attribute.float_value - elif blender_attribute.data_type == "integer": - projected_crs[attribute.name()] = blender_attribute.int_value - elif blender_attribute.data_type == "boolean": - projected_crs[attribute.name()] = blender_attribute.bool_value + projected_crs[attribute.name()] = blender_attribute.get_value() map_unit = "" if not props.is_map_unit_null: diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index 19f843541a..8b5cbc4f50 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -2,6 +2,7 @@ import ifcopenshell.util.geolocation from bpy.types import Panel from ifcopenshell.api.georeference.data import Data from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes, draw_attribute class BIM_PT_gis(Panel): @@ -34,17 +35,7 @@ class BIM_PT_gis(Panel): row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="") row.operator("bim.disable_editing_georeferencing", icon="X", text="") - for attribute in props.projected_crs: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(props.projected_crs, self.layout) row = self.layout.row(align=True) row.prop(props, "map_unit_type", text="MapUnit") @@ -62,17 +53,7 @@ class BIM_PT_gis(Panel): row = self.layout.row(align=True) row.operator("bim.set_ifc_grid_north", text="Set IFC North") row.operator("bim.set_blender_grid_north", text="Set Blender North") - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attribute(attribute, self.layout.row()) row = self.layout.row() row.label(text="True North", icon="LIGHT_SUN") diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 0d071dedbc..1fde8c931a 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -655,17 +655,7 @@ class EditMaterialSetItem(bpy.types.Operator): props = obj.BIMObjectMaterialProperties product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id] - attributes = {} - for attribute in props.material_set_item_attributes: - if attribute.data_type == "string": - value = attribute.string_value - elif attribute.data_type == "float": - value = attribute.float_value - elif attribute.data_type == "integer": - value = attribute.int_value - elif attribute.data_type == "boolean": - value = attribute.bool_value - attributes[attribute.name] = None if attribute.is_null else value + attributes = {attribute.name: attribute.get_value() for attribute in props.material_set_item_attributes} if product_data["type"] == "IfcMaterialConstituentSet": ifcopenshell.api.run( @@ -690,19 +680,7 @@ class EditMaterialSetItem(bpy.types.Operator): ) Data.load_layers() elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage": - profile_attributes = {} - for attribute in props.material_set_item_profile_attributes: - if attribute.data_type == "string": - value = attribute.string_value - elif attribute.data_type == "float": - value = attribute.float_value - elif attribute.data_type == "integer": - value = attribute.int_value - elif attribute.data_type == "boolean": - value = attribute.bool_value - elif attribute.data_type == "enum": - value = attribute.enum_value - profile_attributes[attribute.name] = None if attribute.is_null else value + profile_attributes = {attr.name: attr.get_value() for attr in props.material_set_item_profile_attributes} ifcopenshell.api.run( "material.edit_profile", self.file, diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 1a803aa836..e531eb754c 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -3,6 +3,7 @@ from bpy.types import Panel from ifcopenshell.api.material.data import Data from ifcopenshell.api.profile.data import Data as ProfileData from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes class BIM_PT_material(Panel): @@ -181,17 +182,7 @@ class BIM_PT_object_material(Panel): op.material_set_item = set_item_id row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="") - for attribute in self.props.material_set_item_attributes: - row = box.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.material_set_item_attributes, self.layout) if self.set_item_name == "profile": self.draw_assign_profile_ui(box, item) @@ -213,20 +204,7 @@ class BIM_PT_object_material(Panel): row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="") def draw_editable_profile_ui(self, layout, item): - for attribute in self.props.material_set_item_profile_attributes: - row = layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.material_set_item_profile_attributes, self.layout) def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False): if self.product_data["type"] == "IfcMaterialList": diff --git a/src/blenderbim/blenderbim/bim/module/patch/__init__.py b/src/blenderbim/blenderbim/bim/module/patch/__init__.py index d8a39535d2..1770b7cf78 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/patch/__init__.py @@ -5,6 +5,7 @@ classes = ( operator.SelectIfcPatchInput, operator.SelectIfcPatchOutput, operator.ExecuteIfcPatch, + operator.UpdateIfcPatchArguments, prop.BIMPatchProperties, ui.BIM_PT_patch, ) diff --git a/src/blenderbim/blenderbim/bim/module/patch/helper.py b/src/blenderbim/blenderbim/bim/module/patch/helper.py new file mode 100644 index 0000000000..f5712065b1 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/patch/helper.py @@ -0,0 +1,80 @@ +import typing +import inspect +import collections +import importlib +from types import ModuleType + +def extract_docs( + module: ModuleType, + submodule_name: str, + cls_name: str, + method_name: str, + boilerplate_args : typing.Iterable[str]=None): + """Extract class docstrings and method arguments + + :param module: Parent module from which to extract the submodule class + :param submodule_name: Submodule from which to extract the class + :param cls_name: Class from which to extract the docstring and method arguments + :param method_name: Class Method name from which to extract arguments + :param boilerplate_args: String iterable containing arguments that shall not be parsed + """ + spec = importlib.util.spec_from_file_location(submodule_name, f"{module.__path__[0]}/recipes/{submodule_name}.py") + submodule = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(submodule) + try: + return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args) + except AttributeError as e: + print(e) + except ModuleNotFoundError as e: + print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}") + +def _extract_docs(cls, method_name, boilerplate_args): + inputs = collections.OrderedDict() + method = getattr(cls, method_name) + node_data = {"class": cls} + + signature = inspect.signature(method) + for name, parameter in signature.parameters.items(): + if name == "self": + continue + inputs[name] = {"name": name} + if isinstance(parameter.default, (str, float, int, bool)): + inputs[name]["default"] = parameter.default + + type_hints = typing.get_type_hints(method) + for name, socket_data in inputs.items(): + type_hint = type_hints.get(name, None) + if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??) + continue + if isinstance(type_hint, typing._UnionGenericAlias): + inputs[name]["type"] = [t.__name__ for t in typing.get_args(type_hint)] + else: + inputs[name]["type"] = type_hint.__name__ + + description = "" + doc = method.__doc__ + if doc is not None: + for i, line in enumerate(doc.split("\n")): + line = line.strip() + if i == 0: + node_data["name"] = line + elif line.startswith(":return:"): + node_data["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()} + elif line.startswith(":param"): + param_name = line.split(":")[1].strip().replace("param ", "") + if param_name in inputs: + inputs[param_name]["description"] = line.split(":")[2].strip() + elif i == 2: + description += line + elif i > 2: + description += "\n" + line + + node_data["description"] = description.strip() + node_data["inputs"] = inputs + + if boilerplate_args is not None: + for arg in boilerplate_args: # Remove boilerplate arguments + node_data["inputs"].pop(arg, None) + return node_data + diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index 5015942931..5abf2b3a3b 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -1,6 +1,8 @@ import os import bpy import json +import ifcpatch +from .helper import extract_docs class SelectIfcPatchInput(bpy.types.Operator): @@ -39,6 +41,7 @@ class ExecuteIfcPatch(bpy.types.Operator): bl_idname = "bim.execute_ifc_patch" bl_label = "Execute IFCPatch" file_format: bpy.props.StringProperty() + use_json_for_args: bpy.props.BoolProperty() @classmethod def poll(cls, context): @@ -46,15 +49,47 @@ class ExecuteIfcPatch(bpy.types.Operator): return os.path.isfile(input_file) and "ifc" in os.path.splitext(input_file)[1] def execute(self, context): - import ifcpatch + props = context.scene.BIMPatchProperties + if self.use_json_for_args or not props.ifc_patch_args_attr: + arguments = json.loads(props.ifc_patch_args or "[]") + else: + arguments = [arg.get_value() for arg in props.ifc_patch_args_attr] ifcpatch.execute( { - "input": context.scene.BIMPatchProperties.ifc_patch_input, - "output": context.scene.BIMPatchProperties.ifc_patch_output, - "recipe": context.scene.BIMPatchProperties.ifc_patch_recipes, - "arguments": json.loads(context.scene.BIMPatchProperties.ifc_patch_args or "[]"), + "input": props.ifc_patch_input, + "output": props.ifc_patch_output, + "recipe": props.ifc_patch_recipes, + "arguments": arguments, "log": os.path.join(context.scene.BIMProperties.data_dir, "process.log"), } ) return {"FINISHED"} + + +class UpdateIfcPatchArguments(bpy.types.Operator): + bl_idname = "bim.update_ifc_patch_arguments" + bl_label = "Update IFC Patch arguments" + recipe: bpy.props.StringProperty() + + def execute(self, context): + if self.recipe == "": + print("No Recipe Selected. Impossible to load arguments") + return {"FINISHED"} + patch_args = context.scene.BIMPatchProperties.ifc_patch_args_attr + patch_args.clear() + docs = extract_docs(ifcpatch, self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args")) + if docs and "inputs" in docs: + inputs = docs["inputs"] + for arg_name in inputs: + arg_info = inputs[arg_name] + new_attr = patch_args.add() + new_attr.data_type = { + "str": "string", + "float": "float", + "int": "integer", + "bool": "boolean", + }[arg_info.get("type", "str")] + new_attr.name = arg_name + new_attr.set_value(arg_info.get("default", new_attr.get_value_default())) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/patch/prop.py b/src/blenderbim/blenderbim/bim/module/patch/prop.py index bfee66f4b0..86608eb6e0 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/prop.py +++ b/src/blenderbim/blenderbim/bim/module/patch/prop.py @@ -1,6 +1,7 @@ import bpy import importlib from pathlib import Path +import ifcpatch from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( @@ -13,6 +14,8 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from .helper import extract_docs +from .operator import UpdateIfcPatchArguments ifcpatchrecipes_enum = [] @@ -32,12 +35,17 @@ def getIfcPatchRecipes(self, context): f = str(filename.stem) if f == "__init__": continue - ifcpatchrecipes_enum.append((f, f, "")) + docs = extract_docs(ifcpatch, f, "Patcher", "__init__", ("src", "file", "logger", "args")) + ifcpatchrecipes_enum.append((f, f, docs.get("description","") if docs else "")) return ifcpatchrecipes_enum +def update_ifc_patch_recipe(self, context): + bpy.ops.bim.update_ifc_patch_arguments(recipe = self.ifc_patch_recipes) + class BIMPatchProperties(PropertyGroup): - ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes") + ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes", update=update_ifc_patch_recipe) ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC") ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC") ifc_patch_args: StringProperty(default="", name="Arguments") + ifc_patch_args_attr: CollectionProperty(type=Attribute, name="Arguments") diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py index 49600d2d1c..80c3fab2c2 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/ui.py +++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py @@ -1,8 +1,8 @@ import bpy -from bpy.types import Panel +from blenderbim.bim.helper import draw_attributes -class BIM_PT_patch(Panel): +class BIM_PT_patch(bpy.types.Panel): bl_label = "IFC Patch" bl_idname = "BIM_PT_patch" bl_options = {"DEFAULT_CLOSED"} @@ -13,20 +13,25 @@ class BIM_PT_patch(Panel): def draw(self, context): layout = self.layout layout.use_property_split = True + layout.use_property_decorate = False scene = context.scene props = scene.BIMPatchProperties - row = layout.row() - row.prop(props, "ifc_patch_recipes") + row.prop(props, "ifc_patch_recipes") + + row = layout.row(align=True) row.prop(props, "ifc_patch_input") row.operator("bim.select_ifc_patch_input", icon="FILE_FOLDER", text="") + row = layout.row(align=True) row.prop(props, "ifc_patch_output") row.operator("bim.select_ifc_patch_output", icon="FILE_FOLDER", text="") - row = layout.row() - row.prop(props, "ifc_patch_args") - row = layout.row() - op = row.operator("bim.execute_ifc_patch") + if props.ifc_patch_args_attr: + draw_attributes(props.ifc_patch_args_attr, layout) + else: + layout.row().prop(props, "ifc_patch_args") + op = layout.operator("bim.execute_ifc_patch") + op.use_json_for_args = len(props.ifc_patch_args_attr) == 0 diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 071d5dab3b..b34586c14b 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -136,31 +136,11 @@ class EnablePsetEditing(bpy.types.Operator): prop = Data.properties[prop_id] value = prop["NominalValue"] - if isinstance(value, str): - data_type = "string" - elif isinstance(value, float): - data_type = "float" - elif isinstance(value, bool): - data_type = "boolean" - elif isinstance(value, int): - data_type = "integer" - else: - data_type = "string" - value = str(value) - new = self.props.properties.add() + new.set_value(value) new.name = prop["Name"] - new.is_null = prop["NominalValue"] is None - new.data_type = data_type - - if data_type == "string": - new.string_value = "" if new.is_null else value - elif data_type == "integer": - new.int_value = 0 if new.is_null else value - elif data_type == "float": - new.float_value = 0.0 if new.is_null else value - elif data_type == "boolean": - new.bool_value = False if new.is_null else value + new.is_null = value is None + new.set_value(new.get_value_default() if new.is_null else value) class DisablePsetEditing(bpy.types.Operator): @@ -200,18 +180,7 @@ class EditPset(bpy.types.Operator): else: data = Data.psets if pset_id in Data.psets else Data.qtos for prop in props.properties: - if prop.is_null: - properties[prop.name] = None - elif prop.data_type == "string": - properties[prop.name] = prop.string_value - elif prop.data_type == "boolean": - properties[prop.name] = prop.bool_value - elif prop.data_type == "integer": - properties[prop.name] = prop.int_value - elif prop.data_type == "float": - properties[prop.name] = prop.float_value - elif prop.data_type == "enum": - properties[prop.name] = prop.enum_value + properties[prop.name] = prop.get_value() if pset_id in Data.psets: ifcopenshell.api.run( diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 4bed5d65dc..53fae14dda 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -1,6 +1,7 @@ from bpy.types import Panel from ifcopenshell.api.pset.data import Data from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attribute def get_active_pset_obj_name(context, obj_type): @@ -67,18 +68,8 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type): def draw_psetqto_editable_ui(box, props, prop): - row = box.row(align=True) - if prop.data_type == "string": - row.prop(prop, "string_value", text=prop.name) - elif prop.data_type == "integer": - row.prop(prop, "int_value", text=prop.name) - elif prop.data_type == "float": - row.prop(prop, "float_value", text=prop.name) - elif prop.data_type == "boolean": - row.prop(prop, "bool_value", text=prop.name) - elif prop.data_type == "enum": - row.prop(prop, "enum_value", text=prop.name) - row.prop(prop, "is_null", icon="RADIOBUT_OFF" if prop.is_null else "RADIOBUT_ON", text="") + row = box.row() + draw_attribute(prop, row) if ( "length" in prop.name.lower() or "width" in prop.name.lower() diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 79083e4060..d294c8865b 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -2,6 +2,7 @@ import isodate import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.sequence.data import Data from ifcopenshell.api.resource.data import Data as ResourceData import blenderbim.bim.module.sequence.helper as helper @@ -55,14 +56,7 @@ class BIM_PT_work_plans(Panel): self.draw_work_schedule_ui() def draw_editable_ui(self): - for attribute in self.props.work_plan_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.work_plan_attributes, self.layout) def draw_work_schedule_ui(self): row = self.layout.row(align=True) @@ -189,14 +183,7 @@ class BIM_PT_work_schedules(Panel): row.prop(self.props, "speed_multiplier", text="") def draw_editable_work_schedule_ui(self): - for attribute in self.props.work_schedule_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.work_schedule_attributes, self.layout) def draw_editable_task_ui(self, work_schedule_id): self.layout.template_list( @@ -523,15 +510,7 @@ class BIM_PT_work_calendars(Panel): self.draw_editable_work_time_ui(work_time) def draw_editable_work_time_ui(self, work_time): - for attribute in self.props.work_time_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") - + draw_attributes(self.props.work_time_attributes, self.layout) if work_time["RecurrencePattern"]: self.draw_editable_recurrence_pattern_ui(Data.recurrence_patterns[work_time["RecurrencePattern"]]) else: @@ -599,11 +578,4 @@ class BIM_PT_work_calendars(Panel): row.prop(self.props, "occurrences") def draw_editable_ui(self): - for attribute in self.props.work_calendar_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.work_calendar_attributes, self.layout) diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 6b036bccb0..38133c4abc 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -279,15 +279,7 @@ class EditStructuralAnalysisModel(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMStructuralProperties - attributes = {} - for attribute in props.structural_analysis_model_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = {attribute.name: attribute.get_value() for attribute in props.structural_analysis_model_attributes} self.file = IfcStore.get_file() ifcopenshell.api.run( "structural.edit_structural_analysis_model", diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index 5ce31355b8..6e64fd7ea8 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -2,6 +2,7 @@ import bpy import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.structural.data import Data @@ -293,14 +294,7 @@ class BIM_PT_structural_analysis_models(Panel): self.draw_editable_ui(context) def draw_editable_ui(self, context): - for attribute in self.props.structural_analysis_model_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.structural_analysis_model_attributes, self.layout) class BIM_UL_structural_analysis_models(UIList): @@ -389,16 +383,7 @@ class BIM_PT_structural_load_cases(Panel): self.draw_editable_load_case_group_ui(load_case) def draw_editable_load_case_ui(self): - for attribute in self.props.load_case_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "float": - row.prop(attribute, "float_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + draw_attributes(self.props.load_case_attributes, self.layout) def draw_editable_load_case_group_ui(self, load_case): box = self.layout.box() diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 4e71c57112..0e891b744f 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -126,43 +126,66 @@ class StrProperty(PropertyGroup): pass -def updateAttributeStringValue(self, context): - updateAttributeValue(self, self.string_value) - - -def updateAttributeBoolValue(self, context): - updateAttributeValue(self, self.bool_value) - - -def updateAttributeIntValue(self, context): - updateAttributeValue(self, self.int_value) - - -def updateAttributeFloatValue(self, context): - updateAttributeValue(self, self.float_value) - - -def updateAttributeEnumValue(self, context): - updateAttributeValue(self, self.enum_value) - - -def updateAttributeValue(self, value): - if value: +def updateAttributeValue(self, context): + if getattr(self, str(self.get_value_attr()), None): # Do not use get_value since it returns None if is_null is True self.is_null = False class Attribute(PropertyGroup): name: StringProperty(name="Name") data_type: StringProperty(name="Data Type") - string_value: StringProperty(name="Value", update=updateAttributeStringValue) - bool_value: BoolProperty(name="Value", update=updateAttributeBoolValue) - int_value: IntProperty(name="Value", update=updateAttributeIntValue) - float_value: FloatProperty(name="Value", update=updateAttributeFloatValue) + string_value: StringProperty(name="Value", update=updateAttributeValue) + bool_value: BoolProperty(name="Value", update=updateAttributeValue) + int_value: IntProperty(name="Value", update=updateAttributeValue) + float_value: FloatProperty(name="Value", update=updateAttributeValue) is_null: BoolProperty(name="Is Null") is_optional: BoolProperty(name="Is Optional") enum_items: StringProperty(name="Value") - enum_value: EnumProperty(items=getAttributeEnumValues, name="Value", update=updateAttributeEnumValue) + enum_value: EnumProperty(items=getAttributeEnumValues, name="Value", update=updateAttributeValue) + def get_value(self): + if self.is_null: + return None + return getattr(self, str(self.get_value_attr()), None) + + def get_value_default(self): + if self.data_type == "string": + return "" + elif self.data_type == "integer": + return 0 + elif self.data_type == "float": + return 0.0 + elif self.data_type == "boolean": + return False + elif self.data_type == "enum": + return "0" + + def get_value_attr(self): + if self.data_type == "string": + return "string_value" + elif self.data_type == "boolean": + return "bool_value" + elif self.data_type == "integer": + return "int_value" + elif self.data_type == "float": + return "float_value" + elif self.data_type == "enum": + return "enum_value" + + def set_value(self, value): + if isinstance(value, str): + self.data_type = "string" + elif isinstance(value, float): + self.data_type = "float" + elif isinstance(value, bool): # Make sure this is evaluated BEFORE integer + self.data_type = "boolean" + elif isinstance(value, int): + self.data_type = "integer" + else: + self.data_type = "string" + value = str(value) + setattr(self, self.get_value_attr(), value) + class BIMProperties(PropertyGroup): schema_dir: StringProperty( diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 8c7b4b161e..6c2b60a369 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -32,7 +32,11 @@ def execute(args, is_library=None): print("# Loading patch recipe ...") recipes = getattr(__import__("ifcpatch.recipes.{}".format(args["recipe"])), "recipes") recipe = getattr(recipes, args["recipe"]) - patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"]) + try: + # We unpack the arguments if the Patcher has been type-hinted and docstringed + patcher = recipe.Patcher(args["input"], ifc_file, logger, *args["arguments"]) + except TypeError: + patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"]) print("# Patching ...") patcher.patch() ifc_file = getattr(patcher, "file_patched", patcher.file) From 745411ee4753948c0cf2f065075e21194338f020 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 09:15:43 +1000 Subject: [PATCH 06/72] Minor fix & refactor, run black, fix pset optionality. See #1655. --- src/blenderbim/blenderbim/bim/helper.py | 32 +++++++++---------- .../bim/module/georeference/operator.py | 26 +++++---------- .../bim/module/material/operator.py | 4 +-- .../blenderbim/bim/module/patch/operator.py | 7 +++- .../blenderbim/bim/module/pset/operator.py | 7 ++-- .../blenderbim/bim/module/pset/ui.py | 2 +- .../bim/module/structural/operator.py | 2 +- src/blenderbim/blenderbim/bim/prop.py | 18 +++++------ 8 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 8eef8750c1..ba6d146bfe 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -11,24 +11,24 @@ from blenderbim.bim.ifc import IfcStore def draw_attributes(props, layout, copy_operator=None): for attribute in props: row = layout.row(align=True) - draw_attribute(attribute, row) - value = attribute.get_value() - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") - if copy_operator: - op = row.operator(f"{copy_operator}", text="", icon="COPYDOWN") - op.data = json.dumps({"name": attribute.name, "value": value, "is_null": attribute.is_null}) + draw_attribute(attribute, row, copy_operator) -def draw_attribute(attribute, layout): - if not attribute.get_value_attr(): +def draw_attribute(attribute, layout, copy_operator=None): + value_name = attribute.get_value_name() + if not value_name: layout.label(text=attribute.name) - else: - layout.prop( - attribute, - attribute.get_value_attr(), - text=attribute.name, - ) + return + layout.prop( + attribute, + value_name, + text=attribute.name, + ) + if attribute.is_optional: + layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + if copy_operator: + op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN") + op.data = json.dumps({"name": attribute.name, "value": attribute.get_value(), "is_null": attribute.is_null}) def import_attributes(ifc_class, props, data, callback=None): @@ -66,6 +66,6 @@ def export_attributes(props, callback=None): for prop in props: is_handled_by_callback = callback(attributes, prop) if callback else False if is_handled_by_callback: - continue # Our job is done + continue # Our job is done attributes[prop.name] = prop.get_value() return attributes diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index ff1970152b..020537c687 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -4,6 +4,7 @@ import ifcopenshell import ifcopenshell.util.unit import ifcopenshell.util.attribute import ifcopenshell.api +import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.georeference.data import Data from math import radians, degrees, atan, tan, cos, sin @@ -96,29 +97,13 @@ class EditGeoreferencing(bpy.types.Operator): self.file = IfcStore.get_file() props = context.scene.BIMGeoreferenceProperties - projected_crs = {} - for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - blender_attribute = props.projected_crs.get(attribute.name()) - projected_crs[attribute.name()] = blender_attribute.get_value() + projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs) map_unit = "" if not props.is_map_unit_null: map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial - map_conversion = {} - for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity" or data_type == "select": - continue - blender_attribute = props.map_conversion.get(attribute.name()) - if blender_attribute.is_null: - map_conversion[attribute.name()] = None - elif blender_attribute.data_type == "string": - # We store our floats as string to prevent single precision data loss - map_conversion[attribute.name()] = float(blender_attribute.string_value) + map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, self.export_attributes) true_north = None if props.has_true_north: @@ -141,6 +126,11 @@ class EditGeoreferencing(bpy.types.Operator): bpy.ops.bim.disable_editing_georeferencing() return {"FINISHED"} + def export_attributes(self, attributes, prop): + if not prop.is_null and prop.data_type == "string": + attributes[prop.name] = float(prop.string_value) + return True + class SetBlenderGridNorth(bpy.types.Operator): bl_idname = "bim.set_blender_grid_north" diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 1fde8c931a..349e8991e3 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -655,7 +655,7 @@ class EditMaterialSetItem(bpy.types.Operator): props = obj.BIMObjectMaterialProperties product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id] - attributes = {attribute.name: attribute.get_value() for attribute in props.material_set_item_attributes} + attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_attributes) if product_data["type"] == "IfcMaterialConstituentSet": ifcopenshell.api.run( @@ -680,7 +680,7 @@ class EditMaterialSetItem(bpy.types.Operator): ) Data.load_layers() elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage": - profile_attributes = {attr.name: attr.get_value() for attr in props.material_set_item_profile_attributes} + profile_attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_profile_attributes) ifcopenshell.api.run( "material.edit_profile", self.file, diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index 5abf2b3a3b..e59e3e04af 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -1,7 +1,12 @@ import os import bpy import json -import ifcpatch + +try: + import ifcpatch +except: + print("IfcPatch not available") + from .helper import extract_docs diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index b34586c14b..2ef9b1cf01 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -100,8 +100,9 @@ class EnablePsetEditing(bpy.types.Operator): IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel") ) except: - # TODO: Occurs if the data type is something that exists in IFC4 and not in IFC2X3. To fully fix - # this we need to generate the IFC2X3 pset template definitions. + # TODO: Occurs if the data type is something that exists in + # IFC4 and not in IFC2X3. To fully fix this we need to + # generate the IFC2X3 pset template definitions. continue elif prop_template.TemplateType == "P_ENUMERATEDVALUE": data_type = "enum" @@ -116,6 +117,7 @@ class EnablePsetEditing(bpy.types.Operator): new = self.props.properties.add() new.name = prop_template.Name new.is_null = data.get(prop_template.Name, None) is None + new.is_optional = True new.data_type = data_type if data_type == "string": @@ -140,6 +142,7 @@ class EnablePsetEditing(bpy.types.Operator): new.set_value(value) new.name = prop["Name"] new.is_null = value is None + new.is_optional = True new.set_value(new.get_value_default() if new.is_null else value) diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 53fae14dda..6fe7e91593 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -68,7 +68,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type): def draw_psetqto_editable_ui(box, props, prop): - row = box.row() + row = box.row(align=True) draw_attribute(prop, row) if ( "length" in prop.name.lower() diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 38133c4abc..58864171c7 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -279,7 +279,7 @@ class EditStructuralAnalysisModel(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMStructuralProperties - attributes = {attribute.name: attribute.get_value() for attribute in props.structural_analysis_model_attributes} + attributes = blenderbim.bim.helper.export_attributes(props.structural_analysis_model_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "structural.edit_structural_analysis_model", diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 0e891b744f..613646f6e4 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -127,7 +127,7 @@ class StrProperty(PropertyGroup): def updateAttributeValue(self, context): - if getattr(self, str(self.get_value_attr()), None): # Do not use get_value since it returns None if is_null is True + if getattr(self, str(self.get_value_name()), None): # Do not use get_value since it returns None if is_null is True self.is_null = False @@ -146,8 +146,8 @@ class Attribute(PropertyGroup): def get_value(self): if self.is_null: return None - return getattr(self, str(self.get_value_attr()), None) - + return getattr(self, str(self.get_value_name()), None) + def get_value_default(self): if self.data_type == "string": return "" @@ -159,8 +159,8 @@ class Attribute(PropertyGroup): return False elif self.data_type == "enum": return "0" - - def get_value_attr(self): + + def get_value_name(self): if self.data_type == "string": return "string_value" elif self.data_type == "boolean": @@ -171,21 +171,21 @@ class Attribute(PropertyGroup): return "float_value" elif self.data_type == "enum": return "enum_value" - + def set_value(self, value): if isinstance(value, str): self.data_type = "string" elif isinstance(value, float): self.data_type = "float" - elif isinstance(value, bool): # Make sure this is evaluated BEFORE integer + elif isinstance(value, bool): # Make sure this is evaluated BEFORE integer self.data_type = "boolean" elif isinstance(value, int): self.data_type = "integer" else: self.data_type = "string" value = str(value) - setattr(self, self.get_value_attr(), value) - + setattr(self, self.get_value_name(), value) + class BIMProperties(PropertyGroup): schema_dir: StringProperty( From b4256c504f76de6d59201dac7caf86f4e00b6312 Mon Sep 17 00:00:00 2001 From: bosonprojets Date: Mon, 16 Aug 2021 01:35:14 +0100 Subject: [PATCH 07/72] Implement metric constraints in the ifcopenshell.api --- .../ifcopenshell/api/constraint/add_metric.py | 21 ++++++++++ .../api/constraint/assign_constraint.py | 1 + .../ifcopenshell/api/constraint/data.py | 40 +++++++++++++++++++ .../api/constraint/edit_metric.py | 13 ++++++ .../api/constraint/remove_metric.py | 15 +++++++ 5 files changed, 90 insertions(+) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/constraint/remove_metric.py diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py new file mode 100644 index 0000000000..ce273a61ec --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py @@ -0,0 +1,21 @@ +import ifcopenshell + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "objective": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + metric = self.file.create_entity("IfcMetric", **{ + "Name": "Unnamed", + "ConstraintGrade": "NOTDEFINED", + "Benchmark": "EQUALTO", + }) + if self.settings["objective"]: + benchmark_values = list(self.settings["objective"].BenchmarkValues or []) + benchmark_values.append(metric) + return metric diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py index 7dc946e911..0489af80d6 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py @@ -16,6 +16,7 @@ class Usecase: related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set() related_objects.add(self.settings["product"]) rel.RelatedObjects = list(related_objects) + return rel def get_constraint_rel(self): for rel in self.file.by_type("IfcRelAssociatesConstraint"): diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py index aaae145eaf..3a4791feeb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py @@ -7,12 +7,17 @@ class Data: is_loaded = False products = {} objectives = {} + metrics = {} + references = {} @classmethod def purge(cls): cls.is_loaded = False cls.products = {} cls.objectives = {} + cls.metrics ={} + cls.references = {} + @classmethod def load(cls, file, product_id=None): @@ -22,6 +27,8 @@ class Data: if product_id: return cls.load_product(product_id) cls.load_objectives() + cls.load_metrics() + cls.load_references() cls.is_loaded = True @classmethod @@ -41,8 +48,41 @@ class Data: cls.objectives = {} for constraint in cls._file.by_type("IfcObjective"): data = constraint.get_info() + for key, value in data.items(): + if not value: + continue + if cls._file.schema == "IFC2X3": for attribute in ["CreationTime"]: if data[attribute]: data[attribute] = ifcopenshell.util.date.ifc2datetime(data[attribute]).isoformat() + data["BenchmarkValues"] = [metric.id() for metric in constraint.BenchmarkValues or []] cls.objectives[constraint.id()] = data + + @classmethod + def load_metrics(cls): + cls.metrics = {} + for metric in cls._file.by_type("IfcMetric"): + data = metric.get_info() + for key, value in data.items(): + if not value: + continue + data["ConstrainedObjects"] = [] + for association in cls._file.by_type("IfcRelAssociatesConstraint"): + if association.RelatingConstraint.id() == metric.id(): + data["ConstrainedObjects"] = [o.id() for o in association.RelatedObjects or []] + if metric.DataValue: + data["DataValue"] = data["DataValue"].id() + if metric.ReferencePath: + data["ReferencePath"] = data["ReferencePath"].id() + cls.metrics[metric.id()] = data + + @classmethod + def load_references(cls): + cls.references = {} + for reference in cls._file.by_type("IfcReference"): + data = reference.get_info() + for key, value in data.items(): + if not value: + continue + cls.references[refenrece.id()] = data diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py new file mode 100644 index 0000000000..f2859769c2 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "metric": None, + "attributes": {} + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["metric"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/remove_metric.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/remove_metric.py new file mode 100644 index 0000000000..addec2e94f --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/remove_metric.py @@ -0,0 +1,15 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"metric": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.file.remove(self.settings["metric"]) + for rel in self.file.by_type("IfcRelAssociatesConstraint"): + if not rel.RelatingConstraint: + self.file.remove(rel) + for resource_rel in self.file.by_type("IfcResourceConstraintRelationship"): + if not resource_rel.RelatingConstraint: + self.file.remove(resource_rel) From 897d7a6b2d4298331b35d951b7c88b44faf09ec3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 10:41:14 +1000 Subject: [PATCH 08/72] Half complete "add window" tool prototype --- .../bim/module/georeference/operator.py | 1 + .../blenderbim/bim/module/model/opening.py | 99 +++++++++++++++---- .../blenderbim/bim/module/model/product.py | 78 +++++++++------ .../blenderbim/bim/module/model/workspace.py | 7 +- .../blenderbim/bim/module/patch/helper.py | 2 + 5 files changed, 134 insertions(+), 53 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 020537c687..571ce07fa4 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -128,6 +128,7 @@ class EditGeoreferencing(bpy.types.Operator): def export_attributes(self, attributes, prop): if not prop.is_null and prop.data_type == "string": + # We store our floats as string to prevent single precision data loss attributes[prop.name] = float(prop.string_value) return True diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index 9c0c482e3c..c7bd2008e7 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -5,6 +5,7 @@ import ifcopenshell import ifcopenshell.util.representation from blenderbim.bim.ifc import IfcStore from math import pi +from mathutils import Vector from bpy.types import Operator from bpy.props import FloatProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add @@ -56,40 +57,96 @@ class AddElementOpening(bpy.types.Operator): bl_idname = "bim.add_element_opening" bl_label = "Add Element Opening" bl_options = {"REGISTER", "UNDO"} + voided_building_element: bpy.props.StringProperty() + filling_building_element: bpy.props.StringProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - selected_objs = context.selected_objects - if len(selected_objs) == 0 or not context.active_object: + voided_obj = self.get_voided_building_element(context) + filling_obj = self.get_filling_building_element(context) + + if not voided_obj: return {"FINISHED"} - obj = context.active_object - if not obj.BIMObjectProperties.ifc_definition_id: - return {"FINISHED"} - element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id) - local_location = obj.matrix_world.inverted() @ context.scene.cursor.location - raycast = obj.closest_point_on_mesh(local_location, distance=0.01) + + element = IfcStore.get_file().by_id(voided_obj.BIMObjectProperties.ifc_definition_id) + local_location = voided_obj.matrix_world.inverted() @ context.scene.cursor.location + raycast = voided_obj.closest_point_on_mesh(local_location, distance=0.01) if not raycast[0]: return {"FINISHED"} - # The opening shall be based on the smallest bounding dimension of the element - dimension = min(obj.dimensions) - bpy.ops.mesh.primitive_cube_add(size=dimension * 2) - opening = context.selected_objects[0] + if filling_obj: + opening = self.generate_opening_from_filling(filling_obj) + else: + # The opening shall be based on the smallest bounding dimension of the element + dimension = min(voided_obj.dimensions) + bpy.ops.mesh.primitive_cube_add(size=dimension * 2) + opening = context.selected_objects[0] - # Place the opening in the middle of the element - global_location = obj.matrix_world @ raycast[1] - normal = raycast[2] - normal.negate() - global_normal = obj.matrix_world.to_quaternion() @ normal - opening.location = global_location + (global_normal * (dimension / 2)) + # Place the opening in the middle of the element + global_location = voided_obj.matrix_world @ raycast[1] + normal = raycast[2] + normal.negate() + global_normal = voided_obj.matrix_world.to_quaternion() @ normal + opening.location = global_location + (global_normal * (dimension / 2)) + opening.rotation_euler = voided_obj.rotation_euler + opening.name = "Opening" - opening.rotation_euler = obj.rotation_euler - opening.name = "Opening" - bpy.ops.bim.add_opening(opening=opening.name, obj=obj.name) + bpy.ops.bim.add_opening(opening=opening.name, obj=voided_obj.name) return {"FINISHED"} + def get_voided_building_element(self, context): + obj = None + if self.voided_building_element: + obj = bpy.data.objects.get(self.voided_building_element) + else: + total_selected = len(context.selected_objects) + if total_selected == 1 or total_selected == 2: + obj = context.active_object + if obj and obj.BIMObjectProperties.ifc_definition_id: + return obj + + def get_filling_building_element(self, context): + obj = None + if self.filling_building_element: + obj = bpy.data.objects.get(self.filling_building_element) + else: + total_selected = len(context.selected_objects) + if total_selected == 2: + if context.selected_objects[0] == context.active_object: + obj = context.selected_objects[1] + else: + obj = context.selected_objects[0] + if obj and obj.BIMObjectProperties.ifc_definition_id: + return obj + + def generate_opening_from_filling(self, filling_obj): + x, y, z = filling_obj.dimensions + verts = [ + Vector((0, 0, 0)), + Vector((0, 0, z)), + Vector((0, y, 0)), + Vector((0, y, z)), + Vector((x, 0, 0)), + Vector((x, 0, z)), + Vector((x, y, 0)), + Vector((x, y, z)), + ] + edges = [] + faces = [ + [0, 1, 3, 2], + [2, 3, 7, 6], + [6, 7, 5, 4], + [4, 5, 1, 0], + [2, 6, 4, 0], + [7, 3, 1, 5], + ] + mesh = bpy.data.meshes.new(name="Opening") + mesh.from_pydata(verts, edges, faces) + obj = bpy.data.objects.new("Opening", mesh) + obj.matrix_world = filling_obj.matrix_world + return obj def add_object(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index 0226167c5f..eae81c5cc5 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -51,37 +51,26 @@ class AddTypeInstance(bpy.types.Operator): tprops = context.scene.BIMTypeProperties ifc_class = self.ifc_class or tprops.ifc_class relating_type_id = self.relating_type or tprops.relating_type + if not ifc_class or not relating_type_id: return {"FINISHED"} + self.file = IfcStore.get_file() instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0] relating_type = self.file.by_id(int(relating_type_id)) material = ifcopenshell.util.element.get_material(relating_type) - if material.is_a("IfcMaterialProfileSet"): - obj = profile.DumbProfileGenerator(relating_type).generate() - if obj: + + if material and material.is_a("IfcMaterialProfileSet"): + if profile.DumbProfileGenerator(relating_type).generate(): return {"FINISHED"} - elif material.is_a("IfcMaterialLayerSet"): - layer_set_direction = None - - parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) - if layer_set_direction is None: - if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]: - layer_set_direction = "AXIS3" - else: - layer_set_direction = "AXIS2" - - if layer_set_direction == "AXIS3": - obj = slab.DumbSlabGenerator(relating_type).generate() - elif layer_set_direction == "AXIS2": - obj = wall.DumbWallGenerator(relating_type).generate() - else: - obj = None # Dumb block generator? Eh? :) - - if obj: + elif material and material.is_a("IfcMaterialLayerSet"): + if self.generate_layered_element(ifc_class, relating_type): return {"FINISHED"} + + building_obj = None + if len(context.selected_objects) == 1 and context.active_object: + building_obj = context.active_object + # A cube verts = [ Vector((-1, -1, -1)), @@ -95,12 +84,12 @@ class AddTypeInstance(bpy.types.Operator): ] edges = [] faces = [ - [0, 2, 3, 1], + [0, 1, 3, 2], [2, 3, 7, 6], - [4, 5, 7, 6], - [0, 1, 5, 4], - [1, 3, 7, 5], - [0, 2, 6, 4], + [6, 7, 5, 4], + [4, 5, 1, 0], + [2, 6, 4, 0], + [7, 3, 1, 5], ] mesh = bpy.data.meshes.new(name="Instance") mesh.from_pydata(verts, edges, faces) @@ -111,10 +100,39 @@ class AddTypeInstance(bpy.types.Operator): collection_obj = bpy.data.objects.get(collection.name) bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class) bpy.ops.bim.assign_type(relating_type=int(tprops.relating_type), related_object=obj.name) - if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id: - obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box]) + + if building_obj: + if instance_class == "IfcWindow": + # TODO For now we are hardcoding windows as a prototype + bpy.ops.bim.add_element_opening( + voided_building_element=building_obj.name, filling_building_element=obj.name + ) + else: + if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id: + obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box]) return {"FINISHED"} + def generate_layered_element(self, ifc_class, relating_type): + layer_set_direction = None + + parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") + if parametric: + layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) + if layer_set_direction is None: + if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]: + layer_set_direction = "AXIS3" + else: + layer_set_direction = "AXIS2" + + if layer_set_direction == "AXIS3": + if slab.DumbSlabGenerator(relating_type).generate(): + return True + elif layer_set_direction == "AXIS2": + if wall.DumbWallGenerator(relating_type).generate(): + return True + else: + pass # Dumb block generator? Eh? :) + class AlignProduct(bpy.types.Operator): bl_idname = "bim.align_product" diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 6bf9b0ac2a..82d633f507 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -17,7 +17,7 @@ class BimTool(WorkSpaceTool): bl_keymap = ( # ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}), # ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}), - ("bim.add_type_instance", {"type": "A", "value": "PRESS", "shift": True}, {"properties": []}), + ("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}), ("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}), ("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}), ("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}), @@ -113,6 +113,9 @@ class Hotkey(bpy.types.Operator): getattr(self, f"hotkey_{self.hotkey}")() return {"FINISHED"} + def hotkey_S_A(self): + bpy.ops.bim.add_type_instance() + def hotkey_S_C(self): if self.props.ifc_class == "IfcWallType": bpy.ops.bim.align_wall(align_type="CENTERLINE") @@ -138,7 +141,7 @@ class Hotkey(bpy.types.Operator): bpy.ops.bim.align_product(align_type="NEGATIVE") def hotkey_S_O(self): - bpy.ops.bim.add_element_opening() + bpy.ops.bim.add_element_opening(voided_building_element="", filling_building_element="") def hotkey_A_D(self): bpy.ops.bim.toggle_decomposition_parenting() diff --git a/src/blenderbim/blenderbim/bim/module/patch/helper.py b/src/blenderbim/blenderbim/bim/module/patch/helper.py index f5712065b1..24a11c441d 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/helper.py +++ b/src/blenderbim/blenderbim/bim/module/patch/helper.py @@ -28,6 +28,8 @@ def extract_docs( print(e) except ModuleNotFoundError as e: print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}") + except: + print(f"Error : IFCPatch {str(submodule)} could not load") def _extract_docs(cls, method_name, boilerplate_args): inputs = collections.OrderedDict() From 6ee0027f33c4b325676ec5c211880c2a604a5c4d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 15:07:09 +1000 Subject: [PATCH 09/72] Import of CSV-based schedule of rates is now possible --- .../blenderbim/bim/module/cost/operator.py | 23 +++++-- .../blenderbim/bim/module/cost/ui.py | 2 +- .../blenderbim/bim/module/unit/ui.py | 2 +- src/ifc5d/ifc5d/csv2ifc.py | 55 ++++++++++++---- src/ifc5d/rates.csv | 14 ++++ src/ifc5d/{test.csv => schedule.csv} | 0 .../ifcopenshell/api/cost/edit_cost_value.py | 13 ++-- .../ifcopenshell/util/unit.py | 64 ++++++++++++++----- 8 files changed, 130 insertions(+), 43 deletions(-) create mode 100644 src/ifc5d/rates.csv rename src/ifc5d/{test.csv => schedule.csv} (100%) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 766c9941bc..08563d8c14 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -592,11 +592,21 @@ class EnableEditingCostItemValue(bpy.types.Operator): prop.is_null = prop.is_optional = False units = {} for unit_id, unit in UnitData.units.items(): - if unit.get("UnitType", None) in ["AREAUNIT", "LENGTHUNIT", "TIMEUNIT", "VOLUMEUNIT", "MASSUNIT"]: - name = unit["Name"] - if unit.get("Prefix", None): - name = f"(unit['Prefix']) {name}" - units[unit_id] = f"{unit['UnitType']} / {name}" + if unit.get("UnitType", None) in [ + "AREAUNIT", + "LENGTHUNIT", + "TIMEUNIT", + "VOLUMEUNIT", + "MASSUNIT", + "USERDEFINED", + ]: + if unit["type"] == "IfcContextDependentUnit": + units[unit_id] = f"{unit['UnitType']} / {unit['Name']}" + else: + name = unit["Name"] + if unit.get("Prefix", None): + name = f"(unit['Prefix']) {name}" + units[unit_id] = f"{unit['UnitType']} / {name}" prop.enum_items = json.dumps(units) if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]: prop.enum_value = str(data["UnitBasis"]["UnitComponent"]) @@ -719,6 +729,7 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): bl_options = {"REGISTER", "UNDO"} filename_ext = ".csv" filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"}) + is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False) def execute(self, context): from ifc5d.csv2ifc import Csv2Ifc @@ -728,8 +739,10 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): csv2ifc = Csv2Ifc() csv2ifc.csv = self.filepath csv2ifc.file = self.file + csv2ifc.is_schedule_of_rates = self.is_schedule_of_rates csv2ifc.execute() Data.load(IfcStore.get_file()) + UnitData.load(IfcStore.get_file()) print("Import finished in {:.2f} seconds".format(time.time() - start)) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 779d329d4f..4170f025b9 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -223,7 +223,7 @@ class BIM_PT_cost_schedules(Panel): op.cost_value = cost_value_id def draw_editable_cost_value_ui(self, layout, cost_value): - draw_attributes(self.props.cost_value_attributes, self.layout) + draw_attributes(self.props.cost_value_attributes, layout) class BIM_PT_cost_item_quantities(Panel): bl_label = "IFC Cost Item Quantities" diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py index 7b904a4b2d..d091e8bf2e 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py @@ -24,7 +24,7 @@ class BIM_PT_units(Panel): self.props = context.scene.BIMUnitProperties row = self.layout.row(align=True) - row.label(text="{} Units Found".format(len(Data.unit_assignment)), icon="SNAP_GRID") + row.label(text="{} Units Found".format(len(Data.units)), icon="SNAP_GRID") if self.props.is_editing: row.operator("bim.disable_unit_editing_ui", text="", icon="CANCEL") else: diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index 656f7716dc..2e362d302e 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -28,6 +28,8 @@ class Csv2Ifc: self.file = None self.cost_items = [] self.cost_schedule = None + self.is_schedule_of_rates = False + self.units = {} def execute(self): self.parse_csv() @@ -61,8 +63,8 @@ class Csv2Ifc: def get_row_cost_data(self, row): name = row[self.headers["Name"]] identification = row[self.headers["Identification"]] if "Identification" in self.headers else None - cost_quantities = row[self.headers["Quantity"]] - cost_quantities_unit = row[self.headers["Unit"]] + quantity = row[self.headers["Quantity"]] + unit = row[self.headers["Unit"]] if self.has_categories: cost_values = { k: float(row[v]) @@ -75,8 +77,8 @@ class Csv2Ifc: return { "Identification": str(identification) if identification else None, "Name": str(name) if name else None, - "CostQuantities": float(cost_quantities) if cost_quantities else None, - "CostQuantitiesUnit": str(cost_quantities_unit) if cost_quantities_unit else None, + "Quantity": float(quantity) if quantity else None, + "Unit": str(unit) if unit else None, "CostValues": cost_values, "children": [], } @@ -86,6 +88,8 @@ class Csv2Ifc: self.create_boilerplate_ifc() if not self.cost_schedule: self.cost_schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="CSV Import") + if self.is_schedule_of_rates: + self.cost_schedule.PredefinedType = "SCHEDULEOFRATES" self.create_cost_items(self.cost_items) def create_cost_items(self, cost_items, parent=None): @@ -94,9 +98,7 @@ class Csv2Ifc: def create_cost_item(self, cost_item, parent): if parent is None: - cost_item["ifc"] = ifcopenshell.api.run( - "cost.add_cost_item", self.file, cost_schedule=self.cost_schedule - ) + cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=self.cost_schedule) else: cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=parent) @@ -104,8 +106,9 @@ class Csv2Ifc: cost_item["ifc"].Identification = cost_item["Identification"] if not cost_item["CostValues"]: - cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) - cost_value.Category = "*" + if not self.is_schedule_of_rates: + cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) + cost_value.Category = "*" elif self.has_categories: for category, value in cost_item["CostValues"].items(): cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) @@ -114,14 +117,42 @@ class Csv2Ifc: else: cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) cost_value.AppliedValue = self.file.createIfcMonetaryMeasure(cost_item["CostValues"]) + if self.is_schedule_of_rates: + measure_class = ifcopenshell.util.unit.get_symbol_measure_class(cost_item["Unit"]) + value_component = self.file.create_entity(measure_class, cost_item["Quantity"]) + unit_component = None - if cost_item["CostQuantities"]: - quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["CostQuantitiesUnit"]) + if measure_class == "IfcNumericMeasure": + unit_component = self.create_unit(cost_item["Unit"]) + else: + unit_type = ifcopenshell.util.unit.get_measure_unit_type(measure_class) + unit_assignment = ifcopenshell.util.unit.get_unit_assignment(self.file) + if unit_assignment: + units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type] + if units: + unit_component = units[0] + if not unit_component: + unit_component = self.create_unit(cost_item["Unit"]) + + cost_value.UnitBasis = self.file.createIfcMeasureWithUnit(value_component, unit_component) + + if not self.is_schedule_of_rates and cost_item["Quantity"]: + quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["Unit"]) quantity = ifcopenshell.api.run( "cost.add_cost_item_quantity", self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class ) - quantity[3] = cost_item["CostQuantities"] + quantity[3] = cost_item["Quantity"] self.create_cost_items(cost_item["children"], cost_item["ifc"]) + def create_unit(self, symbol): + unit = self.units.get(symbol, None) + if unit: + return unit + unit = self.file.createIfcContextDependentUnit( + self.file.createIfcDimensionalExponents(0, 0, 0, 0, 0, 0, 0), "USERDEFINED", symbol + ) + self.units[symbol] = unit + return unit + def create_boilerplate_ifc(self): self.file = ifcopenshell.file(schema="IFC4") diff --git a/src/ifc5d/rates.csv b/src/ifc5d/rates.csv new file mode 100644 index 0000000000..824084c52b --- /dev/null +++ b/src/ifc5d/rates.csv @@ -0,0 +1,14 @@ +"Hierarchy","Identification","Name","Quantity","Unit","Value" +1,1,"Rates",,, +,,,,, +2,1.1,"Category A",,, +,,,,, +3,"1.1.1","Cleaning",1,"m",5 +3,"1.1.2","Hiring",1,"m2",6 +3,"1.1.3","Building",1,"m3",7 +,,,,, +2,1.2,"Category B",,, +,,,,, +3,"1.2.1","Cleaning",1,"hr",8 +3,"1.2.2","Hiring",2,"bob",9 +3,"1.2.3","Building",3,"kg",10 diff --git a/src/ifc5d/test.csv b/src/ifc5d/schedule.csv similarity index 100% rename from src/ifc5d/test.csv rename to src/ifc5d/schedule.csv diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py index 692ccc958b..48bbf4411f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py @@ -16,18 +16,13 @@ class Usecase: # TODO: support all applied value select types value = self.file.createIfcMonetaryMeasure(value) elif name == "UnitBasis": - self.remove_existing_unit_basis() + old_unit_basis = self.settings["cost_value"].UnitBasis if value: value_component = self.file.create_entity( - ifcopenshell.util.unit.get_unit_measure_type(value["UnitComponent"].UnitType), + ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType), value["ValueComponent"], ) value = self.file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"]) + if old_unit_basis and len(self.file.get_inverse(old_unit_basis)) == 0: + ifcopenshell.util.element.remove_deep(self.file, old_unit_basis) setattr(self.settings["cost_value"], name, value) - - def remove_existing_unit_basis(self): - if ( - self.settings["cost_value"].UnitBasis - and len(self.file.get_inverse(self.settings["cost_value"].UnitBasis)) == 1 - ): - ifcopenshell.util.element.remove_deep(self.file, self.settings["cost_value"].UnitBasis) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 36ed4cd0e6..10992888b7 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -246,45 +246,79 @@ def get_named_dimensions(name): return named_dimensions.get(name, (0, 0, 0, 0, 0, 0, 0)) +def get_unit_assignment(ifc_file): + unit_assignments = ifc_file.by_type("IfcUnitAssignment") + if unit_assignments: + return unit_assignments[0] + + def get_property_unit(prop, ifc_file): unit = getattr(prop, "Unit", None) if unit: return unit - unit_assignment = ifc_file.by_type("IfcUnitAssignment") + unit_assignment = get_unit_assignment(ifc_file) if not unit_assignment: return entity = prop.wrapped_data.declaration().as_entity() if prop.is_a("IfcPhysicalSimpleQuantity"): - measure_type = entity.attribute_by_index(3).type_of_attribute().declared_type().name() + measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name() elif prop.is_a("IfcPropertySingleValue") and prop.NominalValue: - measure_type = prop.NominalValue.is_a() - for text in ("Ifc", "Measure", "Non", "Positive", "Negative"): - measure_type = measure_type.replace(text, "") - measure_type = measure_type.upper() + "UNIT" - units = [u for u in unit_assignment[0].Units if getattr(u, "UnitType", None) == measure_type] + measure_class = prop.NominalValue.is_a() + unit_type = get_measure_unit_type(measure_class) + units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type] if units: return units[0] -def get_unit_measure_type(unit_type): +def get_unit_measure_class(unit_type): + if unit_type == "USERDEFINED": + # See https://github.com/buildingSMART/IFC4.3.x-development/issues/71 + return "IfcNumericMeasure" return "Ifc" + unit_type[0:-4].lower().capitalize() + "Measure" +def get_measure_unit_type(measure_class): + if measure_class == "IfcNumericMeasure": + # See https://github.com/buildingSMART/IFC4.3.x-development/issues/71 + return "USERDEFINED" + for text in ("Ifc", "Measure", "Non", "Positive", "Negative"): + measure_class = measure_class.replace(text, "") + return measure_class.upper() + "UNIT" + + +def get_symbol_measure_class(symbol): + # Dumb, but everybody gets it, unlike regex golf + if not symbol: + return "IfcNumericMeasure" + symbol = symbol.lower() + if symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]: + return "IfcLengthMeasure" + elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]: + return "IfcAreaMeasure" + elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]: + return "IfcVolumeMeasure" + elif symbol in ["kg", "g", "mt", "kt", "t"]: + return "IfcMassMeasure" + elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]: + return "IfcTimeMeasure" + return "IfcNumericMeasure" + + def get_symbol_quantity_class(symbol): # Dumb, but everybody gets it, unlike regex golf if not symbol: return "IfcQuantityCount" symbol = symbol.lower() - if symbol in ["kg", "g", "mt", "kt", "t"]: + if symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]: + return "IfcQuantityLength" + elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]: + return "IfcQuantityArea" + elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]: + return "IfcQuantityVolume" + elif symbol in ["kg", "g", "mt", "kt", "t"]: return "IfcQuantityWeight" elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]: return "IfcQuantityTime" - elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]: - return "IfcQuantityVolume" - elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]: - return "IfcQuantityArea" - elif symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]: - return "IfcQuantityLength" return "IfcQuantityCount" From 6b7b81765c539c7acac3fdd461f4154dffe53101 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 15:58:44 +1000 Subject: [PATCH 10/72] You can now add and edit context dependent units including dimensional exponents --- .../blenderbim/bim/module/unit/__init__.py | 1 + .../blenderbim/bim/module/unit/operator.py | 47 +++++++++++++++++-- .../blenderbim/bim/module/unit/ui.py | 3 +- .../api/unit/add_context_dependent_unit.py | 14 ++++++ .../ifcopenshell/api/unit/edit_named_unit.py | 8 ++++ .../ifcopenshell/api/unit/remove_unit.py | 15 +++--- 6 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/unit/add_context_dependent_unit.py diff --git a/src/blenderbim/blenderbim/bim/module/unit/__init__.py b/src/blenderbim/blenderbim/bim/module/unit/__init__.py index a74eb70da1..c4fa9cf228 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/unit/__init__.py @@ -9,6 +9,7 @@ classes = ( operator.RemoveUnit, operator.AddMonetaryUnit, operator.AddSIUnit, + operator.AddContextDependentUnit, operator.EnableEditingUnit, operator.DisableEditingUnit, operator.EditUnit, diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py index 9a4f3bfc00..2c9664c870 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py @@ -1,4 +1,5 @@ import bpy +import json import ifcopenshell.api import ifcopenshell.util.unit import blenderbim.bim.helper @@ -174,7 +175,7 @@ class AddSIUnit(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMUnitProperties self.file = IfcStore.get_file() - unit = ifcopenshell.api.run( + ifcopenshell.api.run( "unit.add_si_unit", self.file, unit_type=props.named_unit_types, @@ -185,6 +186,26 @@ class AddSIUnit(bpy.types.Operator): return {"FINISHED"} +class AddContextDependentUnit(bpy.types.Operator): + bl_idname = "bim.add_context_dependent_unit" + bl_label = "Add Context Dependent Unit" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMUnitProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "unit.add_context_dependent_unit", self.file, unit_type=props.named_unit_types, name="THINGAMAJIG" + ) + Data.load(self.file) + bpy.ops.bim.load_units() + return {"FINISHED"} + + + class EnableEditingUnit(bpy.types.Operator): bl_idname = "bim.enable_editing_unit" bl_label = "Enable Editing Unit" @@ -195,13 +216,21 @@ class EnableEditingUnit(bpy.types.Operator): props = context.scene.BIMUnitProperties while len(props.unit_attributes) > 0: props.unit_attributes.remove(0) - data = Data.units[self.unit] - - blenderbim.bim.helper.import_attributes(data["type"], props.unit_attributes, data) + blenderbim.bim.helper.import_attributes(data["type"], props.unit_attributes, data, self.import_attributes) props.active_unit_id = self.unit return {"FINISHED"} + def import_attributes(self, name, prop, data): + if name == "Dimensions": + new = bpy.context.scene.BIMUnitProperties.unit_attributes.add() + new.name = name + new.is_null = data[name] is None + new.is_optional = False + new.data_type = "string" + new.string_value = json.dumps([e for e in IfcStore.get_file().by_id(data["id"]).Dimensions]) + return True + class DisableEditingUnit(bpy.types.Operator): bl_idname = "bim.disable_editing_unit" @@ -223,7 +252,7 @@ class EditUnit(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMUnitProperties - attributes = blenderbim.bim.helper.export_attributes(props.unit_attributes) + attributes = blenderbim.bim.helper.export_attributes(props.unit_attributes, self.export_attributes) self.file = IfcStore.get_file() unit = self.file.by_id(props.active_unit_id) if unit.is_a("IfcMonetaryUnit"): @@ -235,3 +264,11 @@ class EditUnit(bpy.types.Operator): Data.load(IfcStore.get_file()) bpy.ops.bim.load_units() return {"FINISHED"} + + def export_attributes(self, attributes, prop): + if prop.name == "Dimensions": + try: + attributes[prop.name] = json.loads(prop.get_value()) + except: + attributes[prop.name] = (0, 0, 0, 0, 0, 0, 0) + return True diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py index d091e8bf2e..c31f9ff179 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py @@ -43,8 +43,9 @@ class BIM_PT_units(Panel): elif self.props.unit_classes == "IfcSIUnit": row.prop(self.props, "named_unit_types", text="") row.operator("bim.add_si_unit", text="", icon="ADD") - else: + elif self.props.unit_classes == "IfcContextDependentUnit": row.prop(self.props, "named_unit_types", text="") + row.operator("bim.add_context_dependent_unit", text="", icon="ADD") self.layout.template_list( "BIM_UL_units", diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/add_context_dependent_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/add_context_dependent_unit.py new file mode 100644 index 0000000000..5d6d8023a5 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/add_context_dependent_unit.py @@ -0,0 +1,14 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"unit_type": "USERDEFINED", "name": "THINGAMAJIG", "dimensions": (0, 0, 0, 0, 0, 0, 0)} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + return self.file.create_entity( + "IfcContextDependentUnit", + Dimensions=self.file.createIfcDimensionalExponents(*self.settings["dimensions"]), + UnitType=self.settings["unit_type"], + Name=self.settings["name"], + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py index 65f8cc871e..292b528675 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py @@ -7,4 +7,12 @@ class Usecase: def execute(self): for name, value in self.settings["attributes"].items(): + if name == "Dimensions": + dimensions = self.settings["unit"].Dimensions + if len(self.file.get_inverse(dimensions)) > 1: + self.settings["unit"].Dimensions = self.file.createIfcDimensionalExponents(*value) + else: + for i, exponent in enumerate(value): + dimensions[i] = exponent + continue setattr(self.settings["unit"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py index 949bf919e5..ceb8d7d401 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py @@ -1,3 +1,4 @@ +import ifcopenshell.util.unit import ifcopenshell.util.element @@ -9,10 +10,12 @@ class Usecase(): self.settings[key] = value def execute(self): - unit_assignment = self.file.by_type("IfcUnitAssignment")[0] - units = list(unit_assignment.Units) - units.remove(self.settings["unit"]) - if not units: - return - unit_assignment.Units = units + unit_assignment = ifcopenshell.util.unit.get_unit_assignment(self.file) + if unit_assignment and self.settings["unit"] in unit_assignment.Units: + units = list(unit_assignment.Units) + units.remove(self.settings["unit"]) + if units: + unit_assignment.Units = units + else: + self.file.remove(unit_assignment) ifcopenshell.util.element.remove_deep(self.file, self.settings["unit"]) From f040dbcb6c10b520b5f26a31781715379485b6bf Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 17:46:10 +1000 Subject: [PATCH 11/72] You can now assign / unassign cost items in a schedule of rates to type products --- .../blenderbim/bim/module/cost/__init__.py | 6 + .../blenderbim/bim/module/cost/operator.py | 87 ++++++++++++++ .../blenderbim/bim/module/cost/prop.py | 7 ++ .../blenderbim/bim/module/cost/ui.py | 108 +++++++++++++++++- 4 files changed, 207 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index fa370240d8..885fcb2060 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -26,6 +26,8 @@ classes = ( operator.ExpandCostItem, operator.ContractCostItem, operator.RemoveCostItem, + operator.AssignCostItemType, + operator.UnassignCostItemType, operator.AssignCostItemQuantity, operator.UnassignCostItemQuantity, operator.AddCostItemQuantity, @@ -37,14 +39,18 @@ classes = ( operator.SelectCostScheduleProducts, operator.ImportCostScheduleCsv, operator.LoadCostItemQuantities, + operator.LoadCostItemTypes, prop.CostItem, prop.CostItemQuantity, + prop.CostItemType, prop.BIMCostProperties, ui.BIM_PT_cost_schedules, ui.BIM_PT_cost_item_quantities, + ui.BIM_PT_cost_item_types, ui.BIM_UL_cost_items, ui.BIM_UL_cost_columns, ui.BIM_UL_cost_item_quantities, + ui.BIM_UL_cost_item_types, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 08563d8c14..be7248a4e6 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -303,6 +303,63 @@ class EditCostItem(bpy.types.Operator): return {"FINISHED"} +class AssignCostItemType(bpy.types.Operator): + bl_idname = "bim.assign_cost_item_type" + bl_label = "Assign Cost Item Type Product" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + prop_name: bpy.props.StringProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + self.file = IfcStore.get_file() + cost_item = self.file.by_id(self.cost_item) + for obj in context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + if product.is_a("IfcTypeProduct"): + ifcopenshell.api.run( + "control.assign_control", self.file, relating_control=cost_item, related_object=product + ) + Data.load(self.file) + bpy.ops.bim.load_cost_item_types() + return {"FINISHED"} + + +class UnassignCostItemType(bpy.types.Operator): + bl_idname = "bim.unassign_cost_item_type" + bl_label = "Unassign Cost Item Type" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + related_object: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + self.file = IfcStore.get_file() + cost_item = self.file.by_id(self.cost_item) + if self.related_object: + products = [self.file.by_id(self.related_object)] + else: + for obj in context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + if product.is_a("IfcTypeProduct"): + products.append(product) + for product in products: + ifcopenshell.api.run( + "control.unassign_control", self.file, relating_control=cost_item, related_object=product + ) + Data.load(self.file) + bpy.ops.bim.load_cost_item_types() + return {"FINISHED"} + + class AssignCostItemQuantity(bpy.types.Operator): bl_idname = "bim.assign_cost_item_quantity" bl_label = "Assign Cost Item Quantity" @@ -806,3 +863,33 @@ class LoadCostItemQuantities(bpy.types.Operator): total_quantity += self.file.by_id(quantity_id)[3] new.total_quantity = total_quantity return {"FINISHED"} + + +class LoadCostItemTypes(bpy.types.Operator): + bl_idname = "bim.load_cost_item_types" + bl_label = "Load Cost Item Types" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + self.props = context.scene.BIMCostProperties + self.file = IfcStore.get_file() + while len(self.props.cost_item_type_products) > 0: + self.props.cost_item_type_products.remove(0) + # TODO implement process and resource types + #while len(self.props.cost_item_processes) > 0: + # self.props.cost_item_processes.remove(0) + #while len(self.props.cost_item_resources) > 0: + # self.props.cost_item_resources.remove(0) + ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id + for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items(): + related_object = self.file.by_id(control_id) + if related_object.is_a("IfcTypeProduct"): + new = self.props.cost_item_type_products.add() + # TODO implement process and resource types + #elif related_object.is_a("IfcProcess"): + # new = self.props.cost_item_processes.add() + #elif related_object.is_a("IfcResource"): + # new = self.props.cost_item_resources.add() + new.ifc_definition_id = control_id + new.name = related_object.Name or "Unnamed" + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index d57d373960..63253a4fe6 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -175,6 +175,11 @@ class CostItemQuantity(PropertyGroup): total_quantity: FloatProperty(name="Total Quantity") +class CostItemType(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + class BIMCostProperties(PropertyGroup): is_cost_update_enabled: BoolProperty(name="Is Cost Update Enabled", default=True) cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute) @@ -213,3 +218,5 @@ class BIMCostProperties(PropertyGroup): active_cost_item_process_index: IntProperty(name="Active Cost Item Process Index") cost_item_resources: CollectionProperty(name="Cost Item Resources", type=CostItemQuantity) active_cost_item_resource_index: IntProperty(name="Active Cost Item Resource Index") + cost_item_type_products: CollectionProperty(name="Cost Item Type Products", type=CostItemType) + active_cost_item_type_product_index: IntProperty(name="Active Cost Item TYpe Product Index") diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 4170f025b9..9a87d0dd24 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -225,6 +225,93 @@ class BIM_PT_cost_schedules(Panel): def draw_editable_cost_value_ui(self, layout, cost_value): draw_attributes(self.props.cost_value_attributes, layout) + +class BIM_PT_cost_item_types(Panel): + bl_label = "IFC Cost Item Types" + bl_idname = "BIM_PT_cost_item_types" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_cost_schedules" + + @classmethod + def poll(cls, context): + props = context.scene.BIMCostProperties + total_cost_items = len(props.cost_items) + if not props.active_cost_schedule_id: + return False + if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES": + return False + if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: + return True + return False + + def draw(self, context): + self.props = context.scene.BIMCostProperties + + cost_item = self.props.cost_items[self.props.active_cost_item_index] + + grid = self.layout.grid_flow(columns=3, even_columns=True) + + # Column1 + col = grid.column() + + row2 = col.row(align=True) + row2.label(text="Elements") + op = row2.operator("bim.assign_cost_item_type", text="", icon="ADD") + op.cost_item = cost_item.ifc_definition_id + op = row2.operator("bim.unassign_cost_item_type", text="", icon="REMOVE") + op.cost_item = cost_item.ifc_definition_id + op.related_object = 0 + op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") + op.cost_item = cost_item.ifc_definition_id + + row2 = col.row() + row2.template_list( + "BIM_UL_cost_item_types", + "", + self.props, + "cost_item_type_products", + self.props, + "active_cost_item_type_product_index", + ) + + # Column2 + # TODO + col = grid.column() + + row2 = col.row(align=True) + row2.label(text="Tasks") + + row2 = col.row() + row2.template_list( + "BIM_UL_cost_item_quantities", + "", + self.props, + "cost_item_processes", + self.props, + "active_cost_item_process_index", + ) + + # Column3 + # TODO + col = grid.column() + + row2 = col.row(align=True) + row2.label(text="Resources") + + row2 = col.row() + row2.template_list( + "BIM_UL_cost_item_quantities", + "", + self.props, + "cost_item_resources", + self.props, + "active_cost_item_resource_index", + ) + + class BIM_PT_cost_item_quantities(Panel): bl_label = "IFC Cost Item Quantities" bl_idname = "BIM_PT_cost_item_quantities" @@ -238,6 +325,10 @@ class BIM_PT_cost_item_quantities(Panel): def poll(cls, context): props = context.scene.BIMCostProperties total_cost_items = len(props.cost_items) + if not props.active_cost_schedule_id: + return False + if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + return False if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: return True return False @@ -355,7 +446,9 @@ class BIM_UL_cost_items(UIList): split2.prop(item, "name", emboss=False, text="") if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES": - split2.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") + split2.label( + text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})" + ) split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"])) @@ -383,6 +476,19 @@ class BIM_UL_cost_columns(UIList): row.operator("bim.remove_cost_column", text="", icon="X").name = item.name +class BIM_UL_cost_item_types(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + props = context.scene.BIMCostProperties + cost_item = props.cost_items[props.active_cost_item_index] + + if item: + row = layout.row(align=True) + row.label(text=item.name) + op = row.operator("bim.unassign_cost_item_type", text="", icon="X") + op.cost_item = cost_item.ifc_definition_id + op.related_object = item.ifc_definition_id + + class BIM_UL_cost_item_quantities(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): props = context.scene.BIMCostProperties From 21c975540db4c897d70dbe7c5c5c6e288f76c41e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 18:45:44 +1000 Subject: [PATCH 12/72] Fix #1658. Fix bug where changing slab layer thicknesses didn't work if it didn't have a solidify modifier already. --- .../blenderbim/bim/module/model/slab.py | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 499ecba7af..79fb98b385 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -34,26 +34,7 @@ def mode_callback(obj, data): return if obj.mode == "EDIT": IfcStore.edited_objs.add(obj) - modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"] - if modifier: - return - depth = obj.dimensions.z - bm = bmesh.from_edit_mesh(obj.data) - bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) - bm.faces.ensure_lookup_table() - non_bottom_faces = [] - for face in bm.faces: - if face.normal.z > -0.9: - non_bottom_faces.append(face) - else: - face.normal_flip() - bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES") - bmesh.update_edit_mesh(obj.data) - bm.free() - modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY") - modifier.use_even_offset = True - modifier.offset = 1 - modifier.thickness = depth + ensure_solidify_modifier(obj) else: new_origin = obj.matrix_world @ Vector(obj.bound_box[0]) obj.data.transform( @@ -64,6 +45,41 @@ def mode_callback(obj, data): obj.matrix_world.translation = new_origin +def ensure_solidify_modifier(obj): + modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"] + if modifier: + return modifier[0] + depth = obj.dimensions.z + + if obj.mode == "EDIT": + bm = bmesh.from_edit_mesh(obj.data) + else: + bm = bmesh.new() + bm.from_mesh(obj.data) + + bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) + bm.faces.ensure_lookup_table() + non_bottom_faces = [] + for face in bm.faces: + if face.normal.z > -0.9: + non_bottom_faces.append(face) + else: + face.normal_flip() + bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES") + + if obj.mode == "EDIT": + bmesh.update_edit_mesh(obj.data) + else: + bm.to_mesh(obj.data) + + bm.free() + modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY") + modifier.use_even_offset = True + modifier.offset = 1 + modifier.thickness = depth + return modifier + + def ensure_solid(usecase_path, ifc_file, settings): product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id) parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric") @@ -326,10 +342,6 @@ class DumbSlabPlaner: if round(delta_thickness, 2) == 0: return - modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"] - if modifier: - modifier = modifier[0] - else: - pass + modifier = ensure_solidify_modifier(obj) modifier.thickness += delta_thickness obj.location[2] -= delta_thickness From be08d0ad1bcfc40122a034e992acea54c33ba31d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 19:41:30 +1000 Subject: [PATCH 13/72] Fix #1657. --- src/blenderbim/blenderbim/bim/module/model/wall.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index ebc556fabe..5adeea5b23 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -938,11 +938,11 @@ class DumbWallPlaner: for edge in vert.link_edges: other_vert = edge.verts[1] if edge.verts[0] == vert else edge.verts[0] if delta_thickness > 0: - potential_slide_vector = vert.co - other_vert.co + potential_slide_vector = (vert.co - other_vert.co).normalized() if potential_slide_vector.y < 0: continue else: - potential_slide_vector = other_vert.co - vert.co + potential_slide_vector = (other_vert.co - vert.co).normalized() if potential_slide_vector.y > 0: continue if abs(potential_slide_vector.x) > 0.9 or abs(potential_slide_vector.z) > 0.9: From ee0d594f9f557fbbc4ffd22f2d2a1df30e39912b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 20:34:28 +1000 Subject: [PATCH 14/72] WIP window tool progress. --- .../blenderbim/bim/module/model/opening.py | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index c7bd2008e7..dfbbc37aad 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -77,7 +77,7 @@ class AddElementOpening(bpy.types.Operator): return {"FINISHED"} if filling_obj: - opening = self.generate_opening_from_filling(filling_obj) + opening = self.generate_opening_from_filling(filling_obj, voided_obj) else: # The opening shall be based on the smallest bounding dimension of the element dimension = min(voided_obj.dimensions) @@ -121,18 +121,42 @@ class AddElementOpening(bpy.types.Operator): if obj and obj.BIMObjectProperties.ifc_definition_id: return obj - def generate_opening_from_filling(self, filling_obj): + def generate_opening_from_filling(self, filling_obj, voided_obj): x, y, z = filling_obj.dimensions - verts = [ - Vector((0, 0, 0)), - Vector((0, 0, z)), - Vector((0, y, 0)), - Vector((0, y, z)), - Vector((x, 0, 0)), - Vector((x, 0, z)), - Vector((x, y, 0)), - Vector((x, y, z)), - ] + dimension = min(voided_obj.dimensions) + if dimension == voided_obj.dimensions[0]: + verts = [ + Vector((-0.1, 0, 0)), + Vector((-0.1, 0, z)), + Vector((-0.1, y, 0)), + Vector((-0.1, y, z)), + Vector((dimension+0.1, 0, 0)), + Vector((dimension+0.1, 0, z)), + Vector((dimension+0.1, y, 0)), + Vector((dimension+0.1, y, z)), + ] + elif dimension == voided_obj.dimensions[1]: + verts = [ + Vector((0, -0.1, 0)), + Vector((0, -0.1, z)), + Vector((0, dimension+0.1, 0)), + Vector((0, dimension+0.1, z)), + Vector((x, -0.1, 0)), + Vector((x, -0.1, z)), + Vector((x, dimension+0.1, 0)), + Vector((x, dimension+0.1, z)), + ] + elif dimension == voided_obj.dimensions[2]: + verts = [ + Vector((0, 0, -0.1)), + Vector((0, 0, dimension+0.1)), + Vector((0, y, -0.1)), + Vector((0, y, dimension+0.1)), + Vector((x, 0, -0.1)), + Vector((x, 0, dimension+0.1)), + Vector((x, y, -0.1)), + Vector((x, y, dimension+0.1)), + ] edges = [] faces = [ [0, 1, 3, 2], @@ -146,6 +170,10 @@ class AddElementOpening(bpy.types.Operator): mesh.from_pydata(verts, edges, faces) obj = bpy.data.objects.new("Opening", mesh) obj.matrix_world = filling_obj.matrix_world + + filling_obj.rotation_euler = voided_obj.rotation_euler + obj.parent = filling_obj + obj.matrix_parent_inverse = filling_obj.matrix_world.inverted() return obj From 1b4d0bf57748fce66f21c3944aa80279e990fb5e Mon Sep 17 00:00:00 2001 From: Prabhat Singh <59395410+TestPrab@users.noreply.github.com> Date: Tue, 17 Aug 2021 03:18:55 +0530 Subject: [PATCH 15/72] updated routes (#1661) --- src/bcf/bcf/v3/bcfapi.py | 20 +- .../bcfserver/bcf/project.json | 373 +++++++ src/foundationserver/bcfserver/bcf/routes.py | 916 +++++++++--------- 3 files changed, 820 insertions(+), 489 deletions(-) diff --git a/src/bcf/bcf/v3/bcfapi.py b/src/bcf/bcf/v3/bcfapi.py index 7d54ee07c5..218caab3db 100644 --- a/src/bcf/bcf/v3/bcfapi.py +++ b/src/bcf/bcf/v3/bcfapi.py @@ -141,33 +141,43 @@ class BcfClient: def get(self, endpoint, params=None, is_auth_required=False): # TODO: handle error http status codes and raise exception. Follow error.json standard. headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()} - return requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None).json() + response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"{response.status_code}, {response.text}") def post(self, endpoint, data=None, params=None): headers = { "Authorization": "Bearer " + self.foundation_client.get_access_token(), "Content-type": "application/json", } - resp = requests.post( + response = requests.post( f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None, ) - return resp.status_code, resp.text + if response.status_code == 201: + return response.status_code, response.text + else: + raise Exception(f"{response.status_code}, {response.text}") def put(self, endpoint, data=None, params=None): headers = { "Authorization": "Bearer " + self.foundation_client.get_access_token(), "Content-type": "application/json", } - resp = requests.put( + response = requests.put( f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None, ) - return resp.status_code, resp.text + if response.status_code == 200: + return response.status_code, response.text + else: + raise Exception(f"{response.status_code}, {response.text}") def delete(self, endpoint, params=None): headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()} diff --git a/src/foundationserver/bcfserver/bcf/project.json b/src/foundationserver/bcfserver/bcf/project.json index d1be22bd72..8033bc5933 100644 --- a/src/foundationserver/bcfserver/bcf/project.json +++ b/src/foundationserver/bcfserver/bcf/project.json @@ -67,5 +67,378 @@ "comment_actions": [ "update" ] +}], +"Topics": [{ + "guid": "A245F4F2-2C01-B43B-B612-5E456BEF8116", + "server_assigned_id": "ISSUE-00001", + "creation_author": "Architect@example.com", + "title": "Example topic 1", + "labels": [ + "Architecture", + "Structural" + ], + "creation_date": "2013-10-21T17:34:22.409Z" +}, { + "guid": "A211FCC2-3A3B-EAA4-C321-DE22ABC8414", + "server_assigned_id": "ISSUE-00078", + "creation_author": "Architect@example.com", + "title": "Example topic 2", + "labels": [ + "Architecture", + "Heating", + "Electrical" + ], + "creation_date": "2014-11-19T14:24:11.316Z" +}, +{ + "guid": "B345F4F2-3A04-B43B-A713-5E456BEF8228", + "server_assigned_id": "ISSUE-00549", + "creation_author": "Architect@example.com", + "creation_date": "2016-08-01T17:34:22.409Z", + "topic_type": "Clash", + "topic_status": "open", + "title": "Example topic 3", + "priority": "high", + "labels": [ + "Architecture", + "Heating" + ], + "assigned_to": "harry.muster@example.com", + "bim_snippet": { + "snippet_type": "clash", + "is_external": true, + "reference": "https://example.com/bcf/1.0/ADFE23AA11BCFF444122BB", + "reference_schema": "https://example.com/bcf/1.0/clash.xsd" + }, + "authorization": { + "topic_actions": [ + "createComment", + "createViewpoint" + ] + } +} +], +"Files":[{ + "display_information": [{ + "field_display_name": "Model Name", + "field_value": "ARCH-Z100-051" + }, { + "field_display_name": "Revision Date", + "field_value": "May 3 2020" + }], + "file": { + "ifc_project": "0J$yPqHBD12v72y4qF6XcD", + "file_name": "OfficeBuilding_Architecture_0001.ifc", + "reference": "https://example.com/files/0J$yPqHBD12v72y4qF6XcD_0001.ifc" + } +}, { + "display_information": [{ + "field_display_name": "Model Name", + "field_value": "MEP-Z100-015" + }, { + "field_display_name": "Revision Date", + "field_value": "Apr 30 2020" + }], + "file": { + "ifc_project": "3hwBHP91jBRwPsmyf$3Hea", + "file_name": "OfficeBuilding_Heating_0003.ifc", + "reference": "cf37bae6-0900-46be-b37f-b34754fe0b4a" + } +}], +"Comments":[ + { + "guid": "C4215F4D-AC45-A43A-D615-AA456BEF832B", + "date": "2016-08-01T12:34:22.409Z", + "author": "max.muster@example.com", + "comment": "Clash found", + "topic_guid": "B345F4F2-3A04-B43B-A713-5E456BEF8228", + "authorization": { + "comment_actions": [ + "update" + ] + } + }, + { + "guid": "A333FCA8-1A31-CAAC-A321-BB33ABC8414", + "date": "2016-08-01T14:24:11.316Z", + "author": "bob.heater@example.com", + "comment": "will rework the heating model", + "topic_guid": "B345F4F2-3A04-B43B-A713-5E456BEF8228" + } + ], +"RelatedTopics":[{ + "related_topic_guid": "db49df2b-0e42-473b-a3ee-f7b785d783c4" +}, { + "related_topic_guid": "6963a846-54d1-4050-954d-607cd5e48aa3" +}], +"Viewpoints":[{ + "guid": "b24a82e9-f67b-43b8-bda0-4946abf39624", + "perspective_camera": { + "camera_view_point": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "camera_direction": { + "x": 1.0, + "y": 1.0, + "z": 2.0 + }, + "camera_up_vector": { + "x": 0.0, + "y": 0.0, + "z": 1.0 + }, + "field_of_view": 90.0, + "aspect_ratio": 1.33 + }, + "lines": [{ + "start_point": { + "x": 2.0, + "y": 1.0, + "z": 1.0 + }, + "end_point": { + "x": 0.0, + "y": 1.0, + "z": 0.7 + } + }], + "clipping_planes": [{ + "location": { + "x": 0.7, + "y": 0.3, + "z": -0.2 + }, + "direction": { + "x": 1.0, + "y": 0.4, + "z": 0.1 + } + }] +}, { + "guid": "a11a82e7-e66c-34b4-ada1-5846abf39133", + "perspective_camera": { + "camera_view_point": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "camera_direction": { + "x": 1.0, + "y": 1.0, + "z": 2.0 + }, + "camera_up_vector": { + "x": 0.0, + "y": 0.0, + "z": 1.0 + }, + "field_of_view": 90.0, + "aspect_ratio": 1.33 + }, + "lines": [{ + "start_point": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + }, + "end_point": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }], + "clipping_planes": [{ + "location": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "direction": { + "x": 1.0, + "y": 0.0, + "z": 0.0 + } + }], + "coloring": [ + { + "color": "#ff0000", + "components": [ + { + "ifc_guid": "2MF28NhmDBiRVyFakgdbCT", + "originating_system": "Example CAD Application", + "authoring_tool_id": "EXCAD/v1.0" + }, + { + "ifc_guid": "3$cshxZO9AJBebsni$z9Yk" + } + ] + } + ], + "visibility": { + "default_visibility": true, + "exceptions": [ + { + "ifc_guid": "2MF28NhmDBiRVyFakgdbCT", + "originating_system": "Example CAD Application", + "authoring_tool_id": "EXCAD/v1.0" + }, + { + "ifc_guid": "3$cshxZO9AJBebsni$z9Yk" + } + ], + "view_setup_hints": { + "spaces_visible": true, + "space_boundaries_visible": false, + "openings_visible": true + } + } +}, +{ + "guid": "a11a82e7-e66c-34b4-ada1-5846abf39133", + "index": 10, + "perspective_camera": { + "camera_view_point": { + "x": 0, + "y": 0, + "z": 0 + }, + "camera_direction": { + "x": 1, + "y": 1, + "z": 2 + }, + "camera_up_vector": { + "x": 0, + "y": 0, + "z": 1 + }, + "field_of_view": 90, + "aspect_ratio": 1.33 + }, + "lines": [ + { + "start_point": { + "x": 1, + "y": 1, + "z": 1 + }, + "end_point": { + "x": 0, + "y": 0, + "z": 0 + } + } + ], + "clipping_planes": [ + { + "location": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "direction": { + "x": 1, + "y": 0, + "z": 0 + } + } + ], + "bitmaps": [ + { + "guid": "20c1cb56-315f-4a0a-922d-ed7a4a8edf55", + "bitmap_type": "jpg", + "location": { + "x": 10, + "y": -10, + "z": 7 + }, + "normal": { + "x": -1, + "y": 1.25, + "z": 0 + }, + "up": { + "x": -5.4, + "y": -4.3, + "z": 1 + }, + "height": 1666 + } + ], + "snapshot": { + "snapshot_type": "png" + }, + "selection": [ + { + "ifc_guid": "2MF28NhmDBiRVyFakgdbCT", + "originating_system": "Example CAD Application", + "authoring_tool_id": "EXCAD/v1.0" + }, + { + "ifc_guid": "3$cshxZO9AJBebsni$z9Yk" + } + ] + }], +"DocumentReferences":[{ + "guid": "212ab37a-6122-448e-86fc-86503183b520", + "url": "http://example.com/files/LegalRequirements.pdf", + "description": "The legal requirements for buildings." +}, { + "guid": "6cbfe31d-95c3-4f4d-92a6-420c23698721", + "document_guid": "472ab37a-6122-448e-86fc-86503183b520", + "description": "The building owners global design parameters for buildings." +}], +"Documents":[{ + "guid": "472ab37a-6122-448e-86fc-86503183b520", + "filename": "LegalRequirements.pdf" +}, { + "guid": "6cbfe31d-95c3-4f4d-92a6-420c23698721", + "filename": "DesignParameters.pdf" +}], +"Events": [{ + "topic_guid": "A211FCC2-3A3B-EAA4-C321-DE22ABC8414", + "date": "2014-11-19T14:24:11.316Z", + "author": "Architect@example.com", + "events": [ + { + "type": "status_updated", + "value": "Closed" + } + ] +}, { + "topic_guid": "A245F4F2-2C01-B43B-B612-5E456BEF8116", + "date": "2013-10-21T17:34:22.409Z", + "author": "Architect@example.com", + "events": [ + { + "type": "type_updated", + "value": "Warning" + } + ] +}], +"EventComments":[{ + "comment_guid": "C4215F4D-AC45-A43A-D615-AA456BEF832B", + "topic_guid": "A211FCC2-3A3B-EAA4-C321-DE22ABC8414", + "date": "2014-11-19T14:24:11.316Z", + "author": "Architect@example.com", + "events": [ + { + "type": "comment_created", + "value": null + } + ] +}, { + "comment_guid": "C4215F4D-AC45-A43A-D615-AA456BEF832B", + "topic_guid": "A245F4F2-2C01-B43B-B612-5E456BEF8116", + "date": "2013-10-21T17:34:22.409Z", + "author": "Architect@example.com", + "events": [ + { + "type": "viewpoint_updated", + "value": "b24a82e9-f67b-43b8-bda0-4946abf39624" + } + ] }] } \ No newline at end of file diff --git a/src/foundationserver/bcfserver/bcf/routes.py b/src/foundationserver/bcfserver/bcf/routes.py index 9f11847e6a..699aa8011b 100644 --- a/src/foundationserver/bcfserver/bcf/routes.py +++ b/src/foundationserver/bcfserver/bcf/routes.py @@ -26,20 +26,19 @@ def validate_client(request): def invalid_user(): - response = jsonify({"error": "User not recognized"}) + response = jsonify({"message": "User not recognized"}) response.status = 401 return response def invalid_project(): response = jsonify({"message": "Project not found"}) - response.status = 404 + response.status_code = 404 return response @bcf.route("/projects") def projects(): - return invalid_user() if validate_client(request): return jsonify(jdata["Projects"]) return invalid_user() @@ -63,22 +62,15 @@ def project_details(project_id): @bcf.route("/projects/", methods=["PUT"]) def update_project(project_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + response = jsonify(data) return response - response = invalid_project() - return response + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//extensions") @@ -93,428 +85,433 @@ def extensions(project_id): @bcf.route("/projects//topics") def topics(project_id): - access_token = validate_client(request) - if access_token: - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - response = app.response_class( - response=jdata["Topics"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request): + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + return jsonify(jdata["Topics"]) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics/", methods=["POST"]) def create_topic(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "POST": - body = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=body, - status=201, - mimetype="application/json", - ) + + if validate_client(request) and request.method == "POST": + body = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + response = jsonify(body) + response.status_code = 201 return response - response = invalid_project() - return response + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics/") def topic_details(project_id, topic_id): - access_token = validate_client(request) - if access_token: - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=j, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request): + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(topic) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics/", methods=["PUT"]) def update_topic(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(data) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics/", methods=["DELETE"]) def delete_topic(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "DELETE": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - message = {"DELETED"} - response = app.response_class( - response=json.dumps(message), - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "DELETE": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify({"OK"}) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//snippet", methods=["GET"]) def get_snippet(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=json.dumps("Snippet test successfull"), - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify({"Get Snippet Executed successfully"}) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//snippet", methods=["PUT"]) def update_snippet(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(data) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//files_information", methods=["GET"]) def get_files_information(project_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - response = app.response_class( - response=jdata["Files"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + return jsonify(jdata["Files"]) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//files", methods=["GET"]) def get_files(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=json.dumps("Get request for files successfull"), - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify("Get request for files successfull") + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//files", methods=["PUT"]) def update_files(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(data) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//comments", methods=["GET"]) def get_comments(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=jdata["Comments"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(jdata["Comments"]) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//comments", methods=["POST"]) def create_comments(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "POST": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) + if validate_client(request) and request.method == "POST": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + response = jsonify(data) + response.status_code = 201 return response - response = invalid_project() - return response + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() -@bcf.route( - "/projects//topics//comments/", methods=["GET"] -) +@bcf.route("/projects//topics//comments/", methods=["GET"]) def get_comment(project_id, topic_id, comment_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["Comments"]: - if (k["guid"]) == comment_id: - response = app.response_class( - response=k, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for comment in jdata["Comments"]: + if (comment["guid"]) == comment_id: + return jsonify(comment) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() -@bcf.route( - "/projects//topics//comments/", methods=["PUT"] -) +@bcf.route("/projects//topics//comments/", methods=["PUT"]) def update_comment(project_id, topic_id, comment_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["Comments"]: - if (k["guid"]) == comment_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for comment in jdata["Comments"]: + if (comment["guid"]) == comment_id: + return jsonify(data) + return invalid_project() else: - response = invalid_user() - return response + return invalid_user() -@bcf.route( - "/projects//topics//comments/", methods=["DELETE"] -) +@bcf.route("/projects//topics//comments/", methods=["DELETE"]) def delete_comment(project_id, topic_id, comment_id): - access_token = validate_client(request) - if access_token and request.method == "DELETE": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["Comments"]: - if (k["guid"]) == comment_id: - message = {"DELETED"} - response = app.response_class( - response=json.dumps(message), - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + if validate_client(request) and request.method == "DELETE": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for comment in jdata["Comments"]: + if (comment["guid"]) == comment_id: + return jsonify("OK") + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() -""" -***Add ViewPoints Routes Here *** -""" +@bcf.route("/projects//topics//viewpoints", methods=["GET"]) +def get_viewpoints(project_id, topic_id): + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + return jsonify(jdata["Viewpoints"]) + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints", methods=["POST"]) +def create_viewpoints(project_id, topic_id): + if validate_client(request) and request.method == "POST": + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + data = request.get_json() + response = jsonify(data) + response.status_code = 201 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints/", methods=["GET"]) +def get_viewpoint(project_id, topic_id, viewpoint_id): + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + response = jsonify(viewpoint) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints/", methods=["DELETE"]) +def delete_viewpoint(project_id, topic_id, viewpoint_id): + if validate_client(request) and request.method == "DELETE": + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + response = jsonify(viewpoint) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints//snapshot", methods=["GET"]) +def get_snapshot(project_id, topic_id, viewpoint_id): + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + response = jsonify(viewpoint["snapshot"]) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints//bitmaps/", methods=["GET"]) +def get_bitmap(project_id, topic_id, viewpoint_id, bitmap_id): + if validate_client(request): + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + for bitmap in viewpoint["bitmaps"]: + if bitmap["guid"] == bitmap_id: + response = jsonify(bitmap) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints//selection", methods=["GET"]) +def get_selection(project_id, topic_id, viewpoint_id): + if validate_client(request): + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + response = jsonify(viewpoint["selection"]) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints//coloring", methods=["GET"]) +def get_coloring(project_id, topic_id, viewpoint_id): + if validate_client(request): + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + response = jsonify(viewpoint["coloring"]) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() + + +@bcf.route("/projects//topics//viewpoints//visibility", methods=["GET"]) +def get_visibility(project_id, topic_id, viewpoint_id): + if validate_client(request): + for project in jdata["Projects"]: + if project["project_id"] == project_id: + for topic in jdata["Topics"]: + if topic["guid"] == topic_id: + for viewpoint in jdata["Viewpoints"]: + if viewpoint["guid"] == viewpoint_id: + response = jsonify(viewpoint["visibility"]) + response.status_code = 200 + return response + return invalid_project() + else: + return invalid_user() @bcf.route("/projects//topics//related_topics", methods=["GET"]) def get_related_topics(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=jdata["RelatedTopics"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(jdata["RelatedTopics"]) + return invalid_project() return response else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//related_topics", methods=["PUT"]) def update_related_topics(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(data) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() -@bcf.route( - "/projects//topics//document_references", methods=["GET"] -) +@bcf.route("/projects//topics//document_references", methods=["GET"]) def get_document_references(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=jdata["DocumentReferences"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(jdata["DocumentReferences"]) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() -@bcf.route( - "/projects//topics//document_references", methods=["POST"] -) +@bcf.route("/projects//topics//document_references", methods=["POST"]) def create_document_references(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "POST": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) + + if validate_client(request) and request.method == "POST": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + response = jsonify(data) + response.status_code = 201 return response - response = invalid_project() - return response + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route( @@ -522,156 +519,113 @@ def create_document_references(project_id, topic_id): methods=["PUT"], ) def update_document_references(project_id, topic_id, document_reference_id): - access_token = validate_client(request) - if access_token and request.method == "PUT": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["DocumentReferences"]: - if (k["guid"]) == document_reference_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "PUT": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for document in jdata["DocumentReferences"]: + if (document["guid"]) == document_reference_id: + return jsonify(data) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//documents", methods=["GET"]) def get_documents(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=jdata["Documents"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + return jsonify(jdata["Documents"]) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//documents", methods=["POST"]) def create_documents(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "POST": - data = request.form["data"] - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - response = app.response_class( - response=data, - status=200, - mimetype="application/json", - ) + + if validate_client(request) and request.method == "POST": + data = request.get_json() + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + response = jsonify(data) + response.status_code = 201 return response - response = invalid_project() - return response + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() -@bcf.route( - "/projects//topics//documents/", methods=["GET"] -) +@bcf.route("/projects//topics//documents/", methods=["GET"]) def get_document(project_id, topic_id, document_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["Documents"]: - if (k["guid"]) == document_id: - response = app.response_class( - response=k, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for document in jdata["Documents"]: + if (document["guid"]) == document_id: + return jsonify(document) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics/events", methods=["GET"]) def get_events(project_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - response = app.response_class( - response=jdata["Events"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + return jsonify(jdata["Events"]) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics//events", methods=["GET"]) def get_topic_events(project_id, topic_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["Events"]: - if (k["topic_guid"]) == topic_id: - response = app.response_class( - response=k, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for event in jdata["Events"]: + if (event["topic_guid"]) == topic_id: + return jsonify(event) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route("/projects//topics/comments/events", methods=["GET"]) def get_comments_events(project_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - response = app.response_class( - response=jdata["EventComments"], - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + return jsonify(jdata["EventComments"]) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() @bcf.route( @@ -679,22 +633,16 @@ def get_comments_events(project_id): methods=["GET"], ) def get_comment_events(project_id, topic_id, comment_id): - access_token = validate_client(request) - if access_token and request.method == "GET": - for i in jdata["Projects"]: - if (i["project_id"]) == project_id: - for j in jdata["Topics"]: - if (j["guid"]) == topic_id: - for k in jdata["EventComments"]: - if (k["comment_guid"]) == comment_id: - response = app.response_class( - response=k, - status=200, - mimetype="application/json", - ) - return response - response = invalid_project() - return response + + if validate_client(request) and request.method == "GET": + for project in jdata["Projects"]: + if (project["project_id"]) == project_id: + for topic in jdata["Topics"]: + if (topic["guid"]) == topic_id: + for comment in jdata["EventComments"]: + if (comment["comment_guid"]) == comment_id: + return jsonify(comment) + return invalid_project() + else: - response = invalid_user() - return response + return invalid_user() From 06b43e7f80a62cad2d7b08426164000ba36cae65 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 16 Aug 2021 23:52:55 +0200 Subject: [PATCH 16/72] Make BCF project UI (more) foolproof (#1662) * Various code fixes and optimizations * Prevent running operators if conditions not met --- .../blenderbim/bim/module/bcf/operator.py | 176 ++++++++++++------ 1 file changed, 121 insertions(+), 55 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index 1477d06616..93d0622277 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -7,6 +7,7 @@ import numpy as np import ifcopenshell import ifcopenshell.util.unit from . import bcfstore +from blenderbim.bim.module.bcf.prop import getBcfViewpoints from blenderbim.bim.ifc import IfcStore from math import radians, degrees, atan, tan, cos, sin from mathutils import Vector, Matrix, Euler, geometry @@ -56,13 +57,10 @@ class LoadBcfTopics(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml.get_topics() - while len(context.scene.BCFProperties.topics) > 0: - context.scene.BCFProperties.topics.remove(0) - index = 0 - for topic_guid in bcfxml.topics.keys(): + context.scene.BCFProperties.topics.clear() + for index, topic_guid in enumerate(bcfxml.topics.keys()): new = context.scene.BCFProperties.topics.add() bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index) - index += 1 return {"FINISHED"} @@ -95,16 +93,17 @@ class LoadBcfTopic(bpy.types.Operator): for key, value in data_map.items(): if value is not None: setattr(new, key, str(value)) - while len(new.reference_links) > 0: - new.reference_links.remove(0) + + new.reference_links.clear() for reference_link in topic.reference_links: - new2 = new.reference_links.add() - new2.name = reference_link - while len(new.labels) > 0: - new.labels.remove(0) + new_reference_link = new.reference_links.add() + new_reference_link.name = reference_link + + new.labels.clear() for label in topic.labels: - new2 = new.labels.add() - new2.name = label + new_label = new.labels.add() + new_label.name = label + if topic.bim_snippet: data_map = { "type": topic.bim_snippet.snippet_type, @@ -115,10 +114,10 @@ class LoadBcfTopic(bpy.types.Operator): for key, value in data_map.items(): if value is not None: setattr(new.bim_snippet, key, value) - while len(new.document_references) > 0: - new.document_references.remove(0) + + new.document_references.clear() for doc in topic.document_references: - new2 = new.document_references.add() + new_document_references = new.document_references.add() data_map = { "reference": doc.referenced_document, "description": doc.description, @@ -127,12 +126,13 @@ class LoadBcfTopic(bpy.types.Operator): } for key, value in data_map.items(): if value is not None: - setattr(new2, key, value) - while len(new.related_topics) > 0: - new.related_topics.remove(0) + setattr(new_document_references, key, value) + + new.related_topics.clear() for related_topic in topic.related_topics: - new2 = new.related_topics.add() - new2.name = related_topic.guid + new_related_topic = new.related_topics.add() + new_related_topic.name = related_topic.guid + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) return {"FINISHED"} @@ -250,11 +250,13 @@ class AddBcfTopic(bpy.types.Operator): bl_label = "Add BCF Topic" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return context.scene.BCFProperties.author + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml.add_topic() - new = context.scene.BCFProperties.topics.add() - new.name = "New Topic" bpy.ops.bim.load_bcf_topics() return {"FINISHED"} @@ -264,6 +266,15 @@ class AddBcfBimSnippet(bpy.types.Operator): bl_label = "Add BCF BIM Snippet" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BCFProperties + return all((getattr(props.topics[props.active_topic_index], attr, False) for attr in ( + "bim_snippet_reference", + "bim_snippet_schema", + "bim_snippet_type" + ))) + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties @@ -283,10 +294,16 @@ class AddBcfRelatedTopic(bpy.types.Operator): bl_label = "Add BCF Related Topic" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + @classmethod + def poll(cls, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] + if not blender_topic.related_topic: + return False + if blender_topic.related_topic == blender_topic.title: + # Prevent adding self as related topic + return False related_topic = None for topic in bcfxml.topics.values(): if topic.title == blender_topic.related_topic: @@ -294,7 +311,18 @@ class AddBcfRelatedTopic(bpy.types.Operator): related_topic.guid = topic.guid break if not related_topic: - return {"FINISHED"} + return False + if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]: + # Prevent adding the same related topic more than once + return False + return True + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + related_topic = bcf.v2.data.RelatedTopic() + related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == blender_topic.related_topic)).guid topic = bcfxml.topics[blender_topic.name] topic.related_topics.append(related_topic) bcfxml.edit_topic(topic) @@ -307,6 +335,11 @@ class AddBcfHeaderFile(bpy.types.Operator): bl_label = "Add BCF Header File" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BCFProperties + return props.topics[props.active_topic_index].file_reference + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties @@ -333,7 +366,7 @@ class ViewBcfTopic(bpy.types.Operator): def execute(self, context): for index, topic in enumerate(context.scene.BCFProperties.topics): - if topic.guid.lower() == self.topic_guid.lower(): + if topic.name.lower() == self.topic_guid.lower(): context.scene.BCFProperties.active_topic_index = index return {"FINISHED"} @@ -343,45 +376,49 @@ class AddBcfViewpoint(bpy.types.Operator): bl_label = "Add BCF Viewpoint" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return context.scene.camera + def execute(self, context): - if not context.scene.camera: - return {"FINISHED"} bcfxml = bcfstore.BcfStore.get_bcfxml() + blender_camera = context.scene.camera props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] viewpoint = bcf.v2.data.Viewpoint() - if context.scene.camera.data.type == "ORTHO": + if blender_camera.data.type == "ORTHO": camera = bcf.v2.data.OrthogonalCamera() - camera.view_to_world_scale = context.scene.camera.data.ortho_scale + camera.view_to_world_scale = blender_camera.data.ortho_scale viewpoint.orthogonal_camera = camera - elif context.scene.camera.data.type == "PERSP": + elif blender_camera.data.type == "PERSP": camera = bcf.v2.data.PerspectiveCamera() - camera.field_of_view = degrees(context.scene.camera.data.angle) + camera.field_of_view = degrees(blender_camera.data.angle) viewpoint.perspective_camera = camera - camera.camera_view_point.x = context.scene.camera.location.x - camera.camera_view_point.y = context.scene.camera.location.y - camera.camera_view_point.z = context.scene.camera.location.z - direction = context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) + camera.camera_view_point.x = blender_camera.location.x + camera.camera_view_point.y = blender_camera.location.y + camera.camera_view_point.z = blender_camera.location.z + direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) camera.camera_direction.x = direction.x camera.camera_direction.y = direction.y camera.camera_direction.z = direction.z - up = context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) + up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) camera.camera_up_vector.x = up.x camera.camera_up_vector.y = up.y camera.camera_up_vector.z = up.z - old_file_format = context.scene.render.image_settings.file_format - context.scene.render.image_settings.file_format = "PNG" - old_filepath = context.scene.render.filepath - context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") + blender_render = context.scene.render + old_file_format = blender_render.image_settings.file_format + blender_render.image_settings.file_format = "PNG" + old_filepath = blender_render.filepath + blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") bpy.ops.render.opengl(write_still=True) - viewpoint.snapshot = context.scene.render.filepath + viewpoint.snapshot = blender_render.filepath bcfxml.add_viewpoint(topic, viewpoint) - context.scene.render.filepath = old_filepath - context.scene.render.image_settings.file_format = old_file_format + blender_render.filepath = old_filepath + blender_render.image_settings.file_format = old_file_format props.active_topic_index = props.active_topic_index # refreshes the BCF Topic return {"FINISHED"} @@ -391,6 +428,10 @@ class RemoveBcfViewpoint(bpy.types.Operator): bl_label = "Remove BCF Viewpoint" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return getBcfViewpoints(None, context) + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties @@ -423,13 +464,16 @@ class AddBcfReferenceLink(bpy.types.Operator): bl_label = "Add BCF Reference Link" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BCFProperties + return props.topics[props.active_topic_index].reference_link + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - if not blender_topic.reference_link: - return {"FINISHED"} topic.reference_links.append(blender_topic.reference_link) bcfxml.edit_topic(topic) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) @@ -442,13 +486,16 @@ class AddBcfDocumentReference(bpy.types.Operator): bl_label = "Add BCF Document Reference" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BCFProperties + return props.topics[props.active_topic_index].document_reference + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - if not blender_topic.document_reference: - return {"FINISHED"} document_reference = bcf.v2.data.DocumentReference() document_reference.referenced_document = blender_topic.document_reference document_reference.description = blender_topic.document_reference_description or None @@ -464,13 +511,16 @@ class AddBcfLabel(bpy.types.Operator): bl_label = "Add BCF Label" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BCFProperties + return props.topics[props.active_topic_index].label + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - if not blender_topic.label: - return {"FINISHED"} new = blender_topic.labels.add() new.name = blender_topic.label topic.labels.append(blender_topic.label) @@ -508,6 +558,8 @@ class EditBcfLabels(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] for index, label in enumerate(blender_topic.labels): + if index >= len(topic.labels): + break if label.name == topic.labels[index]: continue topic.labels[index] = blender_topic.labels[index].name @@ -640,16 +692,24 @@ class AddBcfComment(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} comment_guid: bpy.props.StringProperty() + @classmethod + def poll(cls, context): + props = context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + if not topic.comment: + return False + if topic.has_related_viewpoint and not getBcfViewpoints(None, context): + return False + return True + def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - if not blender_topic.comment: - return {"FINISHED"} comment = bcf.v2.data.Comment() comment.comment = blender_topic.comment - if blender_topic.has_related_viewpoint and blender_topic.viewpoints: + if blender_topic.has_related_viewpoint: comment.viewpoint = bcf.v2.data.Viewpoint() comment.viewpoint.guid = blender_topic.viewpoints bcfxml.add_comment(topic, comment) @@ -662,14 +722,20 @@ class ActivateBcfViewpoint(bpy.types.Operator): bl_label = "Activate BCF Viewpoint" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + return topic.viewpoints + def execute(self, context): self.file = IfcStore.get_file() bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - if not topic.viewpoints: - return {"FINISHED"} viewpoint_guid = blender_topic.viewpoints viewpoint = topic.viewpoints[viewpoint_guid] From 2dbf9665be21bf9492742f83cde4a71aff9d08b1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 17 Aug 2021 08:39:38 +1000 Subject: [PATCH 17/72] IfcSverchok licensing. See #1082. --- src/ifcsverchok/__init__.py | 19 +++++++++++++++++++ src/ifcsverchok/helper.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/__init__.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/__init__.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/add.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/api.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/by_guid.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/by_id.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/by_query.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/by_type.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/create_entity.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/create_file.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/create_shape.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/generate_guid.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/get_attribute.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/get_property.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/read_entity.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/read_file.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/remove.py | 19 +++++++++++++++++++ .../nodes/ifc/select_blender_objects.py | 19 +++++++++++++++++++ src/ifcsverchok/nodes/ifc/write_file.py | 19 +++++++++++++++++++ 21 files changed, 399 insertions(+) diff --git a/src/ifcsverchok/__init__.py b/src/ifcsverchok/__init__.py index 91890a934a..5749501973 100644 --- a/src/ifcsverchok/__init__.py +++ b/src/ifcsverchok/__init__.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + bl_info = { "name": "IFC for Sverchok", "author": "Dion Moult", diff --git a/src/ifcsverchok/helper.py b/src/ifcsverchok/helper.py index de541317d9..fc19144865 100644 --- a/src/ifcsverchok/helper.py +++ b/src/ifcsverchok/helper.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy from sverchok.data_structure import zip_long_repeat diff --git a/src/ifcsverchok/nodes/__init__.py b/src/ifcsverchok/nodes/__init__.py index e69de29bb2..ec93090e2d 100644 --- a/src/ifcsverchok/nodes/__init__.py +++ b/src/ifcsverchok/nodes/__init__.py @@ -0,0 +1,19 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + diff --git a/src/ifcsverchok/nodes/ifc/__init__.py b/src/ifcsverchok/nodes/ifc/__init__.py index e69de29bb2..ec93090e2d 100644 --- a/src/ifcsverchok/nodes/ifc/__init__.py +++ b/src/ifcsverchok/nodes/ifc/__init__.py @@ -0,0 +1,19 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + diff --git a/src/ifcsverchok/nodes/ifc/add.py b/src/ifcsverchok/nodes/ifc/add.py index c763299678..53f049087b 100644 --- a/src/ifcsverchok/nodes/ifc/add.py +++ b/src/ifcsverchok/nodes/ifc/add.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/api.py b/src/ifcsverchok/nodes/ifc/api.py index 81bb53c06a..f81e1a9948 100644 --- a/src/ifcsverchok/nodes/ifc/api.py +++ b/src/ifcsverchok/nodes/ifc/api.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcopenshell.api diff --git a/src/ifcsverchok/nodes/ifc/by_guid.py b/src/ifcsverchok/nodes/ifc/by_guid.py index 65d4da7eaa..f632459f36 100644 --- a/src/ifcsverchok/nodes/ifc/by_guid.py +++ b/src/ifcsverchok/nodes/ifc/by_guid.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/by_id.py b/src/ifcsverchok/nodes/ifc/by_id.py index 7be5345f6c..a2888e8475 100644 --- a/src/ifcsverchok/nodes/ifc/by_id.py +++ b/src/ifcsverchok/nodes/ifc/by_id.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/by_query.py b/src/ifcsverchok/nodes/ifc/by_query.py index be99b97dc7..6b9c6d22ed 100644 --- a/src/ifcsverchok/nodes/ifc/by_query.py +++ b/src/ifcsverchok/nodes/ifc/by_query.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.selector diff --git a/src/ifcsverchok/nodes/ifc/by_type.py b/src/ifcsverchok/nodes/ifc/by_type.py index af91d22903..ae91242e1a 100644 --- a/src/ifcsverchok/nodes/ifc/by_type.py +++ b/src/ifcsverchok/nodes/ifc/by_type.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/create_entity.py b/src/ifcsverchok/nodes/ifc/create_entity.py index 81144a733f..43bd9160f0 100644 --- a/src/ifcsverchok/nodes/ifc/create_entity.py +++ b/src/ifcsverchok/nodes/ifc/create_entity.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/create_file.py b/src/ifcsverchok/nodes/ifc/create_file.py index 41aab59da3..1e8b1869ff 100644 --- a/src/ifcsverchok/nodes/ifc/create_file.py +++ b/src/ifcsverchok/nodes/ifc/create_file.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/create_shape.py b/src/ifcsverchok/nodes/ifc/create_shape.py index 6d0a4a2f92..1f5cad07b1 100644 --- a/src/ifcsverchok/nodes/ifc/create_shape.py +++ b/src/ifcsverchok/nodes/ifc/create_shape.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import logging import ifcopenshell diff --git a/src/ifcsverchok/nodes/ifc/generate_guid.py b/src/ifcsverchok/nodes/ifc/generate_guid.py index 5294a28fe2..391e57c383 100644 --- a/src/ifcsverchok/nodes/ifc/generate_guid.py +++ b/src/ifcsverchok/nodes/ifc/generate_guid.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/get_attribute.py b/src/ifcsverchok/nodes/ifc/get_attribute.py index deb98421d2..deb9b8083e 100644 --- a/src/ifcsverchok/nodes/ifc/get_attribute.py +++ b/src/ifcsverchok/nodes/ifc/get_attribute.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.element diff --git a/src/ifcsverchok/nodes/ifc/get_property.py b/src/ifcsverchok/nodes/ifc/get_property.py index 3702aa8d0c..e889e52c1b 100644 --- a/src/ifcsverchok/nodes/ifc/get_property.py +++ b/src/ifcsverchok/nodes/ifc/get_property.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.element diff --git a/src/ifcsverchok/nodes/ifc/read_entity.py b/src/ifcsverchok/nodes/ifc/read_entity.py index ebaa1556bf..50e721385f 100644 --- a/src/ifcsverchok/nodes/ifc/read_entity.py +++ b/src/ifcsverchok/nodes/ifc/read_entity.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/read_file.py b/src/ifcsverchok/nodes/ifc/read_file.py index 1c9a39771c..1f10056c55 100644 --- a/src/ifcsverchok/nodes/ifc/read_file.py +++ b/src/ifcsverchok/nodes/ifc/read_file.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/remove.py b/src/ifcsverchok/nodes/ifc/remove.py index cde4f6e2a0..4a7e76db75 100644 --- a/src/ifcsverchok/nodes/ifc/remove.py +++ b/src/ifcsverchok/nodes/ifc/remove.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper diff --git a/src/ifcsverchok/nodes/ifc/select_blender_objects.py b/src/ifcsverchok/nodes/ifc/select_blender_objects.py index 77e491c85c..797eac5081 100644 --- a/src/ifcsverchok/nodes/ifc/select_blender_objects.py +++ b/src/ifcsverchok/nodes/ifc/select_blender_objects.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.selector diff --git a/src/ifcsverchok/nodes/ifc/write_file.py b/src/ifcsverchok/nodes/ifc/write_file.py index fc00a8f18c..59df9fc84f 100644 --- a/src/ifcsverchok/nodes/ifc/write_file.py +++ b/src/ifcsverchok/nodes/ifc/write_file.py @@ -1,3 +1,22 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of IfcSverchok. +# +# IfcSverchok is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcSverchok is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with IfcSverchok. If not, see . + import bpy import ifcopenshell import ifcsverchok.helper From 2284f7b16e317ab15c2c734c913f619e7b885417 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 17 Aug 2021 08:55:29 +1000 Subject: [PATCH 18/72] BlenderBIM Add-on licensing. See #1082. --- src/blenderbim/RenameObjects.py | 119 ------------------ src/blenderbim/blenderbim/__init__.py | 19 +++ src/blenderbim/blenderbim/bim/__init__.py | 19 +++ .../blenderbim/bim/data/styles/default.css | 20 +++ .../blenderbim/bim/data/styles/sample.css | 20 +++ src/blenderbim/blenderbim/bim/export_ifc.py | 19 +++ src/blenderbim/blenderbim/bim/handler.py | 19 +++ src/blenderbim/blenderbim/bim/helper.py | 19 +++ src/blenderbim/blenderbim/bim/ifc.py | 19 +++ src/blenderbim/blenderbim/bim/import_ifc.py | 19 +++ .../bim/module/aggregate/__init__.py | 19 +++ .../bim/module/aggregate/operator.py | 19 +++ .../blenderbim/bim/module/aggregate/ui.py | 19 +++ .../bim/module/attribute/__init__.py | 19 +++ .../bim/module/attribute/operator.py | 19 +++ .../blenderbim/bim/module/attribute/prop.py | 19 +++ .../blenderbim/bim/module/attribute/ui.py | 19 +++ .../blenderbim/bim/module/augin/__init__.py | 19 +++ .../blenderbim/bim/module/augin/operator.py | 19 +++ .../blenderbim/bim/module/augin/prop.py | 19 +++ .../blenderbim/bim/module/augin/ui.py | 19 +++ .../blenderbim/bim/module/bcf/__init__.py | 19 +++ .../blenderbim/bim/module/bcf/bcfstore.py | 19 +++ .../blenderbim/bim/module/bcf/operator.py | 19 +++ .../blenderbim/bim/module/bcf/prop.py | 19 +++ .../blenderbim/bim/module/bcf/ui.py | 19 +++ .../bim/module/bimtester/__init__.py | 19 +++ .../bim/module/bimtester/operator.py | 19 +++ .../blenderbim/bim/module/bimtester/prop.py | 19 +++ .../blenderbim/bim/module/bimtester/ui.py | 19 +++ .../bim/module/boundary/__init__.py | 19 +++ .../blenderbim/bim/module/boundary/ui.py | 19 +++ .../blenderbim/bim/module/clash/__init__.py | 19 +++ .../blenderbim/bim/module/clash/operator.py | 19 +++ .../blenderbim/bim/module/clash/prop.py | 19 +++ .../blenderbim/bim/module/clash/ui.py | 19 +++ .../bim/module/classification/__init__.py | 19 +++ .../bim/module/classification/operator.py | 19 +++ .../bim/module/classification/prop.py | 19 +++ .../bim/module/classification/ui.py | 19 +++ .../blenderbim/bim/module/cobie/__init__.py | 19 +++ .../blenderbim/bim/module/cobie/operator.py | 19 +++ .../blenderbim/bim/module/cobie/prop.py | 19 +++ .../blenderbim/bim/module/cobie/ui.py | 19 +++ .../bim/module/constraint/__init__.py | 19 +++ .../bim/module/constraint/operator.py | 19 +++ .../blenderbim/bim/module/constraint/prop.py | 19 +++ .../blenderbim/bim/module/constraint/ui.py | 19 +++ .../blenderbim/bim/module/context/__init__.py | 19 +++ .../blenderbim/bim/module/context/operator.py | 19 +++ .../blenderbim/bim/module/context/ui.py | 19 +++ .../blenderbim/bim/module/cost/__init__.py | 19 +++ .../blenderbim/bim/module/cost/operator.py | 19 +++ .../blenderbim/bim/module/cost/prop.py | 19 +++ .../blenderbim/bim/module/cost/ui.py | 19 +++ .../bim/module/covetool/__init__.py | 19 +++ .../blenderbim/bim/module/covetool/api.py | 19 +++ .../bim/module/covetool/operator.py | 19 +++ .../blenderbim/bim/module/covetool/prop.py | 19 +++ .../blenderbim/bim/module/covetool/ui.py | 19 +++ .../blenderbim/bim/module/csv/__init__.py | 19 +++ .../blenderbim/bim/module/csv/operator.py | 19 +++ .../blenderbim/bim/module/csv/prop.py | 19 +++ .../blenderbim/bim/module/csv/ui.py | 19 +++ .../blenderbim/bim/module/debug/__init__.py | 19 +++ .../blenderbim/bim/module/debug/operator.py | 19 +++ .../blenderbim/bim/module/debug/prop.py | 19 +++ .../blenderbim/bim/module/debug/ui.py | 19 +++ .../blenderbim/bim/module/diff/__init__.py | 19 +++ .../blenderbim/bim/module/diff/operator.py | 19 +++ .../blenderbim/bim/module/diff/prop.py | 19 +++ .../blenderbim/bim/module/diff/ui.py | 19 +++ .../bim/module/document/__init__.py | 19 +++ .../bim/module/document/operator.py | 19 +++ .../blenderbim/bim/module/document/prop.py | 19 +++ .../blenderbim/bim/module/document/ui.py | 19 +++ .../blenderbim/bim/module/drawing/__init__.py | 19 +++ .../bim/module/drawing/annotation.py | 19 +++ .../bim/module/drawing/decoration.py | 19 +++ .../blenderbim/bim/module/drawing/gizmos.py | 19 +++ .../blenderbim/bim/module/drawing/handler.py | 19 +++ .../blenderbim/bim/module/drawing/helper.py | 19 +++ .../blenderbim/bim/module/drawing/operator.py | 19 +++ .../blenderbim/bim/module/drawing/prop.py | 19 +++ .../bim/module/drawing/scheduler.py | 19 +++ .../blenderbim/bim/module/drawing/shaders.py | 19 +++ .../blenderbim/bim/module/drawing/sheeter.py | 19 +++ .../bim/module/drawing/svgwriter.py | 19 +++ .../blenderbim/bim/module/drawing/ui.py | 19 +++ .../bim/module/geometry/__init__.py | 19 +++ .../blenderbim/bim/module/geometry/helper.py | 19 +++ .../bim/module/geometry/operator.py | 19 +++ .../blenderbim/bim/module/geometry/prop.py | 19 +++ .../blenderbim/bim/module/geometry/ui.py | 19 +++ .../bim/module/georeference/__init__.py | 19 +++ .../bim/module/georeference/operator.py | 19 +++ .../bim/module/georeference/prop.py | 19 +++ .../blenderbim/bim/module/georeference/ui.py | 19 +++ .../blenderbim/bim/module/group/__init__.py | 19 +++ .../blenderbim/bim/module/group/operator.py | 19 +++ .../blenderbim/bim/module/group/prop.py | 19 +++ .../blenderbim/bim/module/group/ui.py | 19 +++ .../blenderbim/bim/module/layer/__init__.py | 19 +++ .../blenderbim/bim/module/layer/operator.py | 19 +++ .../blenderbim/bim/module/layer/prop.py | 19 +++ .../blenderbim/bim/module/layer/ui.py | 19 +++ .../bim/module/material/__init__.py | 19 +++ .../bim/module/material/operator.py | 19 +++ .../blenderbim/bim/module/material/prop.py | 19 +++ .../blenderbim/bim/module/material/ui.py | 19 +++ .../blenderbim/bim/module/model/__init__.py | 19 +++ .../blenderbim/bim/module/model/door.py | 19 +++ .../blenderbim/bim/module/model/grid.py | 19 +++ .../blenderbim/bim/module/model/handler.py | 19 +++ .../blenderbim/bim/module/model/opening.py | 19 +++ .../blenderbim/bim/module/model/pie.py | 19 +++ .../blenderbim/bim/module/model/product.py | 19 +++ .../blenderbim/bim/module/model/profile.py | 19 +++ .../blenderbim/bim/module/model/prop.py | 19 +++ .../blenderbim/bim/module/model/root.py | 19 +++ .../blenderbim/bim/module/model/slab.py | 19 +++ .../blenderbim/bim/module/model/stair.py | 19 +++ .../blenderbim/bim/module/model/task.py | 19 +++ .../blenderbim/bim/module/model/ui.py | 19 +++ .../blenderbim/bim/module/model/wall.py | 19 +++ .../blenderbim/bim/module/model/window.py | 19 +++ .../blenderbim/bim/module/model/workspace.py | 19 +++ .../blenderbim/bim/module/owner/__init__.py | 19 +++ .../blenderbim/bim/module/owner/operator.py | 19 +++ .../blenderbim/bim/module/owner/prop.py | 19 +++ .../blenderbim/bim/module/owner/ui.py | 19 +++ .../blenderbim/bim/module/patch/__init__.py | 19 +++ .../blenderbim/bim/module/patch/helper.py | 19 +++ .../blenderbim/bim/module/patch/operator.py | 19 +++ .../blenderbim/bim/module/patch/prop.py | 19 +++ .../blenderbim/bim/module/patch/ui.py | 19 +++ .../blenderbim/bim/module/profile/__init__.py | 19 +++ .../blenderbim/bim/module/profile/operator.py | 19 +++ .../blenderbim/bim/module/profile/prop.py | 19 +++ .../blenderbim/bim/module/profile/ui.py | 19 +++ .../blenderbim/bim/module/project/__init__.py | 19 +++ .../blenderbim/bim/module/project/operator.py | 19 +++ .../blenderbim/bim/module/project/prop.py | 19 +++ .../blenderbim/bim/module/project/ui.py | 19 +++ .../blenderbim/bim/module/pset/__init__.py | 19 +++ .../blenderbim/bim/module/pset/operator.py | 19 +++ .../blenderbim/bim/module/pset/prop.py | 19 +++ .../bim/module/pset/qto_calculator.py | 19 +++ .../blenderbim/bim/module/pset/ui.py | 19 +++ .../bim/module/pset_template/__init__.py | 19 +++ .../bim/module/pset_template/operator.py | 19 +++ .../bim/module/pset_template/prop.py | 19 +++ .../blenderbim/bim/module/pset_template/ui.py | 19 +++ .../blenderbim/bim/module/qto/__init__.py | 19 +++ .../blenderbim/bim/module/qto/helper.py | 19 +++ .../blenderbim/bim/module/qto/operator.py | 19 +++ .../blenderbim/bim/module/qto/prop.py | 19 +++ .../blenderbim/bim/module/qto/ui.py | 19 +++ .../bim/module/resource/__init__.py | 19 +++ .../bim/module/resource/operator.py | 19 +++ .../blenderbim/bim/module/resource/prop.py | 19 +++ .../blenderbim/bim/module/resource/ui.py | 19 +++ .../blenderbim/bim/module/root/__init__.py | 19 +++ .../blenderbim/bim/module/root/operator.py | 19 +++ .../blenderbim/bim/module/root/prop.py | 19 +++ .../blenderbim/bim/module/root/ui.py | 19 +++ .../blenderbim/bim/module/search/__init__.py | 19 +++ .../blenderbim/bim/module/search/operator.py | 19 +++ .../blenderbim/bim/module/search/prop.py | 19 +++ .../blenderbim/bim/module/search/ui.py | 19 +++ .../bim/module/sequence/__init__.py | 19 +++ .../blenderbim/bim/module/sequence/helper.py | 19 +++ .../bim/module/sequence/operator.py | 19 +++ .../blenderbim/bim/module/sequence/prop.py | 19 +++ .../blenderbim/bim/module/sequence/ui.py | 19 +++ .../blenderbim/bim/module/spatial/__init__.py | 19 +++ .../blenderbim/bim/module/spatial/operator.py | 19 +++ .../blenderbim/bim/module/spatial/prop.py | 19 +++ .../blenderbim/bim/module/spatial/ui.py | 19 +++ .../bim/module/structural/__init__.py | 19 +++ .../bim/module/structural/operator.py | 19 +++ .../blenderbim/bim/module/structural/prop.py | 19 +++ .../blenderbim/bim/module/structural/ui.py | 19 +++ .../blenderbim/bim/module/style/__init__.py | 19 +++ .../blenderbim/bim/module/style/operator.py | 19 +++ .../blenderbim/bim/module/style/prop.py | 19 +++ .../blenderbim/bim/module/style/ui.py | 19 +++ .../blenderbim/bim/module/system/__init__.py | 19 +++ .../blenderbim/bim/module/system/operator.py | 19 +++ .../blenderbim/bim/module/system/prop.py | 19 +++ .../blenderbim/bim/module/system/ui.py | 19 +++ .../blenderbim/bim/module/type/__init__.py | 19 +++ .../blenderbim/bim/module/type/operator.py | 19 +++ .../blenderbim/bim/module/type/prop.py | 19 +++ .../blenderbim/bim/module/type/ui.py | 19 +++ .../blenderbim/bim/module/unit/__init__.py | 19 +++ .../blenderbim/bim/module/unit/operator.py | 19 +++ .../blenderbim/bim/module/unit/prop.py | 19 +++ .../blenderbim/bim/module/unit/ui.py | 19 +++ .../blenderbim/bim/module/void/__init__.py | 19 +++ .../blenderbim/bim/module/void/operator.py | 19 +++ .../blenderbim/bim/module/void/prop.py | 20 +++ .../blenderbim/bim/module/void/ui.py | 19 +++ src/blenderbim/blenderbim/bim/operator.py | 19 +++ src/blenderbim/blenderbim/bim/prop.py | 19 +++ src/blenderbim/blenderbim/bim/schema.py | 19 +++ src/blenderbim/blenderbim/bim/ui.py | 19 +++ src/blenderbim/docs/conf.py | 19 +++ src/blenderbim/dxf2ifc.py | 19 +++ src/blenderbim/extract.py | 19 +++ src/blenderbim/gbxml.py | 19 +++ src/blenderbim/generate_demo_library.py | 19 +++ src/blenderbim/generate_util_type_json.py | 19 +++ src/blenderbim/getIfcElements.py | 19 +++ src/blenderbim/get_description.py | 19 +++ src/blenderbim/get_volume_by_material.py | 19 +++ src/blenderbim/runpytest.py | 19 +++ src/blenderbim/tests/test_segment_clipping.py | 19 +++ 218 files changed, 4126 insertions(+), 119 deletions(-) delete mode 100644 src/blenderbim/RenameObjects.py diff --git a/src/blenderbim/RenameObjects.py b/src/blenderbim/RenameObjects.py deleted file mode 100644 index f3e38daafc..0000000000 --- a/src/blenderbim/RenameObjects.py +++ /dev/null @@ -1,119 +0,0 @@ -import bpy -from bpy.types import Operator - - -class Object_OT_RenameObjects(Operator): - bl_idname = "object.renameobjects" - bl_label = "Rename Object(s)" - - # Multi Object rename UI - BNameCB: bpy.props.BoolProperty(name="Base Name:") - BaseName: bpy.props.StringProperty(name="") - PreFixCB: bpy.props.BoolProperty(name="Prefix:") - PreFix: bpy.props.StringProperty(name="") - RemFrst: bpy.props.BoolProperty(name="Remove First") - DgtFrst: bpy.props.IntProperty(name="Digits") - SuffixCB: bpy.props.BoolProperty(name="Suffix") - Suffix: bpy.props.StringProperty(name="") - RemLast: bpy.props.BoolProperty(name="Remove Last") - DgtLast: bpy.props.IntProperty(name="Digits") - NumbredCB: bpy.props.BoolProperty(name="Numbred") - BaseNum: bpy.props.IntProperty(name="Base Number") - Step: bpy.props.IntProperty(name="Step", default=1) - findCB: bpy.props.BoolProperty(name="Replace") - find: bpy.props.StringProperty(name="") - replace: bpy.props.StringProperty(name="") - # Single rename UI - Name: bpy.props.StringProperty(name="Name") - - def draw(self, ctx): - SelCount = len(bpy.context.selected_objects) - if SelCount > 1: - box = self.layout.box() - row = box.row() - row.prop(self, "BNameCB") - row.prop(self, "BaseName") - row = box.row() - row.prop(self, "PreFixCB") - row.prop(self, "PreFix") - row = box.row() - row.prop(self, "RemFrst") - row.prop(self, "DgtFrst") - row = box.row() - row.prop(self, "SuffixCB") - row.prop(self, "Suffix") - row = box.row() - row.prop(self, "RemLast") - row.prop(self, "DgtLast") - row = box.row(align=True) - row.prop(self, "NumbredCB") - row.prop(self, "BaseNum") - row.prop(self, "Step") - row = box.row(align=True) - row.prop(self, "findCB") - row.prop(self, "find") - row.prop(self, "replace") - if SelCount == 1: - box = self.layout.box() - row = box.row() - row.prop(self, "Name") - if SelCount == 0: - box = self.layout.box() - row = box.row() - row.label("No Selected Object") - - def __init__(self): - if len(bpy.context.selected_objects) == 1: - self.Name = bpy.context.selected_objects[0].name - - def execute(self, context): - SelCount = len(bpy.context.selected_objects) - if SelCount > 1: - SelObj = bpy.context.selected_objects - Index = self.BaseNum - for i in range(0, SelCount): - # Get Object Original Name # - NewName = SelObj[i].name - # Set the Base name # - if self.BNameCB: - NewName = self.BaseName - # Remove First characters # - if self.RemFrst: - NewName = NewName[self.DgtFrst : self.DgtFrst + len(NewName)] - # Remove Last Characters # - if self.RemLast: - NewName = NewName[1 : len(NewName) - self.DgtLast] - # Add Prefix to the new name # - if self.PreFixCB: - NewName = self.PreFix + NewName - # Add Suffix to the new name # - if self.SuffixCB: - NewName = NewName + self.Suffix - # Add Digits to end of new name # - if self.NumbredCB: - NewName += str(Index) - Index += self.Step - # Find and Replace # - if self.findCB: - NewName = NewName.replace(self.find, self.replace) - # Set the new name to the object # - SelObj[i].name = NewName - elif SelCount == 1: - bpy.context.selected_objects[0].name = self.Name - return {"FINISHED"} - - def invoke(self, context, event): - wm = context.window_manager - return wm.invoke_props_dialog(self) - - -def register(): - bpy.utils.register_class(Object_OT_RenameObjects) - - -def unregister(): - bpy.utils.unregister_class(Object_OT_RenameObjects) - - -if __name__ == "__main__": - register() diff --git a/src/blenderbim/blenderbim/__init__.py b/src/blenderbim/blenderbim/__init__.py index 3251fdd873..1fd0f93247 100644 --- a/src/blenderbim/blenderbim/__init__.py +++ b/src/blenderbim/blenderbim/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + bl_info = { "name": "BlenderBIM", "description": "Author, import, and export files in the " "Industry Foundation Classes (.ifc) file format", diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 92699dc4dd..4ec6641b11 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + # Check if we are running in Blender before loading, to allow for multiprocessing import sys diff --git a/src/blenderbim/blenderbim/bim/data/styles/default.css b/src/blenderbim/blenderbim/bim/data/styles/default.css index 133c191ce1..a84543626e 100644 --- a/src/blenderbim/blenderbim/bim/data/styles/default.css +++ b/src/blenderbim/blenderbim/bim/data/styles/default.css @@ -1,3 +1,23 @@ +/* + * BlenderBIM Add-on - OpenBIM Blender Add-on + * Copyright (C) 2020, 2021 Dion Moult + * + * This file is part of BlenderBIM Add-on. + * + * BlenderBIM Add-on is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BlenderBIM Add-on is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BlenderBIM Add-on. If not, see . + */ + * { stroke-linecap: round; stroke-linejoin: round; } *[id] { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; } .projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } diff --git a/src/blenderbim/blenderbim/bim/data/styles/sample.css b/src/blenderbim/blenderbim/bim/data/styles/sample.css index a265db20a6..c0a7b0ec74 100644 --- a/src/blenderbim/blenderbim/bim/data/styles/sample.css +++ b/src/blenderbim/blenderbim/bim/data/styles/sample.css @@ -1,3 +1,23 @@ +/* + * BlenderBIM Add-on - OpenBIM Blender Add-on + * Copyright (C) 2020, 2021 Dion Moult + * + * This file is part of BlenderBIM Add-on. + * + * BlenderBIM Add-on is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BlenderBIM Add-on is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BlenderBIM Add-on. If not, see . + */ + .cut { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; } .background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index 7b849b6fc3..5a2113fa08 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import json diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 447f1f9eb4..53bda75b12 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import addon_utils diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index ba6d146bfe..29bb267932 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import math diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index c83dc0e613..58c173acbf 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import uuid import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index bd32c16cfd..907beecd39 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import ifcopenshell import ifcopenshell.geom import ifcopenshell.util.geolocation diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py b/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py index d70cecd453..a97637b771 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, operator diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index b6f49c1568..d5b972791a 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py index d136bc9afc..0be2ff6be6 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from ifcopenshell.api.aggregate.data import Data from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/attribute/__init__.py b/src/blenderbim/blenderbim/bim/module/attribute/__init__.py index f7c565111c..afa3ad0e18 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/attribute/operator.py b/src/blenderbim/blenderbim/bim/module/attribute/operator.py index 673c0ced6f..70a2b1e516 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/operator.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/attribute/prop.py b/src/blenderbim/blenderbim/bim/module/attribute/prop.py index 28701a4600..d402546092 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/prop.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.ifc import IfcStore from blenderbim.bim.prop import StrProperty, Attribute diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py index 243b25353f..1fcb0c13cf 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.attribute.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/augin/__init__.py b/src/blenderbim/blenderbim/bim/module/augin/__init__.py index 9bfda2e096..9f659c7088 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/augin/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/augin/operator.py b/src/blenderbim/blenderbim/bim/module/augin/operator.py index f7b92fbab1..d4b5b0d316 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/operator.py +++ b/src/blenderbim/blenderbim/bim/module/augin/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import json diff --git a/src/blenderbim/blenderbim/bim/module/augin/prop.py b/src/blenderbim/blenderbim/bim/module/augin/prop.py index 35087503dc..d111ab0263 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/prop.py +++ b/src/blenderbim/blenderbim/bim/module/augin/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy.props import bpy.types diff --git a/src/blenderbim/blenderbim/bim/module/augin/ui.py b/src/blenderbim/blenderbim/bim/module/augin/ui.py index 6199ba55b3..30f593846b 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/ui.py +++ b/src/blenderbim/blenderbim/bim/module/augin/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy.types diff --git a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py index ab9af36d50..985caae11f 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py b/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py index 853b0c928b..88ec005b3e 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bcf import bcf.v2.bcfxml diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index 93d0622277..638e043be3 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import bcf diff --git a/src/blenderbim/blenderbim/bim/module/bcf/prop.py b/src/blenderbim/blenderbim/bim/module/bcf/prop.py index f64ec46382..9f522227df 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import bcfstore from blenderbim.bim.prop import StrProperty diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index f90983d609..35f11d9773 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy from . import bcfstore diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py b/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py index 42feab2dcb..a9793f5c5d 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py index 150ef4c5c0..5e71a956cd 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import tempfile diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/prop.py b/src/blenderbim/blenderbim/bim/module/bimtester/prop.py index db245767c7..1518ea234e 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os from pathlib import Path from blenderbim.bim.prop import StrProperty diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py index b1508ad84f..af557c77fc 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py index aab371b6fe..32c66b3b15 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py index 4e0886a054..f401fbdc0b 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import blenderbim.bim.helper from bpy.types import Panel, UIList diff --git a/src/blenderbim/blenderbim/bim/module/clash/__init__.py b/src/blenderbim/blenderbim/bim/module/clash/__init__.py index 63448c96e3..8dcca175c7 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/clash/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py index 45b9f5cecf..5469454ae4 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/operator.py +++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import json diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py index 8dcb14594b..5ed121757f 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/prop.py +++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py index a6a37a7680..2080a0c723 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/ui.py +++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel diff --git a/src/blenderbim/blenderbim/bim/module/classification/__init__.py b/src/blenderbim/blenderbim/bim/module/classification/__init__.py index f486b4e3fa..f03523e884 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/classification/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/classification/operator.py b/src/blenderbim/blenderbim/bim/module/classification/operator.py index 7603b09d04..336341110e 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/operator.py +++ b/src/blenderbim/blenderbim/bim/module/classification/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/classification/prop.py b/src/blenderbim/blenderbim/bim/module/classification/prop.py index 932335a3a3..06f6595423 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/prop.py +++ b/src/blenderbim/blenderbim/bim/module/classification/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from ifcopenshell.api.classification.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py index efcdc6c42e..b2fc143c85 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/ui.py +++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.classification.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/cobie/__init__.py b/src/blenderbim/blenderbim/bim/module/cobie/__init__.py index 46ec55b5b6..55ce5b8358 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/cobie/operator.py b/src/blenderbim/blenderbim/bim/module/cobie/operator.py index 6d8221189a..6cfabed02e 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import os import logging diff --git a/src/blenderbim/blenderbim/bim/module/cobie/prop.py b/src/blenderbim/blenderbim/bim/module/cobie/prop.py index 095622298f..0eb6788f79 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import PropertyGroup from bpy.props import ( diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py index cbce5b033e..c311e58e26 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/constraint/__init__.py b/src/blenderbim/blenderbim/bim/module/constraint/__init__.py index 190ce3fe12..8469bd48bf 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/constraint/operator.py b/src/blenderbim/blenderbim/bim/module/constraint/operator.py index a5be6e3674..0af5c1e8d5 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/operator.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/constraint/prop.py b/src/blenderbim/blenderbim/bim/module/constraint/prop.py index b4474db3d1..3bd87458ec 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/prop.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py index 22b09f092a..a26c4ad193 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes diff --git a/src/blenderbim/blenderbim/bim/module/context/__init__.py b/src/blenderbim/blenderbim/bim/module/context/__init__.py index fa07da07e8..f4fbdab85e 100644 --- a/src/blenderbim/blenderbim/bim/module/context/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/context/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, operator diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py index 9936e1787c..263967f774 100644 --- a/src/blenderbim/blenderbim/bim/module/context/operator.py +++ b/src/blenderbim/blenderbim/bim/module/context/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/context/ui.py b/src/blenderbim/blenderbim/bim/module/context/ui.py index 2573cfa80f..614795bb8d 100644 --- a/src/blenderbim/blenderbim/bim/module/context/ui.py +++ b/src/blenderbim/blenderbim/bim/module/context/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from ifcopenshell.api.context.data import Data from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index 885fcb2060..c4893f0c7f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index be7248a4e6..d467bf4e7f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import json diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 63253a4fe6..d0691be0f0 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 9a87d0dd24..61f6fb1d75 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import blenderbim.bim.module.cost.prop as CostProp from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/covetool/__init__.py b/src/blenderbim/blenderbim/bim/module/covetool/__init__.py index 0113965696..837f9b8382 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/covetool/api.py b/src/blenderbim/blenderbim/bim/module/covetool/api.py index 50f39fdef4..d52e332b09 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/api.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/api.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import requests diff --git a/src/blenderbim/blenderbim/bim/module/covetool/operator.py b/src/blenderbim/blenderbim/bim/module/covetool/operator.py index a1db49bdba..10820227c6 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/operator.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/covetool/prop.py b/src/blenderbim/blenderbim/bim/module/covetool/prop.py index 122d6b1cb1..018cac6566 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/prop.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy.props import bpy.types diff --git a/src/blenderbim/blenderbim/bim/module/covetool/ui.py b/src/blenderbim/blenderbim/bim/module/covetool/ui.py index 70b35ef6c0..d5a91d578c 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/ui.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy.types diff --git a/src/blenderbim/blenderbim/bim/module/csv/__init__.py b/src/blenderbim/blenderbim/bim/module/csv/__init__.py index 3e0e3722b9..24722c9420 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/csv/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/csv/operator.py b/src/blenderbim/blenderbim/bim/module/csv/operator.py index 8618de15ca..e9f9c0739c 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/operator.py +++ b/src/blenderbim/blenderbim/bim/module/csv/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifccsv import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/csv/prop.py b/src/blenderbim/blenderbim/bim/module/csv/prop.py index a21893cc94..f1e0dc93f0 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/prop.py +++ b/src/blenderbim/blenderbim/bim/module/csv/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py index 025dfc7396..347877f616 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/ui.py +++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/debug/__init__.py b/src/blenderbim/blenderbim/bim/module/debug/__init__.py index b5d7474567..9406090275 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/debug/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py index bcb196c9b4..ecdda3629e 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/operator.py +++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import logging import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/debug/prop.py b/src/blenderbim/blenderbim/bim/module/debug/prop.py index d7b6868eeb..49369f4ff0 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/prop.py +++ b/src/blenderbim/blenderbim/bim/module/debug/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py index 7575fa4f7b..68ce199db7 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/ui.py +++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel diff --git a/src/blenderbim/blenderbim/bim/module/diff/__init__.py b/src/blenderbim/blenderbim/bim/module/diff/__init__.py index 6c551c11ce..a296d69a9e 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/diff/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/diff/operator.py b/src/blenderbim/blenderbim/bim/module/diff/operator.py index 6054df59b8..8befc8e1c2 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/operator.py +++ b/src/blenderbim/blenderbim/bim/module/diff/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifccsv import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/diff/prop.py b/src/blenderbim/blenderbim/bim/module/diff/prop.py index 3de92192a7..6233a02a9a 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/prop.py +++ b/src/blenderbim/blenderbim/bim/module/diff/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py index 692b79d37e..9cfbd3efde 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/ui.py +++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/document/__init__.py b/src/blenderbim/blenderbim/bim/module/document/__init__.py index cd2ef94e5d..6d1a6ab13e 100644 --- a/src/blenderbim/blenderbim/bim/module/document/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/document/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/document/operator.py b/src/blenderbim/blenderbim/bim/module/document/operator.py index 906fbb97e5..347f0e941e 100644 --- a/src/blenderbim/blenderbim/bim/module/document/operator.py +++ b/src/blenderbim/blenderbim/bim/module/document/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/document/prop.py b/src/blenderbim/blenderbim/bim/module/document/prop.py index 2208551acf..e93fc291d9 100644 --- a/src/blenderbim/blenderbim/bim/module/document/prop.py +++ b/src/blenderbim/blenderbim/bim/module/document/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index 70e9edcce5..27c6c0f8e5 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index e289ea4aa1..a100f5fc1a 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator, handler, gizmos diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index d2dd4b0f7d..8eaffe09f8 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import os from mathutils import Vector diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index ac36c8506c..219d52c3fb 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + """Viewport decorations""" import math from functools import reduce diff --git a/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py b/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py index 65fbe7e339..3f8965fe14 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import blf import math diff --git a/src/blenderbim/blenderbim/bim/module/drawing/handler.py b/src/blenderbim/blenderbim/bim/module/drawing/handler.py index ef581c6d78..1c6fff0512 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/handler.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/handler.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import blenderbim.bim.module.drawing.decoration as decoration from bpy.app.handlers import persistent diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py index 6285251c4d..eab5fbf9b0 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import math import mathutils.geometry diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 54181dff45..222e74ae7d 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import re import bpy diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index b64a1dcba6..7f11f1710a 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import blenderbim.bim.module.drawing.annotation as annotation diff --git a/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py b/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py index 81555cb55c..902324e6b0 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import svgwrite from odf.opendocument import load diff --git a/src/blenderbim/blenderbim/bim/module/drawing/shaders.py b/src/blenderbim/blenderbim/bim/module/drawing/shaders.py index 8b9c79efc8..ff85417bbd 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/shaders.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/shaders.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bgl from mathutils import Matrix from gpu.types import GPUShader diff --git a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py index 5290337ab7..901771ddea 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import xml.etree.ElementTree as ET import urllib.parse import pystache diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index c7f9ae1f22..e00dc0c84f 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import re import bpy diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index cd4212e926..464f3713e9 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy from bpy.types import Panel diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py index 433e308052..61cf9906d3 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/geometry/helper.py b/src/blenderbim/blenderbim/bim/module/geometry/helper.py index 1733ea1d9c..d27582d8b6 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/helper.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/helper.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh import mathutils diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 485ef7d77b..4af285fa94 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import numpy as np import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/geometry/prop.py b/src/blenderbim/blenderbim/bim/module/geometry/prop.py index 983754d987..f120f57282 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/prop.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py index ee387ab8f9..b05acc8d03 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel from ifcopenshell.api.geometry.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py index 48ec27e3c9..babbbc870d 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 571ce07fa4..0343f3af56 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/georeference/prop.py b/src/blenderbim/blenderbim/bim/module/georeference/prop.py index 8406f823bf..f1cda83e62 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/prop.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index 8b5cbc4f50..b7de779592 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import ifcopenshell.util.geolocation from bpy.types import Panel from ifcopenshell.api.georeference.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/group/__init__.py b/src/blenderbim/blenderbim/bim/module/group/__init__.py index d8871b4131..4cf5131a2f 100644 --- a/src/blenderbim/blenderbim/bim/module/group/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/group/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py index 76f2f245a4..c882258bc6 100644 --- a/src/blenderbim/blenderbim/bim/module/group/operator.py +++ b/src/blenderbim/blenderbim/bim/module/group/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.util.attribute import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/group/prop.py b/src/blenderbim/blenderbim/bim/module/group/prop.py index f754cd37b7..05decfb78d 100644 --- a/src/blenderbim/blenderbim/bim/module/group/prop.py +++ b/src/blenderbim/blenderbim/bim/module/group/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index 21cd9a9870..479a4c787a 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.group.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/layer/__init__.py b/src/blenderbim/blenderbim/bim/module/layer/__init__.py index ea302841c6..136e70fa88 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/layer/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/layer/operator.py b/src/blenderbim/blenderbim/bim/module/layer/operator.py index 4ec6daa8c2..8474f08bc8 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/operator.py +++ b/src/blenderbim/blenderbim/bim/module/layer/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.util.attribute diff --git a/src/blenderbim/blenderbim/bim/module/layer/prop.py b/src/blenderbim/blenderbim/bim/module/layer/prop.py index c832e45c22..61c310bdb2 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/prop.py +++ b/src/blenderbim/blenderbim/bim/module/layer/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py index 7ed1cc4da6..2bd9292928 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/ui.py +++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList, Mesh from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.layer.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index 76d4dca153..ea1ff8cd0d 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 349e8991e3..3a382bbc94 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 9373737ab1..9412dfe35a 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from ifcopenshell.api.material.data import Data from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index e531eb754c..f5630e2aec 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import blenderbim.bim.helper from bpy.types import Panel from ifcopenshell.api.material.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index 3fede4f2ef..f1698528bb 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import handler, prop, ui, grid, product, wall, slab, stair, door, window, opening, pie, workspace diff --git a/src/blenderbim/blenderbim/bim/module/model/door.py b/src/blenderbim/blenderbim/bim/module/model/door.py index 9c4a7f7935..197f1e6400 100644 --- a/src/blenderbim/blenderbim/bim/module/model/door.py +++ b/src/blenderbim/blenderbim/bim/module/model/door.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import math import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/model/grid.py b/src/blenderbim/blenderbim/bim/module/model/grid.py index 78c2e0bdff..f1b0353f9f 100644 --- a/src/blenderbim/blenderbim/bim/module/model/grid.py +++ b/src/blenderbim/blenderbim/bim/module/model/grid.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from bpy.types import Operator diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index bde632c0b3..16b1c16b02 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index dfbbc37aad..68cfcec36a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh import blenderbim.bim.handler diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py index d2f2f2f296..102a1bdb16 100644 --- a/src/blenderbim/blenderbim/bim/module/model/pie.py +++ b/src/blenderbim/blenderbim/bim/module/model/pie.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index eae81c5cc5..bb3c92e584 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import mathutils import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 493d3db501..6c9c82f139 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh import math diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index bc7bf5d5f7..55389ded4c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.util.type from blenderbim.bim.prop import StrProperty, Attribute diff --git a/src/blenderbim/blenderbim/bim/module/model/root.py b/src/blenderbim/blenderbim/bim/module/model/root.py index 729d477b94..4870d1094c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/root.py +++ b/src/blenderbim/blenderbim/bim/module/model/root.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.style.data import Data as StyleData diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 79fb98b385..c99e30f79a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh import math diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index b0074f34c2..aacb1b40ad 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Operator from bpy.props import FloatProperty, IntProperty diff --git a/src/blenderbim/blenderbim/bim/module/model/task.py b/src/blenderbim/blenderbim/bim/module/model/task.py index 2b3dd9a038..a7d75b0318 100644 --- a/src/blenderbim/blenderbim/bim/module/model/task.py +++ b/src/blenderbim/blenderbim/bim/module/model/task.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import ifcopenshell import ifcopenshell.api import ifcopenshell.util.date diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 9e5db798a4..dbec2fe8c7 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 5adeea5b23..c18acfba78 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import math import bmesh diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index ba491f4413..8890051ac3 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import math import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 82d633f507..59c8fd3234 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy from bpy.types import WorkSpaceTool diff --git a/src/blenderbim/blenderbim/bim/module/owner/__init__.py b/src/blenderbim/blenderbim/bim/module/owner/__init__.py index 592e0e0167..b60dc2c370 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/owner/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/owner/operator.py b/src/blenderbim/blenderbim/bim/module/owner/operator.py index 744822911a..50cb544dd3 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/operator.py +++ b/src/blenderbim/blenderbim/bim/module/owner/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/owner/prop.py b/src/blenderbim/blenderbim/bim/module/owner/prop.py index 367aa13784..9629575be1 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/prop.py +++ b/src/blenderbim/blenderbim/bim/module/owner/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py index 42c91c5603..fbefd1193b 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/ui.py +++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel from ifcopenshell.api.owner.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/patch/__init__.py b/src/blenderbim/blenderbim/bim/module/patch/__init__.py index 1770b7cf78..12aec24743 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/patch/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/patch/helper.py b/src/blenderbim/blenderbim/bim/module/patch/helper.py index 24a11c441d..296fa167c9 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/helper.py +++ b/src/blenderbim/blenderbim/bim/module/patch/helper.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import typing import inspect import collections diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index e59e3e04af..518bd381b2 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import json diff --git a/src/blenderbim/blenderbim/bim/module/patch/prop.py b/src/blenderbim/blenderbim/bim/module/patch/prop.py index 86608eb6e0..eb34c1f65d 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/prop.py +++ b/src/blenderbim/blenderbim/bim/module/patch/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import importlib from pathlib import Path diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py index 80c3fab2c2..1617adbb27 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/ui.py +++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.helper import draw_attributes diff --git a/src/blenderbim/blenderbim/bim/module/profile/__init__.py b/src/blenderbim/blenderbim/bim/module/profile/__init__.py index 8b4d495a80..60e3baa2c2 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/profile/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py index c5999173f4..d682a97393 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/operator.py +++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api import blenderbim.bim.helper diff --git a/src/blenderbim/blenderbim/bim/module/profile/prop.py b/src/blenderbim/blenderbim/bim/module/profile/prop.py index b2392e8b06..868093f64a 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/prop.py +++ b/src/blenderbim/blenderbim/bim/module/profile/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.schema diff --git a/src/blenderbim/blenderbim/bim/module/profile/ui.py b/src/blenderbim/blenderbim/bim/module/profile/ui.py index 3a0dcec9a7..3d81fce607 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/ui.py +++ b/src/blenderbim/blenderbim/bim/module/profile/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py index c01b78d775..e05b1c8294 100644 --- a/src/blenderbim/blenderbim/bim/module/project/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 416cc9c950..33247c2183 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import logging import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py index a4052984a9..4a263dc3eb 100644 --- a/src/blenderbim/blenderbim/bim/module/project/prop.py +++ b/src/blenderbim/blenderbim/bim/module/project/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 435ff66d9a..75198cb957 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py index ad512786a3..09838383c3 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 2ef9b1cf01..092b88eade 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index 2b9a255971..98b32d970a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import blenderbim.bim.schema from blenderbim.bim.prop import Attribute diff --git a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py index b1a056caeb..4e2469d0f6 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from mathutils import Vector diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 6fe7e91593..5a0d78269c 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from ifcopenshell.api.pset.data import Data from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py index e62901c133..4094532ac0 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py index 452470240c..496ab64aac 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py index 83b134c399..dae0f8d77f 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py index b252e4e496..b68a4e3702 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/qto/__init__.py b/src/blenderbim/blenderbim/bim/module/qto/__init__.py index 2cd6381ffb..c99f657c4e 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/qto/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/qto/helper.py b/src/blenderbim/blenderbim/bim/module/qto/helper.py index 56ff71b73a..d2c53baaff 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/helper.py +++ b/src/blenderbim/blenderbim/bim/module/qto/helper.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh diff --git a/src/blenderbim/blenderbim/bim/module/qto/operator.py b/src/blenderbim/blenderbim/bim/module/qto/operator.py index ef6b12268c..b77d067e14 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/operator.py +++ b/src/blenderbim/blenderbim/bim/module/qto/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/qto/prop.py b/src/blenderbim/blenderbim/bim/module/qto/prop.py index a081e0cb54..4fec6fec26 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/prop.py +++ b/src/blenderbim/blenderbim/bim/module/qto/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/qto/ui.py b/src/blenderbim/blenderbim/bim/module/qto/ui.py index 9bdcbb8147..aea862d0ce 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/ui.py +++ b/src/blenderbim/blenderbim/bim/module/qto/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel diff --git a/src/blenderbim/blenderbim/bim/module/resource/__init__.py b/src/blenderbim/blenderbim/bim/module/resource/__init__.py index c401265b4d..8794902494 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/resource/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py index 2150b39182..6204d3ab23 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/operator.py +++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/resource/prop.py b/src/blenderbim/blenderbim/bim/module/resource/prop.py index d7b285d372..5681deba24 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/prop.py +++ b/src/blenderbim/blenderbim/bim/module/resource/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index bb5c517202..c00d61bedf 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.resource.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/root/__init__.py b/src/blenderbim/blenderbim/bim/module/root/__init__.py index 0d41a5eba9..4c7c15696e 100644 --- a/src/blenderbim/blenderbim/bim/module/root/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/root/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index d3207eca2e..544357b7c4 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import numpy as np import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/root/prop.py b/src/blenderbim/blenderbim/bim/module/root/prop.py index b79a2a94ea..3a003d368e 100644 --- a/src/blenderbim/blenderbim/bim/module/root/prop.py +++ b/src/blenderbim/blenderbim/bim/module/root/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.schema diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index 6c92e39e26..60e816f496 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel from ifcopenshell.api.root.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index 827083f847..d11e217190 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 3b39499657..af4743877f 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import re import bpy import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index 7723434add..722d051314 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import PropertyGroup from bpy.props import ( diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index c45339201f..b02a328b7b 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index a161269d79..956326cb60 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py index 3c6aec4199..6c5282b693 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import math import isodate diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 40ab311107..5d66fa86a2 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import re import os import bpy diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 556878e411..61711c1ba7 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api import ifcopenshell.util.attribute diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index d294c8865b..bbdc8c20c0 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import isodate import blenderbim.bim.helper from bpy.types import Panel, UIList diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py index 8671309965..9335ac64d4 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 9f7d5648d0..e10939a59f 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api import ifcopenshell.util.element diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index dfea8823de..cf1896ff2b 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from ifcopenshell.api.spatial.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index f5ed85f61b..8d88132e5f 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.spatial.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py index 7c49636828..edccdc92a5 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 58864171c7..102f66082b 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index 513c7fcf03..5da6624f1c 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.ifc import IfcStore from math import radians diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index 6e64fd7ea8..ee8b39fcf7 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import blenderbim.bim.helper from bpy.types import Panel, UIList diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py index e161508777..b60aac300f 100644 --- a/src/blenderbim/blenderbim/bim/module/style/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 400a396d24..4c8e2c53d7 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api import ifcopenshell.util.representation diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py index 11fec121cd..fe0fcbf6b9 100644 --- a/src/blenderbim/blenderbim/bim/module/style/prop.py +++ b/src/blenderbim/blenderbim/bim/module/style/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.ifc import IfcStore from blenderbim.bim.prop import StrProperty, Attribute diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 089db774cf..01273d90bf 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import blenderbim.bim.helper from bpy.types import Panel from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/system/__init__.py b/src/blenderbim/blenderbim/bim/module/system/__init__.py index 0059a29f4d..c5652828e0 100644 --- a/src/blenderbim/blenderbim/bim/module/system/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/system/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index ffff58bc78..d1e8423e9b 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.util.attribute import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/system/prop.py b/src/blenderbim/blenderbim/bim/module/system/prop.py index 5bed5a3fd9..f792ac8597 100644 --- a/src/blenderbim/blenderbim/bim/module/system/prop.py +++ b/src/blenderbim/blenderbim/bim/module/system/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py index 067dd038fc..516e382a90 100644 --- a/src/blenderbim/blenderbim/bim/module/system/ui.py +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.system.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/type/__init__.py b/src/blenderbim/blenderbim/bim/module/type/__init__.py index 09d2184577..329a930875 100644 --- a/src/blenderbim/blenderbim/bim/module/type/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/type/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index d332341a37..3d54041ebe 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.util.schema import ifcopenshell.util.type diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index 1b20b1367a..06fcfb14a2 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.util.type from blenderbim.bim.prop import StrProperty, Attribute diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 0e319198da..533f3e61ee 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + from bpy.types import Panel from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.type.data import Data diff --git a/src/blenderbim/blenderbim/bim/module/unit/__init__.py b/src/blenderbim/blenderbim/bim/module/unit/__init__.py index c4fa9cf228..a0942261df 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/unit/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py index 2c9664c870..af1657d60d 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import json import ifcopenshell.api diff --git a/src/blenderbim/blenderbim/bim/module/unit/prop.py b/src/blenderbim/blenderbim/bim/module/unit/prop.py index 1d863fdb8d..d28d093c73 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/prop.py +++ b/src/blenderbim/blenderbim/bim/module/unit/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell import ifcopenshell.util.schema diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py index c31f9ff179..7fb0f9d5c4 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/void/__init__.py b/src/blenderbim/blenderbim/bim/module/void/__init__.py index f5b5a750d8..95c3503a82 100644 --- a/src/blenderbim/blenderbim/bim/module/void/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/void/__init__.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from . import ui, prop, operator diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 0a2d8ee319..4185daa588 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/bim/module/void/prop.py b/src/blenderbim/blenderbim/bim/module/void/prop.py index 4dd1185fe6..08866a8897 100644 --- a/src/blenderbim/blenderbim/bim/module/void/prop.py +++ b/src/blenderbim/blenderbim/bim/module/void/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import PropertyGroup from bpy.props import PointerProperty @@ -5,3 +24,4 @@ from bpy.props import PointerProperty class VoidProperties(PropertyGroup): desired_opening: PointerProperty(name="Desired Opening To Fill", type=bpy.types.Object) + diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py index 48c29cdcdc..792867c864 100644 --- a/src/blenderbim/blenderbim/bim/module/void/ui.py +++ b/src/blenderbim/blenderbim/bim/module/void/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy from bpy.types import Panel from ifcopenshell.api.void.data import Data diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 7278897ab8..9614ab6018 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import time diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 613646f6e4..04dd9c38e8 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy import json diff --git a/src/blenderbim/blenderbim/bim/schema.py b/src/blenderbim/blenderbim/bim/schema.py index 0542d9ffe9..4cdeeb2b7e 100644 --- a/src/blenderbim/blenderbim/bim/schema.py +++ b/src/blenderbim/blenderbim/bim/schema.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import json import ifcopenshell diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index cb6661e8c4..cb06ed0c52 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import bpy from . import ifc diff --git a/src/blenderbim/docs/conf.py b/src/blenderbim/docs/conf.py index 3c39f711c1..c441ae8a0a 100644 --- a/src/blenderbim/docs/conf.py +++ b/src/blenderbim/docs/conf.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full diff --git a/src/blenderbim/dxf2ifc.py b/src/blenderbim/dxf2ifc.py index 223e5b8fff..62bdfd9659 100644 --- a/src/blenderbim/dxf2ifc.py +++ b/src/blenderbim/dxf2ifc.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import ifcopenshell import ezdxf diff --git a/src/blenderbim/extract.py b/src/blenderbim/extract.py index 79de9fa269..fcc1ac0d3a 100644 --- a/src/blenderbim/extract.py +++ b/src/blenderbim/extract.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import xml.sax, json, copy, pathlib from bs4 import BeautifulSoup import sys diff --git a/src/blenderbim/gbxml.py b/src/blenderbim/gbxml.py index c96df23a26..50abaaaee9 100644 --- a/src/blenderbim/gbxml.py +++ b/src/blenderbim/gbxml.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import uuid import math diff --git a/src/blenderbim/generate_demo_library.py b/src/blenderbim/generate_demo_library.py index c9a95c0342..2911a5fae9 100644 --- a/src/blenderbim/generate_demo_library.py +++ b/src/blenderbim/generate_demo_library.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import ifcopenshell import ifcopenshell.api diff --git a/src/blenderbim/generate_util_type_json.py b/src/blenderbim/generate_util_type_json.py index bd5313b9dc..aa93b0b0f8 100644 --- a/src/blenderbim/generate_util_type_json.py +++ b/src/blenderbim/generate_util_type_json.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + # See bug #1300. Dodgy quick results that we should rebuild later. import json import ifcopenshell diff --git a/src/blenderbim/getIfcElements.py b/src/blenderbim/getIfcElements.py index f4edf13493..76ed473c31 100644 --- a/src/blenderbim/getIfcElements.py +++ b/src/blenderbim/getIfcElements.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + """This script converts the computer iterpretable listing ifcXML XSD into a JSON file""" import xml.etree.ElementTree as ET diff --git a/src/blenderbim/get_description.py b/src/blenderbim/get_description.py index 09846d8fae..ebb673eff8 100644 --- a/src/blenderbim/get_description.py +++ b/src/blenderbim/get_description.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import os import re import html diff --git a/src/blenderbim/get_volume_by_material.py b/src/blenderbim/get_volume_by_material.py index a7cf8ca6b7..0f880dd698 100644 --- a/src/blenderbim/get_volume_by_material.py +++ b/src/blenderbim/get_volume_by_material.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import bpy import bmesh import pprint diff --git a/src/blenderbim/runpytest.py b/src/blenderbim/runpytest.py index ec07e21d49..259800d0d9 100755 --- a/src/blenderbim/runpytest.py +++ b/src/blenderbim/runpytest.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + """ Requires pytest installed under blender diff --git a/src/blenderbim/tests/test_segment_clipping.py b/src/blenderbim/tests/test_segment_clipping.py index d36291fbe1..f89526f786 100644 --- a/src/blenderbim/tests/test_segment_clipping.py +++ b/src/blenderbim/tests/test_segment_clipping.py @@ -1,3 +1,22 @@ + +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + import pytest from mathutils import Vector From f2aec9440f34555f2836de46c07de8a076d619da Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 17 Aug 2021 09:44:43 +1000 Subject: [PATCH 19/72] UoM and unit rates are now shown for schedule of rates in costing --- .../blenderbim/bim/module/cost/ui.py | 29 ++++++++++++++----- .../ifcopenshell/api/cost/data.py | 7 +++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 61f6fb1d75..4503551bfd 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -46,8 +45,13 @@ class BIM_PT_cost_schedules(Panel): row = self.layout.row() row.operator("bim.add_cost_schedule", icon="ADD") - for cost_schedule_id, cost_schedule in Data.cost_schedules.items(): - self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule) + if self.props.active_cost_schedule_id: + self.draw_cost_schedule_ui( + self.props.active_cost_schedule_id, Data.cost_schedules[self.props.active_cost_schedule_id] + ) + else: + for cost_schedule_id, cost_schedule in Data.cost_schedules.items(): + self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule) def draw_cost_schedule_ui(self, cost_schedule_id, cost_schedule): row = self.layout.row(align=True) @@ -464,10 +468,10 @@ class BIM_UL_cost_items(UIList): split2.alignment = "RIGHT" split2.prop(item, "name", emboss=False, text="") - if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES": - split2.label( - text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})" - ) + if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + self.draw_uom_column(split2, cost_item) + else: + self.draw_quantity_column(split2, cost_item) split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"])) @@ -477,6 +481,17 @@ class BIM_UL_cost_items(UIList): split2.label(text="{0:.2f}".format(cost_item["TotalCostValue"])) self.draw_buttons(split2, item, cost_item) + def draw_uom_column(self, layout, cost_item): + text = "-" + if cost_item["CostValues"]: + unit_basis = Data.cost_values[cost_item["CostValues"][0]]["UnitBasis"] + if unit_basis: + text = "{0:.2f}".format(unit_basis["ValueComponent"]) + f" ({unit_basis['UnitSymbol'] or '?'})" + layout.label(text=text) + + def draw_quantity_column(self, layout, cost_item): + layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") + def draw_buttons(self, row, item, cost_item): pass # TODO: reimplement somewhere with better UX # elif self.props.active_cost_item_id: diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py index 234b4b7fe7..138b96c799 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py @@ -111,19 +111,20 @@ class Data: data["TotalAppliedValue"] = 0.0 data["CategoryValues"] = {} for cost_value in cost_item.CostValues or []: - cls.load_cost_item_value(data, cost_item, cost_value) + cls.load_cost_item_value(cost_item, data, cost_value) data["CostValues"].append(cost_value.id()) data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"] data["TotalCostValue"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] @classmethod - def load_cost_item_value(cls, cost_item_data, cost_item, cost_value): + def load_cost_item_value(cls, cost_item, cost_item_data, cost_value): value_data = cost_value.get_info() del value_data["AppliedValue"] if value_data["UnitBasis"]: data = cost_value.UnitBasis.get_info() data["ValueComponent"] = data["ValueComponent"].wrappedValue data["UnitComponent"] = data["UnitComponent"].id() + data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent) value_data["UnitBasis"] = data if value_data["ApplicableDate"]: value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"]) @@ -138,7 +139,7 @@ class Data: cls.cost_values[cost_value.id()] = value_data for component in cost_value.Components or []: - cls.load_cost_item_value(cost_item_data, cost_item, component) + cls.load_cost_item_value(cost_item, cost_item_data, component) @classmethod def calculate_applied_value(cls, cost_item, cost_value, category_filter=None): From 899a0c6fb594174bd692d52bef98a2b74e89c569 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 17 Aug 2021 11:01:41 +1000 Subject: [PATCH 20/72] You can now browse a schedule of rates alongside a cost schedule --- .../blenderbim/bim/module/cost/__init__.py | 6 ++ .../blenderbim/bim/module/cost/operator.py | 85 +++++++++++++++++- .../blenderbim/bim/module/cost/prop.py | 28 +++++- .../blenderbim/bim/module/cost/ui.py | 88 ++++++++++++++----- 4 files changed, 178 insertions(+), 29 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index c4893f0c7f..c768025826 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -59,6 +59,10 @@ classes = ( operator.ImportCostScheduleCsv, operator.LoadCostItemQuantities, operator.LoadCostItemTypes, + operator.AssignCostValue, + operator.LoadScheduleOfRates, + operator.ExpandCostItemRate, + operator.ContractCostItemRate, prop.CostItem, prop.CostItemQuantity, prop.CostItemType, @@ -66,10 +70,12 @@ classes = ( ui.BIM_PT_cost_schedules, ui.BIM_PT_cost_item_quantities, ui.BIM_PT_cost_item_types, + ui.BIM_PT_cost_item_rates, ui.BIM_UL_cost_items, ui.BIM_UL_cost_columns, ui.BIM_UL_cost_item_quantities, ui.BIM_UL_cost_item_types, + ui.BIM_UL_cost_item_rates, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index d467bf4e7f..9a617b481d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -62,6 +62,7 @@ class EditCostSchedule(bpy.types.Operator): **{"cost_schedule": self.file.by_id(props.active_cost_schedule_id), "attributes": attributes}, ) Data.load(IfcStore.get_file()) + purge() bpy.ops.bim.disable_editing_cost_schedule() return {"FINISHED"} @@ -124,8 +125,7 @@ class EnableEditingCostItems(bpy.types.Operator): self.props = context.scene.BIMCostProperties self.props.is_cost_update_enabled = False self.props.active_cost_schedule_id = self.cost_schedule - while len(self.props.cost_items) > 0: - self.props.cost_items.remove(0) + self.props.cost_items.clear() self.contracted_cost_items = json.loads(self.props.contracted_cost_items) for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]: @@ -164,8 +164,6 @@ class EnableEditingCostItems(bpy.types.Operator): for related_object_id in cost_item["IsNestedBy"]: self.create_new_cost_item_li(related_object_id, level_index + 1) - return {"FINISHED"} - class DisableEditingCostSchedule(bpy.types.Operator): bl_idname = "bim.disable_editing_cost_schedule" @@ -808,6 +806,9 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False) def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): from ifc5d.csv2ifc import Csv2Ifc self.file = IfcStore.get_file() @@ -819,6 +820,7 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): csv2ifc.execute() Data.load(IfcStore.get_file()) UnitData.load(IfcStore.get_file()) + purge() print("Import finished in {:.2f} seconds".format(time.time() - start)) return {"FINISHED"} @@ -912,3 +914,78 @@ class LoadCostItemTypes(bpy.types.Operator): new.ifc_definition_id = control_id new.name = related_object.Name or "Unnamed" return {"FINISHED"} + + +class AssignCostValue(bpy.types.Operator): + bl_idname = "bim.assign_cost_value" + bl_label = "Assign Cost Value" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + return {"FINISHED"} + + +class LoadScheduleOfRates(bpy.types.Operator): + bl_idname = "bim.load_schedule_of_rates" + bl_label = "Load Schedule of Rates" + bl_options = {"REGISTER", "UNDO"} + cost_schedule: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMCostProperties + self.props.is_cost_update_enabled = False + self.props.cost_item_rates.clear() + self.contracted_cost_item_rates = json.loads(self.props.contracted_cost_item_rates) + for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]: + self.create_new_cost_item_li(related_object_id, 0) + self.props.is_cost_update_enabled = True + return {"FINISHED"} + + def create_new_cost_item_li(self, related_object_id, level_index): + cost_item = Data.cost_items[related_object_id] + new = self.props.cost_item_rates.add() + new.ifc_definition_id = related_object_id + new.name = cost_item["Name"] or "Unnamed" + new.identification = cost_item["Identification"] or "XXX" + new.is_expanded = related_object_id not in self.contracted_cost_item_rates + new.level_index = level_index + if cost_item["IsNestedBy"]: + new.has_children = True + if new.is_expanded: + for related_object_id in cost_item["IsNestedBy"]: + self.create_new_cost_item_li(related_object_id, level_index + 1) + + +class ExpandCostItemRate(bpy.types.Operator): + bl_idname = "bim.expand_cost_item_rate" + bl_label = "Expand Cost Item Rate" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMCostProperties + self.file = IfcStore.get_file() + contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + contracted_cost_item_rates.remove(self.cost_item) + props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) + bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) + return {"FINISHED"} + + +class ContractCostItemRate(bpy.types.Operator): + bl_idname = "bim.contract_cost_item_rate" + bl_label = "Contract Cost Item Rate" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMCostProperties + self.file = IfcStore.get_file() + contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + contracted_cost_item_rates.append(self.cost_item) + props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) + bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index d0691be0f0..82880f430a 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -43,6 +42,7 @@ processquantitynames_enum = [] processquantitynames_id = 0 resourcequantitynames_enum = [] resourcequantitynames_id = 0 +scheduleofrates_enum = [] def purge(): @@ -53,6 +53,7 @@ def purge(): global processquantitynames_id global resourcequantitynames_enum global resourcequantitynames_id + global scheduleofrates_enum quantitytypes_enum = [] productquantitynames_enum = [] productquantitynames_count = [] @@ -60,12 +61,27 @@ def purge(): processquantitynames_id = 0 resourcequantitynames_enum = [] resourcequantitynames_id = 0 + scheduleofrates_enum = [] + + +def get_schedule_of_rates(self, context): + global scheduleofrates_enum + if len(scheduleofrates_enum) == 0: + scheduleofrates_enum.extend( + (str(ifc_definition_id), schedule["Name"] or "Unnamed", "") + for ifc_definition_id, schedule in Data.cost_schedules.items() + if schedule["PredefinedType"] == "SCHEDULEOFRATES" + ) + return scheduleofrates_enum + + +def update_schedule_of_rates(self, context): + bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(self.schedule_of_rates)) def getQuantityTypes(self, context): global quantitytypes_enum if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): - quantitytypes_enum = [] quantitytypes_enum.extend( [ (t.name(), t.name(), "") @@ -238,4 +254,10 @@ class BIMCostProperties(PropertyGroup): cost_item_resources: CollectionProperty(name="Cost Item Resources", type=CostItemQuantity) active_cost_item_resource_index: IntProperty(name="Active Cost Item Resource Index") cost_item_type_products: CollectionProperty(name="Cost Item Type Products", type=CostItemType) - active_cost_item_type_product_index: IntProperty(name="Active Cost Item TYpe Product Index") + active_cost_item_type_product_index: IntProperty(name="Active Cost Item Type Product Index") + schedule_of_rates: EnumProperty( + items=get_schedule_of_rates, name="Schedule Of Rates", update=update_schedule_of_rates + ) + cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem) + active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index") + contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]") diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 4503551bfd..cc88f37e4d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -441,26 +441,51 @@ class BIM_PT_cost_item_quantities(Panel): op.prop_name = self.props.resource_quantity_names -class BIM_UL_cost_items(UIList): +class BIM_PT_cost_item_rates(Panel): + bl_label = "IFC Cost Item Rates" + bl_idname = "BIM_PT_cost_item_rates" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_cost_schedules" + + @classmethod + def poll(cls, context): + props = context.scene.BIMCostProperties + total_cost_items = len(props.cost_items) + if not props.active_cost_schedule_id: + return False + if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + return False + if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: + return True + return False + + def draw(self, context): + self.props = context.scene.BIMCostProperties + cost_item = self.props.cost_items[self.props.active_cost_item_index] + row = self.layout.row(align=True) + row.prop(self.props, "schedule_of_rates", text="") + row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") + self.layout.template_list( + "BIM_UL_cost_item_rates", + "", + self.props, + "cost_item_rates", + self.props, + "active_cost_item_rate_index", + ) + + +class BIM_UL_cost_items_trait(): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: self.props = context.scene.BIMCostProperties cost_item = Data.cost_items[item.ifc_definition_id] row = layout.row(align=True) - for i in range(0, item.level_index): - row.label(text="", icon="BLANK1") - if item.has_children: - if item.is_expanded: - row.operator( - "bim.contract_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" - ).cost_item = item.ifc_definition_id - else: - row.operator( - "bim.expand_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" - ).cost_item = item.ifc_definition_id - else: - row.label(text="", icon="DOT") + self.draw_hierarchy(row, item) split1 = row.split(factor=0.1) split1.prop(item, "identification", emboss=False, text="") @@ -479,7 +504,19 @@ class BIM_UL_cost_items(UIList): split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) split2.label(text="{0:.2f}".format(cost_item["TotalCostValue"])) - self.draw_buttons(split2, item, cost_item) + # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX + + def draw_hierarchy(self, row, item): + for i in range(0, item.level_index): + row.label(text="", icon="BLANK1") + if item.has_children: + if item.is_expanded: + op = row.operator(self.contract_operator, text="", emboss=False, icon="DISCLOSURE_TRI_DOWN") + else: + op = row.operator(self.expand_operator, text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT") + op.cost_item = item.ifc_definition_id + else: + row.label(text="", icon="DOT") def draw_uom_column(self, layout, cost_item): text = "-" @@ -492,13 +529,20 @@ class BIM_UL_cost_items(UIList): def draw_quantity_column(self, layout, cost_item): layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") - def draw_buttons(self, row, item, cost_item): - pass # TODO: reimplement somewhere with better UX - # elif self.props.active_cost_item_id: - # if self.props.cost_item_editing_type == "VALUES": - # op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN") - # op.source = self.props.active_cost_item_id - # op.destination = item.ifc_definition_id + +class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList): + def __init__(self): + self.contract_operator = "bim.contract_cost_item" + self.expand_operator = "bim.expand_cost_item" + + +class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): + # A schedule of rates UIList is identical to a regular cost items UIList but + # we want a separate UIList instance so that you can browse both lists + # independently in Blender. So we use a trait. + def __init__(self): + self.contract_operator = "bim.contract_cost_item_rate" + self.expand_operator = "bim.expand_cost_item_rate" class BIM_UL_cost_columns(UIList): From 912699e63c4ab77cbabbc1323564b89f2286666b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 17 Aug 2021 12:22:57 +1000 Subject: [PATCH 21/72] You can now assign rates from a schedule of rates to a cost plan --- .../blenderbim/bim/module/cost/operator.py | 28 ++++++++++--- .../blenderbim/bim/module/cost/prop.py | 5 ++- .../blenderbim/bim/module/cost/ui.py | 39 +++++++++++++------ .../api/cost/assign_cost_value.py | 17 ++++++++ .../api/cost/remove_cost_item_value.py | 14 ++++++- 5 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 9a617b481d..405d22d20c 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -610,6 +609,7 @@ class RemoveCostItemValue(bpy.types.Operator): bl_idname = "bim.remove_cost_item_value" bl_label = "Add Cost Item Value" bl_options = {"REGISTER", "UNDO"} + parent: bpy.props.IntProperty() cost_value: bpy.props.IntProperty() def execute(self, context): @@ -617,7 +617,12 @@ class RemoveCostItemValue(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() - ifcopenshell.api.run("cost.remove_cost_item_value", self.file, cost_value=self.file.by_id(self.cost_value)) + ifcopenshell.api.run( + "cost.remove_cost_item_value", + self.file, + parent=self.file.by_id(self.parent), + cost_value=self.file.by_id(self.cost_value), + ) Data.load(self.file) return {"FINISHED"} @@ -660,6 +665,7 @@ class EnableEditingCostItemValue(bpy.types.Operator): prop.float_value = data["UnitBasis"]["ValueComponent"] or 0 else: prop.float_value = 0 + prop = self.props.cost_value_attributes.add() prop.name = "UnitBasisUnit" prop.data_type = "enum" @@ -897,9 +903,9 @@ class LoadCostItemTypes(bpy.types.Operator): while len(self.props.cost_item_type_products) > 0: self.props.cost_item_type_products.remove(0) # TODO implement process and resource types - #while len(self.props.cost_item_processes) > 0: + # while len(self.props.cost_item_processes) > 0: # self.props.cost_item_processes.remove(0) - #while len(self.props.cost_item_resources) > 0: + # while len(self.props.cost_item_resources) > 0: # self.props.cost_item_resources.remove(0) ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items(): @@ -907,9 +913,9 @@ class LoadCostItemTypes(bpy.types.Operator): if related_object.is_a("IfcTypeProduct"): new = self.props.cost_item_type_products.add() # TODO implement process and resource types - #elif related_object.is_a("IfcProcess"): + # elif related_object.is_a("IfcProcess"): # new = self.props.cost_item_processes.add() - #elif related_object.is_a("IfcResource"): + # elif related_object.is_a("IfcResource"): # new = self.props.cost_item_resources.add() new.ifc_definition_id = control_id new.name = related_object.Name or "Unnamed" @@ -920,11 +926,21 @@ class AssignCostValue(bpy.types.Operator): bl_idname = "bim.assign_cost_value" bl_label = "Assign Cost Value" bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + cost_rate: bpy.props.IntProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.assign_cost_value", + self.file, + cost_item=self.file.by_id(self.cost_item), + cost_rate=self.file.by_id(self.cost_rate), + ) + Data.load(self.file) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 82880f430a..00a5ef18af 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -160,7 +160,10 @@ def getResourceQuantityNames(self, context): def update_cost_item_index(self, context): - bpy.ops.bim.load_cost_item_quantities() + if Data.cost_schedules[self.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + bpy.ops.bim.load_cost_item_types() + else: + bpy.ops.bim.load_cost_item_quantities() def updateCostItemIdentification(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index cc88f37e4d..2b9626fc0f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -21,6 +21,7 @@ from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.cost.data import Data +from ifcopenshell.api.unit.data import Data as UnitData class BIM_PT_cost_schedules(Panel): @@ -42,6 +43,9 @@ class BIM_PT_cost_schedules(Panel): if not Data.is_loaded: Data.load(IfcStore.get_file()) + if not UnitData.is_loaded: + UnitData.load(IfcStore.get_file()) + row = self.layout.row() row.operator("bim.add_cost_schedule", icon="ADD") @@ -193,19 +197,20 @@ class BIM_PT_cost_schedules(Panel): self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id]) def draw_readonly_cost_value_ui(self, layout, cost_value_id): + # This UI is really poor. Delete and start again. cost_value = Data.cost_values[cost_value_id] cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) if cost_value["Category"]: cost_value_label += " ({})".format(cost_value["Category"]) layout.label(text="", icon="DISC") - self.draw_cost_value_operator_ui(layout, cost_value_id) + self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_cost_item_id) layout.label(text=cost_value_label) for component_id in cost_value["Components"] or []: - self.draw_readonly_component_cost_value_ui(layout, component_id) + self.draw_readonly_component_cost_value_ui(layout, component_id, cost_value["id"]) - def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, level=1): - self.draw_cost_value_operator_ui(layout, cost_value_id) + def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, parent_id, level=1): + self.draw_cost_value_operator_ui(layout, cost_value_id, parent_id) cost_value = Data.cost_values[cost_value_id] cost_value_label = ">" * level cost_value_label += "{0:.2f}".format(cost_value["AppliedValue"]) @@ -214,9 +219,9 @@ class BIM_PT_cost_schedules(Panel): layout.label(text=cost_value_label) for component_id in cost_value["Components"] or []: - self.draw_readonly_component_cost_value_ui(layout, component_id, level + 1) + self.draw_readonly_component_cost_value_ui(layout, component_id, cost_value["id"], level + 1) - def draw_cost_value_operator_ui(self, layout, cost_value_id): + def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id): if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id: op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK") op.cost_value = cost_value_id @@ -233,6 +238,7 @@ class BIM_PT_cost_schedules(Panel): if self.props.cost_types == "CATEGORY": op.cost_category = self.props.cost_category op = layout.operator("bim.remove_cost_item_value", text="", icon="X") + op.parent = parent_id op.cost_value = cost_value_id else: op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL") @@ -243,6 +249,7 @@ class BIM_PT_cost_schedules(Panel): if self.props.cost_types == "CATEGORY": op.cost_category = self.props.cost_category op = layout.operator("bim.remove_cost_item_value", text="", icon="X") + op.parent = parent_id op.cost_value = cost_value_id def draw_editable_cost_value_ui(self, layout, cost_value): @@ -467,7 +474,9 @@ class BIM_PT_cost_item_rates(Panel): cost_item = self.props.cost_items[self.props.active_cost_item_index] row = self.layout.row(align=True) row.prop(self.props, "schedule_of_rates", text="") - row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") + op = row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") + op.cost_item = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id + op.cost_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index].ifc_definition_id self.layout.template_list( "BIM_UL_cost_item_rates", "", @@ -493,10 +502,7 @@ class BIM_UL_cost_items_trait(): split2.alignment = "RIGHT" split2.prop(item, "name", emboss=False, text="") - if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": - self.draw_uom_column(split2, cost_item) - else: - self.draw_quantity_column(split2, cost_item) + self.draw_quantity_column(split2, cost_item) split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"])) @@ -518,6 +524,12 @@ class BIM_UL_cost_items_trait(): else: row.label(text="", icon="DOT") + def draw_quantity_column(self, layout, cost_item): + if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + self.draw_uom_column(layout, cost_item) + else: + self.draw_total_quantity_column(layout, cost_item) + def draw_uom_column(self, layout, cost_item): text = "-" if cost_item["CostValues"]: @@ -526,7 +538,7 @@ class BIM_UL_cost_items_trait(): text = "{0:.2f}".format(unit_basis["ValueComponent"]) + f" ({unit_basis['UnitSymbol'] or '?'})" layout.label(text=text) - def draw_quantity_column(self, layout, cost_item): + def draw_total_quantity_column(self, layout, cost_item): layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") @@ -544,6 +556,9 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): self.contract_operator = "bim.contract_cost_item_rate" self.expand_operator = "bim.expand_cost_item_rate" + def draw_quantity_column(self, layout, cost_item): + self.draw_uom_column(layout, cost_item) + class BIM_UL_cost_columns(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py new file mode 100644 index 0000000000..af3aab55ba --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py @@ -0,0 +1,17 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"cost_item": None, "cost_rate": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for cost_value in self.settings["cost_item"].CostValues: + ifcopenshell.api.run( + "cost.remove_cost_item_value", self.file, parent=self.settings["cost_item"], cost_value=cost_value + ) + # This is an assumption, and not part of the official IFC documentation + self.settings["cost_item"].CostValues = self.settings["cost_rate"].CostValues diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py index 05d166790c..58eb0b9675 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py @@ -1,9 +1,19 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"cost_value": None} + self.settings = {"parent": None, "cost_value": None} for key, value in settings.items(): self.settings[key] = value def execute(self): - self.file.remove(self.settings["cost_value"]) + if len(self.file.get_inverse(self.settings["cost_value"])) == 1: + self.file.remove(self.settings["cost_value"]) + # TODO deep purge + elif self.settings["parent"].is_a("IfcCostItem"): + values = list(self.settings["parent"].CostValues) + values.remove(self.settings["cost_value"]) + self.settings["parent"].CostValues = values if values else None + elif self.settings["parent"].is_a("IfcCostValue"): + components = list(self.settings["parent"].Components) + components.remove(self.settings["cost_value"]) + self.settings["parent"].Components = components if components else None From 2c270a7315cd47f99664de28579364b86632acc6 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Tue, 17 Aug 2021 09:36:53 +0200 Subject: [PATCH 22/72] Fix https://github.com/IfcOpenShell/IfcOpenShell/issues/1663 : Extract Docs is bundled with IfcPatch module --- .../blenderbim/bim/module/patch/helper.py | 101 ------------------ .../blenderbim/bim/module/patch/operator.py | 4 +- .../blenderbim/bim/module/patch/prop.py | 4 +- src/ifcpatch/ifcpatch/__init__.py | 80 ++++++++++++++ 4 files changed, 82 insertions(+), 107 deletions(-) delete mode 100644 src/blenderbim/blenderbim/bim/module/patch/helper.py diff --git a/src/blenderbim/blenderbim/bim/module/patch/helper.py b/src/blenderbim/blenderbim/bim/module/patch/helper.py deleted file mode 100644 index 296fa167c9..0000000000 --- a/src/blenderbim/blenderbim/bim/module/patch/helper.py +++ /dev/null @@ -1,101 +0,0 @@ - -# BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult -# -# This file is part of BlenderBIM Add-on. -# -# BlenderBIM Add-on is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# BlenderBIM Add-on is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with BlenderBIM Add-on. If not, see . - -import typing -import inspect -import collections -import importlib -from types import ModuleType - -def extract_docs( - module: ModuleType, - submodule_name: str, - cls_name: str, - method_name: str, - boilerplate_args : typing.Iterable[str]=None): - """Extract class docstrings and method arguments - - :param module: Parent module from which to extract the submodule class - :param submodule_name: Submodule from which to extract the class - :param cls_name: Class from which to extract the docstring and method arguments - :param method_name: Class Method name from which to extract arguments - :param boilerplate_args: String iterable containing arguments that shall not be parsed - """ - spec = importlib.util.spec_from_file_location(submodule_name, f"{module.__path__[0]}/recipes/{submodule_name}.py") - submodule = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(submodule) - try: - return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args) - except AttributeError as e: - print(e) - except ModuleNotFoundError as e: - print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}") - except: - print(f"Error : IFCPatch {str(submodule)} could not load") - -def _extract_docs(cls, method_name, boilerplate_args): - inputs = collections.OrderedDict() - method = getattr(cls, method_name) - node_data = {"class": cls} - - signature = inspect.signature(method) - for name, parameter in signature.parameters.items(): - if name == "self": - continue - inputs[name] = {"name": name} - if isinstance(parameter.default, (str, float, int, bool)): - inputs[name]["default"] = parameter.default - - type_hints = typing.get_type_hints(method) - for name, socket_data in inputs.items(): - type_hint = type_hints.get(name, None) - if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??) - continue - if isinstance(type_hint, typing._UnionGenericAlias): - inputs[name]["type"] = [t.__name__ for t in typing.get_args(type_hint)] - else: - inputs[name]["type"] = type_hint.__name__ - - description = "" - doc = method.__doc__ - if doc is not None: - for i, line in enumerate(doc.split("\n")): - line = line.strip() - if i == 0: - node_data["name"] = line - elif line.startswith(":return:"): - node_data["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()} - elif line.startswith(":param"): - param_name = line.split(":")[1].strip().replace("param ", "") - if param_name in inputs: - inputs[param_name]["description"] = line.split(":")[2].strip() - elif i == 2: - description += line - elif i > 2: - description += "\n" + line - - node_data["description"] = description.strip() - node_data["inputs"] = inputs - - if boilerplate_args is not None: - for arg in boilerplate_args: # Remove boilerplate arguments - node_data["inputs"].pop(arg, None) - return node_data - diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index 518bd381b2..ea76cd51c3 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -26,8 +26,6 @@ try: except: print("IfcPatch not available") -from .helper import extract_docs - class SelectIfcPatchInput(bpy.types.Operator): bl_idname = "bim.select_ifc_patch_input" @@ -102,7 +100,7 @@ class UpdateIfcPatchArguments(bpy.types.Operator): return {"FINISHED"} patch_args = context.scene.BIMPatchProperties.ifc_patch_args_attr patch_args.clear() - docs = extract_docs(ifcpatch, self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args")) + docs = ifcpatch.extract_docs(self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args")) if docs and "inputs" in docs: inputs = docs["inputs"] for arg_name in inputs: diff --git a/src/blenderbim/blenderbim/bim/module/patch/prop.py b/src/blenderbim/blenderbim/bim/module/patch/prop.py index eb34c1f65d..ac9e09faa9 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/prop.py +++ b/src/blenderbim/blenderbim/bim/module/patch/prop.py @@ -33,8 +33,6 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) -from .helper import extract_docs -from .operator import UpdateIfcPatchArguments ifcpatchrecipes_enum = [] @@ -54,7 +52,7 @@ def getIfcPatchRecipes(self, context): f = str(filename.stem) if f == "__init__": continue - docs = extract_docs(ifcpatch, f, "Patcher", "__init__", ("src", "file", "logger", "args")) + docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args")) ifcpatchrecipes_enum.append((f, f, docs.get("description","") if docs else "")) return ifcpatchrecipes_enum diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 6c2b60a369..79b9027592 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -22,6 +22,11 @@ import ifcopenshell import logging +import os +import typing +import inspect +import collections +import importlib def execute(args, is_library=None): @@ -51,3 +56,78 @@ def execute(args, is_library=None): else: ifc_file.write(args["output"]) print("# All tasks are complete :-)") + + +def extract_docs( + submodule_name: str, + cls_name: str, + method_name: str="__init__", + boilerplate_args : typing.Iterable[str]=None): + """Extract class docstrings and method arguments + + :param module: Parent module from which to extract the submodule class + :param submodule_name: Submodule from which to extract the class + :param cls_name: Class from which to extract the docstring and method arguments + :param method_name: Class Method name from which to extract arguments + :param boilerplate_args: String iterable containing arguments that shall not be parsed + """ + spec = importlib.util.spec_from_file_location( + submodule_name, + f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py") + submodule = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(submodule) + try: + return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args) + except AttributeError as e: + print(e) + except ModuleNotFoundError as e: + print(f"Error : IFCPatch {str(submodule)} could not load because : {str(e)}") + + +def _extract_docs(cls, method_name, boilerplate_args): + inputs = collections.OrderedDict() + method = getattr(cls, method_name) + docs = {"class": cls} + if boilerplate_args is None: + boilerplate_args = [] + + signature = inspect.signature(method) + for name, parameter in signature.parameters.items(): + if name == "self" or name in boilerplate_args: + continue + inputs[name] = {"name": name} + if isinstance(parameter.default, (str, float, int, bool)): + inputs[name]["default"] = parameter.default + + type_hints = typing.get_type_hints(method) + for input_name in inputs.keys(): + type_hint = type_hints.get(input_name, None) + if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??) + continue + if isinstance(type_hint, typing._UnionGenericAlias): + inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)] + else: + inputs[input_name]["type"] = type_hint.__name__ + + description = "" + doc = method.__doc__ + if doc is not None: + for i, line in enumerate(doc.split("\n")): + line = line.strip() + if i == 0: + docs["name"] = line + elif line.startswith(":return:"): + docs["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()} + elif line.startswith(":param"): + param_name = line.split(":")[1].strip().replace("param ", "") + if param_name in inputs: + inputs[param_name]["description"] = line.split(":")[2].strip() + elif i == 2: + description += line + elif i > 2: + description += "\n" + line + + docs["description"] = description.strip() + docs["inputs"] = inputs + return docs From f4fac9210da2db3191d5982eea075c85fd9f9303 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Tue, 17 Aug 2021 09:46:43 +0200 Subject: [PATCH 23/72] Fix https://github.com/IfcOpenShell/IfcOpenShell/issues/1664 : IFCPatch wouldn't correctly unpack un-hinted arguments --- src/ifcpatch/ifcpatch/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 79b9027592..b9e9166d66 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -37,10 +37,9 @@ def execute(args, is_library=None): print("# Loading patch recipe ...") recipes = getattr(__import__("ifcpatch.recipes.{}".format(args["recipe"])), "recipes") recipe = getattr(recipes, args["recipe"]) - try: - # We unpack the arguments if the Patcher has been type-hinted and docstringed + if recipe.Patcher.__init__.__doc__ is not None: patcher = recipe.Patcher(args["input"], ifc_file, logger, *args["arguments"]) - except TypeError: + else: patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"]) print("# Patching ...") patcher.patch() From 88669701b2d97bf68053f1edce1ba8d520e58cd6 Mon Sep 17 00:00:00 2001 From: LaurensJN Date: Tue, 17 Aug 2021 11:24:21 +0200 Subject: [PATCH 24/72] cityjson: Implement conversion of all LODs instead of only the most detailed --- src/ifccityjson/README.md | 2 +- src/ifccityjson/cityjson2ifc.py | 114 +++++++++++++++--------- src/ifccityjson/example/geometries.json | 4 +- src/ifccityjson/ifccityjson.py | 7 +- 4 files changed, 79 insertions(+), 48 deletions(-) diff --git a/src/ifccityjson/README.md b/src/ifccityjson/README.md index d15fb01142..1f61274082 100644 --- a/src/ifccityjson/README.md +++ b/src/ifccityjson/README.md @@ -35,4 +35,4 @@ The example file that could be used is example/3D_BAG_example.json - [x] Do not use template IFC for new IFC file, but make IFC file from scratch - [x] Create mapping to IFC for all CityJSON object types & semantic surfaces - [ ] Implement conversion of all CitYJSON geometries -- [ ] Implement conversion of all LODs instead of only the most detailed +- [x] Implement conversion of all LODs instead of only the most detailed diff --git a/src/ifccityjson/cityjson2ifc.py b/src/ifccityjson/cityjson2ifc.py index 8ed37c0d96..d2c3ea3b99 100644 --- a/src/ifccityjson/cityjson2ifc.py +++ b/src/ifccityjson/cityjson2ifc.py @@ -1,4 +1,3 @@ - # ifccityjson - Python CityJSON to IFC converter # Copyright (C) 2021 Laurens J.N. Oostwegel # @@ -26,9 +25,9 @@ JSON_TO_IFC = { "Building": ["IfcBuilding"], "BuildingPart": ["IfcBuilding", {"CompositionType": "PARTIAL"}], "BuildingInstallation": ["IfcBuildingElementProxy"], - "Road": ["IfcCivilElement"], # Update for IFC4.3 - "Railway": ["IfcCivilElement"], # Update for IFC4.3 - "TransportSquare": ["IfcCivilElement"], # Update for IFC4.3 + "Road": ["IfcCivilElement"], # Update for IFC4.3 + "Railway": ["IfcCivilElement"], # Update for IFC4.3 + "TransportSquare": ["IfcCivilElement"], # Update for IFC4.3 "TINRelief": ["IfcGeographicElement", {"PredefinedType": "TERRAIN"}], "WaterBody": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED", "ObjectType": "WaterBody"}], # Update for IFC4.3 @@ -66,6 +65,7 @@ JSON_TO_IFC = { "AuxiliaryTrafficArea": ["IfcCivilElement"] # Update for IFC4.3 } + class Cityjson2ifc: def __init__(self): self.city_model = None @@ -74,11 +74,10 @@ class Cityjson2ifc: self.geometry = GeometryIO() self.configuration() - - def configuration(self, file_destination="output.ifc", name_attribute=None): + def configuration(self, file_destination="output.ifc", name_attribute=None, split=True): self.properties["file_destination"] = file_destination self.properties["name_attribute"] = name_attribute - + self.properties["split"] = split def convert(self, city_model): self.city_model = city_model @@ -109,7 +108,8 @@ class Cityjson2ifc: # Meter is assumed as unit for now unit = self.IFC_model.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE") crs = self.IFC_model.create_entity("IfcProjectedCrs", Name=f"epsg:{epsg}") - self.IFC_model.create_entity("IfcMapConversion", self.IFC_representation_context, **self.properties["local_translation"]) + self.IFC_model.create_entity("IfcMapConversion", self.IFC_representation_context, + **self.properties["local_translation"]) def create_new_file(self): self.IFC_model = ifcopenshell.api.run("project.create_file") @@ -118,13 +118,27 @@ class Cityjson2ifc: self.properties["owner_history"] = self.create_owner_history() self.IFC_representation_context = ifcopenshell.api.run("context.add_context", self.IFC_model, **{"context": "Model"}) - self.IFC_representation_sub_context = ifcopenshell.api.run("context.add_context", self.IFC_model, - **{"context": "Model", - "subcontext": "Body", # LODs as subcontext - "target_view": "MODEL_VIEW"}) + + if not self.city_model.has_metadata() or "presentLoDs" not in self.city_model.j["metadata"]: + self.city_model.update_metadata() + self.IFC_representation_sub_contexts = {} + for lod in self.city_model.j["metadata"]["presentLoDs"]: + # TODO in ifcopenshell.api.context.add_context add support for UserDefinedTargetView + # self.IFC_representation_sub_contexts[str(lod)] = ifcopenshell.api.run("context.add_context", self.IFC_model, + # **{"context": "Model", + # "subcontext": "Body", + # "target_view": "USERDEFINED", + # "UserDefinedTargetView":str(lod)}) + self.IFC_representation_sub_contexts[str(lod)] = self.IFC_model.create_entity("IfcGeometricRepresentationSubContext", + **{"ContextType": "Model", + "ContextIdentifier": "Body", + "TargetView": "USERDEFINED", + "ParentContext": self.IFC_representation_context, + "UserDefinedTargetView": "LOD" + str(lod)}) + self.IFC_site = ifcopenshell.api.run("root.create_entity", self.IFC_model, - **{"ifc_class": "IfcSite", - "name": "My Site"}) + **{"ifc_class": "IfcSite", + "name": "My Site"}) def create_owner_history(self): actor = self.IFC_model.createIfcActorRole("ENGINEER", None, None) @@ -137,7 +151,8 @@ class Cityjson2ifc: p_o = self.IFC_model.createIfcPersonAndOrganization(person, organization) application = self.IFC_model.createIfcApplication(organization, "v0.0.x", "ifccityjson", "ifccityjson") timestamp = int(datetime.now().timestamp()) - ownerHistory = self.IFC_model.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, timestamp) + ownerHistory = self.IFC_model.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, + timestamp) return ownerHistory @@ -168,29 +183,26 @@ class Cityjson2ifc: if len(obj.geometry) == 0: print(f"Warning: Object {obj_id} has no geometry.") - for geom in obj.geometry: - if geom.lod > lod: - geometry = geom - lod = geom.lod - IFC_semantic_surface_children = [] - if geometry and geometry.surfaces: - for surface_id in geometry.surfaces: - IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0] - child_data = {"GlobalId": ifcopenshell.guid.new(), - "Name": IFC_child_class - } - - # CREATE ENTITY - surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id) - if surface_geometry: - child_data["Representation"] = self.create_IFC_representation(surface_geometry, 'brep') - IFC_semantic_surface_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data)) - - elif geometry: - IFC_geometry, shape_representation_type = self.geometry.create_IFC_geometry(self.IFC_model, geometry) + IFC_shape_representations = [] + for geometry in obj.geometry: + lod = geometry.lod + IFC_geometry, shape_representation_type = None, None + # representation = self.create_IFC_LOD_representation(obj, geometry) + if geometry and geometry.surfaces: + IFC_semantic_surface_children = self.create_IFC_semantic_surface_children(geometry, lod) + if geometry: + IFC_geometry, shape_representation_type = self.geometry.create_IFC_geometry(self.IFC_model, + geometry) if IFC_geometry: - data["Representation"] = self.create_IFC_representation(IFC_geometry, shape_representation_type) + IFC_shape_representation = self.create_IFC_shape_representation(IFC_geometry, + shape_representation_type, + lod) + IFC_shape_representations.append(IFC_shape_representation) + + if len(IFC_shape_representations) > 0: + data["Representation"] = self.IFC_model.create_entity("IfcProductDefinitionShape", + Representations=IFC_shape_representations) data["GlobalId"] = ifcopenshell.guid.new() data["Name"] = IFC_name @@ -212,9 +224,9 @@ class Cityjson2ifc: if IFC_semantic_surface_children: self.IFC_model.create_entity("IfcRelAggregates", - **{"GlobalId": ifcopenshell.guid.new(), - "RelatedObjects": IFC_semantic_surface_children, - "RelatingObject": IFC_object}) + **{"GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": IFC_semantic_surface_children, + "RelatingObject": IFC_object}) self.create_property_set(obj.attributes, IFC_object) @@ -225,16 +237,30 @@ class Cityjson2ifc: "RelatingStructure": parent_children['Parent']} ) - def create_IFC_representation(self, IFC_geometry, shape_representation_type): + def create_IFC_semantic_surface_children(self, geometry, lod): + IFC_semantic_surface_children = [] + for surface_id in geometry.surfaces: + IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0] + child_data = {"GlobalId": ifcopenshell.guid.new(), + "Name": IFC_child_class + } + + # CREATE ENTITY + surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id) + if surface_geometry: + child_data["Representation"] = self.create_IFC_shape_representation(surface_geometry, 'brep', lod) + IFC_semantic_surface_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data)) + return IFC_semantic_surface_children + + def create_IFC_shape_representation(self, IFC_geometry, shape_representation_type, lod): if not isinstance(IFC_geometry, list): IFC_geometry = [IFC_geometry] shape_representation = self.IFC_model.create_entity("IfcShapeRepresentation", - self.IFC_representation_sub_context, 'Body', shape_representation_type, + self.IFC_representation_sub_contexts[str(lod)], 'Body', + shape_representation_type, IFC_geometry) - product_representation = self.IFC_model.create_entity("IfcProductDefinitionShape", - Representations=[shape_representation]) - return product_representation + return shape_representation def create_property_set(self, CJ_attributes, IFC_entity): IFC_object_properties = [] diff --git a/src/ifccityjson/example/geometries.json b/src/ifccityjson/example/geometries.json index cf5f1c7812..076c8142f2 100644 --- a/src/ifccityjson/example/geometries.json +++ b/src/ifccityjson/example/geometries.json @@ -22,7 +22,7 @@ "geometry": [ { "type": "MultiLineString", - "lod": 1, + "lod": 2.3, "boundaries": [ [ 2, @@ -84,7 +84,7 @@ "geometry": [ { "type": "CompositeSurface", - "lod": 1, + "lod": 1.2, "boundaries": [ [ [ diff --git a/src/ifccityjson/ifccityjson.py b/src/ifccityjson/ifccityjson.py index 791cf5d42b..a0fda53df1 100644 --- a/src/ifccityjson/ifccityjson.py +++ b/src/ifccityjson/ifccityjson.py @@ -16,7 +16,6 @@ # # You should have received a copy of the GNU Lesser General Public License # along with ifccityjson. If not, see . - import argparse from cjio import cityjson from cityjson2ifc import Cityjson2ifc @@ -30,6 +29,11 @@ if __name__ == '__main__': parser.add_argument("-i", "--input", type=str, help="input CityJSON file", required=True) parser.add_argument("-o", "--output", type=str, help="output IFC file. Standard is output.ifc") parser.add_argument("-n", "--name", type=str, help="Attribute containing the name") + parser.add_argument('--split-lod', dest='split', action='store_true', + help="Split the file in multiple LoDs") + parser.add_argument('--no-split-lod', dest='split', action='store_false', + help="Do not split the file in multiple LoDs") + parser.set_defaults(split=True) args = parser.parse_args() city_model = cityjson.load(args.input) @@ -38,6 +42,7 @@ if __name__ == '__main__': data["name_attribute"] = args.name if args.output: data["file_destination"] = args.output + data["split"] = args.split converter = Cityjson2ifc() converter.configuration(**data) From 4614bde8d77f9d6e84f5c785e4838fd047c0c924 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Tue, 17 Aug 2021 14:41:15 +0200 Subject: [PATCH 25/72] Cleanup : Use clear instead of while...remove loop --- .../bim/module/attribute/operator.py | 3 +- .../blenderbim/bim/module/bcf/operator.py | 6 ++-- .../blenderbim/bim/module/clash/operator.py | 6 ++-- .../bim/module/classification/operator.py | 6 ++-- .../bim/module/classification/prop.py | 3 +- .../bim/module/constraint/operator.py | 6 ++-- .../blenderbim/bim/module/cost/operator.py | 30 +++++++------------ .../blenderbim/bim/module/debug/operator.py | 18 +++++------ .../bim/module/document/operator.py | 9 ++---- .../blenderbim/bim/module/drawing/operator.py | 11 ++++--- .../bim/module/georeference/operator.py | 6 ++-- .../blenderbim/bim/module/group/operator.py | 6 ++-- .../blenderbim/bim/module/layer/operator.py | 6 ++-- .../bim/module/material/operator.py | 12 +++----- .../blenderbim/bim/module/profile/operator.py | 6 ++-- .../blenderbim/bim/module/project/operator.py | 10 ++----- .../blenderbim/bim/module/pset/operator.py | 3 +- .../bim/module/resource/operator.py | 9 ++---- .../bim/module/sequence/operator.py | 28 ++++++----------- .../blenderbim/bim/module/spatial/prop.py | 3 +- .../bim/module/structural/operator.py | 27 ++++++----------- .../blenderbim/bim/module/style/operator.py | 3 +- .../blenderbim/bim/module/system/operator.py | 6 ++-- .../blenderbim/bim/module/unit/operator.py | 6 ++-- 24 files changed, 79 insertions(+), 150 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/attribute/operator.py b/src/blenderbim/blenderbim/bim/module/attribute/operator.py index 70a2b1e516..ef5798a5d9 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/operator.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/operator.py @@ -40,8 +40,7 @@ class EnableEditingAttributes(bpy.types.Operator): obj = bpy.data.materials.get(self.obj) oprops = obj.BIMObjectProperties props = obj.BIMAttributeProperties - while len(props.attributes) > 0: - props.attributes.remove(0) + props.attributes.clear() for attribute in Data.products[oprops.ifc_definition_id]: new = props.attributes.add() if attribute["type"] == "entity" or (attribute["type"] == "list" and attribute["list_type"] == "entity"): diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index 638e043be3..dd34e95527 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -166,8 +166,7 @@ class LoadBcfComments(bpy.types.Operator): bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml.get_comments(self.topic_guid) blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid) - while len(blender_topic.comments) > 0: - blender_topic.comments.remove(0) + blender_topic.comments.clear() for comment in bcfxml.topics[self.topic_guid].comments.values(): new = blender_topic.comments.add() data_map = { @@ -770,8 +769,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): if viewpoint.snapshot: obj.data.show_background_images = True - while len(obj.data.background_images) > 0: - obj.data.background_images.remove(obj.data.background_images[0]) + obj.data.background_images.clear() background = obj.data.background_images.new() background.image = bpy.data.images.load( os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot) diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py index 5469454ae4..b924905483 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/operator.py +++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py @@ -459,8 +459,7 @@ class SetBlenderClashSetA(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - while len(context.scene.BIMClashProperties.blender_clash_set_a) > 0: - context.scene.BIMClashProperties.blender_clash_set_a.remove(0) + context.scene.BIMClashProperties.blender_clash_set_a.clear() for obj in context.selected_objects: new = context.scene.BIMClashProperties.blender_clash_set_a.add() new.name = obj.name @@ -473,8 +472,7 @@ class SetBlenderClashSetB(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - while len(context.scene.BIMClashProperties.blender_clash_set_b) > 0: - context.scene.BIMClashProperties.blender_clash_set_b.remove(0) + context.scene.BIMClashProperties.blender_clash_set_b.clear() for obj in context.selected_objects: new = context.scene.BIMClashProperties.blender_clash_set_b.add() new.name = obj.name diff --git a/src/blenderbim/blenderbim/bim/module/classification/operator.py b/src/blenderbim/blenderbim/bim/module/classification/operator.py index 336341110e..b1dd53b8fd 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/operator.py +++ b/src/blenderbim/blenderbim/bim/module/classification/operator.py @@ -69,8 +69,7 @@ class EnableEditingClassification(bpy.types.Operator): def execute(self, context): props = context.scene.BIMClassificationProperties - while len(props.classification_attributes) > 0: - props.classification_attributes.remove(0) + props.classification_attributes.clear() classification_data = Data.classifications[self.classification] for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes(): new = props.classification_attributes.add() @@ -154,8 +153,7 @@ class EnableEditingClassificationReference(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object props = obj.BIMClassificationReferenceProperties - while len(props.reference_attributes) > 0: - props.reference_attributes.remove(0) + props.reference_attributes.clear() reference_data = Data.references[self.reference] for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes(): if attribute.name() == "ReferencedSource": diff --git a/src/blenderbim/blenderbim/bim/module/classification/prop.py b/src/blenderbim/blenderbim/bim/module/classification/prop.py index 06f6595423..e760b5eb47 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/prop.py +++ b/src/blenderbim/blenderbim/bim/module/classification/prop.py @@ -56,8 +56,7 @@ def updateClassification(self, context): def getReferences(self, context, parent_id=None): props = context.scene.BIMClassificationProperties - while len(props.available_library_references) > 0: - props.available_library_references.remove(0) + props.available_library_references.clear() for reference in Data.library_file.by_id(parent_id).HasReferences: new = props.available_library_references.add() new.identification = reference.Identification or "" diff --git a/src/blenderbim/blenderbim/bim/module/constraint/operator.py b/src/blenderbim/blenderbim/bim/module/constraint/operator.py index 0af5c1e8d5..d7b409fa93 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/operator.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/operator.py @@ -33,8 +33,7 @@ class LoadObjectives(bpy.types.Operator): def execute(self, context): props = context.scene.BIMConstraintProperties - while len(props.constraints) > 0: - props.constraints.remove(0) + props.constraints.clear() for constraint_id, constraint in Data.objectives.items(): new = props.constraints.add() new.name = constraint["Name"] or "Unnamed" @@ -63,8 +62,7 @@ class EnableEditingConstraint(bpy.types.Operator): def execute(self, context): props = context.scene.BIMConstraintProperties - while len(props.constraint_attributes) > 0: - props.constraint_attributes.remove(0) + props.constraint_attributes.clear() if props.is_editing == "IfcObjective": data = Data.objectives[self.constraint] diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 405d22d20c..0185e31d6d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -94,8 +94,7 @@ class EnableEditingCostSchedule(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMCostProperties self.props.active_cost_schedule_id = self.cost_schedule - while len(self.props.cost_schedule_attributes) > 0: - self.props.cost_schedule_attributes.remove(0) + self.props.cost_schedule_attributes.clear() self.enable_editing_cost_schedule() self.props.is_editing = "COST_SCHEDULE" return {"FINISHED"} @@ -276,8 +275,7 @@ class EnableEditingCostItem(bpy.types.Operator): def execute(self, context): props = context.scene.BIMCostProperties - while len(props.cost_item_attributes) > 0: - props.cost_item_attributes.remove(0) + props.cost_item_attributes.clear() data = Data.cost_items[self.cost_item] blenderbim.bim.helper.import_attributes("IfcCostItem", props.cost_item_attributes, data) @@ -538,8 +536,7 @@ class EnableEditingCostItemQuantity(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMCostProperties - while len(self.props.quantity_attributes) > 0: - self.props.quantity_attributes.remove(0) + self.props.quantity_attributes.clear() self.props.active_cost_item_quantity_id = self.physical_quantity data = Data.physical_quantities[self.physical_quantity] blenderbim.bim.helper.import_attributes(data["type"], self.props.quantity_attributes, data) @@ -635,8 +632,7 @@ class EnableEditingCostItemValue(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMCostProperties - while len(self.props.cost_value_attributes) > 0: - self.props.cost_value_attributes.remove(0) + self.props.cost_value_attributes.clear() self.props.active_cost_item_value_id = self.cost_value data = Data.cost_values[self.cost_value] @@ -868,12 +864,9 @@ class LoadCostItemQuantities(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMCostProperties self.file = IfcStore.get_file() - while len(self.props.cost_item_products) > 0: - self.props.cost_item_products.remove(0) - while len(self.props.cost_item_processes) > 0: - self.props.cost_item_processes.remove(0) - while len(self.props.cost_item_resources) > 0: - self.props.cost_item_resources.remove(0) + self.props.cost_item_products.clear() + self.props.cost_item_processes.clear() + self.props.cost_item_resources.clear() ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items(): related_object = self.file.by_id(control_id) @@ -900,13 +893,10 @@ class LoadCostItemTypes(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMCostProperties self.file = IfcStore.get_file() - while len(self.props.cost_item_type_products) > 0: - self.props.cost_item_type_products.remove(0) + self.props.cost_item_type_products.clear() # TODO implement process and resource types - # while len(self.props.cost_item_processes) > 0: - # self.props.cost_item_processes.remove(0) - # while len(self.props.cost_item_resources) > 0: - # self.props.cost_item_resources.remove(0) + # self.props.cost_item_processes.clear() + # self.props.cost_item_resources.clear() ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items(): related_object = self.file.by_id(control_id) diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py index ecdda3629e..a70f5e8afb 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/operator.py +++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py @@ -197,18 +197,16 @@ class InspectFromStepId(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() - context.scene.BIMDebugProperties.active_step_id = self.step_id + debug_props = context.scene.BIMDebugProperties + debug_props.active_step_id = self.step_id crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add() crumb.name = str(self.step_id) element = self.file.by_id(self.step_id) - while len(context.scene.BIMDebugProperties.attributes) > 0: - context.scene.BIMDebugProperties.attributes.remove(0) - while len(context.scene.BIMDebugProperties.inverse_attributes) > 0: - context.scene.BIMDebugProperties.inverse_attributes.remove(0) - while len(context.scene.BIMDebugProperties.inverse_references) > 0: - context.scene.BIMDebugProperties.inverse_references.remove(0) + debug_props.attributes.clear() + debug_props.inverse_attributes.clear() + debug_props.inverse_references.clear() for key, value in element.get_info().items(): - self.add_attribute(context.scene.BIMDebugProperties.attributes, key, value) + self.add_attribute(debug_props.attributes, key, value) for key in dir(element): if ( not key[0].isalpha() @@ -217,9 +215,9 @@ class InspectFromStepId(bpy.types.Operator): or not getattr(element, key) ): continue - self.add_attribute(context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key)) + self.add_attribute(debug_props.inverse_attributes, key, getattr(element, key)) for inverse in self.file.get_inverse(element): - new = context.scene.BIMDebugProperties.inverse_references.add() + new = debug_props.inverse_references.add() new.string_value = str(inverse) new.int_value = inverse.id() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/document/operator.py b/src/blenderbim/blenderbim/bim/module/document/operator.py index 347f0e941e..1d9ed2929d 100644 --- a/src/blenderbim/blenderbim/bim/module/document/operator.py +++ b/src/blenderbim/blenderbim/bim/module/document/operator.py @@ -33,8 +33,7 @@ class LoadInformation(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMDocumentProperties - while len(props.documents) > 0: - props.documents.remove(0) + props.documents.clear() for information_id, information in Data.information.items(): new = props.documents.add() new.name = information["Name"] or "Unnamed" @@ -56,8 +55,7 @@ class LoadDocumentReferences(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMDocumentProperties - while len(props.documents) > 0: - props.documents.remove(0) + props.documents.clear() for reference_id, reference in Data.references.items(): new = props.documents.add() new.name = reference["Name"] or "Unnamed" @@ -90,8 +88,7 @@ class EnableEditingDocument(bpy.types.Operator): def execute(self, context): props = context.scene.BIMDocumentProperties - while len(props.document_attributes) > 0: - props.document_attributes.remove(0) + props.document_attributes.clear() if props.is_editing == "information": data = Data.information[self.document] diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 222e74ae7d..f050aa2903 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -819,8 +819,7 @@ class PropagateTextData(bpy.types.Operator): obj.data.align_y = source.data.align_y obj.data.BIMTextProperties.font_size = source.data.BIMTextProperties.font_size obj.data.BIMTextProperties.symbol = source.data.BIMTextProperties.symbol - while len(obj.data.BIMTextProperties.variables) > 0: - obj.data.BIMTextProperties.variables.remove(0) + obj.data.BIMTextProperties.variables.clear() for variable in source.data.BIMTextProperties.variables: new_variable = obj.data.BIMTextProperties.variables.add() new_variable.name = variable.name @@ -1144,13 +1143,13 @@ class RefreshDrawingList(bpy.types.Operator): bl_label = "Refresh Drawing List" def execute(self, context): - while len(bpy.context.scene.DocProperties.drawings) > 0: - bpy.context.scene.DocProperties.drawings.remove(0) - for obj in bpy.context.scene.objects: + doc_props = context.scene.DocProperties.drawings + doc_props.drawings.clear() + for obj in context.scene.objects: if not isinstance(obj.data, bpy.types.Camera): continue if "IfcAnnotation/" in obj.name: - new = bpy.context.scene.DocProperties.drawings.add() + new = doc_props.drawings.add() new.name = "/".join(obj.name.split("/")[1:]) new.camera = obj return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 0343f3af56..b9066ab2ac 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -38,8 +38,7 @@ class EnableEditingGeoreferencing(bpy.types.Operator): self.file = IfcStore.get_file() props = context.scene.BIMGeoreferenceProperties - while len(props.projected_crs) > 0: - props.projected_crs.remove(0) + props.projected_crs.clear() for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) @@ -69,8 +68,7 @@ class EnableEditingGeoreferencing(bpy.types.Operator): elif props.map_unit_type == "IfcConversionBasedUnit": props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"] - while len(props.map_conversion) > 0: - props.map_conversion.remove(0) + props.map_conversion.clear() for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py index c882258bc6..0cd773791f 100644 --- a/src/blenderbim/blenderbim/bim/module/group/operator.py +++ b/src/blenderbim/blenderbim/bim/module/group/operator.py @@ -31,8 +31,7 @@ class LoadGroups(bpy.types.Operator): def execute(self, context): props = context.scene.BIMGroupProperties - while len(props.groups) > 0: - props.groups.remove(0) + props.groups.clear() for ifc_definition_id, group in Data.groups.items(): new = props.groups.add() new.ifc_definition_id = ifc_definition_id @@ -119,8 +118,7 @@ class EnableEditingGroup(bpy.types.Operator): def execute(self, context): props = context.scene.BIMGroupProperties - while len(props.group_attributes) > 0: - props.group_attributes.remove(0) + props.group_attributes.clear() data = Data.groups[self.group] diff --git a/src/blenderbim/blenderbim/bim/module/layer/operator.py b/src/blenderbim/blenderbim/bim/module/layer/operator.py index 8474f08bc8..2ebd391809 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/operator.py +++ b/src/blenderbim/blenderbim/bim/module/layer/operator.py @@ -33,8 +33,7 @@ class LoadLayers(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMLayerProperties - while len(props.layers) > 0: - props.layers.remove(0) + props.layers.clear() for layer_id, layer in Data.layers.items(): new = props.layers.add() new.name = layer["Name"] or "Unnamed" @@ -62,8 +61,7 @@ class EnableEditingLayer(bpy.types.Operator): def execute(self, context): props = context.scene.BIMLayerProperties - while len(props.layer_attributes) > 0: - props.layer_attributes.remove(0) + props.layer_attributes.clear() data = Data.layers[self.layer] diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 3a382bbc94..e735461ef8 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -420,16 +420,14 @@ class EnableEditingAssignedMaterial(bpy.types.Operator): else: material_set_data = {} - while len(props.material_set_usage_attributes) > 0: - props.material_set_usage_attributes.remove(0) + props.material_set_usage_attributes.clear() if "Usage" in product_data["type"]: blenderbim.bim.helper.import_attributes( product_data["type"], props.material_set_usage_attributes, material_set_usage, self.import_attributes ) - while len(props.material_set_attributes) > 0: - props.material_set_attributes.remove(0) + props.material_set_attributes.clear() for attribute in IfcStore.get_schema().declaration_by_name(material_set_class).all_attributes(): if "" not in str(attribute.type_of_attribute): @@ -584,8 +582,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): return {"FINISHED"} def load_set_item_attributes(self, material_set_item, material_set_item_data): - while len(self.props.material_set_item_attributes) > 0: - self.props.material_set_item_attributes.remove(0) + self.props.material_set_item_attributes.clear() for attribute in IfcStore.get_schema().declaration_by_name(material_set_item.is_a()).all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) @@ -606,8 +603,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): new.bool_value = False if new.is_null else material_set_item_data[attribute.name()] def load_profile_attributes(self, material_set_item, material_set_item_data): - while len(self.props.material_set_item_profile_attributes) > 0: - self.props.material_set_item_profile_attributes.remove(0) + self.props.material_set_item_profile_attributes.clear() if not material_set_item_data["Profile"]: return diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py index d682a97393..f94f13bfb0 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/operator.py +++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py @@ -31,8 +31,7 @@ class LoadProfiles(bpy.types.Operator): def execute(self, context): props = context.scene.BIMProfileProperties - while len(props.profiles) > 0: - props.profiles.remove(0) + props.profiles.clear() for ifc_definition_id, profile in Data.profiles.items(): new = props.profiles.add() @@ -80,8 +79,7 @@ class EnableEditingProfile(bpy.types.Operator): def execute(self, context): props = context.scene.BIMProfileProperties - while len(props.profile_attributes) > 0: - props.profile_attributes.remove(0) + props.profile_attributes.clear() data = Data.profiles[self.profile] diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 33247c2183..c96a795583 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -182,11 +182,8 @@ class RefreshLibrary(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMProjectProperties - while len(self.props.library_elements) > 0: - self.props.library_elements.remove(0) - - while len(self.props.library_breadcrumb) > 0: - self.props.library_breadcrumb.remove(0) + self.props.library_elements.clear() + self.props.library_breadcrumb.clear() self.props.active_library_element = "" @@ -212,8 +209,7 @@ class ChangeLibraryElement(bpy.types.Operator): crumb.name = self.element_name elements = IfcStore.library_file.by_type(self.element_name) [ifc_classes.add(e.is_a()) for e in elements] - while len(self.props.library_elements) > 0: - self.props.library_elements.remove(0) + self.props.library_elements.clear() if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name: for element in elements: new = self.props.library_elements.add() diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 092b88eade..f208513b0a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -90,8 +90,7 @@ class EnablePsetEditing(bpy.types.Operator): def execute(self, context): self.props = get_pset_props(context, self.obj, self.obj_type) - while len(self.props.properties) > 0: - self.props.properties.remove(0) + self.props.properties.clear() data = Data.psets if self.pset_id in Data.psets else Data.qtos pset_data = data[self.pset_id] diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py index 6204d3ab23..34b70a80f4 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/operator.py +++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py @@ -37,8 +37,7 @@ class LoadResources(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMResourceProperties self.tprops = context.scene.BIMResourceTreeProperties - while len(self.tprops.resources) > 0: - self.tprops.resources.remove(0) + self.tprops.resources.clear() self.contracted_resources = json.loads(self.props.contracted_resources) for resource_id, data in Data.resources.items(): @@ -72,8 +71,7 @@ class EnableEditingResource(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMResourceProperties self.props.active_resource_id = self.resource - while len(self.props.resource_attributes) > 0: - self.props.resource_attributes.remove(0) + self.props.resource_attributes.clear() self.props.editing_resource_type = "ATTRIBUTES" self.enable_editing_resource() return {"FINISHED"} @@ -299,8 +297,7 @@ class EnableEditingResourceTime(bpy.types.Operator): props = context.scene.BIMResourceProperties self.file = IfcStore.get_file() resource_time_id = Data.resources[self.resource]["Usage"] or self.add_resource_time().id() - while len(props.resource_time_attributes) > 0: - props.resource_time_attributes.remove(0) + props.resource_time_attributes.clear() data = Data.resource_times[resource_time_id] diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 5d66fa86a2..8f6ca758ae 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -114,8 +114,7 @@ class EnableEditingWorkPlan(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkPlanProperties - while len(props.work_plan_attributes) > 0: - props.work_plan_attributes.remove(0) + props.work_plan_attributes.clear() data = Data.work_plans[self.work_plan] @@ -279,8 +278,7 @@ class EnableEditingWorkSchedule(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMWorkScheduleProperties self.props.active_work_schedule_id = self.work_schedule - while len(self.props.work_schedule_attributes) > 0: - self.props.work_schedule_attributes.remove(0) + self.props.work_schedule_attributes.clear() self.enable_editing_work_schedule() self.props.editing_type = "WORK_SCHEDULE" return {"FINISHED"} @@ -308,8 +306,7 @@ class EnableEditingTasks(bpy.types.Operator): self.props = context.scene.BIMWorkScheduleProperties self.tprops = context.scene.BIMTaskTreeProperties self.props.active_work_schedule_id = self.work_schedule - while len(self.tprops.tasks) > 0: - self.tprops.tasks.remove(0) + self.tprops.tasks.clear() self.contracted_tasks = json.loads(self.props.contracted_tasks) self.sort_keys = { @@ -542,8 +539,7 @@ class EnableEditingTaskTime(bpy.types.Operator): task_time_id = Data.tasks[self.task]["TaskTime"] or self.add_task_time().id() - while len(props.task_time_attributes) > 0: - props.task_time_attributes.remove(0) + props.task_time_attributes.clear() data = Data.task_times[task_time_id] @@ -616,9 +612,7 @@ class EnableEditingTask(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - while len(props.task_attributes) > 0: - props.task_attributes.remove(0) - + props.task_attributes.clear() data = Data.tasks[self.task] blenderbim.bim.helper.import_attributes("IfcTask", props.task_attributes, data) @@ -1043,8 +1037,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMWorkCalendarProperties - while len(self.props.work_calendar_attributes) > 0: - self.props.work_calendar_attributes.remove(0) + self.props.work_calendar_attributes.clear() data = Data.work_calendars[self.work_calendar] @@ -1150,8 +1143,7 @@ class EnableEditingWorkTime(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMWorkCalendarProperties - while len(self.props.work_time_attributes) > 0: - self.props.work_time_attributes.remove(0) + self.props.work_time_attributes.clear() data = Data.work_times[self.work_time] @@ -1488,8 +1480,7 @@ class EnableEditingSequenceAttributes(bpy.types.Operator): self.props = context.scene.BIMWorkScheduleProperties self.props.active_sequence_id = self.sequence self.props.editing_sequence_type = "ATTRIBUTES" - while len(self.props.sequence_attributes) > 0: - self.props.sequence_attributes.remove(0) + self.props.sequence_attributes.clear() self.enable_editing_sequence_attributes() return {"FINISHED"} @@ -1509,8 +1500,7 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator): self.props = context.scene.BIMWorkScheduleProperties self.props.active_sequence_id = self.sequence self.props.editing_sequence_type = "TIME_LAG" - while len(self.props.time_lag_attributes) > 0: - self.props.time_lag_attributes.remove(0) + self.props.time_lag_attributes.clear() self.enable_editing_attributes() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index cf1896ff2b..b9a4538f2a 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -38,8 +38,7 @@ def getSpatialContainers(self, context, parent_id=None): Data.load(IfcStore.get_file()) self.file = IfcStore.get_file() props = context.scene.BIMSpatialProperties - while len(props.spatial_elements) > 0: - props.spatial_elements.remove(0) + props.spatial_elements.clear() if parent_id: props.active_decomposes_id = parent_id diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 102f66082b..afc0194e60 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -153,8 +153,7 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator): def execute(self, context): obj = context.active_object props = obj.BIMStructuralProperties - while len(props.boundary_condition_attributes) > 0: - props.boundary_condition_attributes.remove(0) + props.boundary_condition_attributes.clear() data = Data.boundary_conditions[self.boundary_condition] @@ -240,8 +239,7 @@ class LoadStructuralAnalysisModels(bpy.types.Operator): def execute(self, context): props = context.scene.BIMStructuralProperties - while len(props.structural_analysis_models) > 0: - props.structural_analysis_models.remove(0) + props.structural_analysis_models.clear() for ifc_definition_id, structural_analysis_model in Data.structural_analysis_models.items(): new = props.structural_analysis_models.add() new.ifc_definition_id = ifc_definition_id @@ -343,8 +341,7 @@ class EnableEditingStructuralAnalysisModel(bpy.types.Operator): def execute(self, context): props = context.scene.BIMStructuralProperties - while len(props.structural_analysis_model_attributes) > 0: - props.structural_analysis_model_attributes.remove(0) + props.structural_analysis_model_attributes.clear() data = Data.structural_analysis_models[self.structural_analysis_model] @@ -697,8 +694,7 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator): self.props = context.scene.BIMStructuralProperties self.props.active_load_case_id = self.load_case self.props.load_case_editing_type = "ATTRIBUTES" - while len(self.props.load_case_attributes) > 0: - self.props.load_case_attributes.remove(0) + self.props.load_case_attributes.clear() data = Data.load_cases[self.load_case] blenderbim.bim.helper.import_attributes( "IfcStructuralLoadCase", self.props.load_case_attributes, data, self.import_attributes @@ -784,8 +780,7 @@ class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator): return {"FINISHED"} def load_structural_activities(self): - while len(self.props.load_group_activities) > 0: - self.props.load_group_activities.remove(0) + self.props.load_group_activities.clear() for activity_id in Data.load_groups[self.load_group]["IsGroupedBy"]: activity = Data.structural_activities[activity_id] new = self.props.load_group_activities.add() @@ -856,8 +851,7 @@ class LoadStructuralLoads(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMStructuralProperties - while len(props.structural_loads) > 0: - props.structural_loads.remove(0) + props.structural_loads.clear() if props.filtered_structural_loads: names = [structural_load["Name"] or "Unnamed" for _, structural_load in Data.structural_loads.items()] for ifc_definition_id, structural_load in Data.structural_loads.items(): @@ -919,8 +913,7 @@ class EnableEditingStructuralLoad(bpy.types.Operator): def execute(self, context): props = context.scene.BIMStructuralProperties - while len(props.structural_load_attributes) > 0: - props.structural_load_attributes.remove(0) + props.structural_load_attributes.clear() data = Data.structural_loads[self.structural_load] blenderbim.bim.helper.import_attributes(data["type"], props.structural_load_attributes, data) @@ -1002,8 +995,7 @@ class LoadBoundaryConditions(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMStructuralProperties - while len(props.boundary_conditions) > 0: - props.boundary_conditions.remove(0) + props.boundary_conditions.clear() if props.filtered_boundary_conditions: names = [ boundary_condition["Name"] or "Unnamed" for _, boundary_condition in Data.boundary_conditions.items() @@ -1082,8 +1074,7 @@ class EnableEditingBoundaryCondition(bpy.types.Operator): def execute(self, context): props = context.scene.BIMStructuralProperties - while len(props.boundary_condition_attributes) > 0: - props.boundary_condition_attributes.remove(0) + props.boundary_condition_attributes.clear() data = Data.boundary_conditions[self.boundary_condition] # blenderbim.bim.helper.import_attributes(data["type"], props.boundary_condition_attributes, data) diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 4c8e2c53d7..598cc9bc3f 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -129,8 +129,7 @@ class EnableEditingStyle(bpy.types.Operator): def execute(self, context): material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material props = material.BIMStyleProperties - while len(props.attributes) > 0: - props.attributes.remove(0) + props.attributes.clear() data = Data.styles[material.BIMMaterialProperties.ifc_style_id] blenderbim.bim.helper.import_attributes("IfcSurfaceStyle", props.attributes, data) diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index d1e8423e9b..51597aa7c0 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -31,8 +31,7 @@ class LoadSystems(bpy.types.Operator): def execute(self, context): props = context.scene.BIMSystemProperties - while len(props.systems) > 0: - props.systems.remove(0) + props.systems.clear() for ifc_definition_id, system in Data.systems.items(): new = props.systems.add() new.ifc_definition_id = ifc_definition_id @@ -119,8 +118,7 @@ class EnableEditingSystem(bpy.types.Operator): def execute(self, context): props = context.scene.BIMSystemProperties - while len(props.system_attributes) > 0: - props.system_attributes.remove(0) + props.system_attributes.clear() data = Data.systems[self.system] diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py index af1657d60d..598f08bd5b 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py @@ -98,8 +98,7 @@ class LoadUnits(bpy.types.Operator): def execute(self, context): props = context.scene.BIMUnitProperties - while len(props.units) > 0: - props.units.remove(0) + props.units.clear() for ifc_definition_id, unit in Data.units.items(): name = unit.get("Name", "") @@ -233,8 +232,7 @@ class EnableEditingUnit(bpy.types.Operator): def execute(self, context): props = context.scene.BIMUnitProperties - while len(props.unit_attributes) > 0: - props.unit_attributes.remove(0) + props.unit_attributes.clear() data = Data.units[self.unit] blenderbim.bim.helper.import_attributes(data["type"], props.unit_attributes, data, self.import_attributes) props.active_unit_id = self.unit From fb1c340664bed07d4d6d6648e8cae2b8e58b9b63 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Tue, 17 Aug 2021 14:55:19 +0200 Subject: [PATCH 26/72] Use passed context instead of bpy.context --- src/blenderbim/blenderbim/bim/handler.py | 26 +-- .../blenderbim/bim/module/bcf/operator.py | 48 +++--- .../blenderbim/bim/module/cost/operator.py | 19 +- .../blenderbim/bim/module/drawing/operator.py | 163 +++++++++--------- .../blenderbim/bim/module/drawing/prop.py | 6 +- .../blenderbim/bim/module/unit/operator.py | 10 +- src/blenderbim/blenderbim/bim/operator.py | 4 +- 7 files changed, 144 insertions(+), 132 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 53bda75b12..3ae69b7008 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -166,8 +166,8 @@ def undo_pre(scene): @persistent def undo_post(scene): - if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: - IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction + if IfcStore.last_transaction != scene.BIMProperties.last_transaction: + IfcStore.last_transaction = scene.BIMProperties.last_transaction IfcStore.undo() purge_module_data() IfcStore.update_undo_redo_stack_objects() @@ -181,8 +181,8 @@ def redo_pre(scene): @persistent def redo_post(scene): - if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: - IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction + if IfcStore.last_transaction != scene.BIMProperties.last_transaction: + IfcStore.last_transaction = scene.BIMProperties.last_transaction IfcStore.redo() purge_module_data() IfcStore.update_undo_redo_stack_objects() @@ -191,7 +191,7 @@ def redo_post(scene): @persistent def ensureIfcExported(scene): - if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file: + if IfcStore.get_file() and not scene.BIMProperties.ifc_file: bpy.ops.export_ifc.bim("INVOKE_DEFAULT") @@ -230,18 +230,18 @@ def setDefaultProperties(scene): key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback ) ifcopenshell.api.owner.settings.get_person = ( - lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person)) - if getPersons(None, bpy.context) and bpy.context.scene.BIMOwnerProperties.user_person + lambda ifc: ifc.by_id(int(scene.BIMOwnerProperties.user_person)) + if getPersons(None, None) and scene.BIMOwnerProperties.user_person else None ) ifcopenshell.api.owner.settings.get_organisation = ( - lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation)) - if getOrganisations(None, bpy.context) and bpy.context.scene.BIMOwnerProperties.user_organisation + lambda ifc: ifc.by_id(int(scene.BIMOwnerProperties.user_organisation)) + if getOrganisations(None, None) and scene.BIMOwnerProperties.user_organisation else None ) ifcopenshell.api.owner.settings.get_application = get_application - if len(bpy.context.scene.DocProperties.drawing_styles) == 0: - drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() + if len(scene.DocProperties.drawing_styles) == 0: + drawing_style = scene.DocProperties.drawing_styles.add() drawing_style.name = "Technical" drawing_style.render_type = "VIEWPORT" drawing_style.raster_style = json.dumps( @@ -272,7 +272,7 @@ def setDefaultProperties(scene): "space.overlay.show_relationship_lines": False, } ) - drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() + drawing_style = scene.DocProperties.drawing_styles.add() drawing_style.name = "Shaded" drawing_style.render_type = "VIEWPORT" drawing_style.raster_style = json.dumps( @@ -303,7 +303,7 @@ def setDefaultProperties(scene): "space.overlay.show_relationship_lines": False, } ) - drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() + drawing_style = scene.DocProperties.drawing_styles.add() drawing_style.name = "Blender Default" drawing_style.render_type = "DEFAULT" bpy.ops.bim.save_drawing_style(index="2") diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index dd34e95527..f49ff7fdbf 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -766,7 +766,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): cam_width = context.scene.render.resolution_x cam_height = context.scene.render.resolution_y cam_aspect = cam_width / cam_height - + if viewpoint.snapshot: obj.data.show_background_images = True obj.data.background_images.clear() @@ -787,7 +787,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): area.spaces[0].region_3d.view_perspective = "CAMERA" if self.file: - self.set_viewpoint_components(viewpoint) + self.set_viewpoint_components(viewpoint, context) gp = bpy.data.grease_pencils.get("BCF") if gp: @@ -803,10 +803,10 @@ class ActivateBcfViewpoint(bpy.types.Operator): if viewpoint.bitmaps: self.create_bitmaps(bcfxml, viewpoint, topic) - self.setup_camera(viewpoint, obj, cam_aspect) + self.setup_camera(viewpoint, obj, cam_aspect, context) return {"FINISHED"} - def setup_camera(self, viewpoint, obj, cam_aspect): + def setup_camera(self, viewpoint, obj, cam_aspect, context): if viewpoint.orthogonal_camera: camera = viewpoint.orthogonal_camera obj.data.type = "ORTHO" @@ -831,7 +831,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): [x_axis[2], y_axis[2], z_axis[2], camera.camera_view_point.z], [0, 0, 0, 1], )) - props = bpy.context.scene.BIMGeoreferenceProperties + props = context.scene.BIMGeoreferenceProperties if props.has_blender_offset: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) matrix = ifcopenshell.util.geolocation.global2local( @@ -844,7 +844,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): ) obj.matrix_world = Matrix(matrix.tolist()) - def set_viewpoint_components(self, viewpoint): + def set_viewpoint_components(self, viewpoint, context): if not viewpoint.components: return @@ -854,10 +854,10 @@ class ActivateBcfViewpoint(bpy.types.Operator): exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions] if viewpoint.components.visibility.default_visibility: - old = bpy.context.area.type - bpy.context.area.type = "VIEW_3D" + old = context.area.type + context.area.type = "VIEW_3D" bpy.ops.object.hide_view_clear() - bpy.context.area.type = old + context.area.type = old for global_id in exception_global_ids: obj = IfcStore.get_element(global_id) if obj: @@ -869,35 +869,35 @@ class ActivateBcfViewpoint(bpy.types.Operator): if obj: objs.append(obj) if objs: - old = bpy.context.area.type - bpy.context.area.type = "VIEW_3D" + old = context.area.type + context.area.type = "VIEW_3D" context_override = {} context_override["object"] = context_override["active_object"] = objs[0] context_override["selected_objects"] = context_override["selected_editable_objects"] = objs bpy.ops.object.hide_view_set(context_override, unselected=True) - bpy.context.area.type = old + context.area.type = old if viewpoint.components.view_setup_hints: if not viewpoint.components.view_setup_hints.spaces_visible: - self.hide_spaces() + self.hide_spaces(context) if viewpoint.components.view_setup_hints.openings_visible is not None: - self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible) + self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible, context) else: - self.hide_spaces() - self.set_openings_visibility(False) + self.hide_spaces(context) + self.set_openings_visibility(False, context) self.set_selection(viewpoint) self.set_colours(viewpoint) - def hide_spaces(self): - old = bpy.context.area.type - bpy.context.area.type = "VIEW_3D" + def hide_spaces(self, context): + old = context.area.type + context.area.type = "VIEW_3D" bpy.ops.object.select_pattern(pattern="IfcSpace/*") bpy.ops.object.hide_view_set({}) - bpy.context.area.type = old + context.area.type = old - def set_openings_visibility(self, is_visible): - for collection in self.get_opening_collections(): + def set_openings_visibility(self, is_visible, context): + for collection in self.get_opening_collections(context): collection.hide_viewport = not is_visible def set_selection(self, viewpoint): @@ -918,9 +918,9 @@ class ActivateBcfViewpoint(bpy.types.Operator): if obj: obj.color = self.hex_to_rgb(color) - def get_opening_collections(self): + def get_opening_collections(self, context): collections = [] - for collection in bpy.context.view_layer.layer_collection.children: + for collection in context.view_layer.layer_collection.children: opening_collection = collection.children.get("IfcOpeningElements") if opening_collection: collections.append(opening_collection) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 0185e31d6d..96bd5cd595 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -438,7 +438,7 @@ class UnassignCostItemQuantity(bpy.types.Operator): else: products = [ self.file.by_id(o.BIMObjectProperties.ifc_definition_id) - for o in bpy.context.selected_objects + for o in context.selected_objects if o.BIMObjectProperties.ifc_definition_id ] ifcopenshell.api.run( @@ -637,11 +637,14 @@ class EnableEditingCostItemValue(bpy.types.Operator): data = Data.cost_values[self.cost_value] blenderbim.bim.helper.import_attributes( - data["type"], self.props.cost_value_attributes, data, self.import_attributes + data["type"], + self.props.cost_value_attributes, + data, + lambda name, prop, data: self.import_attributes(name, prop, data, context) ) return {"FINISHED"} - def import_attributes(self, name, prop, data): + def import_attributes(self, name, prop, data, context): if name == "AppliedValue": # TODO: for now, only support simple values prop.data_type = "float" @@ -649,7 +652,7 @@ class EnableEditingCostItemValue(bpy.types.Operator): return True if ( name == "UnitBasis" - and Data.cost_schedules[bpy.context.scene.BIMCostProperties.active_cost_schedule_id]["PredefinedType"] + and Data.cost_schedules[context.scene.BIMCostProperties.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES" ): prop = self.props.cost_value_attributes.add() @@ -711,7 +714,9 @@ class EditCostValue(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMCostProperties - attributes = blenderbim.bim.helper.export_attributes(props.cost_value_attributes, self.export_attributes) + attributes = blenderbim.bim.helper.export_attributes( + props.cost_value_attributes, + lambda attributes, prop: self.export_attributes(attributes, prop, context)) self.file = IfcStore.get_file() ifcopenshell.api.run( "cost.edit_cost_value", @@ -722,7 +727,7 @@ class EditCostValue(bpy.types.Operator): bpy.ops.bim.disable_editing_cost_item_value() return {"FINISHED"} - def export_attributes(self, attributes, prop): + def export_attributes(self, attributes, prop, context): if prop.name == "UnitBasisValue": if prop.is_null: attributes["UnitBasis"] = None @@ -730,7 +735,7 @@ class EditCostValue(bpy.types.Operator): attributes["UnitBasis"] = { "ValueComponent": prop.float_value or 1, "UnitComponent": IfcStore.get_file().by_id( - int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value) + int(context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value) ), } return True diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index f050aa2903..c51a471d19 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -127,7 +127,7 @@ class CreateDrawing(bpy.types.Operator): self.profile_code("Generate annotation") svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg) self.profile_code("Combine SVG layers") - open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, svg_path) + open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path) print("Total Time: {:.2f}".format(time.time() - start)) return {"FINISHED"} @@ -185,8 +185,8 @@ class CreateDrawing(bpy.types.Operator): if not self.props.has_underlay: return svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg") - bpy.context.scene.render.filepath = svg_path[0:-4] + ".png" - drawing_style = bpy.context.scene.DocProperties.drawing_styles[ + context.scene.render.filepath = svg_path[0:-4] + ".png" + drawing_style = context.scene.DocProperties.drawing_styles[ self.camera.data.BIMCameraProperties.active_drawing_style_index ] @@ -197,7 +197,7 @@ class CreateDrawing(bpy.types.Operator): for obj in self.camera.users_collection[0].objects: previous_visibility[obj.name] = obj.hide_get() obj.hide_set(True) - for obj in bpy.context.visible_objects: + for obj in context.visible_objects: if ( (not obj.data and not obj.instance_collection) or isinstance(obj.data, bpy.types.Camera) @@ -208,14 +208,14 @@ class CreateDrawing(bpy.types.Operator): previous_visibility[obj.name] = obj.hide_get() obj.hide_set(True) - space = self.get_view_3d() + space = self.get_view_3d(context.screen.areas) previous_shading = space.shading.type - previous_format = bpy.context.scene.render.image_settings.file_format + previous_format = context.scene.render.image_settings.file_format space.shading.type = "RENDERED" - bpy.context.scene.render.image_settings.file_format = "PNG" + context.scene.render.image_settings.file_format = "PNG" bpy.ops.render.opengl(write_still=True) space.shading.type = previous_shading - bpy.context.scene.render.image_settings.file_format = previous_format + context.scene.render.image_settings.file_format = previous_format for name, value in previous_visibility.items(): bpy.data.objects[name].hide_set(value) @@ -229,7 +229,7 @@ class CreateDrawing(bpy.types.Operator): svg_writer.human_scale = "NTS" else: svg_writer.human_scale = human_scale - render = bpy.context.scene.render + render = context.scene.render if self.is_landscape(): width = self.camera.data.ortho_scale height = width / render.resolution_x * render.resolution_y @@ -237,13 +237,13 @@ class CreateDrawing(bpy.types.Operator): height = self.camera.data.ortho_scale width = height / render.resolution_y * render.resolution_x svg_writer.output = svg_path - svg_writer.data_dir = bpy.context.scene.BIMProperties.data_dir + svg_writer.data_dir = context.scene.BIMProperties.data_dir svg_writer.vector_style = drawing_style.vector_style svg_writer.camera = self.camera svg_writer.camera_width = width svg_writer.camera_height = height svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))) - svg_writer.background_image = bpy.context.scene.render.filepath + svg_writer.background_image = context.scene.render.filepath svg_writer.write("underlay") return svg_path @@ -332,12 +332,12 @@ class CreateDrawing(bpy.types.Operator): else: svg_writer.human_scale = human_scale - drawing_style = bpy.context.scene.DocProperties.drawing_styles[ + drawing_style = context.scene.DocProperties.drawing_styles[ camera.data.BIMCameraProperties.active_drawing_style_index ] - render = bpy.context.scene.render - if self.is_landscape(): + render = context.scene.render + if self.is_landscape(render): width = camera.data.ortho_scale height = width / render.resolution_x * render.resolution_y else: @@ -346,7 +346,7 @@ class CreateDrawing(bpy.types.Operator): svg_writer.scale = float(numerator) / float(denominator) svg_writer.output = svg_path - svg_writer.data_dir = bpy.context.scene.BIMProperties.data_dir + svg_writer.data_dir = context.scene.BIMProperties.data_dir svg_writer.vector_style = drawing_style.vector_style svg_writer.camera = camera svg_writer.camera_width = width @@ -391,11 +391,11 @@ class CreateDrawing(bpy.types.Operator): svg_writer.write("annotation") return svg_writer.output - def is_landscape(self): - return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y + def is_landscape(self, render): + return render.resolution_x > render.resolution_y - def get_view_3d(self): - for area in bpy.context.screen.areas: + def get_view_3d(self, areas): + for area in areas: if area.type != "VIEW_3D": continue for space in area.spaces: @@ -512,7 +512,7 @@ class AddAnnotation(bpy.types.Operator): bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.object.select_all(action="DESELECT") - bpy.context.view_layer.objects.active = obj + context.view_layer.objects.active = obj obj.select_set(True) bpy.ops.object.mode_set(mode="EDIT") return {"FINISHED"} @@ -525,11 +525,12 @@ class AddSheet(bpy.types.Operator): # TODO: check undo redo def execute(self, context): - new = bpy.context.scene.DocProperties.sheets.add() - new.name = "{} - SHEET".format(len(bpy.context.scene.DocProperties.sheets)) + scene = context.scene + new = scene.DocProperties.sheets.add() + new.name = "{} - SHEET".format(len(scene.DocProperties.sheets)) sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir - sheet_builder.create(new.name, bpy.context.scene.DocProperties.titleblock) + sheet_builder.data_dir = scene.BIMProperties.data_dir + sheet_builder.create(new.name, scene.DocProperties.titleblock) return {"FINISHED"} @@ -538,11 +539,11 @@ class OpenSheet(bpy.types.Operator): bl_label = "Open Sheet" def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties open_with_user_command( - bpy.context.preferences.addons["blenderbim"].preferences.svg_command, + context.preferences.addons["blenderbim"].preferences.svg_command, os.path.join( - bpy.context.scene.BIMProperties.data_dir, "sheets", props.sheets[props.active_sheet_index].name + ".svg" + context.scene.BIMProperties.data_dir, "sheets", props.sheets[props.active_sheet_index].name + ".svg" ), ) return {"FINISHED"} @@ -555,9 +556,9 @@ class AddDrawingToSheet(bpy.types.Operator): # TODO: check undo redo def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir + sheet_builder.data_dir = context.scene.BIMProperties.data_dir try: sheet_builder.add_drawing( props.drawings[props.active_drawing_index].name, props.sheets[props.active_sheet_index].name @@ -573,17 +574,18 @@ class CreateSheets(bpy.types.Operator): # TODO: check undo redo def execute(self, context): - props = bpy.context.scene.DocProperties + scene = context.scene + props = scene.DocProperties name = props.sheets[props.active_sheet_index].name sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir + sheet_builder.data_dir = scene.BIMProperties.data_dir sheet_builder.build(name) - svg2pdf_command = bpy.context.preferences.addons["blenderbim"].preferences.svg2pdf_command - svg2dxf_command = bpy.context.preferences.addons["blenderbim"].preferences.svg2dxf_command + svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command + svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command if svg2pdf_command: - path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name) + path = os.path.join(scene.BIMProperties.data_dir, "build", name) svg = os.path.join(path, name + ".svg") pdf = os.path.join(path, name + ".pdf") # With great power comes great responsibility. Example: @@ -593,7 +595,7 @@ class CreateSheets(bpy.types.Operator): subprocess.run(command) if svg2dxf_command: - path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name) + path = os.path.join(scene.BIMProperties.data_dir, "build", name) svg = os.path.join(path, name + ".svg") eps = os.path.join(path, name + ".eps") dxf = os.path.join(path, name + ".dxf") @@ -605,11 +607,11 @@ class CreateSheets(bpy.types.Operator): subprocess.run(command) if svg2pdf_command: - open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.pdf_command, pdf) + open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf) else: open_with_user_command( - bpy.context.preferences.addons["blenderbim"].preferences.svg_command, - os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name, name + ".svg"), + context.preferences.addons["blenderbim"].preferences.svg_command, + os.path.join(scene.BIMProperties.data_dir, "build", name, name + ".svg"), ) return {"FINISHED"} @@ -621,8 +623,8 @@ class OpenView(bpy.types.Operator): def execute(self, context): open_with_user_command( - bpy.context.preferences.addons["blenderbim"].preferences.svg_command, - os.path.join(bpy.context.scene.BIMProperties.data_dir, "diagrams", self.view + ".svg"), + context.preferences.addons["blenderbim"].preferences.svg_command, + os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.view + ".svg"), ) return {"FINISHED"} @@ -634,13 +636,13 @@ class OpenViewCamera(bpy.types.Operator): view_name: bpy.props.StringProperty() def execute(self, context): - new_drawing_index = bpy.context.scene.DocProperties.drawings.find(self.view_name) - bpy.context.scene.DocProperties.active_drawing_index = new_drawing_index - drawing = bpy.context.scene.DocProperties.drawings[new_drawing_index] - bpy.context.view_layer.objects.active = drawing.camera + new_drawing_index = context.scene.DocProperties.drawings.find(self.view_name) + context.scene.DocProperties.active_drawing_index = new_drawing_index + drawing = context.scene.DocProperties.drawings[new_drawing_index] + context.view_layer.objects.active = drawing.camera bpy.ops.object.select_all(action="DESELECT") drawing.camera.select_set(True) - for area in bpy.context.screen.areas: + for area in context.screen.areas: if area.ui_type == "PROPERTIES": for space in area.spaces: space.context = "DATA" @@ -654,22 +656,22 @@ class ActivateView(bpy.types.Operator): drawing_index: bpy.props.IntProperty() def execute(self, context): - camera = bpy.context.scene.DocProperties.drawings[self.drawing_index].camera + camera = context.scene.DocProperties.drawings[self.drawing_index].camera if not camera: return {"FINISHED"} - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area = next(area for area in context.screen.areas if area.type == "VIEW_3D") is_local_view = area.spaces[0].local_view is not None if is_local_view: bpy.ops.view3d.localview() - bpy.context.scene.camera = camera + context.scene.camera = camera bpy.ops.view3d.localview() else: - bpy.context.scene.camera = camera + context.scene.camera = camera area.spaces[0].region_3d.view_perspective = "CAMERA" views_collection = bpy.data.collections.get("Views") for collection in views_collection.children: # We assume the project collection is at the top level - for project_collection in bpy.context.view_layer.layer_collection.children: + for project_collection in context.view_layer.layer_collection.children: # We assume a convention that the 'Views' collection is directly # in the project collection if ( @@ -678,7 +680,7 @@ class ActivateView(bpy.types.Operator): ): project_collection.children["Views"].children[collection.name].hide_viewport = True bpy.data.collections.get(collection.name).hide_render = True - bpy.context.view_layer.layer_collection.children["Views"].children[ + context.view_layer.layer_collection.children["Views"].children[ camera.users_collection[0].name ].hide_viewport = False bpy.data.collections.get(camera.users_collection[0].name).hide_render = False @@ -694,7 +696,7 @@ class SelectDocIfcFile(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - bpy.context.scene.DocProperties.ifc_files[self.index].name = self.filepath + context.scene.DocProperties.ifc_files[self.index].name = self.filepath return {"FINISHED"} def invoke(self, context, event): @@ -708,7 +710,7 @@ class GenerateReferences(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - self.camera = bpy.context.scene.camera + self.camera = context.scene.camera self.filter_potential_references() if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW": self.generate_grids() @@ -777,7 +779,7 @@ class ResizeText(bpy.types.Operator): # TODO: check undo redo def execute(self, context): - for obj in bpy.context.scene.camera.users_collection[0].objects: + for obj in context.scene.camera.users_collection[0].objects: if isinstance(obj.data, bpy.types.TextCurve): annotation.Annotator.resize_text(obj) return {"FINISHED"} @@ -789,7 +791,7 @@ class AddVariable(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - bpy.context.active_object.data.BIMTextProperties.variables.add() + context.active_object.data.BIMTextProperties.variables.add() return {"FINISHED"} @@ -800,7 +802,7 @@ class RemoveVariable(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - bpy.context.active_object.data.BIMTextProperties.variables.remove(self.index) + context.active_object.data.BIMTextProperties.variables.remove(self.index) return {"FINISHED"} @@ -810,8 +812,8 @@ class PropagateTextData(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - source = bpy.context.active_object - for obj in bpy.context.selected_objects: + source = context.active_object + for obj in context.selected_objects: if obj == source: continue obj.data.body = source.data.body @@ -834,7 +836,7 @@ class RemoveDrawing(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties camera = props.drawings[self.index].camera collection = camera.users_collection[0] for obj in collection.objects: @@ -850,7 +852,7 @@ class AddDrawingStyle(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - new = bpy.context.scene.DocProperties.drawing_styles.add() + new = context.scene.DocProperties.drawing_styles.add() new.name = "New Drawing Style" return {"FINISHED"} @@ -862,7 +864,7 @@ class RemoveDrawingStyle(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - bpy.context.scene.DocProperties.drawing_styles.remove(self.index) + context.scene.DocProperties.drawing_styles.remove(self.index) return {"FINISHED"} @@ -924,11 +926,12 @@ class ActivateDrawingStyle(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - if context.scene.camera.data.BIMCameraProperties.active_drawing_style_index < len( - bpy.context.scene.DocProperties.drawing_styles + scene = context.scene + if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len( + scene.DocProperties.drawing_styles ): - self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[ - context.scene.camera.data.BIMCameraProperties.active_drawing_style_index + self.drawing_style = scene.DocProperties.drawing_styles[ + scene.camera.data.BIMCameraProperties.active_drawing_style_index ] self.set_raster_style() self.set_query() @@ -1039,7 +1042,7 @@ class RemoveSheet(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties props.sheets.remove(self.index) return {"FINISHED"} @@ -1050,8 +1053,8 @@ class AddSchedule(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - new = bpy.context.scene.DocProperties.schedules.add() - new.name = "SCHEDULE {}".format(len(bpy.context.scene.DocProperties.schedules)) + new = context.scene.DocProperties.schedules.add() + new.name = "SCHEDULE {}".format(len(context.scene.DocProperties.schedules)) return {"FINISHED"} @@ -1062,7 +1065,7 @@ class RemoveSchedule(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties props.schedules.remove(self.index) return {"FINISHED"} @@ -1076,7 +1079,7 @@ class SelectScheduleFile(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties props.schedules[props.active_schedule_index].file = self.filepath return {"FINISHED"} @@ -1090,12 +1093,12 @@ class BuildSchedule(bpy.types.Operator): bl_label = "Build Schedule" def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties schedule = props.schedules[props.active_schedule_index] schedule_creator = scheduler.Scheduler() - outfile = os.path.join(bpy.context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg") + outfile = os.path.join(context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg") schedule_creator.schedule(schedule.file, outfile) - open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, outfile) + open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, outfile) return {"FINISHED"} @@ -1106,9 +1109,9 @@ class AddScheduleToSheet(bpy.types.Operator): # TODO: check undo redo def execute(self, context): - props = bpy.context.scene.DocProperties + props = context.scene.DocProperties sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir + sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.add_schedule( props.schedules[props.active_schedule_index].name, props.sheets[props.active_sheet_index].name ) @@ -1121,7 +1124,7 @@ class AddDrawingStyleAttribute(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = bpy.context.scene.camera.data.BIMCameraProperties + props = context.scene.camera.data.BIMCameraProperties context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add() return {"FINISHED"} @@ -1133,7 +1136,7 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - props = bpy.context.scene.camera.data.BIMCameraProperties + props = context.scene.camera.data.BIMCameraProperties context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index) return {"FINISHED"} @@ -1161,9 +1164,9 @@ class CleanWireframes(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - objects = bpy.data.objects - if bpy.context.selected_objects: - objects = bpy.context.selected_objects + objects = context.scene.objects + if context.selected_objects: + objects = context.selected_objects for obj in objects: if not isinstance(obj.data, bpy.types.Mesh): continue diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 7f11f1710a..b678920bab 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -57,10 +57,10 @@ def getDiagramScales(self, context): global diagram_scales_enum if ( len(diagram_scales_enum) < 1 - or (bpy.context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13) - or (bpy.context.scene.unit_settings.system == "METRIC" and len(diagram_scales_enum) == 31) + or (context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13) + or (context.scene.unit_settings.system == "METRIC" and len(diagram_scales_enum) == 31) ): - if bpy.context.scene.unit_settings.system == "IMPERIAL": + if context.scene.unit_settings.system == "IMPERIAL": diagram_scales_enum = [ ("CUSTOM", "Custom", ""), ("1'=1'-0\"|1/1", "1'=1'-0\"", ""), diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py index 598f08bd5b..d55c0cd3e6 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py @@ -234,13 +234,17 @@ class EnableEditingUnit(bpy.types.Operator): props = context.scene.BIMUnitProperties props.unit_attributes.clear() data = Data.units[self.unit] - blenderbim.bim.helper.import_attributes(data["type"], props.unit_attributes, data, self.import_attributes) + blenderbim.bim.helper.import_attributes( + data["type"], + props.unit_attributes, + data, + lambda name, prop, data: self.import_attributes(name, prop, data, context)) props.active_unit_id = self.unit return {"FINISHED"} - def import_attributes(self, name, prop, data): + def import_attributes(self, name, prop, data, context): if name == "Dimensions": - new = bpy.context.scene.BIMUnitProperties.unit_attributes.add() + new = context.scene.BIMUnitProperties.unit_attributes.add() new.name = name new.is_null = data[name] is None new.is_optional = False diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 9614ab6018..fccaab384a 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -64,8 +64,8 @@ class ExportIFC(bpy.types.Operator): def _execute(self, context): start = time.time() logger = logging.getLogger("ExportIFC") - path_log = os.path.join(bpy.context.scene.BIMProperties.data_dir, "process.log") - if not os.access(bpy.context.scene.BIMProperties.data_dir, os.W_OK): + path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") + if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): path_log = os.path.join(tempfile.mkdtemp(), "process.log") logging.basicConfig( filename=path_log, From 4b7058e22ede5b01031ce3cf0a94d9b2dd4c22d0 Mon Sep 17 00:00:00 2001 From: LaurensJN Date: Tue, 17 Aug 2021 16:37:40 +0200 Subject: [PATCH 27/72] ifccityjson: updated georeferencing & added option to split IFC files based on Level of Detail --- src/ifccityjson/cityjson2ifc.py | 66 +++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/src/ifccityjson/cityjson2ifc.py b/src/ifccityjson/cityjson2ifc.py index d2c3ea3b99..8c6cd5b142 100644 --- a/src/ifccityjson/cityjson2ifc.py +++ b/src/ifccityjson/cityjson2ifc.py @@ -20,6 +20,7 @@ import ifcopenshell import ifcopenshell.api from geometry import GeometryIO from datetime import datetime +import os JSON_TO_IFC = { "Building": ["IfcBuilding"], @@ -75,7 +76,7 @@ class Cityjson2ifc: self.configuration() def configuration(self, file_destination="output.ifc", name_attribute=None, split=True): - self.properties["file_destination"] = file_destination + self.properties["file_destination"], self.properties["file_extension"] = os.path.splitext(file_destination) self.properties["name_attribute"] = name_attribute self.properties["split"] = split @@ -88,7 +89,10 @@ class Cityjson2ifc: scale=self.properties["local_scale"]) # self.build_vertices() self.create_IFC_classes() - self.write_file() + if self.properties["split"]: + self.write_files() + else: + self.write_file() def create_metadata(self): # Georeferencing @@ -107,13 +111,13 @@ class Cityjson2ifc: if epsg: # Meter is assumed as unit for now unit = self.IFC_model.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE") - crs = self.IFC_model.create_entity("IfcProjectedCrs", Name=f"epsg:{epsg}") - self.IFC_model.create_entity("IfcMapConversion", self.IFC_representation_context, - **self.properties["local_translation"]) + self.properties["local_translation"]["TargetCRS"] = self.IFC_model.create_entity("IfcProjectedCrs", Name=f"epsg:{epsg}") + self.properties["local_translation"]["SourceCRS"] = self.IFC_representation_context + self.IFC_model.create_entity("IfcMapConversion", **self.properties["local_translation"]) def create_new_file(self): self.IFC_model = ifcopenshell.api.run("project.create_file") - self.IFC_project = ifcopenshell.api.run("root.create_entity", self.IFC_model, **{"ifc_class": "IfcProject"}) + self.IFC_project = ifcopenshell.api.run("root.create_entity", self.IFC_model, **{"ifc_class": "IfcProject", "name": "My Project"}) ifcopenshell.api.run("unit.assign_unit", self.IFC_model, length={"is_metric": True, "raw": "METERS"}) self.properties["owner_history"] = self.create_owner_history() self.IFC_representation_context = ifcopenshell.api.run("context.add_context", self.IFC_model, @@ -139,6 +143,10 @@ class Cityjson2ifc: self.IFC_site = ifcopenshell.api.run("root.create_entity", self.IFC_model, **{"ifc_class": "IfcSite", "name": "My Site"}) + self.IFC_model.create_entity("IfcRelAggregates", + **{"GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": [self.IFC_site], + "RelatingObject": self.IFC_project}) def create_owner_history(self): actor = self.IFC_model.createIfcActorRole("ENGINEER", None, None) @@ -157,7 +165,31 @@ class Cityjson2ifc: return ownerHistory def write_file(self): - self.IFC_model.write(self.properties["file_destination"]) + file = self.properties["file_destination"] + self.properties["file_extension"] + self.IFC_model.write(file) + + def write_files(self): + for lod, IFC_representation_sub_context in self.IFC_representation_sub_contexts.items(): + sub_context_id = IFC_representation_sub_context.id() + + # TODO this method makes a copy of the IFC_model by writing it and importing it, + # TODO but maybe there is a better method. + file = self.properties["file_destination"] + lod + self.properties["file_extension"] + self.IFC_model.write(file) + IFC_copied_model = ifcopenshell.open(file) + + IFC_copied_model_sub_contexts = IFC_copied_model.by_type('IfcGeometricRepresentationSubContext') + for sub_context in IFC_copied_model_sub_contexts: + if sub_context.id() == sub_context_id: + continue + else: + elements = IFC_copied_model.get_inverse(sub_context) + for element in elements: + IFC_copied_model.remove(element) + IFC_copied_model.remove(sub_context) + + IFC_copied_model.write(file) + del IFC_copied_model def create_IFC_classes(self): parents_children_relations = {"IfcSite": {'Parent': self.IFC_site, 'Children': []}} @@ -223,14 +255,21 @@ class Cityjson2ifc: parents_children_relations[obj_id]['Parent'] = IFC_object if IFC_semantic_surface_children: - self.IFC_model.create_entity("IfcRelAggregates", + self.IFC_model.create_entity("IfcRelContainedInSpatialStructure", **{"GlobalId": ifcopenshell.guid.new(), - "RelatedObjects": IFC_semantic_surface_children, - "RelatingObject": IFC_object}) + "RelatedElements": IFC_semantic_surface_children, + "RelatingStructure": IFC_object}) self.create_property_set(obj.attributes, IFC_object) - for parent_children in parents_children_relations.values(): + for parent, parent_children in parents_children_relations.items(): + if parent == "IfcSite": + self.IFC_model.create_entity("IfcRelAggregates", + **{"GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": parent_children['Children'], + "RelatingObject": parent_children['Parent']}) + continue + self.IFC_model.create_entity("IfcRelContainedInSpatialStructure", **{"GlobalId": ifcopenshell.guid.new(), "RelatedElements": parent_children['Children'], @@ -248,8 +287,11 @@ class Cityjson2ifc: # CREATE ENTITY surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id) if surface_geometry: - child_data["Representation"] = self.create_IFC_shape_representation(surface_geometry, 'brep', lod) + IFC_shape_representation =self.create_IFC_shape_representation(surface_geometry, 'brep', lod) + child_data["Representation"] = self.IFC_model.create_entity("IfcProductDefinitionShape", + Representations=[IFC_shape_representation]) IFC_semantic_surface_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data)) + return IFC_semantic_surface_children def create_IFC_shape_representation(self, IFC_geometry, shape_representation_type, lod): From 2d730fbee7c44f7d1787a438749417e9fc8c2ede Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Wed, 18 Aug 2021 00:50:24 +0200 Subject: [PATCH 28/72] Improve drawing style save and load (#1665) * Replace raster style save & load with enum functionality * Rename DrawingStyleProp to RasterStyleProperty * Minor Fix * Correct Typo --- src/blenderbim/blenderbim/bim/handler.py | 97 +++++++-------- .../blenderbim/bim/module/drawing/operator.py | 110 ++++++------------ .../blenderbim/bim/module/drawing/prop.py | 28 +++++ 3 files changed, 112 insertions(+), 123 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 3ae69b7008..2e232c56c4 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -21,6 +21,7 @@ import bpy import json import addon_utils import ifcopenshell.api.owner.settings +from blenderbim.bim.module.drawing.prop import RasterStyleProperty from bpy.app.handlers import persistent from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.owner.prop import getPersons, getOrganisations @@ -246,30 +247,30 @@ def setDefaultProperties(scene): drawing_style.render_type = "VIEWPORT" drawing_style.raster_style = json.dumps( { - "bpy.data.worlds[0].color": (1, 1, 1), - "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", - "bpy.context.scene.render.film_transparent": False, - "bpy.context.scene.display.shading.show_object_outline": True, - "bpy.context.scene.display.shading.show_cavity": False, - "bpy.context.scene.display.shading.cavity_type": "BOTH", - "bpy.context.scene.display.shading.curvature_ridge_factor": 1, - "bpy.context.scene.display.shading.curvature_valley_factor": 1, - "bpy.context.scene.view_settings.view_transform": "Standard", - "bpy.context.scene.display.shading.light": "FLAT", - "bpy.context.scene.display.shading.color_type": "SINGLE", - "bpy.context.scene.display.shading.single_color": (1, 1, 1), - "bpy.context.scene.display.shading.show_shadows": False, - "bpy.context.scene.display.shading.shadow_intensity": 0.5, - "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), - "bpy.context.scene.view_settings.use_curve_mapping": False, - "space.overlay.show_wireframes": True, - "space.overlay.wireframe_threshold": 0, - "space.overlay.show_floor": False, - "space.overlay.show_axis_x": False, - "space.overlay.show_axis_y": False, - "space.overlay.show_axis_z": False, - "space.overlay.show_object_origins": False, - "space.overlay.show_relationship_lines": False, + RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1), + RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH", + RasterStyleProperty.RENDER_TRANSPARENT.value: False, + RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True, + RasterStyleProperty.SHADING_SHOW_CAVITY.value: False, + RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH", + RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1, + RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1, + RasterStyleProperty.VIEW_TRANSFORM.value: "Standard", + RasterStyleProperty.SHADING_LIGHT.value: "FLAT", + RasterStyleProperty.SHADING_COLOR_TYPE.value: "SINGLE", + RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1), + RasterStyleProperty.SHADING_SHOW_SHADOWS.value: False, + RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5, + RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5), + RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False, + RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True, + RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0, + RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False, + RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False, + RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False, + RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False, + RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False, + RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False, } ) drawing_style = scene.DocProperties.drawing_styles.add() @@ -277,30 +278,30 @@ def setDefaultProperties(scene): drawing_style.render_type = "VIEWPORT" drawing_style.raster_style = json.dumps( { - "bpy.data.worlds[0].color": (1, 1, 1), - "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", - "bpy.context.scene.render.film_transparent": False, - "bpy.context.scene.display.shading.show_object_outline": True, - "bpy.context.scene.display.shading.show_cavity": True, - "bpy.context.scene.display.shading.cavity_type": "BOTH", - "bpy.context.scene.display.shading.curvature_ridge_factor": 1, - "bpy.context.scene.display.shading.curvature_valley_factor": 1, - "bpy.context.scene.view_settings.view_transform": "Standard", - "bpy.context.scene.display.shading.light": "STUDIO", - "bpy.context.scene.display.shading.color_type": "MATERIAL", - "bpy.context.scene.display.shading.single_color": (1, 1, 1), - "bpy.context.scene.display.shading.show_shadows": True, - "bpy.context.scene.display.shading.shadow_intensity": 0.5, - "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), - "bpy.context.scene.view_settings.use_curve_mapping": False, - "space.overlay.show_wireframes": True, - "space.overlay.wireframe_threshold": 0, - "space.overlay.show_floor": False, - "space.overlay.show_axis_x": False, - "space.overlay.show_axis_y": False, - "space.overlay.show_axis_z": False, - "space.overlay.show_object_origins": False, - "space.overlay.show_relationship_lines": False, + RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1), + RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH", + RasterStyleProperty.RENDER_TRANSPARENT.value: False, + RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True, + RasterStyleProperty.SHADING_SHOW_CAVITY.value: True, + RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH", + RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1, + RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1, + RasterStyleProperty.VIEW_TRANSFORM.value: "Standard", + RasterStyleProperty.SHADING_LIGHT.value: "STUDIO", + RasterStyleProperty.SHADING_COLOR_TYPE.value: "MATERIAL", + RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1), + RasterStyleProperty.SHADING_SHOW_SHADOWS.value: True, + RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5, + RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5), + RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False, + RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True, + RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0, + RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False, + RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False, + RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False, + RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False, + RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False, + RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False, } ) drawing_style = scene.DocProperties.drawing_styles.add() diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index c51a471d19..8b00164eca 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -36,6 +36,7 @@ import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.sheeter as sheeter import blenderbim.bim.module.drawing.scheduler as scheduler import blenderbim.bim.module.drawing.helper as helper +from blenderbim.bim.module.drawing.prop import RasterStyleProperty from mathutils import Vector, Matrix, Euler, geometry from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.group.data import Data as GroupData @@ -876,42 +877,26 @@ class SaveDrawingStyle(bpy.types.Operator): # TODO: check undo redo def execute(self, context): - space = self.get_view_3d() - style = { - "bpy.data.worlds[0].color": tuple(bpy.data.worlds[0].color), - "bpy.context.scene.render.engine": bpy.context.scene.render.engine, - "bpy.context.scene.render.film_transparent": bpy.context.scene.render.film_transparent, - "bpy.context.scene.display.shading.show_object_outline": bpy.context.scene.display.shading.show_object_outline, - "bpy.context.scene.display.shading.show_cavity": bpy.context.scene.display.shading.show_cavity, - "bpy.context.scene.display.shading.cavity_type": bpy.context.scene.display.shading.cavity_type, - "bpy.context.scene.display.shading.curvature_ridge_factor": bpy.context.scene.display.shading.curvature_ridge_factor, - "bpy.context.scene.display.shading.curvature_valley_factor": bpy.context.scene.display.shading.curvature_valley_factor, - "bpy.context.scene.view_settings.view_transform": bpy.context.scene.view_settings.view_transform, - "bpy.context.scene.display.shading.light": bpy.context.scene.display.shading.light, - "bpy.context.scene.display.shading.color_type": bpy.context.scene.display.shading.color_type, - "bpy.context.scene.display.shading.single_color": tuple(bpy.context.scene.display.shading.single_color), - "bpy.context.scene.display.shading.show_shadows": bpy.context.scene.display.shading.show_shadows, - "bpy.context.scene.display.shading.shadow_intensity": bpy.context.scene.display.shading.shadow_intensity, - "bpy.context.scene.display.light_direction": tuple(bpy.context.scene.display.light_direction), - "bpy.context.scene.view_settings.use_curve_mapping": bpy.context.scene.view_settings.use_curve_mapping, - "space.overlay.show_wireframes": space.overlay.show_wireframes, - "space.overlay.wireframe_threshold": space.overlay.wireframe_threshold, - "space.overlay.show_floor": space.overlay.show_floor, - "space.overlay.show_axis_x": space.overlay.show_axis_x, - "space.overlay.show_axis_y": space.overlay.show_axis_y, - "space.overlay.show_axis_z": space.overlay.show_axis_z, - "space.overlay.show_object_origins": space.overlay.show_object_origins, - "space.overlay.show_relationship_lines": space.overlay.show_relationship_lines, - } + space = self.get_view_3d(context) # Do not remove. It is used later in eval + scene = context.scene + style = {} + for prop in RasterStyleProperty: + value = eval(prop.value) + if not isinstance(value, str): + try: + value = tuple(value) + except TypeError: + pass + style[prop.value] = value if self.index: index = int(self.index) else: - index = bpy.context.active_object.data.BIMCameraProperties.active_drawing_style_index - bpy.context.scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style) + index = context.active_object.data.BIMCameraProperties.active_drawing_style_index + scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style) return {"FINISHED"} - def get_view_3d(self): - for area in bpy.context.screen.areas: + def get_view_3d(self, context): + for area in context.screen.areas: if area.type != "VIEW_3D": continue for space in area.spaces: @@ -933,50 +918,25 @@ class ActivateDrawingStyle(bpy.types.Operator): self.drawing_style = scene.DocProperties.drawing_styles[ scene.camera.data.BIMCameraProperties.active_drawing_style_index ] - self.set_raster_style() - self.set_query() + self.set_raster_style(context) + self.set_query(context) return {"FINISHED"} - def set_raster_style(self): - space = self.get_view_3d() + def set_raster_style(self, context): + scene = context.scene # Do not remove. It is used in exec later + space = self.get_view_3d(context) # Do not remove. It is used in exec later style = json.loads(self.drawing_style.raster_style) - bpy.data.worlds[0].color = style["bpy.data.worlds[0].color"] - bpy.context.scene.render.engine = style["bpy.context.scene.render.engine"] - bpy.context.scene.render.film_transparent = style["bpy.context.scene.render.film_transparent"] - bpy.context.scene.display.shading.show_object_outline = style[ - "bpy.context.scene.display.shading.show_object_outline" - ] - bpy.context.scene.display.shading.show_cavity = style["bpy.context.scene.display.shading.show_cavity"] - bpy.context.scene.display.shading.cavity_type = style["bpy.context.scene.display.shading.cavity_type"] - bpy.context.scene.display.shading.curvature_ridge_factor = style[ - "bpy.context.scene.display.shading.curvature_ridge_factor" - ] - bpy.context.scene.display.shading.curvature_valley_factor = style[ - "bpy.context.scene.display.shading.curvature_valley_factor" - ] - bpy.context.scene.view_settings.view_transform = style["bpy.context.scene.view_settings.view_transform"] - bpy.context.scene.display.shading.light = style["bpy.context.scene.display.shading.light"] - bpy.context.scene.display.shading.color_type = style["bpy.context.scene.display.shading.color_type"] - bpy.context.scene.display.shading.single_color = style["bpy.context.scene.display.shading.single_color"] - bpy.context.scene.display.shading.show_shadows = style["bpy.context.scene.display.shading.show_shadows"] - bpy.context.scene.display.shading.shadow_intensity = style["bpy.context.scene.display.shading.shadow_intensity"] - bpy.context.scene.display.light_direction = style["bpy.context.scene.display.light_direction"] + for path, value in style.items(): + if isinstance(value, str): + exec(f"{path} = '{value}'") + else: + exec(f"{path} = {value}") - bpy.context.scene.view_settings.use_curve_mapping = style["bpy.context.scene.view_settings.use_curve_mapping"] - space.overlay.show_wireframes = style["space.overlay.show_wireframes"] - space.overlay.wireframe_threshold = style["space.overlay.wireframe_threshold"] - space.overlay.show_floor = style["space.overlay.show_floor"] - space.overlay.show_axis_x = style["space.overlay.show_axis_x"] - space.overlay.show_axis_y = style["space.overlay.show_axis_y"] - space.overlay.show_axis_z = style["space.overlay.show_axis_z"] - space.overlay.show_object_origins = style["space.overlay.show_object_origins"] - space.overlay.show_relationship_lines = style["space.overlay.show_relationship_lines"] - - def set_query(self): + def set_query(self, context): self.selector = ifcopenshell.util.selector.Selector() self.include_global_ids = [] self.exclude_global_ids = [] - for ifc_file in bpy.context.scene.DocProperties.ifc_files: + for ifc_file in context.scene.DocProperties.ifc_files: try: ifc = ifcopenshell.open(ifc_file.name) except: @@ -988,15 +948,15 @@ class ActivateDrawingStyle(bpy.types.Operator): results = self.selector.parse(ifc, self.drawing_style.exclude_query) self.exclude_global_ids.extend([e.GlobalId for e in results]) if self.drawing_style.include_query: - self.parse_filter_query("INCLUDE") + self.parse_filter_query("INCLUDE", context) if self.drawing_style.exclude_query: - self.parse_filter_query("EXCLUDE") + self.parse_filter_query("EXCLUDE", context) - def parse_filter_query(self, mode): + def parse_filter_query(self, mode, context): if mode == "INCLUDE": - objects = bpy.context.scene.objects + objects = context.scene.objects elif mode == "EXCLUDE": - objects = bpy.context.visible_objects + objects = context.visible_objects for obj in objects: if mode == "INCLUDE": obj.hide_viewport = False # Note: this breaks alt-H @@ -1011,8 +971,8 @@ class ActivateDrawingStyle(bpy.types.Operator): if global_id in self.exclude_global_ids: obj.hide_viewport = True # Note: this breaks alt-H - def get_view_3d(self): - for area in bpy.context.screen.areas: + def get_view_3d(self, context): + for area in context.screen.areas: if area.type != "VIEW_3D": continue for space in area.spaces: diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index b678920bab..210ad986e8 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -21,6 +21,7 @@ import os import bpy import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.decoration as decoration +import enum from pathlib import Path from blenderbim.bim.prop import Attribute, StrProperty from bpy.types import PropertyGroup @@ -218,6 +219,33 @@ class DrawingStyle(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=StrProperty) +class RasterStyleProperty(enum.Enum): + WORLD_COLOR = "bpy.data.worlds[0].color" + RENDER_ENGINE = "scene.render.engine" + RENDER_TRANSPARENT = "scene.render.film_transparent" + VIEW_TRANSFORM = "scene.view_settings.view_transform" + SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline" + SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity" + SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type" + SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor" + SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor" + SHADING_LIGHT = "scene.display.shading.light" + SHADING_COLOR_TYPE = "scene.display.shading.color_type" + SHADING_SINGLE_COLOR = "scene.display.shading.single_color" + SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows" + SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity" + DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction" + VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping" + OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes" + OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold" + OVERLAY_SHOW_FLOOR = "space.overlay.show_floor" + OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x" + OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y" + OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z" + OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins" + OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines" + + class DocProperties(PropertyGroup): has_underlay: BoolProperty(name="Underlay", default=False) has_linework: BoolProperty(name="Linework", default=True) From 799fb688b5480470437471f06a4f582917d46220 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Wed, 18 Aug 2021 07:59:23 +0200 Subject: [PATCH 29/72] Fix https://github.com/IfcOpenShell/IfcOpenShell/issues/1667 : Passed scene is None when creating file --- src/blenderbim/blenderbim/bim/handler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 2e232c56c4..b56636e9f2 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -182,6 +182,8 @@ def redo_pre(scene): @persistent def redo_post(scene): + if scene is None: + scene = bpy.context.scene if IfcStore.last_transaction != scene.BIMProperties.last_transaction: IfcStore.last_transaction = scene.BIMProperties.last_transaction IfcStore.redo() @@ -192,6 +194,8 @@ def redo_post(scene): @persistent def ensureIfcExported(scene): + if scene is None: + scene = bpy.context.scene if IfcStore.get_file() and not scene.BIMProperties.ifc_file: bpy.ops.export_ifc.bim("INVOKE_DEFAULT") @@ -225,6 +229,8 @@ def get_application_version(): @persistent def setDefaultProperties(scene): + if scene is None: + scene = bpy.context.scene global global_subscription_owner active_object_key = bpy.types.LayerObjects, "active" bpy.msgbus.subscribe_rna( From a6b8e40829f01fe4e59ed54ca1df76989ad34a9c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 18 Aug 2021 18:04:52 +1000 Subject: [PATCH 30/72] Cost subtotals that are calculated from per X units are now supported --- .../blenderbim/bim/module/cost/ui.py | 35 +++++++++++-------- .../ifcopenshell/api/cost/data.py | 23 +++++++++--- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 2b9626fc0f..12d5deaecc 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -474,9 +474,10 @@ class BIM_PT_cost_item_rates(Panel): cost_item = self.props.cost_items[self.props.active_cost_item_index] row = self.layout.row(align=True) row.prop(self.props, "schedule_of_rates", text="") - op = row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") - op.cost_item = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id - op.cost_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index].ifc_definition_id + if self.props.active_cost_item_rate_index < len(self.props.cost_item_rates): + op = row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") + op.cost_item = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id + op.cost_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index].ifc_definition_id self.layout.template_list( "BIM_UL_cost_item_rates", "", @@ -487,7 +488,7 @@ class BIM_PT_cost_item_rates(Panel): ) -class BIM_UL_cost_items_trait(): +class BIM_UL_cost_items_trait: def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: self.props = context.scene.BIMCostProperties @@ -503,13 +504,12 @@ class BIM_UL_cost_items_trait(): split2.prop(item, "name", emboss=False, text="") self.draw_quantity_column(split2, cost_item) - - split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"])) + self.draw_value_column(split2, cost_item) for column in self.props.columns: split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) - split2.label(text="{0:.2f}".format(cost_item["TotalCostValue"])) + self.draw_total_cost_column(split2, cost_item) # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX def draw_hierarchy(self, row, item): @@ -524,22 +524,26 @@ class BIM_UL_cost_items_trait(): else: row.label(text="", icon="DOT") + def draw_total_cost_column(self, layout, cost_item): + layout.label(text="{0:.2f}".format(cost_item["TotalCost"])) + def draw_quantity_column(self, layout, cost_item): if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": self.draw_uom_column(layout, cost_item) else: self.draw_total_quantity_column(layout, cost_item) - def draw_uom_column(self, layout, cost_item): - text = "-" - if cost_item["CostValues"]: - unit_basis = Data.cost_values[cost_item["CostValues"][0]]["UnitBasis"] - if unit_basis: - text = "{0:.2f}".format(unit_basis["ValueComponent"]) + f" ({unit_basis['UnitSymbol'] or '?'})" + def draw_value_column(self, layout, cost_item): + text = "{0:.2f}".format(cost_item["TotalAppliedValue"]) + if cost_item["UnitBasisValueComponent"] not in [None, 1]: + text += " / {}".format(round(cost_item["UnitBasisValueComponent"], 2)) layout.label(text=text) + def draw_uom_column(self, layout, cost_item): + layout.label(text=cost_item["UnitBasisUnitSymbol"] or "?" if cost_item["UnitBasisValueComponent"] else "-") + def draw_total_quantity_column(self, layout, cost_item): - layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") + layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '?'}") class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList): @@ -559,6 +563,9 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): def draw_quantity_column(self, layout, cost_item): self.draw_uom_column(layout, cost_item) + def draw_total_cost_column(self, layout, cost_item): + pass # No such thing as a total cost in a schedule of rates + class BIM_UL_cost_columns(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py index 138b96c799..fdd8d811ec 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py @@ -107,14 +107,25 @@ class Data: @classmethod def load_cost_item_values(cls, cost_item, data): data["CostValues"] = [] - data["TotalCostValue"] = 0.0 - data["TotalAppliedValue"] = 0.0 data["CategoryValues"] = {} + data["UnitBasisValueComponent"] = None + data["UnitBasisUnitSymbol"] = None + data["TotalAppliedValue"] = 0.0 + data["TotalCost"] = 0.0 for cost_value in cost_item.CostValues or []: cls.load_cost_item_value(cost_item, data, cost_value) data["CostValues"].append(cost_value.id()) data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"] - data["TotalCostValue"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] + if cost_value.UnitBasis: + cost_value_data = cls.cost_values[cost_value.id()] + data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"] + data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"] + if data["UnitBasisValueComponent"]: + data["TotalCost"] = ( + data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"] + ) + else: + data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] @classmethod def load_cost_item_value(cls, cost_item, cost_item_data, cost_value): @@ -187,7 +198,11 @@ class Data: continue child_applied_value = cls.calculate_applied_value(child_cost_item, child_cost_value) child_quantity = cls.get_total_quantity(child_cost_item) - result += child_applied_value * child_quantity + if child_cost_value.UnitBasis: + value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue + result += child_quantity / value_component * child_applied_value + else: + result += child_quantity * child_applied_value return result @classmethod From bbd7264a9b235b16c71ba20e01f1cdadb953c3b1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 18 Aug 2021 18:40:54 +1000 Subject: [PATCH 31/72] Improve UX of task tree by shifting buttons and changing column widths --- .../blenderbim/bim/module/sequence/ui.py | 175 ++++++++++-------- 1 file changed, 94 insertions(+), 81 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index bbdc8c20c0..a8bcc7d291 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -152,6 +151,37 @@ class BIM_PT_work_schedules(Panel): elif self.props.editing_type == "TASKS": self.draw_editable_task_ui(work_schedule_id) + def draw_task_operators(self): + row = self.layout.row(align=True) + row.alignment = "RIGHT" + ifc_definition_id = None + if self.tprops.tasks and self.props.active_task_index < len(self.tprops.tasks): + task = self.tprops.tasks[self.props.active_task_index] + ifc_definition_id = task.ifc_definition_id + if ifc_definition_id: + if self.props.active_task_id == ifc_definition_id: + if self.props.editing_task_type == "TASKTIME": + row.operator("bim.edit_task_time", text="", icon="CHECKMARK") + elif self.props.editing_task_type == "ATTRIBUTES": + row.operator("bim.edit_task", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_task", text="", icon="CANCEL") + elif self.props.active_task_id: + row.operator("bim.add_task", text="", icon="ADD").task = ifc_definition_id + row.operator("bim.remove_task", text="", icon="X").task = ifc_definition_id + else: + row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = ifc_definition_id + row.operator( + "bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="" + ).task = ifc_definition_id + row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id + row.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = ifc_definition_id + row.operator( + "bim.enable_assigning_process_to_resources", text="", icon="COMMUNITY" + ).task = ifc_definition_id + row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = ifc_definition_id + row.operator("bim.add_task", text="", icon="ADD").task = ifc_definition_id + row.operator("bim.remove_task", text="", icon="X").task = ifc_definition_id + def draw_column_ui(self): row = self.layout.row(align=True) row.prop(self.props, "column_types", text="") @@ -205,6 +235,7 @@ class BIM_PT_work_schedules(Panel): draw_attributes(self.props.work_schedule_attributes, self.layout) def draw_editable_task_ui(self, work_schedule_id): + self.draw_task_operators() self.layout.template_list( "BIM_UL_tasks", "", @@ -313,6 +344,7 @@ class BIM_PT_work_schedules(Panel): op.task = self.props.active_task_id op.resource = related_obect_id + class BIM_UL_task_columns(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): props = context.scene.BIMWorkScheduleProperties @@ -327,59 +359,21 @@ class BIM_UL_task_columns(UIList): class BIM_UL_tasks(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: - props = context.scene.BIMWorkScheduleProperties + self.props = context.scene.BIMWorkScheduleProperties task = IfcStore.get_file().by_id(item.ifc_definition_id) row = layout.row(align=True) - for i in range(0, item.level_index): - row.label(text="", icon="BLANK1") - if item.has_children: - if item.is_expanded: - row.operator( - "bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" - ).task = item.ifc_definition_id - else: - row.operator( - "bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" - ).task = item.ifc_definition_id - else: - row.label(text="", icon="DOT") - row.prop(item, "identification", emboss=False, text="") - row.prop(item, "name", emboss=False, text="") - for column in props.columns: - if column.name == "IfcTaskTime.ScheduleStart": - if item.derived_start: - row.label(text=item.derived_start + "*") - else: - row.prop(item, "start", emboss=False, text="") - elif column.name == "IfcTaskTime.ScheduleFinish": - if item.derived_finish: - row.label(text=item.derived_finish + "*") - else: - row.prop(item, "finish", emboss=False, text="") - elif column.name == "IfcTaskTime.ScheduleDuration": - if item.derived_duration: - row.label(text=item.derived_duration + "*") - else: - row.prop(item, "duration", emboss=False, text="") - elif column.name == "Controls.Calendar": - if item.derived_calendar: - row.label(text=item.derived_calendar + "*") - else: - row.label(text=item.calendar or "-") - else: - ifc_class, name = column.name.split(".") - if ifc_class == "IfcTask": - value = getattr(task, name) - elif ifc_class == "IfcTaskTime": - value = getattr(task.TaskTime, name) if task.TaskTime else None - if value is None: - value = "-" - row.label(text=str(value)) + self.draw_hierarchy(row, item) + + split1 = row.split(factor=0.1) + split1.prop(item, "identification", emboss=False, text="") + split2 = split1.split(factor=0.9 - min(0.5, 0.15 * len(self.props.columns))) + split2.prop(item, "name", emboss=False, text="") + + self.draw_custom_columns(split2, item, task) if context.active_object: oprops = context.active_object.BIMObjectProperties - row = layout.row(align=True) if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["OperatesOn"]: op = row.operator("bim.unassign_process", text="", icon="MARKER_HLT", emboss=False) @@ -395,7 +389,7 @@ class BIM_UL_tasks(UIList): op = row.operator("bim.assign_product", text="", icon="KEYFRAME", emboss=False) op.task = item.ifc_definition_id - if props.active_task_id and props.editing_task_type == "ATTRIBUTES": + if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES": row.prop( item, "is_selected", @@ -404,47 +398,66 @@ class BIM_UL_tasks(UIList): emboss=False, ) - if props.active_task_id == item.ifc_definition_id: - if props.editing_task_type == "TASKTIME": - row.operator("bim.edit_task_time", text="", icon="CHECKMARK") - elif props.editing_task_type == "ATTRIBUTES": - row.operator("bim.edit_task", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_task", text="", icon="CANCEL") - elif props.active_task_id: - if props.editing_task_type == "SEQUENCE": + if self.props.active_task_id: + if self.props.editing_task_type == "SEQUENCE" and self.props.active_task_id != item.ifc_definition_id: if item.is_predecessor: - row.operator( - "bim.unassign_predecessor", text="", icon="BACK", emboss=False - ).task = item.ifc_definition_id + op = row.operator("bim.unassign_predecessor", text="", icon="BACK", emboss=False) else: - row.operator( - "bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False - ).task = item.ifc_definition_id + op = row.operator("bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False) + op.task = item.ifc_definition_id if item.is_successor: - row.operator( - "bim.unassign_successor", text="", icon="FORWARD", emboss=False - ).task = item.ifc_definition_id + op = row.operator("bim.unassign_successor", text="", icon="FORWARD", emboss=False) else: - row.operator( - "bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False - ).task = item.ifc_definition_id + op = row.operator("bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False) + op.task = item.ifc_definition_id - row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id - row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id + def draw_hierarchy(self, row, item): + for i in range(0, item.level_index): + row.label(text="", icon="BLANK1") + if item.has_children: + if item.is_expanded: + row.operator( + "bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" + ).task = item.ifc_definition_id else: - row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = item.ifc_definition_id row.operator( - "bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="" + "bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" ).task = item.ifc_definition_id - row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id - row.operator( - "bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO" - ).task = item.ifc_definition_id - row.operator("bim.enable_assigning_process_to_resources", text="", icon="COMMUNITY").task = item.ifc_definition_id - row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id - row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id - row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id + else: + row.label(text="", icon="DOT") + + def draw_custom_columns(self, row, item, task): + for column in self.props.columns: + if column.name == "IfcTaskTime.ScheduleStart": + if item.derived_start: + row.label(text=item.derived_start + "*") + else: + row.prop(item, "start", emboss=False, text="") + elif column.name == "IfcTaskTime.ScheduleFinish": + if item.derived_finish: + row.label(text=item.derived_finish + "*") + else: + row.prop(item, "finish", emboss=False, text="") + elif column.name == "IfcTaskTime.ScheduleDuration": + if item.derived_duration: + row.label(text=item.derived_duration + "*") + else: + row.prop(item, "duration", emboss=False, text="") + elif column.name == "Controls.Calendar": + if item.derived_calendar: + row.label(text=item.derived_calendar + "*") + else: + row.label(text=item.calendar or "-") + else: + ifc_class, name = column.name.split(".") + if ifc_class == "IfcTask": + value = getattr(task, name) + elif ifc_class == "IfcTaskTime": + value = getattr(task.TaskTime, name) if task.TaskTime else None + if value is None: + value = "-" + row.label(text=str(value)) class BIM_PT_work_calendars(Panel): From cb2995a25556b04aa9513d35c86ee4639847c469 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Wed, 18 Aug 2021 23:42:41 +0200 Subject: [PATCH 32/72] BlenderBim : Improve BCF Project UX (#1668) * Store fields on BCFProperties class rather than individual topics * Clear input fields on active topic change * BCF : Fix resetting comment has_related_viewpoint default value * Prevent global function use from import * Use dynamic enum to choose related topic * Add newline at eof * Show only .bcf and .bcfzip files when saving project --- .../blenderbim/bim/module/bcf/operator.py | 107 +++++++----------- .../blenderbim/bim/module/bcf/prop.py | 58 +++++++--- .../blenderbim/bim/module/bcf/ui.py | 26 ++--- 3 files changed, 101 insertions(+), 90 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index f49ff7fdbf..12ed1685b7 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -26,7 +26,7 @@ import numpy as np import ifcopenshell import ifcopenshell.util.unit from . import bcfstore -from blenderbim.bim.module.bcf.prop import getBcfViewpoints +import blenderbim.bim.module.bcf.prop as bcf_prop from blenderbim.bim.ifc import IfcStore from math import radians, degrees, atan, tan, cos, sin from mathutils import Vector, Matrix, Euler, geometry @@ -252,6 +252,7 @@ class SaveBcfProject(bpy.types.Operator): bl_label = "Save BCF Project" bl_options = {"REGISTER", "UNDO"} filepath: bpy.props.StringProperty(subtype="FILE_PATH") + filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"}) def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -286,8 +287,7 @@ class AddBcfBimSnippet(bpy.types.Operator): @classmethod def poll(cls, context): - props = context.scene.BCFProperties - return all((getattr(props.topics[props.active_topic_index], attr, False) for attr in ( + return all((getattr(context.scene.BCFProperties, attr, False) for attr in ( "bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type" @@ -299,11 +299,14 @@ class AddBcfBimSnippet(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] bim_snippet = bcf.v2.data.BimSnippet() - bim_snippet.reference = blender_topic.bim_snippet_reference - bim_snippet.reference_schema = blender_topic.bim_snippet_schema - bim_snippet.snippet_type = blender_topic.bim_snippet_type + bim_snippet.reference = props.bim_snippet_reference + bim_snippet.reference_schema = props.bim_snippet_schema + bim_snippet.snippet_type = props.bim_snippet_type bcfxml.add_bim_snippet(topic, bim_snippet) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bim_snippet.reference = "" + bim_snippet.reference_schema = "" + bim_snippet.snippet_type = "" return {"FINISHED"} @@ -314,33 +317,14 @@ class AddBcfRelatedTopic(bpy.types.Operator): @classmethod def poll(cls, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - if not blender_topic.related_topic: - return False - if blender_topic.related_topic == blender_topic.title: - # Prevent adding self as related topic - return False - related_topic = None - for topic in bcfxml.topics.values(): - if topic.title == blender_topic.related_topic: - related_topic = bcf.v2.data.RelatedTopic() - related_topic.guid = topic.guid - break - if not related_topic: - return False - if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]: - # Prevent adding the same related topic more than once - return False - return True + return bcf_prop.get_related_topics(None, context) def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] related_topic = bcf.v2.data.RelatedTopic() - related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == blender_topic.related_topic)).guid + related_topic.guid = props.related_topic topic = bcfxml.topics[blender_topic.name] topic.related_topics.append(related_topic) bcfxml.edit_topic(topic) @@ -355,8 +339,7 @@ class AddBcfHeaderFile(bpy.types.Operator): @classmethod def poll(cls, context): - props = context.scene.BCFProperties - return props.topics[props.active_topic_index].file_reference + return context.scene.BCFProperties.file_reference def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -364,15 +347,18 @@ class AddBcfHeaderFile(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] header_file = bcf.v2.data.HeaderFile() - header_file.reference = blender_topic.file_reference + header_file.reference = props.file_reference if not os.path.exists(header_file.reference): header_file.filename = header_file.reference - if len(blender_topic.file_ifc_project) == 22: - header_file.ifc_project = blender_topic.file_ifc_project - if len(blender_topic.file_ifc_spatial_structure_element) == 22: - header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element + if len(props.file_ifc_project) == 22: + header_file.ifc_project = props.file_ifc_project + if len(props.file_ifc_spatial_structure_element) == 22: + header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element bcfxml.add_file(topic, header_file) props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + props.file_reference = "" + props.file_ifc_project = "" + props.file_ifc_spatial_structure_element = "" return {"FINISHED"} @@ -386,6 +372,7 @@ class ViewBcfTopic(bpy.types.Operator): for index, topic in enumerate(context.scene.BCFProperties.topics): if topic.name.lower() == self.topic_guid.lower(): context.scene.BCFProperties.active_topic_index = index + break return {"FINISHED"} @@ -448,7 +435,7 @@ class RemoveBcfViewpoint(bpy.types.Operator): @classmethod def poll(cls, context): - return getBcfViewpoints(None, context) + return bcf_prop.getBcfViewpoints(None, context) def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -484,18 +471,17 @@ class AddBcfReferenceLink(bpy.types.Operator): @classmethod def poll(cls, context): - props = context.scene.BCFProperties - return props.topics[props.active_topic_index].reference_link + return context.scene.BCFProperties.reference_link def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - topic.reference_links.append(blender_topic.reference_link) + topic.reference_links.append(props.reference_link) bcfxml.edit_topic(topic) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - blender_topic.reference_link = "" + props.reference_link = "" return {"FINISHED"} @@ -506,8 +492,7 @@ class AddBcfDocumentReference(bpy.types.Operator): @classmethod def poll(cls, context): - props = context.scene.BCFProperties - return props.topics[props.active_topic_index].document_reference + return context.scene.BCFProperties.document_reference def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -515,12 +500,12 @@ class AddBcfDocumentReference(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] document_reference = bcf.v2.data.DocumentReference() - document_reference.referenced_document = blender_topic.document_reference - document_reference.description = blender_topic.document_reference_description or None + document_reference.referenced_document = props.document_reference + document_reference.description = props.document_reference_description or None bcfxml.add_document_reference(topic, document_reference) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - blender_topic.document_reference = "" - blender_topic.document_reference_description = "" + props.document_reference = "" + props.document_reference_description = "" return {"FINISHED"} @@ -531,8 +516,7 @@ class AddBcfLabel(bpy.types.Operator): @classmethod def poll(cls, context): - props = context.scene.BCFProperties - return props.topics[props.active_topic_index].label + return context.scene.BCFProperties.label def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -540,10 +524,10 @@ class AddBcfLabel(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] new = blender_topic.labels.add() - new.name = blender_topic.label - topic.labels.append(blender_topic.label) + new.name = props.label + topic.labels.append(props.label) bcfxml.edit_topic(topic) - blender_topic.label = "" + props.label = "" return {"FINISHED"} @@ -713,10 +697,9 @@ class AddBcfComment(bpy.types.Operator): @classmethod def poll(cls, context): props = context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - if not topic.comment: + if not props.comment: return False - if topic.has_related_viewpoint and not getBcfViewpoints(None, context): + if props.has_related_viewpoint and not bcf_prop.getBcfViewpoints(None, context): return False return True @@ -726,12 +709,14 @@ class AddBcfComment(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] comment = bcf.v2.data.Comment() - comment.comment = blender_topic.comment - if blender_topic.has_related_viewpoint: + comment.comment = props.comment + if props.has_related_viewpoint: comment.viewpoint = bcf.v2.data.Viewpoint() comment.viewpoint.guid = blender_topic.viewpoints bcfxml.add_comment(topic, comment) bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + props.comment = "" + props.has_related_viewpoint = False return {"FINISHED"} @@ -1022,9 +1007,7 @@ class SelectBcfHeaderFile(bpy.types.Operator): def execute(self, context): if self.filepath: - props = context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - topic.file_reference = self.filepath + context.scene.BCFProperties.file_reference = self.filepath return {"FINISHED"} def invoke(self, context, event): @@ -1040,9 +1023,7 @@ class SelectBcfBimSnippetReference(bpy.types.Operator): def execute(self, context): if self.filepath: - props = context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - topic.bim_snippet_reference = self.filepath + context.scene.BCFProperties.bim_snippet_reference = self.filepath return {"FINISHED"} def invoke(self, context, event): @@ -1058,9 +1039,7 @@ class SelectBcfDocumentReference(bpy.types.Operator): def execute(self, context): if self.filepath: - props = context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - topic.document_reference = self.filepath + context.scene.BCFProperties.document_reference = self.filepath return {"FINISHED"} def invoke(self, context, event): diff --git a/src/blenderbim/blenderbim/bim/module/bcf/prop.py b/src/blenderbim/blenderbim/bim/module/bcf/prop.py index 9f522227df..d21f315234 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/prop.py @@ -34,11 +34,14 @@ from bpy.props import ( bcfviewpoints_enum = None +related_topic_enum = [] def purge(): global bcfviewpoints_enum + global related_topic_enum bcfviewpoints_enum = None + related_topic_enum.clear() def updateBcfReferenceLink(self, context): @@ -76,6 +79,20 @@ def updateBcfCommentIsEditable(self, context): bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) +def get_related_topics(self, context): + global related_topic + related_topic_enum.clear() + props = context.scene.BCFProperties + current_topic = props.topics[props.active_topic_index] + for topic in props.topics: + if topic == current_topic: + continue + if topic.name in current_topic.related_topics: + continue + related_topic_enum.append((topic.name, topic.title, topic.description)) + return related_topic_enum + + def refreshBcfTopic(self, context): global bcfviewpoints_enum bcfviewpoints_enum = None @@ -84,6 +101,7 @@ def refreshBcfTopic(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() topic = props.topics[props.active_topic_index] header = bcfxml.get_header(topic.name) + props.clear_input_fields() getBcfViewpoints(self, context) @@ -148,26 +166,13 @@ class BcfTopic(PropertyGroup): description: StringProperty(default="", name="Description") viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") files: CollectionProperty(name="Files", type=StrProperty) - file_reference: StringProperty(default="", name="Reference") - file_ifc_project: StringProperty(default="", name="IFC Project") - file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) - reference_link: StringProperty(default="", name="Reference Link") labels: CollectionProperty(name="Labels", type=BcfLabel) - label: StringProperty(default="", name="Label") bim_snippet: PointerProperty(type=BcfBimSnippet) - bim_snippet_type: StringProperty(default="", name="Type") - bim_snippet_reference: StringProperty(default="", name="Reference") - bim_snippet_schema: StringProperty(default="", name="Schema") document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) - document_reference: StringProperty(default="", name="Referenced Document") - document_reference_description: StringProperty(default="", name="Description") related_topics: CollectionProperty(name="Related Topics", type=StrProperty) - related_topic: StringProperty(default="", name="Related Topic") comments: CollectionProperty(name="Comments", type=BcfComment) is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable) - comment: StringProperty(default="", name="Comment") - has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) class BCFProperties(PropertyGroup): @@ -177,3 +182,30 @@ class BCFProperties(PropertyGroup): author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor) topics: CollectionProperty(name="BCF Topics", type=BcfTopic) active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic) + file_reference: StringProperty(default="", name="Reference") + file_ifc_project: StringProperty(default="", name="IFC Project") + file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") + reference_link: StringProperty(default="", name="Reference Link") + label: StringProperty(default="", name="Label") + bim_snippet_reference: StringProperty(default="", name="Reference") + bim_snippet_type: StringProperty(default="", name="Type") + bim_snippet_schema: StringProperty(default="", name="Schema") + document_reference: StringProperty(default="", name="Referenced Document") + document_reference_description: StringProperty(default="", name="Description") + related_topic: EnumProperty(name="Related Topic", items=get_related_topics) + comment: StringProperty(default="", name="Comment") + has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) + + def clear_input_fields(self): + self.file_reference = "" + self.file_ifc_project = "" + self.file_ifc_spatial_structure_element = "" + self.reference_link = "" + self.label = "" + self.bim_snippet_reference = "" + self.bim_snippet_type = "" + self.bim_snippet_schema = "" + self.document_reference = "" + self.document_reference_description = "" + self.comment = "" + self.has_related_viewpoint = False diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index 35f11d9773..4aeb717a7c 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -151,12 +151,12 @@ class BIM_PT_bcf_metadata(Panel): row.label(text=f.ifc_spatial_structure_element) row = layout.row(align=True) - row.prop(topic, "file_reference") + row.prop(props, "file_reference") row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="") row = layout.row() - row.prop(topic, "file_ifc_project") + row.prop(props, "file_ifc_project") row = layout.row() - row.prop(topic, "file_ifc_spatial_structure_element") + row.prop(props, "file_ifc_spatial_structure_element") row = layout.row() row.operator("bim.add_bcf_header_file") @@ -168,7 +168,7 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.open_uri", icon="URL", text="").uri = link.name row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index row = layout.row() - row.prop(topic, "reference_link") + row.prop(props, "reference_link") row = layout.row() row.operator("bim.add_bcf_reference_link") @@ -178,7 +178,7 @@ class BIM_PT_bcf_metadata(Panel): row.prop(label, "name", text="") row.operator("bim.remove_bcf_label", icon="X", text="").index = index row = layout.row() - row.prop(topic, "label") + row.prop(props, "label") row = layout.row() row.operator("bim.add_bcf_label") @@ -199,12 +199,12 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.remove_bcf_bim_snippet", icon="X", text="") else: row = layout.row(align=True) - row.prop(topic, "bim_snippet_reference") + row.prop(props, "bim_snippet_reference") row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="") row = layout.row() - row.prop(topic, "bim_snippet_type") + row.prop(props, "bim_snippet_type") row = layout.row() - row.prop(topic, "bim_snippet_schema") + row.prop(props, "bim_snippet_schema") row = layout.row() row.operator("bim.add_bcf_bim_snippet") @@ -222,10 +222,10 @@ class BIM_PT_bcf_metadata(Panel): row = box.row(align=True) row.prop(doc, "description") row = layout.row(align=True) - row.prop(topic, "document_reference") + row.prop(props, "document_reference") row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="") row = layout.row() - row.prop(topic, "document_reference_description") + row.prop(props, "document_reference_description") row = layout.row() row.operator("bim.add_bcf_document_reference") @@ -235,7 +235,7 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index row = layout.row() - row.prop(topic, "related_topic") + row.prop(props, "related_topic") row = layout.row() row.operator("bim.add_bcf_related_topic") @@ -295,8 +295,8 @@ class BIM_PT_bcf_comments(Panel): col.label(text=" ".join(line_words)) row = layout.row() - row.prop(topic, "comment") + row.prop(props, "comment") row = layout.row() - row.prop(topic, "has_related_viewpoint") + row.prop(props, "has_related_viewpoint") row = layout.row() row.operator("bim.add_bcf_comment") From 92f55504e99cc41e19124103fef4617405dac40c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 19 Aug 2021 09:23:25 +1000 Subject: [PATCH 33/72] New site project library for stuff like site sheds and cranes --- src/blenderbim/generate_site_library.py | 107 ++++++++++++++++++++++++ src/blenderbim/site-library.blend | Bin 0 -> 1014680 bytes 2 files changed, 107 insertions(+) create mode 100644 src/blenderbim/generate_site_library.py create mode 100644 src/blenderbim/site-library.blend diff --git a/src/blenderbim/generate_site_library.py b/src/blenderbim/generate_site_library.py new file mode 100644 index 0000000000..21c9f8d829 --- /dev/null +++ b/src/blenderbim/generate_site_library.py @@ -0,0 +1,107 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import ifcopenshell.api + + +class LibraryGenerator: + def generate(self): + ifcopenshell.api.pre_listeners = {} + ifcopenshell.api.post_listeners = {} + + self.file = ifcopenshell.api.run("project.create_file") + self.project = ifcopenshell.api.run( + "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library" + ) + ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"}) + ifcopenshell.api.run("context.add_context", self.file, context="Model") + self.representations = { + "body": ifcopenshell.api.run( + "context.add_context", self.file, context="Model", subcontext="Body", target_view="MODEL_VIEW" + ), + "clearance": ifcopenshell.api.run( + "context.add_context", self.file, context="Model", subcontext="Clearance", target_view="MODEL_VIEW" + ), + } + ifcopenshell.api.run("context.add_context", self.file, context="Plan") + self.annotation = ifcopenshell.api.run( + "context.add_context", self.file, context="Model", subcontext="Annotation", target_view="PLAN_VIEW" + ) + + self.create_type("IfcBuildingElementProxyType", "Site Shed 3x6m", {"body": "Site Shed 3x6m"}) + self.create_type("IfcBuildingElementProxyType", "Site Shed 3x12m", {"body": "Site Shed 3x12m"}) + self.create_type( + "IfcBuildingElementProxyType", + "Mobile Crane 50T", + {"body": "Mobile Crane 50T", "clearance": "Mobile Crane 50T - Clearance"}, + ) + + self.file.write("blenderbim-site-library.ifc") + + def create_type(self, ifc_class, name, representations): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name) + for rep_name, obj_name in representations.items(): + obj = bpy.data.objects.get(obj_name) + representation = ifcopenshell.api.run( + "geometry.add_representation", + self.file, + context=self.representations[rep_name], + blender_object=obj, + geometry=obj.data, + total_items=max(1, len(obj.material_slots)), + ) + ifcopenshell.api.run( + "style.assign_representation_styles", + self.file, + **{ + "shape_representation": representation, + "styles": [ + ifcopenshell.api.run("style.add_style", self.file, **self.get_style_settings(s.material)) + for s in obj.material_slots + ], + }, + ) + ifcopenshell.api.run( + "geometry.assign_representation", self.file, product=element, representation=representation + ) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + + def get_style_settings(self, material): + transparency = material.diffuse_color[3] + diffuse_colour = material.diffuse_color + if ( + material.use_nodes + and hasattr(material.node_tree, "nodes") + and "Principled BSDF" in material.node_tree.nodes + ): + bsdf = material.node_tree.nodes["Principled BSDF"] + transparency = bsdf.inputs["Alpha"].default_value + diffuse_colour = bsdf.inputs["Base Color"].default_value + transparency = 1 - transparency + return { + "name": material.name, + "external_definition": None, + "surface_colour": tuple(material.diffuse_color), + "transparency": transparency, + "diffuse_colour": tuple(diffuse_colour), + } + + +LibraryGenerator().generate() diff --git a/src/blenderbim/site-library.blend b/src/blenderbim/site-library.blend new file mode 100644 index 0000000000000000000000000000000000000000..e6c007413d3abc433b91817bc23435548378fcc6 GIT binary patch literal 1014680 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

<2jbqS@nN-Ty$|U{ZkNbY%3f$sf7i= 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⪻Re4$`DXN6=g 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 OsEykCnj}O&`n?A&n$LUy literal 0 HcmV?d00001 From 6eaa13b5f1e1cc723181391782da9394ff42931a Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Thu, 19 Aug 2021 10:50:27 +0200 Subject: [PATCH 34/72] BlenderBim : Improve BCF project UX (#1669) * Improve related topic selection UX * Foolproof active topic access with property * Use self when available * Add utility method to explicitely refresh topic * Simplify Refresh topic code * BCF Topics can now be deleted using Blender UI * Revert dynamic enum for related topics * Minor fix and cleanup * Fix bug when trying to display deleted related topic --- .../blenderbim/bim/module/bcf/__init__.py | 1 + .../blenderbim/bim/module/bcf/operator.py | 104 ++++++++++++------ .../blenderbim/bim/module/bcf/prop.py | 56 ++++------ .../blenderbim/bim/module/bcf/ui.py | 23 ++-- 4 files changed, 109 insertions(+), 75 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py index 985caae11f..c64a01ba66 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py @@ -39,6 +39,7 @@ classes = ( operator.AddBcfLabel, operator.AddBcfRelatedTopic, operator.ViewBcfTopic, + operator.RemoveBcfTopic, operator.RemoveBcfComment, operator.RemoveBcfBimSnippet, operator.RemoveBcfReferenceLink, diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index 12ed1685b7..08276a0a33 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -214,7 +214,7 @@ class EditBcfTopicName(bpy.types.Operator): def execute(self, context): props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic bcfxml = bcfstore.BcfStore.get_bcfxml() topic = bcfxml.topics[blender_topic.name] topic.title = blender_topic.title @@ -229,7 +229,7 @@ class EditBcfTopic(bpy.types.Operator): def execute(self, context): props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic bcfxml = bcfstore.BcfStore.get_bcfxml() topic = bcfxml.topics[blender_topic.name] @@ -243,7 +243,7 @@ class EditBcfTopic(bpy.types.Operator): topic.topic_type = blender_topic.type or None bcfxml.edit_topic(topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + props.refresh_topic(context) return {"FINISHED"} @@ -296,7 +296,7 @@ class AddBcfBimSnippet(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bim_snippet = bcf.v2.data.BimSnippet() bim_snippet.reference = props.bim_snippet_reference @@ -316,19 +316,39 @@ class AddBcfRelatedTopic(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} @classmethod - def poll(cls, context): - return bcf_prop.get_related_topics(None, context) + def poll(cls, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = context.scene.BCFProperties + blender_topic = props.active_topic + if not props.related_topic: + return False + if props.related_topic == blender_topic.title: + # Prevent adding self as related topic + return False + related_topic = None + for topic in bcfxml.topics.values(): + if topic.title == props.related_topic: + related_topic = bcf.v2.data.RelatedTopic() + related_topic.guid = topic.guid + break + if not related_topic: + return False + if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]: + # Prevent adding the same related topic more than once + return False + return True def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic related_topic = bcf.v2.data.RelatedTopic() - related_topic.guid = props.related_topic + related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid topic = bcfxml.topics[blender_topic.name] topic.related_topics.append(related_topic) bcfxml.edit_topic(topic) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + props.related_topic = "" return {"FINISHED"} @@ -344,7 +364,7 @@ class AddBcfHeaderFile(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] header_file = bcf.v2.data.HeaderFile() header_file.reference = props.file_reference @@ -355,10 +375,7 @@ class AddBcfHeaderFile(bpy.types.Operator): if len(props.file_ifc_spatial_structure_element) == 22: header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element bcfxml.add_file(topic, header_file) - props.active_topic_index = props.active_topic_index # refreshes the BCF Topic - props.file_reference = "" - props.file_ifc_project = "" - props.file_ifc_spatial_structure_element = "" + props.refresh_topic(context) return {"FINISHED"} @@ -389,7 +406,7 @@ class AddBcfViewpoint(bpy.types.Operator): bcfxml = bcfstore.BcfStore.get_bcfxml() blender_camera = context.scene.camera props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] viewpoint = bcf.v2.data.Viewpoint() @@ -424,7 +441,7 @@ class AddBcfViewpoint(bpy.types.Operator): bcfxml.add_viewpoint(topic, viewpoint) blender_render.filepath = old_filepath blender_render.image_settings.file_format = old_file_format - props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + props.refresh_topic(context) return {"FINISHED"} @@ -440,11 +457,11 @@ class RemoveBcfViewpoint(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic viewpoint_guid = blender_topic.viewpoints topic = bcfxml.topics[blender_topic.name] bcfxml.delete_viewpoint(viewpoint_guid, topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + props.refresh_topic(context) return {"FINISHED"} @@ -457,10 +474,29 @@ class RemoveBcfFile(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_file(topic, self.index) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + props.refresh_topic(context) + return {"FINISHED"} + + +class RemoveBcfTopic(bpy.types.Operator): + bl_idname = "bim.remove_bcf_topic" + bl_label = "Remove BCF Topic" + bl_options = {"REGISTER", "UNDO"} + guid: bpy.props.StringProperty() + + @classmethod + def poll(cls, context): + return context.scene.BCFProperties.topics + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = context.scene.BCFProperties + topic_to_delete = props.active_topic + bcfxml.delete_topic(topic_to_delete.name) + bpy.ops.bim.load_bcf_topics() return {"FINISHED"} @@ -476,7 +512,7 @@ class AddBcfReferenceLink(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] topic.reference_links.append(props.reference_link) bcfxml.edit_topic(topic) @@ -497,7 +533,7 @@ class AddBcfDocumentReference(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] document_reference = bcf.v2.data.DocumentReference() document_reference.referenced_document = props.document_reference @@ -521,7 +557,7 @@ class AddBcfLabel(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] new = blender_topic.labels.add() new.name = props.label @@ -539,7 +575,7 @@ class EditBcfReferenceLinks(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] for index, reference_link in enumerate(topic.reference_links): if reference_link == blender_topic.reference_links[index].name: @@ -557,7 +593,7 @@ class EditBcfLabels(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] for index, label in enumerate(blender_topic.labels): if index >= len(topic.labels): @@ -578,7 +614,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] del topic.reference_links[self.index] bcfxml.edit_topic(topic) @@ -595,7 +631,7 @@ class RemoveBcfLabel(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] del topic.labels[self.index] bcfxml.edit_topic(topic) @@ -611,7 +647,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_bim_snippet(topic) blender_topic.bim_snippet.schema = "" @@ -629,7 +665,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_document_reference(topic, self.index) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) @@ -645,7 +681,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] del topic.related_topics[self.index] bcfxml.edit_topic(topic) @@ -662,7 +698,7 @@ class RemoveBcfComment(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_comment(self.comment_guid, topic) bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) @@ -678,7 +714,7 @@ class EditBcfComment(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic blender_comment = blender_topic.comments.get(self.comment_guid) topic = bcfxml.topics[blender_topic.name] comment = topic.comments[self.comment_guid] @@ -706,7 +742,7 @@ class AddBcfComment(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] comment = bcf.v2.data.Comment() comment.comment = props.comment @@ -729,7 +765,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): def poll(cls, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] return topic.viewpoints @@ -737,7 +773,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): self.file = IfcStore.get_file() bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] viewpoint_guid = blender_topic.viewpoints diff --git a/src/blenderbim/blenderbim/bim/module/bcf/prop.py b/src/blenderbim/blenderbim/bim/module/bcf/prop.py index d21f315234..8f20938e64 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/prop.py @@ -34,14 +34,11 @@ from bpy.props import ( bcfviewpoints_enum = None -related_topic_enum = [] def purge(): global bcfviewpoints_enum - global related_topic_enum bcfviewpoints_enum = None - related_topic_enum.clear() def updateBcfReferenceLink(self, context): @@ -55,12 +52,12 @@ def updateBcfLabel(self, context): def updateBcfProjectName(self, context): - if context.scene.BCFProperties.is_loaded: + if self.is_loaded: bpy.ops.bim.edit_bcf_project_name() def updateBcfAuthor(self, context): - if context.scene.BCFProperties.is_loaded: + if self.is_loaded: bpy.ops.bim.edit_bcf_author() @@ -79,30 +76,9 @@ def updateBcfCommentIsEditable(self, context): bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) -def get_related_topics(self, context): - global related_topic - related_topic_enum.clear() - props = context.scene.BCFProperties - current_topic = props.topics[props.active_topic_index] - for topic in props.topics: - if topic == current_topic: - continue - if topic.name in current_topic.related_topics: - continue - related_topic_enum.append((topic.name, topic.title, topic.description)) - return related_topic_enum - - def refreshBcfTopic(self, context): - global bcfviewpoints_enum - bcfviewpoints_enum = None - - props = context.scene.BCFProperties - bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = props.topics[props.active_topic_index] - header = bcfxml.get_header(topic.name) - props.clear_input_fields() - getBcfViewpoints(self, context) + self.clear_input_fields() + getBcfViewpoints(None, context, force_update=True) class BcfReferenceLink(PropertyGroup): @@ -113,13 +89,13 @@ class BcfLabel(PropertyGroup): name: StringProperty(name="Name", update=updateBcfLabel) -def getBcfViewpoints(self, context): +def getBcfViewpoints(self, context, force_update=False): global bcfviewpoints_enum - if bcfviewpoints_enum is None: + if bcfviewpoints_enum is None or force_update: # Retrieving Viewpoints is slow. Make sure we only do when needed bcfviewpoints_enum = [] props = context.scene.BCFProperties bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = props.topics[props.active_topic_index] + topic = props.active_topic viewpoints = bcfxml.get_viewpoints(topic.name) bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())]) return bcfviewpoints_enum @@ -164,7 +140,7 @@ class BcfTopic(PropertyGroup): assigned_to: StringProperty(default="", name="Assigned To") due_date: StringProperty(default="", name="Due Date") description: StringProperty(default="", name="Description") - viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") + viewpoints: EnumProperty(items=lambda _, context: getBcfViewpoints(_, context), name="BCF Viewpoints") files: CollectionProperty(name="Files", type=StrProperty) reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) labels: CollectionProperty(name="Labels", type=BcfLabel) @@ -192,7 +168,7 @@ class BCFProperties(PropertyGroup): bim_snippet_schema: StringProperty(default="", name="Schema") document_reference: StringProperty(default="", name="Referenced Document") document_reference_description: StringProperty(default="", name="Description") - related_topic: EnumProperty(name="Related Topic", items=get_related_topics) + related_topic: StringProperty(name="Related Topic") comment: StringProperty(default="", name="Comment") has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) @@ -207,5 +183,19 @@ class BCFProperties(PropertyGroup): self.bim_snippet_schema = "" self.document_reference = "" self.document_reference_description = "" + self.related_topic = "" self.comment = "" self.has_related_viewpoint = False + + @property + def active_topic(self): + if len(self.topics) == 0: + return None + if self.active_topic_index < 0: + self.active_topic_index = 0 + if self.active_topic_index >= len(self.topics): + self.active_topic_index = len(self.topics) - 1 + return self.topics[self.active_topic_index] + + def refresh_topic(self, context): + refreshBcfTopic(self, context) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index 4aeb717a7c..dfa8de1aa6 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -58,12 +58,13 @@ class BIM_PT_bcf(Panel): row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index") col = row.column(align=True) col.operator("bim.add_bcf_topic", icon="ADD", text="") + col.operator("bim.remove_bcf_topic", icon="REMOVE", text="") if props.active_topic_index < len(props.topics): - topic = props.topics[props.active_topic_index] + topic = props.active_topic col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True) if props.active_topic_index < len(props.topics): - topic = props.topics[props.active_topic_index] + topic = props.active_topic row = layout.row() row.enabled = topic.is_editable row.prop(topic, "description", text="") @@ -118,7 +119,7 @@ class BIM_PT_bcf_metadata(Panel): layout.label(text="No BCF project is loaded") return - topic = props.topics[props.active_topic_index] + topic = props.active_topic bcfxml = bcfstore.BcfStore.get_bcfxml() bcf_topic = bcfxml.topics[topic.name] @@ -230,10 +231,16 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.add_bcf_document_reference") layout.label(text="Related Topics:") - for index, related_topic in enumerate(topic.related_topics): - row = layout.row(align=True) - row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name - row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index + for index, related_topic in enumerate(topic.related_topics): + try: + row = layout.row(align=True) + op = row.operator( + "bim.view_bcf_topic", + text=f"Select {bcfxml.topics[related_topic.name.lower()].title}") + op.topic_guid = related_topic.name + row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index + except KeyError: + pass row = layout.row() row.prop(props, "related_topic") row = layout.row() @@ -264,7 +271,7 @@ class BIM_PT_bcf_comments(Panel): row = layout.row() row.prop(props, "comment_text_width") - topic = props.topics[props.active_topic_index] + topic = props.active_topic for comment in topic.comments: box = self.layout.box() From 59f156bae343fe6090c559b4880a3a5f90013d04 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 14:04:01 +0200 Subject: [PATCH 35/72] IFC Cobie : Fix Elements not being assigned in selector --- src/ifcopenshell-python/ifcopenshell/util/selector.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index bfdc50c1d0..b5a4034a55 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -124,11 +124,11 @@ class Selector: def get_class_selector(self, class_selector): if class_selector.children[0] == "COBie": - ifcopenshell.util.fm.get_cobie_components(self.file) + elements = ifcopenshell.util.fm.get_cobie_components(self.file) elif class_selector.children[0] == "COBieType": - ifcopenshell.util.fm.get_cobie_types(self.file) + elements = ifcopenshell.util.fm.get_cobie_types(self.file) elif class_selector.children[0] == "FMHEM": - ifcopenshell.util.fm.get_fmhem_types(self.file) + elements = ifcopenshell.util.fm.get_fmhem_types(self.file) else: elements = self.file.by_type(class_selector.children[0]) if len(class_selector.children) > 1 and class_selector.children[1].data == "filter": From ef557e96a78f235b07b83008d41c7a2a738369ee Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 14:06:36 +0200 Subject: [PATCH 36/72] IfcCobie : Prevent executing operator if no ifc file --- .../blenderbim/bim/module/cobie/operator.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cobie/operator.py b/src/blenderbim/blenderbim/bim/module/cobie/operator.py index 6cfabed02e..58022975a8 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/operator.py @@ -62,14 +62,19 @@ class ExecuteIfcCobie(bpy.types.Operator): bl_label = "Execute IFCCOBie" file_format: bpy.props.StringProperty() + @classmethod + def poll(cls, context): + props = context.scene.COBieProperties + return props.should_load_from_memory or props.cobie_ifc_file + def execute(self, context): from cobie import IfcCobieParser props = context.scene.COBieProperties - - output_dir = os.path.dirname(props.cobie_ifc_file) - + if props.should_load_from_memory: output_dir = tempfile.gettempdir() + else: + output_dir = os.path.dirname(props.cobie_ifc_file) output = os.path.join(output_dir, "output") logger = logging.getLogger("IFCtoCOBie") From bf6c3e40d364142d6002cf3872a7b8a73df1ff14 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 21:06:45 +0200 Subject: [PATCH 37/72] Fix console error due to accessing empty dynamic enum --- src/blenderbim/blenderbim/bim/module/type/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 533f3e61ee..48637ddd6e 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import blenderbim.bim.module.type.prop as type_prop from bpy.types import Panel from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.type.data import Data @@ -67,7 +68,7 @@ class BIM_PT_type(Panel): row = self.layout.row(align=True) row.prop(props, "relating_type_class", text="") - if props.relating_type: + if type_prop.getRelatingTypes(None, context): row.prop(props, "relating_type", text="") row.operator("bim.assign_type", icon="CHECKMARK", text="") else: From 17e61afd59804d34daf91f8720444d9b5db0362a Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 21:26:54 +0200 Subject: [PATCH 38/72] Add tooltip to CopyToContainer Operator --- src/blenderbim/blenderbim/bim/module/spatial/operator.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index e10939a59f..a6eb84ddbd 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -162,6 +162,11 @@ class RemoveContainer(bpy.types.Operator): class CopyToContainer(bpy.types.Operator): + """ + Copies selected objects to selected containers + Check the mark next to a container in the container list to select it + Several containers can be selected at a time + """ bl_idname = "bim.copy_to_container" bl_label = "Copy To Container" bl_options = {"REGISTER", "UNDO"} @@ -172,7 +177,7 @@ class CopyToContainer(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() - objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects + objects = list(bpy.data.objects.get(self.obj, context.selected_objects)) sprops = context.scene.BIMSpatialProperties container_ids = [c.ifc_definition_id for c in sprops.spatial_elements if c.is_selected] for obj in objects: From a14c3b181d498598ff3222805b32ef57bc423717 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 21:27:37 +0200 Subject: [PATCH 39/72] Keep active object when removing from containers --- src/blenderbim/blenderbim/bim/module/spatial/operator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index a6eb84ddbd..7f8fe7b3aa 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -134,7 +134,7 @@ class RemoveContainer(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + obj = bpy.data.objects.get(self.obj, context.active_object) oprops = obj.BIMObjectProperties self.file = IfcStore.get_file() ifcopenshell.api.run( @@ -152,6 +152,7 @@ class RemoveContainer(bpy.types.Operator): for collection in obj.users_collection: collection.objects.unlink(obj) context.scene.collection.objects.link(obj) + context.view_layer.objects.active = obj return {"FINISHED"} def remove_collection(self, parent, child): From 95f2d939e8a82b18853baa410480c036d8e94a2f Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 21:43:31 +0200 Subject: [PATCH 40/72] Fix error when filling opening with None object --- src/blenderbim/blenderbim/bim/module/void/operator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 4185daa588..2188c6939b 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -118,8 +118,10 @@ class AddFilling(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - opening = bpy.data.objects.get(self.opening) if self.opening else context.scene.VoidProperties.desired_opening + obj = context.scene.objects.get(self.obj, context.active_object) + opening = context.scene.objects.get(self.opening, context.scene.VoidProperties.desired_opening) + if opening is None: + return {'FINISHED'} self.file = IfcStore.get_file() element_id = obj.BIMObjectProperties.ifc_definition_id ifcopenshell.api.run( From e0bfa8f55a3b72ac8816a865ce09a11bafd7aa6e Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 21:58:37 +0200 Subject: [PATCH 41/72] Fix console error due to accessing empty dynamic enum --- src/blenderbim/blenderbim/bim/module/classification/ui.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py index b2fc143c85..4cd3d4595c 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/ui.py +++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import blenderbim.bim.module.classification.prop as classification_prop from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.classification.data import Data @@ -104,7 +105,7 @@ class BIM_PT_classification_references(Panel): if self.oprops.ifc_definition_id not in Data.products: Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id) - self.draw_add_ui() + self.draw_add_ui(context) reference_ids = Data.products[self.oprops.ifc_definition_id] if not reference_ids: @@ -118,8 +119,8 @@ class BIM_PT_classification_references(Panel): else: self.draw_ui(reference_id, reference) - def draw_add_ui(self): - if not self.sprops.available_classifications: + def draw_add_ui(self, context): + if not classification_prop.getClassifications(self.sprops, context): return name = Data.library_classifications[int(self.sprops.available_classifications)] From d1a39d80042f3bcda4fe29e7167ebf6d2812a0b1 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 21:59:00 +0200 Subject: [PATCH 42/72] Fix possible error due to typo --- src/blenderbim/blenderbim/bim/module/model/pie.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py index 102a1bdb16..5c5480f07f 100644 --- a/src/blenderbim/blenderbim/bim/module/model/pie.py +++ b/src/blenderbim/blenderbim/bim/module/model/pie.py @@ -47,8 +47,8 @@ class PieAddOpening(bpy.types.Operator): elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id: opening_name = obj.children[0].name else: - opj_name = obj.name - bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name) + obj_name = obj.name + bpy.ops.bim.add_opening(obj=obj_name, opening=opening_name) return {"FINISHED"} From 29cda05025cd48915a6b3d207a3f2d219af28d8d Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 22:05:22 +0200 Subject: [PATCH 43/72] Fix error when adding opening with None object --- src/blenderbim/blenderbim/bim/module/void/operator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 2188c6939b..c41582adeb 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -35,8 +35,10 @@ class AddOpening(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - opening = bpy.data.objects.get(self.opening) + obj = context.scene.objects.get(self.obj, context.active_object) + opening = context.scene.objects.get(self.opening) + if opening is None: + return {"FINISHED"} opening.display_type = "WIRE" if not opening.BIMObjectProperties.ifc_definition_id: body_context_id = None From 9b9326721d0b9d4fda8835630dd5b63bcc566c80 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 22:06:56 +0200 Subject: [PATCH 44/72] Fix error when trying to select type if type is not set --- .../blenderbim/bim/module/type/operator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index 3d54041ebe..d1a889b459 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -136,13 +136,13 @@ class SelectType(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() - related_object = bpy.data.objects.get(self.related_object) if self.related_object else context.active_object + related_object = bpy.data.objects.get(self.related_object, context.active_object) oprops = related_object.BIMObjectProperties - obj = IfcStore.get_element( - ifcopenshell.util.element.get_type(self.file.by_id(oprops.ifc_definition_id)).GlobalId - ) - context.view_layer.objects.active = obj - obj.select_set(True) + element_type = ifcopenshell.util.element.get_type(self.file.by_id(oprops.ifc_definition_id)) + if element_type is not None: + obj = IfcStore.get_element(element_type.GlobalId) + context.view_layer.objects.active = obj + obj.select_set(True) return {"FINISHED"} From 0f38cd712edfa4c22ce492cbbfdbe0ab59f6d584 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 22:26:00 +0200 Subject: [PATCH 45/72] Fix errors due to accessing empty enum --- .../blenderbim/bim/module/root/prop.py | 3 ++- src/blenderbim/blenderbim/bim/module/root/ui.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/root/prop.py b/src/blenderbim/blenderbim/bim/module/root/prop.py index 3a003d368e..6a28a40b51 100644 --- a/src/blenderbim/blenderbim/bim/module/root/prop.py +++ b/src/blenderbim/blenderbim/bim/module/root/prop.py @@ -72,7 +72,8 @@ def refreshPredefinedTypes(self, context): global types_enum types_enum.clear() enum = getIfcPredefinedTypes(self, context) - context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0] + if enum: + context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0] def getIfcProducts(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index 60e816f496..aa2ea98ec2 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -18,6 +18,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import blenderbim.bim.module.root.prop as root_prop from bpy.types import Panel from ifcopenshell.api.root.data import Data from blenderbim.bim.ifc import IfcStore @@ -49,7 +50,9 @@ class BIM_PT_class(Panel): row = self.layout.row(align=True) row.operator("bim.reassign_class", icon="CHECKMARK") row.operator("bim.disable_reassign_class", icon="CANCEL", text="") - self.draw_class_dropdowns(context) + self.draw_class_dropdowns( + context, + root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context)) else: data = Data.products[props.ifc_definition_id] name = data["type"] @@ -72,23 +75,24 @@ class BIM_PT_class(Panel): else: row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name else: - self.draw_class_dropdowns(context) + ifc_predefined_types = root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context) + self.draw_class_dropdowns(context, ifc_predefined_types) row = self.layout.row(align=True) op = row.operator("bim.assign_class") op.ifc_class = context.scene.BIMRootProperties.ifc_class - op.predefined_type = context.scene.BIMRootProperties.ifc_predefined_type + op.predefined_type = context.scene.BIMRootProperties.ifc_predefined_type if ifc_predefined_types else "" op.userdefined_type = context.scene.BIMRootProperties.ifc_userdefined_type - def draw_class_dropdowns(self, context): + def draw_class_dropdowns(self, context, ifc_predefined_types): props = context.scene.BIMRootProperties row = self.layout.row() row.prop(props, "ifc_product") row = self.layout.row() row.prop(props, "ifc_class") - if props.ifc_predefined_type: + if ifc_predefined_types: row = self.layout.row() row.prop(props, "ifc_predefined_type") - if props.ifc_predefined_type == "USERDEFINED": + if ifc_predefined_types == "USERDEFINED": row = self.layout.row() row.prop(props, "ifc_userdefined_type") row = self.layout.row() From f9046487836a818d842a9569058af0984e326b07 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 22:29:43 +0200 Subject: [PATCH 46/72] Keep active object as active when creating ifc file --- src/blenderbim/blenderbim/bim/module/project/operator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index c96a795583..8fd33a8906 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -43,6 +43,7 @@ class CreateProject(bpy.types.Operator): return result def _execute(self, context): + active_object = context.view_layer.objects.active self.file = IfcStore.get_file() if self.file: return {"FINISHED"} @@ -79,6 +80,7 @@ class CreateProject(bpy.types.Operator): bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name) bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name) + context.view_layer.objects.active = active_object return {"FINISHED"} def get_name(self, ifc_class, name): From b5ff3cd8188ab937436bc522571a00d7ffc136c8 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 22:31:29 +0200 Subject: [PATCH 47/72] Fix error due to typo --- .../ifcopenshell/api/style/assign_material_style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py index b626740cde..a00b623ea5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py @@ -63,7 +63,7 @@ class Usecase: def get_styled_representation(self, definition_representation): representations = [ r - for r in definition_representation.Representations is r.is_a("IfcStyledRepresentation") + for r in definition_representation.Representations if r.is_a("IfcStyledRepresentation") and r.ContextOfItems == self.settings["context"] ] if representations: From 1c9cd058c8b75edbabd24f80d68ed9a2e3b81b35 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Thu, 19 Aug 2021 22:46:01 +0200 Subject: [PATCH 48/72] Fix errors if no ifc file --- src/blenderbim/blenderbim/bim/module/attribute/ui.py | 2 ++ src/blenderbim/blenderbim/bim/module/pset/ui.py | 3 ++- src/blenderbim/blenderbim/bim/module/style/ui.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py index 1fcb0c13cf..5bdb184576 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py @@ -110,6 +110,8 @@ class BIM_PT_material_attributes(Panel): @classmethod def poll(cls, context): + if not IfcStore.get_file(): + return False try: return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id) except: diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 5a0d78269c..d7fe3aeddb 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -207,7 +207,8 @@ class BIM_PT_material_psets(Panel): props = context.active_object.active_material.BIMObjectProperties if not props.ifc_definition_id: return False - if IfcStore.get_file().schema == "IFC2X3": + file = IfcStore.get_file() + if not file or file.schema == "IFC2X3": return False # We don't support material psets in IFC2X3 because they suck if props.ifc_definition_id not in Data.products: Data.load(IfcStore.get_file(), props.ifc_definition_id) diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 01273d90bf..a2df554fb5 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -59,6 +59,8 @@ class BIM_PT_style_attributes(Panel): @classmethod def poll(cls, context): + if not IfcStore.get_file(): + return False try: return bool(context.active_object.active_material.BIMMaterialProperties.ifc_style_id) except: From 11e36bf744c59803c74aa0ad0475a4f3f2e59246 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 20 Aug 2021 08:01:37 +1000 Subject: [PATCH 49/72] Minor fix --- .../blenderbim/bim/module/drawing/operator.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 8b00164eca..3f76bc278a 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -31,6 +31,7 @@ import ifcopenshell import ifcopenshell.geom import ifcopenshell.util.selector import ifcopenshell.util.representation +import blenderbim.bim.schema import blenderbim.bim.module.drawing.svgwriter as svgwriter import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.sheeter as sheeter @@ -92,14 +93,24 @@ class AddDrawing(bpy.types.Operator): group = self.file.by_id(sorted(GroupData.groups.keys())[-1]) ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": new.name}}) bpy.ops.bim.assign_group(product=camera.name, group=group.id()) - bpy.ops.bim.add_pset(obj=camera.name, obj_type="Object", pset_name="EPset_Drawing") - pset_id = sorted(PsetData.products[camera.BIMObjectProperties.ifc_definition_id]["psets"])[-1] - bpy.ops.bim.edit_pset( - obj=camera.name, - obj_type="Object", - pset_id=pset_id, - properties=json.dumps({"TargetView": "PLAN_VIEW", "Scale": "1/100"}), + pset = ifcopenshell.api.run( + "pset.add_pset", + self.file, + **{ + "product": self.file.by_id(camera.BIMObjectProperties.ifc_definition_id), + "name": "EPset_Drawing", + }, ) + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + **{ + "pset": pset, + "properties": {"TargetView": "PLAN_VIEW", "Scale": "1/100"}, + "pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name("EPset_Drawing"), + }, + ) + PsetData.load(IfcStore.get_file(), camera.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} @@ -1106,7 +1117,7 @@ class RefreshDrawingList(bpy.types.Operator): bl_label = "Refresh Drawing List" def execute(self, context): - doc_props = context.scene.DocProperties.drawings + doc_props = context.scene.DocProperties doc_props.drawings.clear() for obj in context.scene.objects: if not isinstance(obj.data, bpy.types.Camera): From a37338a6d352a807fc6b9982ddd9461918432a64 Mon Sep 17 00:00:00 2001 From: Cyril Waechter Date: Fri, 20 Aug 2021 00:08:49 +0200 Subject: [PATCH 50/72] Complete IfcSIUnitName exponents (#1670) --- .../ifcopenshell/util/unit.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 10992888b7..045facdae5 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -119,33 +119,34 @@ si_type_names = { "VOLUMEUNIT": "CUBIC_METRE", } -# Are you good at physics? Want to help fill these in? :) +# See IfcDimensionalExponents: +# (Length, Mass, Time, ElectricCurrent, ThermodynamicTemperature, AmountOfSubstance, LuminousIntensity) named_dimensions = { - # "ABSORBEDDOSEUNIT": (0, 0, 0, 0, 0, 0, 0), + "ABSORBEDDOSEUNIT": (2, 0, -2, 0, 0, 0, 0), "AMOUNTOFSUBSTANCEUNIT": (0, 0, 0, 0, 0, 1, 0), "AREAUNIT": (2, 0, 0, 0, 0, 0, 0), - # "DOSEEQUIVALENTUNIT": (0, 0, 0, 0, 0, 0, 0), - # "ELECTRICCAPACITANCEUNIT": (0, 0, 0, 0, 0, 0, 0), - # "ELECTRICCHARGEUNIT": (0, 0, 0, 0, 0, 0, 0), - # "ELECTRICCONDUCTANCEUNIT": (0, 0, 0, 0, 0, 0, 0), + "DOSEEQUIVALENTUNIT": (2, 0, -2, 0, 0, 0, 0), + "ELECTRICCAPACITANCEUNIT": (-2, -1, 4, 2, 0, 0, 0), + "ELECTRICCHARGEUNIT": (0, 0, 1, 1, 0, 0, 0), + "ELECTRICCONDUCTANCEUNIT": (-2, -1, 3, 2, 0, 0, 0), "ELECTRICCURRENTUNIT": (0, 0, 0, 1, 0, 0, 0), - # "ELECTRICRESISTANCEUNIT": (0, 0, 0, 0, 0, 0, 0), - # "ELECTRICVOLTAGEUNIT": (0, 0, 0, 0, 0, 0, 0), + "ELECTRICRESISTANCEUNIT": (2, 1, -3, -2, 0, 0, 0), + "ELECTRICVOLTAGEUNIT": (2, 1, -3, -1, 0, 0, 0), "ENERGYUNIT": (2, 1, -2, 0, 0, 0, 0), "FORCEUNIT": (1, 1, -2, 0, 0, 0, 0), "FREQUENCYUNIT": (0, 0, -1, 0, 0, 0, 0), - # "ILLUMINANCEUNIT": (0, 0, 0, 0, 0, 0, 0), - # "INDUCTANCEUNIT": (0, 0, 0, 0, 0, 0, 0), + "ILLUMINANCEUNIT": (-2, 0, 0, 0, 0, 1, 1), + "INDUCTANCEUNIT": (2, 1, -2, -2, 0, 0, 0), "LENGTHUNIT": (1, 0, 0, 0, 0, 0, 0), - # "LUMINOUSFLUXUNIT": (0, 0, 0, 0, 0, 0, 0), + "LUMINOUSFLUXUNIT": (0, 0, 0, 0, 0, 1, 1), "LUMINOUSINTENSITYUNIT": (0, 0, 0, 0, 0, 0, 1), - # "MAGNETICFLUXDENSITYUNIT": (0, 0, 0, 0, 0, 0, 0), - # "MAGNETICFLUXUNIT": (0, 0, 0, 0, 0, 0, 0), + "MAGNETICFLUXDENSITYUNIT": (0, 1, -2, -1, 0, 0, 0), + "MAGNETICFLUXUNIT": (2, 1, -2, -1, 0, 0, 0), "MASSUNIT": (0, 1, 0, 0, 0, 0, 0), "PLANEANGLEUNIT": (0, 0, 0, 0, 0, 0, 0), "POWERUNIT": (2, 1, -3, 0, 0, 0, 0), "PRESSUREUNIT": (-1, 1, -2, 0, 0, 0, 0), - # "RADIOACTIVITYUNIT": (0, 0, 0, 0, 0, 0, 0), + "RADIOACTIVITYUNIT": (0, 0, -1, 0, 0, 0, 0), "SOLIDANGLEUNIT": (0, 0, 0, 0, 0, 0, 0), "THERMODYNAMICTEMPERATUREUNIT": (0, 0, 0, 0, 1, 0, 0), "TIMEUNIT": (0, 0, 1, 0, 0, 0, 0), From a0def600e83af00ee6b763ac251189bfb0d6c4f1 Mon Sep 17 00:00:00 2001 From: Prabhat Singh <59395410+TestPrab@users.noreply.github.com> Date: Fri, 20 Aug 2021 03:40:20 +0530 Subject: [PATCH 51/72] Added Schema Check in bcf routes (#1671) --- src/bcf/bcf/v3/bcfapi.py | 109 ++++++---- src/foundationserver/bcfserver/bcf/routes.py | 194 ++++++++---------- .../Collaboration/Action/comment_actions.json | 10 + .../Collaboration/Action/project_actions.json | 11 + .../Collaboration/Action/topic_actions.json | 16 ++ .../Action/viewpoint_actions.json | 7 + .../Collaboration/Comment/comment_GET.json | 47 +++++ .../Collaboration/Comment/comment_POST.json | 18 ++ .../Collaboration/Comment/comment_PUT.json | 14 ++ .../Collaboration/Document/document_GET.json | 14 ++ .../document_reference_GET.json | 21 ++ .../document_reference_POST.json | 18 ++ .../document_reference_PUT.json | 15 ++ .../Events/comment_event_GET.json | 29 +++ .../Collaboration/Events/event_action.json | 14 ++ .../Collaboration/Events/topic_event_GET.json | 25 +++ .../schemas/Collaboration/File/file_GET.json | 28 +++ .../schemas/Collaboration/File/file_PUT.json | 26 +++ .../project_file_display_information.json | 16 ++ .../File/project_file_information.json | 13 ++ .../File/project_files_information_GET.json | 8 + .../RelatedTopic/related_topic_GET.json | 12 ++ .../RelatedTopic/related_topic_PUT.json | 12 ++ .../Collaboration/Topic/bim_snippet.json | 23 +++ .../Collaboration/Topic/topic_GET.json | 101 +++++++++ .../Collaboration/Topic/topic_POST.json | 68 ++++++ .../Collaboration/Topic/topic_PUT.json | 64 ++++++ .../Collaboration/Viewpoint/bitmap_GET.json | 24 +++ .../Collaboration/Viewpoint/bitmap_POST.json | 25 +++ .../Viewpoint/clipping_plane.json | 13 ++ .../Collaboration/Viewpoint/coloring.json | 12 ++ .../Collaboration/Viewpoint/coloring_GET.json | 14 ++ .../Collaboration/Viewpoint/component.json | 15 ++ .../Viewpoint/component_list.json | 7 + .../Collaboration/Viewpoint/components.json | 18 ++ .../Collaboration/Viewpoint/direction.json | 15 ++ .../schemas/Collaboration/Viewpoint/line.json | 12 ++ .../Collaboration/Viewpoint/location.json | 16 ++ .../Viewpoint/orthogonal_camera.json | 22 ++ .../Viewpoint/perspective_camera.json | 22 ++ .../Collaboration/Viewpoint/point.json | 15 ++ .../Viewpoint/selection_GET.json | 11 + .../Collaboration/Viewpoint/snapshot_GET.json | 10 + .../Viewpoint/snapshot_POST.json | 14 ++ .../Viewpoint/view_setup_hints.json | 18 ++ .../Viewpoint/viewpoint_GET.json | 52 +++++ .../Viewpoint/viewpoint_POST.json | 46 +++++ .../Collaboration/Viewpoint/visibility.json | 16 ++ .../Viewpoint/visibility_GET.json | 12 ++ .../bcf/schemas/Project/extensions_GET.json | 78 +++++++ .../bcf/schemas/Project/project_GET.json | 25 +++ .../bcf/schemas/Project/project_PUT.json | 12 ++ .../bcfserver/bcf/schemas/User/user_GET.json | 14 ++ .../bcfserver/bcf/schemas/error.json | 12 ++ .../bcfserver/bcf/success.txt | 1 + 55 files changed, 1340 insertions(+), 144 deletions(-) create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/comment_actions.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/project_actions.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/topic_actions.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_POST.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_PUT.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Document/document_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/comment_event_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/event_action.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/topic_event_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_PUT.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_display_information.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_information.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_files_information_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/bim_snippet.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_POST.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_PUT.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component_list.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/components.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/direction.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/line.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/location.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/point.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Project/extensions_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Project/project_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/Project/project_PUT.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/User/user_GET.json create mode 100644 src/foundationserver/bcfserver/bcf/schemas/error.json create mode 100644 src/foundationserver/bcfserver/bcf/success.txt diff --git a/src/bcf/bcf/v3/bcfapi.py b/src/bcf/bcf/v3/bcfapi.py index 218caab3db..ad96af138c 100644 --- a/src/bcf/bcf/v3/bcfapi.py +++ b/src/bcf/bcf/v3/bcfapi.py @@ -7,6 +7,8 @@ import webbrowser import http.server import base64 +from werkzeug.datastructures import HeaderSet + client_id, client_secret = "", "" @@ -142,51 +144,66 @@ class BcfClient: # TODO: handle error http status codes and raise exception. Follow error.json standard. headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()} response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None) - if response.status_code == 200: - return response.json() - else: - raise Exception(f"{response.status_code}, {response.text}") + try: + response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None) + if response.status_code == 200: + return response.json() + response.raise_for_status() + except requests.exceptions.HTTPError as e: + print(f"message: {response.reason}' '{response.status_code}' '{ e }") def post(self, endpoint, data=None, params=None): headers = { "Authorization": "Bearer " + self.foundation_client.get_access_token(), "Content-type": "application/json", } - response = requests.post( - f"{self.baseurl}{endpoint}", - headers=headers, - params=params or None, - data=data or None, - ) - if response.status_code == 201: - return response.status_code, response.text - else: - raise Exception(f"{response.status_code}, {response.text}") + try: + response = requests.post( + f"{self.baseurl}{endpoint}", + headers=headers, + params=params or None, + data=data or None, + ) + if response.status_code == 201: + return response.status_code, response.text + response.raise_for_status() + except requests.exceptions.HTTPError as errh: + print(f"message: {response.reason}' '{response.status_code}, {errh}") def put(self, endpoint, data=None, params=None): headers = { "Authorization": "Bearer " + self.foundation_client.get_access_token(), "Content-type": "application/json", } - response = requests.put( - f"{self.baseurl}{endpoint}", - headers=headers, - params=params or None, - data=data or None, - ) - if response.status_code == 200: - return response.status_code, response.text - else: - raise Exception(f"{response.status_code}, {response.text}") + try: + response = requests.put( + f"{self.baseurl}{endpoint}", + headers=headers, + params=params or None, + data=data or None, + ) + if response.status_code == 200: + return response.status_code, response.text + response.raise_for_status() + except requests.exceptions.HTTPError as errh: + print(f"message: {response.reason}' '{response.status_code}, {errh}") def delete(self, endpoint, params=None): - headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()} - resp = requests.put( - f"{self.baseurl}{endpoint}", - headers=headers, - params=params or None, - ) - return resp.status_code + headers = { + "Authorization": "Bearer " + self.foundation_client.get_access_token(), + "Content-type": "application/json", + } + try: + response = requests.delete( + f"{self.baseurl}{endpoint}", + headers=headers, + params=params or None, + ) + if response.status_code == 200: + return response.status_code, response.text + response.raise_for_status() + except requests.exceptions.HTTPError as errh: + print(f"message: {response.reason}' '{response.status_code}, {errh}") def get_projects(self) -> list: return self.get( @@ -256,16 +273,27 @@ class BcfClient: return self.delete(f"/projects/{project_id}/topics/{topic_id}") def get_snippet(self, project_id="", topic_id="") -> str: - return self.get( - f"/projects/{project_id}/topics/{topic_id}/snippet", - { - "project_id": project_id, - "topic_id": topic_id, - }, + headers = { + "Authorization": "Bearer " + self.foundation_client.get_access_token(), + "Content-type": "application/octet-stream", + } + response = requests.get( + f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", + headers=headers, ) + return response.status_code, response.content - def update_snippet(self, project_id="", topic_id="", data=None): - return self.put(f"/projects/{project_id}/topics", data=data) + def update_snippet(self, project_id="", topic_id="", files=None, data=None): + headers = { + "Authorization": "Bearer " + self.foundation_client.get_access_token(), + "Content-type": "application/octet-stream", + } + response = requests.put( + f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", + headers=headers, + files=files, + ) + return response.status_code def get_files_information(self, project_id="") -> list: return self.get( @@ -488,6 +516,7 @@ class BcfClient: project_id="", topic_id="", guid=None, + files=None, data=None, ): headers = { @@ -498,8 +527,10 @@ class BcfClient: f"/projects/{project_id}/topics/{topic_id}/documents", data=data, params={guid}, + files=files, headers=headers, ) + return response.status_code def get_document(self, project_id="", topic_id="", document_id="") -> str: return self.get( diff --git a/src/foundationserver/bcfserver/bcf/routes.py b/src/foundationserver/bcfserver/bcf/routes.py index 699aa8011b..f9c3f2d5b6 100644 --- a/src/foundationserver/bcfserver/bcf/routes.py +++ b/src/foundationserver/bcfserver/bcf/routes.py @@ -1,11 +1,12 @@ -from operator import methodcaller from flask import jsonify, url_for, redirect, render_template, request, session, flash from flask_login import login_user, logout_user, login_required, current_user from flask.blueprints import Blueprint +from werkzeug.utils import send_file from foundation.models import User, OAuth2AuthorizationCode, OAuth2Token, OAuth2Client from run import app, db import json import os +from flask_expects_json import expects_json bcf = Blueprint("bcf", __name__, template_folder="templates", url_prefix="/bcf/3.0") my_absolute_dirpath = os.path.abspath(os.path.dirname(__file__)) @@ -13,6 +14,7 @@ data = open( f"{my_absolute_dirpath}/project.json", ) jdata = json.load(data) +schema_path = os.path.join(my_absolute_dirpath, "schemas") def validate_client(request): @@ -60,7 +62,12 @@ def project_details(project_id): return invalid_user() +project_put = open(f"{schema_path}/Project/project_PUT.json") +project_put = json.load(project_put) + + @bcf.route("/projects/", methods=["PUT"]) +@expects_json(project_put) def update_project(project_id): if validate_client(request) and request.method == "PUT": data = request.get_json() @@ -69,8 +76,7 @@ def update_project(project_id): response = jsonify(data) return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//extensions") @@ -91,14 +97,16 @@ def topics(project_id): if (project["project_id"]) == project_id: return jsonify(jdata["Topics"]) return invalid_project() + return invalid_user() - else: - return invalid_user() + +topic_post = open(f"{schema_path}/Collaboration/Topic/topic_POST.json") +topic_post = json.load(topic_post) @bcf.route("/projects//topics/", methods=["POST"]) +@expects_json(topic_post) def create_topic(project_id, topic_id): - if validate_client(request) and request.method == "POST": body = request.get_json() for project in jdata["Projects"]: @@ -109,9 +117,7 @@ def create_topic(project_id, topic_id): response.status_code = 201 return response return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics/") @@ -123,11 +129,15 @@ def topic_details(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(topic) return invalid_project() - else: - return invalid_user() + return invalid_user() + + +topic_put = open(f"{schema_path}/Collaboration/Topic/topic_PUT.json") +topic_put = json.load(topic_put) @bcf.route("/projects//topics/", methods=["PUT"]) +@expects_json(topic_put) def update_topic(project_id, topic_id): if validate_client(request) and request.method == "PUT": data = request.get_json() @@ -137,8 +147,7 @@ def update_topic(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(data) return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics/", methods=["DELETE"]) @@ -148,10 +157,9 @@ def delete_topic(project_id, topic_id): if (project["project_id"]) == project_id: for topic in jdata["Topics"]: if (topic["guid"]) == topic_id: - return jsonify({"OK"}) + return jsonify("OK") return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//snippet", methods=["GET"]) @@ -161,10 +169,9 @@ def get_snippet(project_id, topic_id): if (project["project_id"]) == project_id: for topic in jdata["Topics"]: if (topic["guid"]) == topic_id: - return jsonify({"Get Snippet Executed successfully"}) + return send_file(f"{my_absolute_dirpath}/success.txt", download_name="snippet.txt") return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//snippet", methods=["PUT"]) @@ -175,10 +182,9 @@ def update_snippet(project_id, topic_id): if (project["project_id"]) == project_id: for topic in jdata["Topics"]: if (topic["guid"]) == topic_id: - return jsonify(data) + return jsonify("PUT Request Sucessfull") return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//files_information", methods=["GET"]) @@ -201,14 +207,16 @@ def get_files(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify("Get request for files successfull") return invalid_project() + return invalid_user() - else: - return invalid_user() + +file_put = open(f"{schema_path}/Collaboration/File/file_PUT.json") +file_put = json.load(file_put) @bcf.route("/projects//topics//files", methods=["PUT"]) +@expects_json(file_put) def update_files(project_id, topic_id): - if validate_client(request) and request.method == "PUT": data = request.get_json() for project in jdata["Projects"]: @@ -217,9 +225,7 @@ def update_files(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(data) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//comments", methods=["GET"]) @@ -231,11 +237,15 @@ def get_comments(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(jdata["Comments"]) return invalid_project() - else: - return invalid_user() + return invalid_user() + + +comment_post = open(f"{schema_path}/Collaboration/Comment/comment_POST.json") +comment_post = json.load(comment_post) @bcf.route("/projects//topics//comments", methods=["POST"]) +@expects_json(comment_post) def create_comments(project_id, topic_id): if validate_client(request) and request.method == "POST": data = request.get_json() @@ -247,8 +257,7 @@ def create_comments(project_id, topic_id): response.status_code = 201 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//comments/", methods=["GET"]) @@ -263,11 +272,15 @@ def get_comment(project_id, topic_id, comment_id): if (comment["guid"]) == comment_id: return jsonify(comment) return invalid_project() - else: - return invalid_user() + return invalid_user() + + +comment_put = open(f"{schema_path}/Collaboration/Comment/comment_PUT.json") +comment_put = json.load(comment_put) @bcf.route("/projects//topics//comments/", methods=["PUT"]) +@expects_json(comment_put) def update_comment(project_id, topic_id, comment_id): if validate_client(request) and request.method == "PUT": data = request.get_json() @@ -279,8 +292,7 @@ def update_comment(project_id, topic_id, comment_id): if (comment["guid"]) == comment_id: return jsonify(data) return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//comments/", methods=["DELETE"]) @@ -294,14 +306,11 @@ def delete_comment(project_id, topic_id, comment_id): if (comment["guid"]) == comment_id: return jsonify("OK") return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints", methods=["GET"]) def get_viewpoints(project_id, topic_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if project["project_id"] == project_id: @@ -309,11 +318,15 @@ def get_viewpoints(project_id, topic_id): if topic["guid"] == topic_id: return jsonify(jdata["Viewpoints"]) return invalid_project() - else: - return invalid_user() + return invalid_user() + + +viewpoint_post = open(f"{schema_path}/Collaboration/Viewpoint/viewpoint_POST.json") +viewpoint_post = json.load(viewpoint_post) @bcf.route("/projects//topics//viewpoints", methods=["POST"]) +@expects_json(viewpoint_post) def create_viewpoints(project_id, topic_id): if validate_client(request) and request.method == "POST": for project in jdata["Projects"]: @@ -325,8 +338,7 @@ def create_viewpoints(project_id, topic_id): response.status_code = 201 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints/", methods=["GET"]) @@ -342,8 +354,7 @@ def get_viewpoint(project_id, topic_id, viewpoint_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints/", methods=["DELETE"]) @@ -359,8 +370,7 @@ def delete_viewpoint(project_id, topic_id, viewpoint_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints//snapshot", methods=["GET"]) @@ -376,8 +386,7 @@ def get_snapshot(project_id, topic_id, viewpoint_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints//bitmaps/", methods=["GET"]) @@ -395,8 +404,7 @@ def get_bitmap(project_id, topic_id, viewpoint_id, bitmap_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints//selection", methods=["GET"]) @@ -412,8 +420,7 @@ def get_selection(project_id, topic_id, viewpoint_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints//coloring", methods=["GET"]) @@ -429,8 +436,7 @@ def get_coloring(project_id, topic_id, viewpoint_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//viewpoints//visibility", methods=["GET"]) @@ -446,13 +452,11 @@ def get_visibility(project_id, topic_id, viewpoint_id): response.status_code = 200 return response return invalid_project() - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//related_topics", methods=["GET"]) def get_related_topics(project_id, topic_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: @@ -460,14 +464,16 @@ def get_related_topics(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(jdata["RelatedTopics"]) return invalid_project() - return response - else: - return invalid_user() + return invalid_user() + + +related_topics_put = open(f"{schema_path}/Collaboration/RelatedTopic/related_topic_PUT.json") +related_topics_put = json.load(related_topics_put) @bcf.route("/projects//topics//related_topics", methods=["PUT"]) +@expects_json(related_topics_put) def update_related_topics(project_id, topic_id): - if validate_client(request) and request.method == "PUT": data = request.get_json() for project in jdata["Projects"]: @@ -476,14 +482,11 @@ def update_related_topics(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(data) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//document_references", methods=["GET"]) def get_document_references(project_id, topic_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: @@ -491,14 +494,16 @@ def get_document_references(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(jdata["DocumentReferences"]) return invalid_project() + return invalid_user() - else: - return invalid_user() + +document_reference_post = open(f"{schema_path}/Collaboration/DocumentReference/document_reference_POST.json") +document_reference_post = json.load(document_reference_post) @bcf.route("/projects//topics//document_references", methods=["POST"]) +@expects_json(document_reference_post) def create_document_references(project_id, topic_id): - if validate_client(request) and request.method == "POST": data = request.get_json() for project in jdata["Projects"]: @@ -509,17 +514,19 @@ def create_document_references(project_id, topic_id): response.status_code = 201 return response return invalid_project() + return invalid_user() - else: - return invalid_user() + +document_reference_put = open(f"{schema_path}/Collaboration/DocumentReference/document_reference_PUT.json") +document_reference_put = json.load(document_reference_put) @bcf.route( "/projects//topics//document_references/", methods=["PUT"], ) +@expects_json(document_reference_put) def update_document_references(project_id, topic_id, document_reference_id): - if validate_client(request) and request.method == "PUT": data = request.get_json() for project in jdata["Projects"]: @@ -530,14 +537,11 @@ def update_document_references(project_id, topic_id, document_reference_id): if (document["guid"]) == document_reference_id: return jsonify(data) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//documents", methods=["GET"]) def get_documents(project_id, topic_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: @@ -545,14 +549,11 @@ def get_documents(project_id, topic_id): if (topic["guid"]) == topic_id: return jsonify(jdata["Documents"]) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//documents", methods=["POST"]) def create_documents(project_id, topic_id): - if validate_client(request) and request.method == "POST": data = request.get_json() for project in jdata["Projects"]: @@ -563,14 +564,11 @@ def create_documents(project_id, topic_id): response.status_code = 201 return response return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//documents/", methods=["GET"]) def get_document(project_id, topic_id, document_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: @@ -578,29 +576,23 @@ def get_document(project_id, topic_id, document_id): if (topic["guid"]) == topic_id: for document in jdata["Documents"]: if (document["guid"]) == document_id: - return jsonify(document) + return send_file(f"{my_absolute_dirpath}/success.txt", download_name="document.txt") return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics/events", methods=["GET"]) def get_events(project_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: return jsonify(jdata["Events"]) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics//events", methods=["GET"]) def get_topic_events(project_id, topic_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: @@ -610,22 +602,17 @@ def get_topic_events(project_id, topic_id): if (event["topic_guid"]) == topic_id: return jsonify(event) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route("/projects//topics/comments/events", methods=["GET"]) def get_comments_events(project_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: return jsonify(jdata["EventComments"]) return invalid_project() - - else: - return invalid_user() + return invalid_user() @bcf.route( @@ -633,7 +620,6 @@ def get_comments_events(project_id): methods=["GET"], ) def get_comment_events(project_id, topic_id, comment_id): - if validate_client(request) and request.method == "GET": for project in jdata["Projects"]: if (project["project_id"]) == project_id: @@ -643,6 +629,4 @@ def get_comment_events(project_id, topic_id, comment_id): if (comment["comment_guid"]) == comment_id: return jsonify(comment) return invalid_project() - - else: - return invalid_user() + return invalid_user() diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/comment_actions.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/comment_actions.json new file mode 100644 index 0000000000..1af06a26dc --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/comment_actions.json @@ -0,0 +1,10 @@ +{ + "title": "comment_actions", + "required": false, + "type": "array", + "items": { + "type": "string", + "enum": ["update", + "delete"] + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/project_actions.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/project_actions.json new file mode 100644 index 0000000000..bf84ca72d5 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/project_actions.json @@ -0,0 +1,11 @@ +{ + "title": "project_actions", + "required": false, + "type": "array", + "items": { + "type": "string", + "enum": ["update", + "createTopic", + "createDocument"] + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/topic_actions.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/topic_actions.json new file mode 100644 index 0000000000..a88e007e11 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/topic_actions.json @@ -0,0 +1,16 @@ +{ + "title": "topic_actions", + "required": false, + "type": "array", + "items": { + "type": "string", + "enum": ["update", + "updateBimSnippet", + "updateRelatedTopics", + "updateDocumentReferences", + "updateFiles", + "createComment", + "createViewpoint", + "delete"] + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json new file mode 100644 index 0000000000..8feb815658 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json @@ -0,0 +1,7 @@ +{ + "required": false, + "type": "array", + "items": { + "enum": ["delete"] + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_GET.json new file mode 100644 index 0000000000..76c2d6a44e --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_GET.json @@ -0,0 +1,47 @@ +{ + "title": "comment_GET", + "type": "object", + "properties": { + "guid": { + "required": true, + "type": "string" + }, + "date": { + "required": true, + "type": "string" + }, + "author": { + "required": true, + "type": "string" + }, + "comment": { + "required": true, + "type": "string" + }, + "topic_guid": { + "required": true, + "type": "string" + }, + "viewpoint_guid": { + "type": ["string", + "null"] + }, + "modified_date": { + "type": ["string", + "null"] + }, + "modified_author": { + "type": ["string", + "null"] + }, + "authorization": { + "type": "object", + "required": false, + "properties": { + "comment_actions": { + "$ref": "../Action/comment_actions.json" + } + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_POST.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_POST.json new file mode 100644 index 0000000000..bb3e60d12a --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_POST.json @@ -0,0 +1,18 @@ +{ + "title": "comment_POST", + "type": "object", + "properties": { + "guid": { + "type": ["string", + "null"] + }, + "comment": { + "required": true, + "type": "string" + }, + "viewpoint_guid": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_PUT.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_PUT.json new file mode 100644 index 0000000000..8ceff5afef --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_PUT.json @@ -0,0 +1,14 @@ +{ + "title": "comment_PUT", + "type": "object", + "properties": { + "comment": { + "required": true, + "type": "string" + }, + "viewpoint_guid": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Document/document_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Document/document_GET.json new file mode 100644 index 0000000000..b8c4025977 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Document/document_GET.json @@ -0,0 +1,14 @@ +{ + "title": "document_GET", + "type": "object", + "properties": { + "guid": { + "required": true, + "type": "string" + }, + "filename": { + "required": true, + "type": "string" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json new file mode 100644 index 0000000000..37d649a06e --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "document_reference_GET", + "description": "Schema for a single document reference GET, BCF REST API.", + "type": "object", + "properties": { + "guid": { + "required": true, + "type": "string" + }, + "document_guid": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "description": { + "type": ["string", "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json new file mode 100644 index 0000000000..0e198490db --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json @@ -0,0 +1,18 @@ +{ + "title": "document_reference_POST", + "type": "object", + "properties": { + "guid": { + "type": ["string", "null"] + }, + "document_guid": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "description": { + "type": ["string", "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json new file mode 100644 index 0000000000..2e2dbd4753 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json @@ -0,0 +1,15 @@ +{ + "title": "document_reference_PUT", + "type": "object", + "properties": { + "document_guid": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "description": { + "type": ["string", "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/comment_event_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/comment_event_GET.json new file mode 100644 index 0000000000..b623d8ee34 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/comment_event_GET.json @@ -0,0 +1,29 @@ +{ + "title": "comment_event_GET", + "type": "object", + "properties": { + "comment_guid": { + "required": true, + "type": "string" + }, + "topic_guid": { + "required": true, + "type": "string" + }, + "date": { + "required": true, + "type": "string" + }, + "author": { + "required": true, + "type": "string" + }, + "actions": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "event_action.json" + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/event_action.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/event_action.json new file mode 100644 index 0000000000..a90c0604bc --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/event_action.json @@ -0,0 +1,14 @@ +{ + "title": "event_action", + "type": ["object", "null"], + "properties": { + "type": { + "required": true, + "type": "string" + }, + "value": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/topic_event_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/topic_event_GET.json new file mode 100644 index 0000000000..1192e67ca1 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/topic_event_GET.json @@ -0,0 +1,25 @@ +{ + "title": "topic_event_GET", + "type": "object", + "properties": { + "topic_guid": { + "required": true, + "type": "string" + }, + "date": { + "required": true, + "type": "string" + }, + "author": { + "required": true, + "type": "string" + }, + "actions": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "event_action.json" + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_GET.json new file mode 100644 index 0000000000..55d6b8642f --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_GET.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "file_GET", + "description": "Schema for a single file GET, BCF REST API.", + "type": "object", + "properties": { + "ifc_project": { + "type": ["string", + "null"] + }, + "ifc_spatial_structure_element": { + "type": ["string", + "null"] + }, + "filename": { + "type": ["string", + "null"] + }, + "date": { + "type": ["string", + "null"] + }, + "reference": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_PUT.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_PUT.json new file mode 100644 index 0000000000..7152d6890c --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_PUT.json @@ -0,0 +1,26 @@ +{ + "title": "file_PUT", + "type": "object", + "properties": { + "ifc_project": { + "type": ["string", + "null"] + }, + "ifc_spatial_structure_element": { + "type": ["string", + "null"] + }, + "filename": { + "type": ["string", + "null"] + }, + "date": { + "type": ["string", + "null"] + }, + "reference": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_display_information.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_display_information.json new file mode 100644 index 0000000000..bb808f5dde --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_display_information.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "project_file_display_information", + "description": "Schema for the display information of a project file, BCF REST API.", + "type": "object", + "properties": { + "field_display_name": { + "required": true, + "type": "string" + }, + "field_value": { + "required": true, + "type": "string" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_information.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_information.json new file mode 100644 index 0000000000..b85bf59a1b --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_information.json @@ -0,0 +1,13 @@ +{ + "title": "project_file_information", + "description": "Schema for a single project file information, BCF REST API.", + "type": "object", + "properties": { + "display_information": { + "type": "array", + "items": { "$ref": "project_file_display_information.json" }, + "default": [] + }, + "file": { "$ref": "file_GET.json" } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_files_information_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_files_information_GET.json new file mode 100644 index 0000000000..3a85e6a247 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_files_information_GET.json @@ -0,0 +1,8 @@ +{ + "description": "Schema for the listing the response of the GET project files information service", + "title": "project_files_information_GET", + "type": "array", + "items": { + "$ref": "project_file_information.json" + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json new file mode 100644 index 0000000000..654ec6fc19 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "related_topic_GET", + "description": "Schema for single related topic GET, BCF REST API.", + "type": "object", + "properties": { + "related_topic_guid": { + "required": true, + "type": "string" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json new file mode 100644 index 0000000000..8511a768ea --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "related_topic_PUT", + "description": "Schema for related topic PUT, BCF REST API.", + "type": "object", + "properties": { + "related_topic_guid": { + "required": true, + "type": "string" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/bim_snippet.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/bim_snippet.json new file mode 100644 index 0000000000..3be8fb8f73 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/bim_snippet.json @@ -0,0 +1,23 @@ +{ + "title": "bim_snippet", + "type": ["object", + "null"], + "properties": { + "snippet_type": { + "required": true, + "type": "string" + }, + "is_external": { + "required": true, + "type": "boolean" + }, + "reference": { + "required": true, + "type": "string" + }, + "reference_schema": { + "required": true, + "type": "string" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_GET.json new file mode 100644 index 0000000000..584d041702 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_GET.json @@ -0,0 +1,101 @@ +{ + "title": "topic_GET", + "type": "object", + "properties": { + "guid": { + "required": true, + "type": "string" + }, + "server_assigned_id": { + "required": true, + "type": "string" + }, + "topic_type": { + "type": ["string", + "null"] + }, + "topic_status": { + "type": ["string", + "null"] + }, + "reference_links": { + "type": ["array", + "null"], + "items": { + "type": "string" + } + }, + "title": { + "required": true, + "type": "string" + }, + "priority": { + "type": ["string", + "null"] + }, + "index": { + "type": [ + "integer", + "null" + ] + }, + "labels": { + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "creation_date": { + "required": true, + "type": "string" + }, + "creation_author": { + "required": true, + "type": "string" + }, + "modified_date": { + "type": "string" + }, + "modified_author": { + "type": ["string", + "null"] + }, + "assigned_to": { + "type": ["string", + "null"] + }, + "stage": { + "type": ["string", + "null"] + }, + "description": { + "type": ["string", + "null"] + }, + "bim_snippet": { + "$ref": "bim_snippet.json" + }, + "due_date": { + "type": ["string", + "null"] + }, + "authorization": { + "type": "object", + "required": false, + "properties": { + "topic_actions": { + "$ref": "../Action/topic_actions.json" + }, + "topic_status": { + "type": ["array", + "null"], + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_POST.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_POST.json new file mode 100644 index 0000000000..6ae49158fc --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_POST.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "topic_POST", + "description": "Schema for topic POST, BCF REST API.", + "type": "object", + "properties": { + "guid": { + "type": ["string", + "null"] + }, + "topic_type": { + "type": ["string", + "null"] + }, + "topic_status": { + "type": ["string", + "null"] + }, + "reference_links": { + "type": ["array", + "null"], + "items": { + "type": "string" + } + }, + "title": { + "required": true, + "type": "string" + }, + "priority": { + "type": ["string", + "null"] + }, + "index": { + "type": [ + "integer", + "null" + ] + }, + "labels": { + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "assigned_to": { + "type": ["string", + "null"] + }, + "stage": { + "type": ["string", + "null"] + }, + "description": { + "type": ["string", + "null"] + }, + "bim_snippet": { + "$ref": "bim_snippet.json" + }, + "due_date": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_PUT.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_PUT.json new file mode 100644 index 0000000000..2b0935eb9a --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_PUT.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "topic_PUT", + "description": "Schema for topic PUT, BCF REST API.", + "type": "object", + "properties": { + "topic_type": { + "type": ["string", + "null"] + }, + "topic_status": { + "type": ["string", + "null"] + }, + "reference_links": { + "type": ["array", + "null"], + "items": { + "type": "string" + } + }, + "title": { + "required": true, + "type": "string" + }, + "priority": { + "type": ["string", + "null"] + }, + "index": { + "type": [ + "integer", + "null" + ] + }, + "labels": { + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "assigned_to": { + "type": ["string", + "null"] + }, + "stage": { + "type": ["string", + "null"] + }, + "description": { + "type": ["string", + "null"] + }, + "bim_snippet": { + "$ref": "bim_snippet.json" + }, + "due_date": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json new file mode 100644 index 0000000000..5373008612 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "properties": { + "guid": { + "type": "string" + }, + "bitmap_type": { + "type": "string", + "enum": ["jpg", "png"] + }, + "location": { + "$ref": "location.json" + }, + "normal": { + "$ref": "direction.json" + }, + "up": { + "$ref": "direction.json" + }, + "height": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json new file mode 100644 index 0000000000..cd7932dcb3 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json @@ -0,0 +1,25 @@ +{ + "type": "object", + "properties": { + "bitmap_type": { + "type": "string", + "enum": ["jpg", "png"] + }, + "bitmap_data": { + "type": "string", + "format": "base64" + }, + "location": { + "$ref": "location.json" + }, + "normal": { + "$ref": "direction.json" + }, + "up": { + "$ref": "direction.json" + }, + "height": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json new file mode 100644 index 0000000000..e9fad7b61e --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json @@ -0,0 +1,13 @@ +{ + "title": "clipping_plane", + "type": ["object", + "null"], + "properties": { + "location": { + "$ref": "location.json" + }, + "direction": { + "$ref": "direction.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring.json new file mode 100644 index 0000000000..89d3270270 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring.json @@ -0,0 +1,12 @@ +{ + "title": "coloring", + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "components": { + "$ref": "component_list.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json new file mode 100644 index 0000000000..c738f20a86 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "coloring_GET", + "description": "Schema for coloring GET, BCF REST API.", + "type": "object", + "properties": { + "coloring": { + "type": ["array", "null"], + "items": { + "$ref": "coloring.json" + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component.json new file mode 100644 index 0000000000..49df1c0e1e --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component.json @@ -0,0 +1,15 @@ +{ + "title": "component", + "type": ["object"], + "properties": { + "ifc_guid": { + "type": ["string", "null"] + }, + "originating_system": { + "type": ["string", "null"] + }, + "authoring_tool_id": { + "type": ["string", "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component_list.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component_list.json new file mode 100644 index 0000000000..0ec6d59fe6 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component_list.json @@ -0,0 +1,7 @@ +{ + "title": "component list", + "type": ["array", "null"], + "items": { + "$ref": "component.json" + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/components.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/components.json new file mode 100644 index 0000000000..66a233ae5f --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/components.json @@ -0,0 +1,18 @@ +{ + "title": "components", + "type": ["object", "null"], + "properties": { + "selection": { + "$ref": "component_list.json" + }, + "coloring": { + "type": ["array", "null"], + "items": { + "$ref": "coloring.json" + } + }, + "visibility": { + "$ref": "visibility.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/direction.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/direction.json new file mode 100644 index 0000000000..da6e137aee --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/direction.json @@ -0,0 +1,15 @@ +{ + "title": "direction", + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/line.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/line.json new file mode 100644 index 0000000000..4f4e0f3af4 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/line.json @@ -0,0 +1,12 @@ +{ + "title": "line", + "type": ["object", "null"], + "properties": { + "start_point": { + "$ref": "point.json" + }, + "end_point": { + "$ref": "point.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/location.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/location.json new file mode 100644 index 0000000000..c38696d938 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/location.json @@ -0,0 +1,16 @@ +{ + "title": "location", + "type": ["object", + "null"], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json new file mode 100644 index 0000000000..f9d69e47b8 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json @@ -0,0 +1,22 @@ +{ + "title": "orthogonal_camera", + "type": ["object", + "null"], + "properties": { + "camera_view_point": { + "$ref": "point.json" + }, + "camera_direction": { + "$ref": "direction.json" + }, + "camera_up_vector": { + "$ref": "direction.json" + }, + "view_to_world_scale": { + "type": "number" + }, + "aspect_ratio": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json new file mode 100644 index 0000000000..a3afcdd7ad --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json @@ -0,0 +1,22 @@ +{ + "title": "perspective_camera", + "type": ["object", + "null"], + "properties": { + "camera_view_point": { + "$ref": "point.json" + }, + "camera_direction": { + "$ref": "direction.json" + }, + "camera_up_vector": { + "$ref": "direction.json" + }, + "field_of_view": { + "type": "number" + }, + "aspect_ratio": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/point.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/point.json new file mode 100644 index 0000000000..19558a426d --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/point.json @@ -0,0 +1,15 @@ +{ + "title": "point", + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json new file mode 100644 index 0000000000..2296b027f3 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "selection_GET", + "description": "Schema for selection GET, BCF REST API.", + "type": "object", + "properties": { + "selection": { + "$ref": "component_list.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json new file mode 100644 index 0000000000..6235b0add1 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json @@ -0,0 +1,10 @@ +{ + "title": "snapshot_GET", + "type": ["object", "null"], + "properties": { + "snapshot_type": { + "type": ["string"], + "enum": ["jpg", "png"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json new file mode 100644 index 0000000000..a349e91cdb --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json @@ -0,0 +1,14 @@ +{ + "title": "snapshot_POST", + "type": ["object", "null"], + "properties": { + "snapshot_type": { + "type": ["string"], + "enum": ["jpg", "png"] + }, + "snapshot_data": { + "type": ["string"], + "format": "base64" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json new file mode 100644 index 0000000000..81f8ab7f34 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json @@ -0,0 +1,18 @@ +{ + "title": "view_setup_hints", + "type": ["object", "null"], + "properties": { + "spaces_visible": { + "type": "boolean", + "default": "false" + }, + "space_boundaries_visible": { + "type": "boolean", + "default": "false" + }, + "openings_visible": { + "type": "boolean", + "default": "false" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json new file mode 100644 index 0000000000..513c26d4ad --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json @@ -0,0 +1,52 @@ +{ + "title": "viewpoint_GET", + "type": "object", + "properties": { + "index": { + "type": [ + "integer", + "null" + ] + }, + "guid": { + "required": true, + "type": "string" + }, + "orthogonal_camera": { + "$ref": "orthogonal_camera.json" + }, + "perspective_camera": { + "$ref": "perspective_camera.json" + }, + "lines": { + "type": ["array", "null"], + "items": { + "$ref": "line.json" + } + }, + "clipping_planes": { + "type": ["array", "null"], + "items": { + "$ref": "clipping_plane.json" + } + }, + "bitmaps": { + "type": ["array", "null"], + "items": { + "$ref": "bitmap_GET.json" + } + }, + "snapshot": { + "$ref": "snapshot_GET.json" + }, + "authorization": { + "type": "object", + "required": false, + "properties": { + "viewpoint_actions": { + "$ref": "../Action/viewpoint_actions.json" + } + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json new file mode 100644 index 0000000000..b9408d0d9d --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json @@ -0,0 +1,46 @@ +{ + "title": "viewpoint_POST", + "type": [ "object" ], + "properties": { + "guid": { + "type": ["string", + "null"] + }, + "index": { + "type": [ + "integer", + "null" + ] + }, + "orthogonal_camera": { + "$ref": "orthogonal_camera.json" + }, + "perspective_camera": { + "$ref": "perspective_camera.json" + }, + "lines": { + "type": ["array", "null"], + "items": { + "$ref": "line.json" + } + }, + "clipping_planes": { + "type": ["array", "null"], + "items": { + "$ref": "clipping_plane.json" + } + }, + "bitmaps": { + "type": ["array", "null"], + "items": { + "$ref": "bitmap_POST.json" + } + }, + "snapshot": { + "$ref": "snapshot_POST.json" + }, + "components": { + "$ref": "components.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility.json new file mode 100644 index 0000000000..13ea0d08fe --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility.json @@ -0,0 +1,16 @@ +{ + "title": "visibility", + "type": "object", + "properties": { + "default_visibility": { + "type": "boolean", + "default": "false" + }, + "exceptions": { + "$ref": "component_list.json" + }, + "view_setup_hints": { + "$ref": "view_setup_hints.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json new file mode 100644 index 0000000000..5fe8714168 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "visibility_GET", + "description": "Schema for visibility GET, BCF REST API.", + "type": "object", + + "properties": { + "visibility": { + "$ref": "visibility.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Project/extensions_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Project/extensions_GET.json new file mode 100644 index 0000000000..92de386640 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Project/extensions_GET.json @@ -0,0 +1,78 @@ +{ + "title": "extensions_GET", + "type": "object", + "properties": { + "topic_type": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "topic_status": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "topic_label": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "snippet_type": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "priority": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "users": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "stage": { + "required": true, + "type": ["array", + "null"], + "items": { + "type": ["string", + "null"] + } + }, + "project_actions": { + "$ref": "../Collaboration/Action/project_actions.json" + }, + "topic_actions": { + "$ref": "../Collaboration/Action/topic_actions.json" + }, + "comment_actions": { + "$ref": "../Collaboration/Action/comment_actions.json" + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Project/project_GET.json b/src/foundationserver/bcfserver/bcf/schemas/Project/project_GET.json new file mode 100644 index 0000000000..e239d4e73a --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Project/project_GET.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "project_GET", + "description": "Schema for single project GET, BCF REST API.", + "type": "object", + "properties": { + "project_id": { + "type": "string", + "required": true + }, + "name": { + "type": "string", + "required": true + }, + "authorization": { + "type": "object", + "required": false, + "properties": { + "project_actions": { + "$ref": "../Collaboration/Action/project_actions.json" + } + } + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/Project/project_PUT.json b/src/foundationserver/bcfserver/bcf/schemas/Project/project_PUT.json new file mode 100644 index 0000000000..5b317ab276 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/Project/project_PUT.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "project_PUT", + "description": "Schema for project PUT, BCF REST API.", + "type": "object", + "properties": { + "name": { + "type": "string", + "required": true + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/User/user_GET.json b/src/foundationserver/bcfserver/bcf/schemas/User/user_GET.json new file mode 100644 index 0000000000..873129c155 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/User/user_GET.json @@ -0,0 +1,14 @@ +{ + "title": "user_GET", + "type": "object", + "properties": { + "id": { + "required": true, + "type": "string" + }, + "name": { + "type": ["string", + "null"] + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/schemas/error.json b/src/foundationserver/bcfserver/bcf/schemas/error.json new file mode 100644 index 0000000000..66a5550975 --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/schemas/error.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Error", + "description": "Schema for error, BCF REST API.", + "type": "object", + "properties": { + "message": { + "type": "string", + "required": true + } + } +} diff --git a/src/foundationserver/bcfserver/bcf/success.txt b/src/foundationserver/bcfserver/bcf/success.txt new file mode 100644 index 0000000000..60f280850b --- /dev/null +++ b/src/foundationserver/bcfserver/bcf/success.txt @@ -0,0 +1 @@ +Your request was successfull \ No newline at end of file From 6c6d4c047cb93443069f37935641bde3a7425519 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 20 Aug 2021 08:50:57 +1000 Subject: [PATCH 52/72] Fix #1672. Bug where style attributes couldn't be edited. --- src/blenderbim/blenderbim/bim/helper.py | 2 +- src/ifcopenshell-python/ifcopenshell/util/attribute.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 29bb267932..8b7dfc6c1d 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -53,7 +53,7 @@ def draw_attribute(attribute, layout, copy_operator=None): def import_attributes(ifc_class, props, data, callback=None): for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)): + if isinstance(data_type, tuple) or data_type == "entity": callback(attribute.name(), None, data) if callback else None continue new = props.add() diff --git a/src/ifcopenshell-python/ifcopenshell/util/attribute.py b/src/ifcopenshell-python/ifcopenshell/util/attribute.py index 5e56c62f2f..e874dff672 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/attribute.py +++ b/src/ifcopenshell-python/ifcopenshell/util/attribute.py @@ -3,10 +3,12 @@ def get_primitive_type(attribute_or_data_type): data_type = str(attribute_or_data_type.type_of_attribute()) else: data_type = str(attribute_or_data_type) - if "" in data_type: From 7fa79a2cec89ec16dcd7ee35ac517792f8db87a4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 20 Aug 2021 09:42:03 +1000 Subject: [PATCH 53/72] Work schedule psets are now supported, so you can set work day/week/month durations --- .../blenderbim/bim/module/pset/__init__.py | 4 +++ .../blenderbim/bim/module/pset/operator.py | 5 +++- .../blenderbim/bim/module/pset/prop.py | 16 ++++++++++ .../blenderbim/bim/module/pset/ui.py | 30 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py index 09838383c3..ace6e1253f 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -34,12 +34,14 @@ classes = ( prop.TaskPsetProperties, prop.ResourcePsetProperties, prop.ProfilePsetProperties, + prop.WorkSchedulePsetProperties, ui.BIM_PT_object_psets, ui.BIM_PT_object_qtos, ui.BIM_PT_material_psets, ui.BIM_PT_task_qtos, ui.BIM_PT_resource_qtos, ui.BIM_PT_profile_psets, + ui.BIM_PT_work_schedule_psets, ) @@ -49,6 +51,7 @@ def register(): bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.TaskPsetProperties) bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.ResourcePsetProperties) bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.ProfilePsetProperties) + bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.WorkSchedulePsetProperties) def unregister(): @@ -57,3 +60,4 @@ def unregister(): del bpy.types.Scene.TaskPsetProperties del bpy.types.Scene.ResourcePsetProperties del bpy.types.Scene.ProfilePsetProperties + del bpy.types.Scene.WorkSchedulePsetProperties diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index f208513b0a..3e6a6edf82 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -43,6 +43,8 @@ def get_pset_props(context, obj, obj_type): return context.scene.ResourcePsetProperties elif obj_type == "Profile": return context.scene.ProfilePsetProperties + elif obj_type == "WorkSchedule": + return context.scene.WorkSchedulePsetProperties def get_pset_obj_ifc_definition_id(context, obj, obj_type): @@ -64,7 +66,8 @@ def get_pset_obj_ifc_definition_id(context, obj, obj_type): return context.scene.BIMProfileProperties.profiles[ context.scene.BIMProfileProperties.active_profile_index ].ifc_definition_id - + elif obj_type == "WorkSchedule": + return context.scene.BIMWorkScheduleProperties.active_work_schedule_id class TogglePsetExpansion(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index 98b32d970a..ee2c1c4f4a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -101,6 +101,15 @@ def getProfilePsetNames(self, context): return psetnames[ifc_class] +def getWorkSchedulePsetNames(self, context): + global psetnames + ifc_class = "IfcWorkSchedule" + if ifc_class not in psetnames: + psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + return psetnames[ifc_class] + + def getQtoNames(self, context): global qtonames if "/" in context.active_object.name: @@ -146,3 +155,10 @@ class ProfilePsetProperties(PropertyGroup): active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=Attribute) pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name") + + +class WorkSchedulePsetProperties(PropertyGroup): + active_pset_id: IntProperty(name="Active Pset ID") + active_pset_name: StringProperty(name="Pset Name") + properties: CollectionProperty(name="Properties", type=Attribute) + pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name") diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index d7fe3aeddb..5b6b2b5a32 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -332,3 +332,33 @@ class BIM_PT_profile_psets(Panel): psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]] for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Profile") + + +class BIM_PT_work_schedule_psets(Panel): + bl_label = "IFC Work Schedule Property Sets" + bl_idname = "BIM_PT_work_schedule_psets" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_work_schedules" + + @classmethod + def poll(cls, context): + props = context.scene.BIMWorkScheduleProperties + if not context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + return False + return True + + def draw(self, context): + props = context.scene.WorkSchedulePsetProperties + ifc_definition_id = context.scene.BIMWorkScheduleProperties.active_work_schedule_id + if ifc_definition_id not in Data.products: + Data.load(IfcStore.get_file(), ifc_definition_id) + row = self.layout.row(align=True) + row.prop(props, "pset_name", text="") + op = row.operator("bim.add_pset", icon="ADD", text="") + op.obj_type = "WorkSchedule" + + psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]] + for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): + draw_psetqto_ui(context, pset_id, pset, props, self.layout, "WorkSchedule") From 5c62cb244a30b29758d6f0309f9e38f2ed9d3343 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Fri, 20 Aug 2021 09:55:44 +0200 Subject: [PATCH 54/72] Create Drawing op: Add tooltip and use poll method --- .../blenderbim/bim/module/drawing/operator.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 3f76bc278a..eca290ca9c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -115,22 +115,30 @@ class AddDrawing(bpy.types.Operator): class CreateDrawing(bpy.types.Operator): + """Creates a svg drawing + + Only available if : + - IFC file is created + - Camera is in Orthographic mode + """ bl_idname = "bim.create_drawing" bl_label = "Create Drawing" + @classmethod + def poll(cls, context): + camera = context.scene.camera + return IfcStore.get_file() \ + and camera.type == "CAMERA" and camera.data.type == "ORTHO" \ + and camera.BIMObjectProperties.ifc_definition_id + def execute(self, context): self.camera = context.scene.camera - if ( - not (self.camera.type == "CAMERA" and self.camera.data.type == "ORTHO") - or not self.camera.BIMObjectProperties.ifc_definition_id - ): - return self.file = IfcStore.get_file() self.time = None start = time.time() self.profile_code("Start drawing generation process") self.props = context.scene.DocProperties - self.drawing_name = IfcStore.get_file().by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name + self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name underlay_svg = self.generate_underlay(context) self.profile_code("Generate underlay") linework_svg = self.generate_linework(context) From 447c07460a72eb76f816ad215ff8c911e264b328 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Fri, 20 Aug 2021 10:13:00 +0200 Subject: [PATCH 55/72] Disable editing operators : Change icon --- src/blenderbim/blenderbim/bim/module/aggregate/ui.py | 2 +- src/blenderbim/blenderbim/bim/module/classification/ui.py | 2 +- src/blenderbim/blenderbim/bim/module/constraint/ui.py | 4 ++-- src/blenderbim/blenderbim/bim/module/document/ui.py | 8 ++++---- src/blenderbim/blenderbim/bim/module/georeference/ui.py | 2 +- src/blenderbim/blenderbim/bim/module/layer/ui.py | 4 ++-- src/blenderbim/blenderbim/bim/module/owner/ui.py | 8 ++++---- src/blenderbim/blenderbim/bim/module/pset_template/ui.py | 4 ++-- src/blenderbim/blenderbim/bim/module/sequence/ui.py | 4 ++-- src/blenderbim/blenderbim/bim/module/structural/ui.py | 4 ++-- src/blenderbim/blenderbim/bim/module/type/ui.py | 3 +-- 11 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py index 0be2ff6be6..7f9c033b12 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py @@ -57,7 +57,7 @@ class BIM_PT_aggregate(Panel): row.prop(props, "relating_object", text="") if props.relating_object: row.operator("bim.assign_object", icon="CHECKMARK", text="").relating_object = props.relating_object.name - row.operator("bim.disable_editing_aggregate", icon="X", text="") + row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="") else: row = self.layout.row(align=True) name = "{}/{}".format( diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py index 4cd3d4595c..cdc6357589 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/ui.py +++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py @@ -147,7 +147,7 @@ class BIM_PT_classification_references(Panel): row = self.layout.row(align=True) row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER") row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_classification_reference", text="", icon="X") + row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL") for attribute in self.props.reference_attributes: if attribute.name == "Name": diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py index a26c4ad193..5264196e75 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py @@ -115,7 +115,7 @@ class BIM_PT_object_constraints(Panel): row = self.layout.row(align=True) icon = "LIGHT" if self.props.is_adding == "IfcObjective" else "FILE_HIDDEN" row.label(text="Adding {}".format(self.props.is_adding), icon=icon) - row.operator("bim.disable_assigning_constraint", text="", icon="X") + row.operator("bim.disable_assigning_constraint", text="", icon="CANCEL") self.layout.template_list( "BIM_UL_object_constraints", "", @@ -138,7 +138,7 @@ class BIM_UL_constraints(UIList): if context.scene.BIMConstraintProperties.active_constraint_id == item.ifc_definition_id: if context.scene.BIMConstraintProperties.is_editing == "IfcObjective": row.operator("bim.edit_objective", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_constraint", text="", icon="X") + row.operator("bim.disable_editing_constraint", text="", icon="CANCEL") elif context.scene.BIMConstraintProperties.active_constraint_id: row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id else: diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index 27c6c0f8e5..e13e2fcb95 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -46,7 +46,7 @@ class BIM_PT_documents(Panel): row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE") if self.props.is_editing == "information": row.operator("bim.add_information", text="", icon="ADD") - row.operator("bim.disable_document_editing_ui", text="", icon="X") + row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL") else: row.operator("bim.load_information", text="", icon="IMPORT") @@ -55,7 +55,7 @@ class BIM_PT_documents(Panel): row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN") if self.props.is_editing == "reference": row.operator("bim.add_document_reference", text="", icon="ADD") - row.operator("bim.disable_document_editing_ui", text="", icon="X") + row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL") else: row.operator("bim.load_document_references", text="", icon="IMPORT") @@ -129,7 +129,7 @@ class BIM_PT_object_documents(Panel): row = self.layout.row(align=True) icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN" row.label(text="Adding {}".format(self.props.is_adding), icon=icon) - row.operator("bim.disable_assigning_document", text="", icon="X") + row.operator("bim.disable_assigning_document", text="", icon="CANCEL") self.layout.template_list( "BIM_UL_object_documents", "", @@ -155,7 +155,7 @@ class BIM_UL_documents(UIList): row.operator("bim.edit_information", text="", icon="CHECKMARK") elif context.scene.BIMDocumentProperties.is_editing == "reference": row.operator("bim.edit_document_reference", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_document", text="", icon="X") + row.operator("bim.disable_editing_document", text="", icon="CANCEL") elif context.scene.BIMDocumentProperties.active_document_id: row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id else: diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index b7de779592..cf5a9e8481 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -52,7 +52,7 @@ class BIM_PT_gis(Panel): row = self.layout.row(align=True) row.label(text="Projected CRS", icon="WORLD") row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_georeferencing", icon="X", text="") + row.operator("bim.disable_editing_georeferencing", icon="CANCEL", text="") draw_attributes(props.projected_crs, self.layout) diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py index 2bd9292928..542556adca 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/ui.py +++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py @@ -44,7 +44,7 @@ class BIM_PT_layers(Panel): row.label(text="{} Layers Found".format(len(Data.layers.keys()))) if self.props.is_editing: row.operator("bim.add_presentation_layer", text="", icon="ADD") - row.operator("bim.disable_layer_editing_ui", text="", icon="X") + row.operator("bim.disable_layer_editing_ui", text="", icon="CANCEL") else: row.operator("bim.load_layers", text="", icon="GREASEPENCIL") @@ -92,7 +92,7 @@ class BIM_UL_layers(UIList): if context.scene.BIMLayerProperties.active_layer_id == item.ifc_definition_id: row.operator("bim.edit_presentation_layer", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_layer", text="", icon="X") + row.operator("bim.disable_editing_layer", text="", icon="CANCEL") elif context.scene.BIMLayerProperties.active_layer_id: row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id else: diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py index fbefd1193b..e451c3af2c 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/ui.py +++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py @@ -64,7 +64,7 @@ def draw_roles_ui(box, assigned_object_id, roles, context): box2 = box.box() row = draw_prop_on_new_row(box2, blender_role, "name", align=True, icon="MOD_CLOTH", text="") row.operator("bim.edit_role", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_role", icon="X", text="") + row.operator("bim.disable_editing_role", icon="CANCEL", text="") if blender_role.name == "USERDEFINED": draw_prop_on_new_row(box2, blender_role, "user_defined_role") draw_prop_on_new_row(box2, blender_role, "description") @@ -92,7 +92,7 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file, context): box2 = box.box() row = draw_prop_on_new_row(box2, blender_address, "purpose", align=True, icon="MOD_CLOTH", text="") row.operator("bim.edit_address", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_address", icon="X", text="") + row.operator("bim.disable_editing_address", icon="CANCEL", text="") if blender_address.purpose == "USERDEFINED": draw_prop_on_new_row(box2, blender_address, "user_defined_purpose") draw_prop_on_new_row(box2, blender_address, "description") @@ -150,7 +150,7 @@ class BIM_PT_people(Panel): box = self.layout.box() row = draw_prop_on_new_row(box, blender_person, "name", align=True, icon="USER", text="") row.operator("bim.edit_person", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_person", icon="X", text="") + row.operator("bim.disable_editing_person", icon="CANCEL", text="") draw_prop_on_new_row(box, blender_person, "family_name") draw_prop_on_new_row(box, blender_person, "given_name") draw_string_collection(box, blender_person, "middle_names") @@ -204,7 +204,7 @@ class BIM_PT_organisations(Panel): row = box.row(align=True) row.prop(blender_organisation, "name", icon="USER", text="") row.operator("bim.edit_organisation", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_organisation", icon="X", text="") + row.operator("bim.disable_editing_organisation", icon="CANCEL", text="") draw_prop_on_new_row(box, blender_organisation, "identification") draw_prop_on_new_row(box, blender_organisation, "description") diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py index b68a4e3702..0080ebb877 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py @@ -48,7 +48,7 @@ class BIM_PT_pset_template(Panel): if props.active_pset_template_id: row.operator("bim.edit_pset_template", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_pset_template", text="", icon="X") + row.operator("bim.disable_editing_pset_template", text="", icon="CANCEL") else: row.operator("bim.add_pset_template", text="", icon="ADD") row.operator("bim.enable_editing_pset_template", text="", icon="GREASEPENCIL") @@ -97,7 +97,7 @@ class BIM_PT_pset_template(Panel): if props.active_prop_template_id and props.active_prop_template_id == prop_template_id: op = row.operator("bim.edit_prop_template", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_prop_template", icon="X", text="") + row.operator("bim.disable_editing_prop_template", icon="CANCEL", text="") elif props.active_prop_template_id and props.active_prop_template_id != prop_template_id: row.operator("bim.remove_prop_template", icon="X", text="").prop_template = prop_template_id else: diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index a8bcc7d291..57a10f9ae4 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -282,12 +282,12 @@ class BIM_PT_work_schedules(Panel): if self.props.active_sequence_id == sequence["id"]: if self.props.editing_sequence_type == "ATTRIBUTES": row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_sequence", text="", icon="X") + row.operator("bim.disable_editing_sequence", text="", icon="CANCEL") self.draw_editable_sequence_attributes_ui() elif self.props.editing_sequence_type == "TIME_LAG": op = row.operator("bim.edit_sequence_time_lag", text="", icon="CHECKMARK") op.lag_time = sequence["TimeLag"] - row.operator("bim.disable_editing_sequence", text="", icon="X") + row.operator("bim.disable_editing_sequence", text="", icon="CANCEL") self.draw_editable_sequence_time_lag_ui() else: if sequence["TimeLag"]: diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index ee8b39fcf7..0f04cc71f2 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -40,7 +40,7 @@ def draw_boundary_condition_ui(layout, boundary_condition_id, connection_id, pro if props.active_boundary_condition and props.active_boundary_condition == boundary_condition_id: row.label(text=data["type"], icon="CON_TRACKTO") row.operator("bim.edit_structural_boundary_condition", text="", icon="CHECKMARK").connection = connection_id - row.operator("bim.disable_editing_structural_boundary_condition", text="", icon="X") + row.operator("bim.disable_editing_structural_boundary_condition", text="", icon="CANCEL") elif props.active_boundary_condition and props.active_boundary_condition != boundary_condition_id: row.label(text=data["type"], icon="CON_TRACKTO") row.operator("bim.remove_structural_boundary_condition", text="", icon="X").connection = connection_id @@ -338,7 +338,7 @@ class BIM_UL_structural_analysis_models(UIList): if context.scene.BIMStructuralProperties.active_structural_analysis_model_id == item.ifc_definition_id: row.operator("bim.edit_structural_analysis_model", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_structural_analysis_model", text="", icon="X") + row.operator("bim.disable_editing_structural_analysis_model", text="", icon="CANCEL") elif context.scene.BIMStructuralProperties.active_structural_analysis_model_id: op = row.operator("bim.remove_structural_analysis_model", text="", icon="X") op.structural_analysis_model = item.ifc_definition_id diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 48637ddd6e..d58961fc3a 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -72,8 +72,7 @@ class BIM_PT_type(Panel): row.prop(props, "relating_type", text="") row.operator("bim.assign_type", icon="CHECKMARK", text="") else: - row.prop(props, "blank_relating_type", text="") - row.operator("bim.disable_editing_type", icon="X", text="") + row.operator("bim.disable_editing_type", icon="CANCEL", text="") else: row = self.layout.row(align=True) name = "{}/{}".format( From 3b36029b1d5bab6040d92d0725b7955c15e06ea5 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Fri, 20 Aug 2021 10:16:58 +0200 Subject: [PATCH 56/72] Use label instead of dummy dynamic enum And fix error in last commit... --- src/blenderbim/blenderbim/bim/module/type/prop.py | 2 -- src/blenderbim/blenderbim/bim/module/type/ui.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index 06fcfb14a2..c5b6c2221d 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -107,5 +107,3 @@ class BIMTypeObjectProperties(PropertyGroup): is_editing_type: BoolProperty(name="Is Editing Type", update=updateTypeDropdowns) relating_type_class: EnumProperty(items=getApplicableTypes, name="Relating Type Class") relating_type: EnumProperty(items=getRelatingTypes, name="Relating Type") - # This is purely a UX thing - blank_relating_type: EnumProperty(items=[("None", "No Types Found", "")], name="Relating Type") diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index d58961fc3a..84deb86a70 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -72,6 +72,7 @@ class BIM_PT_type(Panel): row.prop(props, "relating_type", text="") row.operator("bim.assign_type", icon="CHECKMARK", text="") else: + row.label(text="No Types Found") row.operator("bim.disable_editing_type", icon="CANCEL", text="") else: row = self.layout.row(align=True) From fd603f7035a6310472460e6186874305f5ffb873 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 21 Aug 2021 16:16:53 +1000 Subject: [PATCH 57/72] IfcZip and IfcXML can now be set manually, and prevent double loading when importing, which can save ~20 seconds for large file imports. --- src/blenderbim/blenderbim/bim/handler.py | 3 +-- src/blenderbim/blenderbim/bim/ifc.py | 19 +++++++++++++++++-- src/blenderbim/blenderbim/bim/import_ifc.py | 21 +-------------------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index b56636e9f2..50dfea5ff8 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -153,8 +153,7 @@ def purge_module_data(): def loadIfcStore(scene): IfcStore.purge() purge_module_data() - ifc_file = IfcStore.get_file() - if not ifc_file: + if not IfcStore.get_file(): return IfcStore.get_schema() IfcStore.reload_linked_elements() diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index 58c173acbf..e779befc27 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -60,11 +60,26 @@ class IfcStore: IfcStore.path = bpy.context.scene.BIMProperties.ifc_file if IfcStore.path: try: - IfcStore.file = ifcopenshell.open(IfcStore.path) + IfcStore.load_file(IfcStore.path) except: - IfcStore.file + pass return IfcStore.file + @staticmethod + def load_file(path): + extension = path.split(".")[-1] + if extension.lower() == "ifczip": + with tempfile.TemporaryDirectory() as unzipped_path: + with zipfile.ZipFile(path, "r") as zip_ref: + zip_ref.extractall(unzipped_path) + for filename in Path(unzipped_path).glob("**/*.ifc"): + IfcStore.file = ifcopenshell.open(filename) + return + elif extension.lower() == "ifcxml": + IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path)) + elif extension.lower() == "ifc": + IfcStore.file = ifcopenshell.open(path) + @staticmethod def get_schema(): if IfcStore.file is None: diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 907beecd39..df92f37c47 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -214,8 +214,6 @@ class IfcImporter: self.profile_code("Purge diffs") self.load_file() self.profile_code("Loading file") - self.set_ifc_file() - self.profile_code("Setting file") self.calculate_unit_scale() self.profile_code("Calculate unit scale") self.calculate_model_offset() @@ -973,25 +971,8 @@ class IfcImporter: def load_file(self): self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file) - extension = self.ifc_import_settings.input_file.split(".")[-1] - if extension.lower() == "ifczip": - with tempfile.TemporaryDirectory() as unzipped_path: - with zipfile.ZipFile(self.ifc_import_settings.input_file, "r") as zip_ref: - zip_ref.extractall(unzipped_path) - for filename in Path(unzipped_path).glob("**/*.ifc"): - self.file = ifcopenshell.open(filename) - break - elif extension.lower() == "ifcxml": - self.file = ifcopenshell.file( - ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(self.ifc_import_settings.input_file) - ) - elif extension.lower() == "ifc": - self.file = ifcopenshell.open(self.ifc_import_settings.input_file) - IfcStore.file = self.file - - def set_ifc_file(self): bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file - IfcStore.path = self.ifc_import_settings.input_file + self.file = IfcStore.get_file() def calculate_unit_scale(self): self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) From 5e6343885cf17e33dfcd87116f2d57bc4c7f000e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 21 Aug 2021 21:09:48 +1000 Subject: [PATCH 58/72] Filtered imports is now much faster and works with omitted aggregates --- src/blenderbim/blenderbim/bim/import_ifc.py | 48 ++++++++++++------- .../ifcopenshell/util/attribute.py | 4 +- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index df92f37c47..1458f0f184 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -224,6 +224,8 @@ class IfcImporter: self.profile_code("Create project") self.create_spatial_hierarchy() self.profile_code("Create spatial hierarchy") + self.process_element_filter() + self.profile_code("Process element filter") self.create_aggregates() self.profile_code("Create aggregates") self.create_aggregate_tree() @@ -234,8 +236,6 @@ class IfcImporter: self.profile_code("Create materials") self.create_styles() self.profile_code("Create styles") - self.process_element_filter() - self.profile_code("Process element filter") self.parse_native_elements() self.profile_code("Parsing native elements") self.create_grids() @@ -290,16 +290,20 @@ class IfcImporter: return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit def process_element_filter(self): - if not self.ifc_import_settings.ifc_selector: + if self.ifc_import_settings.ifc_import_filter == "NONE" or not self.ifc_import_settings.ifc_selector: + self.elements = self.file.by_type("IfcElement") return + selector = ifcopenshell.util.selector.Selector() elements = selector.parse(self.file, self.ifc_import_settings.ifc_selector) if self.ifc_import_settings.ifc_import_filter == "WHITELIST": self.filter_mode = "WHITELIST" self.include_elements = set(elements) + self.elements = self.include_elements elif self.ifc_import_settings.ifc_import_filter == "BLACKLIST": - self.exclude_elements = set(elements) self.filter_mode = "BLACKLIST" + self.exclude_elements = set(elements) + self.elements = [e for e in self.file.by_type("IfcElement") if e not in self.exclude_elements] def parse_native_elements(self): if self.filter_mode == "WHITELIST": @@ -637,16 +641,8 @@ class IfcImporter: def create_empty_and_2d_elements(self): curve_products = [] - if self.filter_mode == "WHITELIST": - self.elements = self.include_elements - elif self.filter_mode == "BLACKLIST": - self.elements = [e for e in self.file.by_type("IfcElement") if e not in self.exclude_elements] - else: - self.elements = self.file.by_type("IfcElement") - - for element in self.elements: - if element.id() in self.added_data: - continue + unadded_data = set([e.id() for e in self.elements]) - set(self.added_data) + for element in unadded_data: if element.is_a("IfcPort"): continue if not element.Representation: @@ -1038,9 +1034,17 @@ class IfcImporter: self.add_related_objects(collection, rel_aggregate.RelatedObjects) def create_aggregates(self): - rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")] - for rel_aggregate in rel_aggregates: - self.create_aggregate(rel_aggregate) + if self.filter_mode in ["WHITELIST", "BLACKLIST"]: + rel_aggregates = [e.IsDecomposedBy[0].RelatingObject for e in self.elements if e.IsDecomposedBy] + else: + rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")] + if len(rel_aggregates) > 10000: + # More than 10,000 collections makes Blender unhappy + print("Falling back to SPATIAL_DECOMPOSITION collection mode") + self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION" + else: + for rel_aggregate in rel_aggregates: + self.create_aggregate(rel_aggregate) def create_aggregate_tree(self): for aggregate in self.aggregates.values(): @@ -1191,8 +1195,15 @@ class IfcImporter: ): bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"]) collection = self.spatial_structure_elements[global_id]["blender"] + elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": + # TODO: refactor this to a more holistic collection mode feature + return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj) else: - collection = self.aggregates[element.Decomposes[0].RelatingObject.GlobalId]["blender"] + aggregate = element.Decomposes[0].RelatingObject + aggregate_data = self.aggregates.get(aggregate.GlobalId) + if not aggregate_data: + return self.place_object_in_spatial_tree(aggregate, obj) + collection = aggregate_data["blender"] if collection: collection.objects.link(obj) else: @@ -1472,6 +1483,7 @@ class IfcImportSettings: self.model_offset_coordinates = (0, 0, 0) self.ifc_import_filter = "NONE" self.ifc_selector = "" + self.collection_mode = "DECOMPOSITION" @staticmethod def factory(context, input_file, logger): diff --git a/src/ifcopenshell-python/ifcopenshell/util/attribute.py b/src/ifcopenshell-python/ifcopenshell/util/attribute.py index e874dff672..5d9ebbc0f0 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/attribute.py +++ b/src/ifcopenshell-python/ifcopenshell/util/attribute.py @@ -3,7 +3,9 @@ def get_primitive_type(attribute_or_data_type): data_type = str(attribute_or_data_type.type_of_attribute()) else: data_type = str(attribute_or_data_type) - if data_type.find(" Date: Sat, 21 Aug 2021 21:53:16 +1000 Subject: [PATCH 59/72] Minor fix --- src/blenderbim/blenderbim/bim/import_ifc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 1458f0f184..0b174f0c77 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -641,8 +641,9 @@ class IfcImporter: def create_empty_and_2d_elements(self): curve_products = [] - unadded_data = set([e.id() for e in self.elements]) - set(self.added_data) - for element in unadded_data: + unadded_element_ids = set([e.id() for e in self.elements]) - set(self.added_data.keys()) + for element_id in unadded_element_ids: + element = self.file.by_id(element_id) if element.is_a("IfcPort"): continue if not element.Representation: From 8178dd330d60da63cbe9fb87c8af2786f075104a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 22 Aug 2021 10:53:43 +1000 Subject: [PATCH 60/72] Fix #1679 --- src/blenderbim/blenderbim/bim/module/structural/operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index afc0194e60..5bb3e1469b 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -164,7 +164,7 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator): new.name = attribute.name() new.is_null = value is None new.is_optional = attribute.optional() - if data_type == "select": + if isinstance(data_type, tuple) and data_type[0] == "select": enum_items = [s.name() for s in ifcopenshell.util.attribute.get_select_items(attribute)] new.enum_items = json.dumps(enum_items) if isinstance(value, bool): From 6db1a8a85f4ced41d022ae3dafab32d4915f16db Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 22 Aug 2021 11:04:39 +1000 Subject: [PATCH 61/72] Minor fix --- src/ifcopenshell-python/ifcopenshell/util/attribute.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/attribute.py b/src/ifcopenshell-python/ifcopenshell/util/attribute.py index 5d9ebbc0f0..28b4d4a530 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/attribute.py +++ b/src/ifcopenshell-python/ifcopenshell/util/attribute.py @@ -10,7 +10,9 @@ def get_primitive_type(attribute_or_data_type): elif data_type.find("" in data_type: From 52f4b564b3f43fdc384086f896d90cf5a1b403ce Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 22 Aug 2021 19:11:17 +1000 Subject: [PATCH 62/72] Minor fix. It seems as though StructRNA of type Scene may be removed during undo/redo, which results in handlers failing. --- src/blenderbim/blenderbim/bim/handler.py | 32 ++++++++++-------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 50dfea5ff8..ac0d0206ca 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -166,8 +166,8 @@ def undo_pre(scene): @persistent def undo_post(scene): - if IfcStore.last_transaction != scene.BIMProperties.last_transaction: - IfcStore.last_transaction = scene.BIMProperties.last_transaction + if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: + IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.undo() purge_module_data() IfcStore.update_undo_redo_stack_objects() @@ -181,10 +181,8 @@ def redo_pre(scene): @persistent def redo_post(scene): - if scene is None: - scene = bpy.context.scene - if IfcStore.last_transaction != scene.BIMProperties.last_transaction: - IfcStore.last_transaction = scene.BIMProperties.last_transaction + if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: + IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.redo() purge_module_data() IfcStore.update_undo_redo_stack_objects() @@ -193,9 +191,7 @@ def redo_post(scene): @persistent def ensureIfcExported(scene): - if scene is None: - scene = bpy.context.scene - if IfcStore.get_file() and not scene.BIMProperties.ifc_file: + if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file: bpy.ops.export_ifc.bim("INVOKE_DEFAULT") @@ -228,26 +224,24 @@ def get_application_version(): @persistent def setDefaultProperties(scene): - if scene is None: - scene = bpy.context.scene global global_subscription_owner active_object_key = bpy.types.LayerObjects, "active" bpy.msgbus.subscribe_rna( key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback ) ifcopenshell.api.owner.settings.get_person = ( - lambda ifc: ifc.by_id(int(scene.BIMOwnerProperties.user_person)) - if getPersons(None, None) and scene.BIMOwnerProperties.user_person + lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person)) + if getPersons(None, None) and bpy.context.scene.BIMOwnerProperties.user_person else None ) ifcopenshell.api.owner.settings.get_organisation = ( - lambda ifc: ifc.by_id(int(scene.BIMOwnerProperties.user_organisation)) - if getOrganisations(None, None) and scene.BIMOwnerProperties.user_organisation + lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation)) + if getOrganisations(None, None) and bpy.context.scene.BIMOwnerProperties.user_organisation else None ) ifcopenshell.api.owner.settings.get_application = get_application - if len(scene.DocProperties.drawing_styles) == 0: - drawing_style = scene.DocProperties.drawing_styles.add() + if len(bpy.context.scene.DocProperties.drawing_styles) == 0: + drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = "Technical" drawing_style.render_type = "VIEWPORT" drawing_style.raster_style = json.dumps( @@ -278,7 +272,7 @@ def setDefaultProperties(scene): RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False, } ) - drawing_style = scene.DocProperties.drawing_styles.add() + drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = "Shaded" drawing_style.render_type = "VIEWPORT" drawing_style.raster_style = json.dumps( @@ -309,7 +303,7 @@ def setDefaultProperties(scene): RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False, } ) - drawing_style = scene.DocProperties.drawing_styles.add() + drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = "Blender Default" drawing_style.render_type = "DEFAULT" bpy.ops.bim.save_drawing_style(index="2") From 3c235208ded2fcf50156151c79009870dd77ce02 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 22 Aug 2021 20:54:05 +1000 Subject: [PATCH 63/72] Fix #1679. Again. --- src/blenderbim/blenderbim/bim/prop.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 04dd9c38e8..21fb7b6d3a 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -146,8 +146,17 @@ class StrProperty(PropertyGroup): def updateAttributeValue(self, context): - if getattr(self, str(self.get_value_name()), None): # Do not use get_value since it returns None if is_null is True - self.is_null = False + value_name = self.get_value_name() + if value_name: + value_names = [value_name] + else: + # We may not have a value name in