mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Compare commits
1194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9d003c22f | |||
| 2e71e002fc | |||
| 40ed2fe3ab | |||
| 379fe632a3 | |||
| 2ecaee9024 | |||
| f9a3c9a994 | |||
| 77acbff50b | |||
| 3f2cad0490 | |||
| 5ec974ff94 | |||
| 2c585b649a | |||
| 36c28f3953 | |||
| d1763237d2 | |||
| e8544d78ee | |||
| e715a9467d | |||
| 1f8c12a4c8 | |||
| 89a6eac535 | |||
| d1ffb329c8 | |||
| 4396126a43 | |||
| 7448feed44 | |||
| 6bc4046b17 | |||
| 1a83e52935 | |||
| f16d42ba14 | |||
| 75135fe45d | |||
| de89ab31cf | |||
| c8a4f1bbd2 | |||
| 5be1858767 | |||
| ace1db739f | |||
| 80504310b6 | |||
| a4dae9650f | |||
| c27a995480 | |||
| 5b4c54abd2 | |||
| 57d0beedee | |||
| d0ba675b1b | |||
| f4f2fa9535 | |||
| 67707914b9 | |||
| 12766d61d4 | |||
| 34374cba41 | |||
| f8bb04eae3 | |||
| aee8cbe479 | |||
| cb36e10279 | |||
| 7ca44b5677 | |||
| a6921b83a1 | |||
| c3c94b9c59 | |||
| 576ef336fc | |||
| b1fb541912 | |||
| 6345b052da | |||
| 061554d921 | |||
| bcaa412f4b | |||
| 9f6536b263 | |||
| 226581f76e | |||
| e833d0cd13 | |||
| ed3c95d9d7 | |||
| 7a4823a1bc | |||
| 80c0932174 | |||
| 73a238e7b0 | |||
| b95900f154 | |||
| da66e3c908 | |||
| e04cc47022 | |||
| ce427698d9 | |||
| d3beae1374 | |||
| 389d481364 | |||
| 3efd88b0d6 | |||
| 0a5388f386 | |||
| 71fa002707 | |||
| dd80115aa2 | |||
| bc66710fec | |||
| 921241fbdd | |||
| 44b9dd64f6 | |||
| bfa77255a5 | |||
| b125c651a8 | |||
| eb1c81514b | |||
| 6df5632f91 | |||
| 391a412e65 | |||
| b10415b1b3 | |||
| b1c44d42e3 | |||
| 5f65e21878 | |||
| 0d3be9d101 | |||
| eea585a3cd | |||
| 1789406299 | |||
| b503061dc0 | |||
| 90ccaf4aab | |||
| 05ff47fcff | |||
| c40ecdd550 | |||
| 3a0622e9df | |||
| 0bb03750dc | |||
| 89defa9b6b | |||
| 459aad54aa | |||
| 6f3065f6ce | |||
| 69863ce4a9 | |||
| e5ae19fd34 | |||
| afe617fb93 | |||
| d922f0b9ad | |||
| f9d2d494b1 | |||
| 806aa14bd0 | |||
| 80066ba9e7 | |||
| 7cb1f07a0b | |||
| bdb52dcffd | |||
| 933ad6c801 | |||
| 1594b9505c | |||
| 325745a822 | |||
| 74f8b047fb | |||
| 0f8c6ffe9e | |||
| fa46fe0cf0 | |||
| f0a1efa1f2 | |||
| 71c1058e9e | |||
| 7575bd4dfb | |||
| d697ed3384 | |||
| aed70c8a02 | |||
| e82ca0e932 | |||
| 5910b39766 | |||
| ea9e0c9ab4 | |||
| 2e951eb952 | |||
| 2497611abd | |||
| 90bf56d9ba | |||
| e1b73ef0ef | |||
| bcb5fd7810 | |||
| 40c5145a13 | |||
| e304c3ad31 | |||
| 0494bd9677 | |||
| fc0cdfabb9 | |||
| 2dac137801 | |||
| 8ffa6fcf47 | |||
| 8824e6e2c2 | |||
| 5dbfe539ac | |||
| d1453e1139 | |||
| 5b5dcb3c6d | |||
| 4f467d199e | |||
| a6d20c0cc4 | |||
| d5d942bb2e | |||
| 9e2c7e653f | |||
| 200c0bf773 | |||
| 381b0d379a | |||
| f48b5b9d92 | |||
| 1f47aadc94 | |||
| bb3cd7e49c | |||
| edd01d44de | |||
| 52b899d070 | |||
| 008292df0f | |||
| 87bfd182d7 | |||
| 8fcc622b8a | |||
| 9415878f57 | |||
| 919ac41a74 | |||
| 80093f3777 | |||
| 28f5d6e91c | |||
| 550779deee | |||
| e2cf60f0c1 | |||
| 546a042d40 | |||
| 86b89f2153 | |||
| 34f4aa430a | |||
| 67678cb7f1 | |||
| f05b0d946d | |||
| 486651e0b7 | |||
| 5885924bb7 | |||
| ead849625d | |||
| f52aafdd6a | |||
| b0ae0a2c82 | |||
| 4d688170e0 | |||
| 322a2179e5 | |||
| 8014980b88 | |||
| 65237cab65 | |||
| 155827528a | |||
| fb15435296 | |||
| 6bba3ebeeb | |||
| 3343f3fb35 | |||
| 1b451d45cf | |||
| f5f676cf51 | |||
| 1dd477d382 | |||
| 96d05300bb | |||
| 0073a4c0a2 | |||
| d3a5071d9d | |||
| 471bd34fc6 | |||
| 44cc12763b | |||
| b68aca04ae | |||
| dd22264877 | |||
| cc6ef0268f | |||
| feecb89c8e | |||
| cb5c488cfd | |||
| b68e8dedd2 | |||
| 0975eb455e | |||
| df0e8bba8c | |||
| 71d5239823 | |||
| 92ae79f64e | |||
| 9c1f636cd4 | |||
| 23c60b38fd | |||
| 89c039252b | |||
| 60d85b4259 | |||
| e8a382b02a | |||
| 2b3ec66017 | |||
| 97dd37b345 | |||
| e4877e8246 | |||
| e216d345c1 | |||
| 14c1b70768 | |||
| e15f222a19 | |||
| 4ab2664529 | |||
| bd0cfa5366 | |||
| 2e4b0e83a2 | |||
| cf61111386 | |||
| 3aa5ddb070 | |||
| 25dacc8fed | |||
| 7cba63af9c | |||
| 3c1d6abc7e | |||
| 8da4b9f23f | |||
| 99fa77edee | |||
| 839c5ad932 | |||
| b68ce08811 | |||
| e662308665 | |||
| a2ccff67ca | |||
| 8e045d772c | |||
| 16fd614886 | |||
| f31df307c6 | |||
| 11873a7c51 | |||
| ccccbe23f4 | |||
| 2173a562be | |||
| a11f81f2ce | |||
| d3b553c4fb | |||
| 5eb11c3516 | |||
| c195799801 | |||
| ac5a44a962 | |||
| 86880e9e62 | |||
| f8ccdf1170 | |||
| e3d8473d47 | |||
| bd5d4cecbf | |||
| 2ede8a2206 | |||
| cb77f9bd26 | |||
| 0bb21fedae | |||
| 478f90c2d4 | |||
| a016f6e5c8 | |||
| 1b2fa4f994 | |||
| 2d8383efc6 | |||
| add5132dec | |||
| fe333051df | |||
| 8d1b533f91 | |||
| fb304e141a | |||
| bd094f930e | |||
| b61000d2fe | |||
| c72cadb234 | |||
| 10642177a9 | |||
| 0b8ea28463 | |||
| 9b24f54f0d | |||
| 289645dacc | |||
| c365acb517 | |||
| 785cf56621 | |||
| 60617eecce | |||
| 26c68c00b7 | |||
| ab0a602f9f | |||
| 066326ab3b | |||
| 02713dbd39 | |||
| d58dae5d2f | |||
| ca3685900f | |||
| 5e10e61aca | |||
| 92e25ce3cb | |||
| 804a670454 | |||
| efa8382fc5 | |||
| 92f9d5f506 | |||
| 8800c14105 | |||
| ec2d0bfa4a | |||
| d684d47dc8 | |||
| babf95785c | |||
| f6ebf80fab | |||
| ea2cff3311 | |||
| bf4aa8cbb1 | |||
| 8b751577f4 | |||
| 6fbe982623 | |||
| 0b93694097 | |||
| 2027c71d99 | |||
| 6da51c2a6e | |||
| 53def55dc9 | |||
| 8722350c0b | |||
| 34c92d4d9c | |||
| 725cb40ef2 | |||
| 0c81b41ac7 | |||
| a8a82450a5 | |||
| 1b5b9fdcf6 | |||
| af1e542064 | |||
| 78154d17bf | |||
| 9bc8012aa4 | |||
| 407962ec93 | |||
| 21fd09b424 | |||
| 2264e69a1e | |||
| e02d6c3f4b | |||
| ec0abb7b37 | |||
| 477becab0b | |||
| 60bf424461 | |||
| 923e8423f9 | |||
| e228095391 | |||
| 1492a660c8 | |||
| 7a931ea6f2 | |||
| 2d59bb9cb8 | |||
| 06f5ded038 | |||
| 91f371a46e | |||
| 573e53ebfd | |||
| a492be5971 | |||
| ceac85ca02 | |||
| bfa981709a | |||
| c64b477e53 | |||
| 4f7501e1e8 | |||
| a1f20ec7bb | |||
| e2ee6c6e56 | |||
| 6940a05f2b | |||
| ff8f4ecd3c | |||
| de2bd3ff1a | |||
| cfa21b3cbf | |||
| db7c3e3b01 | |||
| 0532ceb029 | |||
| 0fe869c169 | |||
| bcde68d851 | |||
| 960a90e36c | |||
| aea0395fd6 | |||
| 416d51f7c7 | |||
| a9b13fd056 | |||
| 080056806e | |||
| 7a33a9eb02 | |||
| 07ba483a13 | |||
| 4925741060 | |||
| 7c4505b2fd | |||
| f9ee838cd2 | |||
| cce0c39ab2 | |||
| cd57fd4b1d | |||
| 6b705f0448 | |||
| 9cff6c87f0 | |||
| cac41064e0 | |||
| 350cf0406d | |||
| 47fd52b7c1 | |||
| 8a33e2fea1 | |||
| d4c02a312a | |||
| c20294b993 | |||
| 7500631b81 | |||
| 7cc1a849a6 | |||
| 4df93af1fd | |||
| fc1c8f2071 | |||
| deedffa84e | |||
| e8d924426f | |||
| 5a3f809d03 | |||
| 58ffd8bf62 | |||
| b22452aaac | |||
| 6c05e2ea8c | |||
| a1773b6f46 | |||
| b3583d4191 | |||
| 50e8d9f67e | |||
| 0d4819d6e7 | |||
| ce91d296b6 | |||
| 21137b9268 | |||
| 0dc319e510 | |||
| 798beb0030 | |||
| a717b348e0 | |||
| 14ed4d1d08 | |||
| bd3250aff6 | |||
| 1ee5bb73fa | |||
| 9a6dc54f6c | |||
| ab59173135 | |||
| b6abb24858 | |||
| 2b24d5ebe5 | |||
| 10a99fff05 | |||
| 64aa1d07fb | |||
| 730eafa74f | |||
| c077727374 | |||
| 2e4241e435 | |||
| 8a03a1379e | |||
| 8bb7ef5e25 | |||
| da4508ef9a | |||
| f3dedba320 | |||
| f659b9bb7b | |||
| 488dee82d7 | |||
| 3c4020be8d | |||
| ede4e49c0c | |||
| 3d2f361585 | |||
| 4505ed1c98 | |||
| 4cbad8804f | |||
| 3ec7b71565 | |||
| c0ec2e7e2b | |||
| 048a94ddc5 | |||
| ec43415822 | |||
| 235eb3c83c | |||
| 4d0737c49b | |||
| b499686c7c | |||
| 1857d06245 | |||
| 663d233e3c | |||
| fa1936c10a | |||
| 8c89ba497b | |||
| 2284ddd68c | |||
| 8daf6b0b04 | |||
| e7a6f86299 | |||
| cae711b393 | |||
| 566c9afa77 | |||
| 867852688e | |||
| 94851016d7 | |||
| 1dd3c298df | |||
| c384ac26cd | |||
| a57080da74 | |||
| 71e59fe4c2 | |||
| aedae0b3c1 | |||
| 658f9b87a5 | |||
| fa9105e174 | |||
| d0d5f972df | |||
| f5220fba56 | |||
| 365f4f114b | |||
| b5b64d6c4a | |||
| 632e45af18 | |||
| 559636ef0b | |||
| 5a2ed576c4 | |||
| cc7732ecbf | |||
| a3c6e295c8 | |||
| 87069f3ac3 | |||
| 6469f79343 | |||
| a3b04997ef | |||
| 4c1af9e2f5 | |||
| 646f2f2e32 | |||
| 629d6c85ae | |||
| be66d4884d | |||
| 5de96c9f95 | |||
| c661bb62ca | |||
| b1b95ec2db | |||
| 5f50e6fcf1 | |||
| 23b48c43a6 | |||
| 75e1d0e3c7 | |||
| 0d942f968a | |||
| b5ccb93618 | |||
| 41fb8ca583 | |||
| a86904dd41 | |||
| 8a82343e27 | |||
| 8a841b449a | |||
| 24bbe9fb69 | |||
| 9a38455465 | |||
| ced52862a1 | |||
| f0cd8c59c7 | |||
| e795cec56b | |||
| b1248be1b8 | |||
| 79672f795e | |||
| 6ec12fe7c7 | |||
| 4ee6863d43 | |||
| 6497d5f78e | |||
| 5b96df89ea | |||
| ce4ad3c3a7 | |||
| 8d714ed3bc | |||
| 9d99de2340 | |||
| 41f9b1522e | |||
| 42b1cf039d | |||
| db832946e6 | |||
| 05e8680b48 | |||
| e8b1b6f783 | |||
| 9367da61a0 | |||
| c7e53e7762 | |||
| 2260f9983c | |||
| e68cc7201c | |||
| e2b39620a0 | |||
| 36da945e2d | |||
| 371aeed3f6 | |||
| 86902c159b | |||
| f4be8812e2 | |||
| 12bcf1d2c7 | |||
| 10e805b3f1 | |||
| 1e9239b352 | |||
| 7bea3d9d10 | |||
| e9bad17502 | |||
| be79d3712d | |||
| e13dbd4767 | |||
| c61d4bb70a | |||
| ee632b7c37 | |||
| f86e5abb85 | |||
| b5275f5ab2 | |||
| a925226cbc | |||
| 06a89f9b17 | |||
| 231d047ce9 | |||
| a50fc3184b | |||
| de8c8d3bed | |||
| f8e523daff | |||
| 8ca9bd63dc | |||
| 761b69ebfe | |||
| e28979ceb7 | |||
| d552f247fa | |||
| 8ef083e3af | |||
| 49bbf0cb9e | |||
| a2f0bc8a17 | |||
| 62295beab2 | |||
| a0b93265c2 | |||
| 338d3cf089 | |||
| 39702cc543 | |||
| a9c0c1f4ba | |||
| abfb56350d | |||
| fcb263f59a | |||
| 679fa6d876 | |||
| 876c5ac2e6 | |||
| 29a86c6748 | |||
| a4a04e6941 | |||
| 9aafa32008 | |||
| 4f8e37d733 | |||
| 311f279b61 | |||
| 3ad7c52cc4 | |||
| a5d9fe551a | |||
| 1672a7da32 | |||
| 10dcd9a923 | |||
| be72887216 | |||
| 5400712a7a | |||
| 1dea8ca37b | |||
| f945a94fed | |||
| 14ae8680e3 | |||
| 9ec0732a84 | |||
| 7b490bbc69 | |||
| 0cf3bb3da9 | |||
| 062bf8e60b | |||
| f457a7d831 | |||
| 1861baaaa9 | |||
| 39253c6a6d | |||
| 21137de8a0 | |||
| a53017d9dc | |||
| 70286f2bcb | |||
| 26467a1d0e | |||
| 09f9b23153 | |||
| 6ac95e1ed3 | |||
| 25c0ed6759 | |||
| f75d629990 | |||
| b492110652 | |||
| 942cb586b2 | |||
| 49b9f99557 | |||
| 15b69bfa98 | |||
| 5f964bd33c | |||
| 274a5cbfad | |||
| b6fa1759ef | |||
| f016d72787 | |||
| 8c12bd0322 | |||
| 90e6838ddb | |||
| 5304072892 | |||
| 59a5bf2344 | |||
| d4918edcd9 | |||
| d901506029 | |||
| aaf980b6b3 | |||
| d419e50ece | |||
| d5ff48fbc9 | |||
| 99ca1c7eb4 | |||
| df4f565176 | |||
| ed37ac3066 | |||
| 1433240ae6 | |||
| d9fe751163 | |||
| 555621ceae | |||
| d7ddbd2b99 | |||
| eba3ced5fe | |||
| d36205ea8b | |||
| a831884ee6 | |||
| 01b64674ab | |||
| 78958567cf | |||
| e6ea58cd72 | |||
| 6c607241a2 | |||
| 18ad13cb8b | |||
| 55e54f0b37 | |||
| 453949ee01 | |||
| e4d08ce6b7 | |||
| ce02e7a2a0 | |||
| f43ddf2cd5 | |||
| 9d0eb4f994 | |||
| 67fb569669 | |||
| da9be7cff0 | |||
| a839183ac2 | |||
| 34bf374047 | |||
| 43c35eaf24 | |||
| d91362a6f7 | |||
| 76e3b8dd55 | |||
| d004410ccb | |||
| abbc5c2159 | |||
| ae771cb37d | |||
| b1a0b7a7d0 | |||
| 556eb128d6 | |||
| dfaa8d510c | |||
| 93c2a4fe66 | |||
| df726a2d61 | |||
| 2cf5011813 | |||
| 9330e73a70 | |||
| a73c60a467 | |||
| f29b0d776a | |||
| e6a7c5334b | |||
| 54b448cdb4 | |||
| 6bbff22a45 | |||
| 51ae3f4c5e | |||
| df1b748b39 | |||
| 01ea1f627d | |||
| 0d3545c621 | |||
| 12cfa21d9b | |||
| c8a60cc4d5 | |||
| 6702e912e7 | |||
| b5aeab6404 | |||
| 7eea995b88 | |||
| d9265765c5 | |||
| d2a560d144 | |||
| 974e62290c | |||
| 1a1583405c | |||
| ef10c23842 | |||
| 77acfd7069 | |||
| 79546807d9 | |||
| e14974a1b5 | |||
| 4b5aa1221c | |||
| b448bea279 | |||
| a219d335ea | |||
| 47f7afb33b | |||
| 3245d0af5c | |||
| b6712602e2 | |||
| 2c6956ec02 | |||
| 322fbe27d6 | |||
| a65fa1e606 | |||
| 71a6bf25bc | |||
| 43fbdf57c8 | |||
| 39100b7c97 | |||
| 1cd32be2fc | |||
| c090073858 | |||
| c97a1faecd | |||
| 9dd7b488d6 | |||
| 4dca91c56e | |||
| b92871818a | |||
| d01bf9690d | |||
| 604e0b0d94 | |||
| 8420b98f3f | |||
| 46693baacf | |||
| c665c9d7e5 | |||
| b05c83e612 | |||
| a958f1373c | |||
| b2c20b15b4 | |||
| b9b12520f7 | |||
| 11a048bf18 | |||
| 5a06715843 | |||
| 6d9650e826 | |||
| 2369f72798 | |||
| 234cb76cd7 | |||
| 0cd5c673c3 | |||
| 1de5940294 | |||
| a0cc94a614 | |||
| 4835829700 | |||
| e5fe2df552 | |||
| 5719c3db2b | |||
| 9109fcec12 | |||
| 4e71ae7d85 | |||
| 96336d6f68 | |||
| 115d3d4498 | |||
| 6095c523d7 | |||
| 71f1a7d0a4 | |||
| a0c92638d2 | |||
| ff3fa48332 | |||
| 8888e45115 | |||
| 92d4a89a43 | |||
| ea53c96134 | |||
| 2f62064814 | |||
| 722e819e11 | |||
| 862aa71a77 | |||
| e920379ca3 | |||
| 4011666162 | |||
| 9d9e9ed4cb | |||
| 9331e89d15 | |||
| 8903d6a85c | |||
| cb551e1248 | |||
| 915c38947d | |||
| 190d9f8e3f | |||
| d2c7c1532c | |||
| f7e8643f30 | |||
| dccd9d4139 | |||
| d31aafac5b | |||
| 2949a86f09 | |||
| 4cbf551c75 | |||
| 3ef5b13bf8 | |||
| f1114745fb | |||
| 1d2b1dcbc8 | |||
| ea360733e4 | |||
| 1103cb6c03 | |||
| 6bf39b3d6a | |||
| b971135ce4 | |||
| ef24bb52a7 | |||
| 6286d85850 | |||
| 3d82eb954f | |||
| 2951aecc14 | |||
| c4d54f7709 | |||
| bd8cdf5a82 | |||
| 53e9837858 | |||
| ddbe62772d | |||
| f9dc7d5d94 | |||
| aa97cc25d1 | |||
| 0088f3933f | |||
| 0054b27254 | |||
| 126accd6a2 | |||
| bd0e16cee5 | |||
| f5cb2dd21f | |||
| b024ba05da | |||
| 1fcf2fb53f | |||
| 6bc318b15c | |||
| 40a895b5ad | |||
| 3cd2963b01 | |||
| ff3c5b849e | |||
| 8294dacaa4 | |||
| ca03d5d0c5 | |||
| 74a3e8a4e6 | |||
| 41142c73bf | |||
| 6c0e2ce323 | |||
| 89c54291b0 | |||
| 7311777a57 | |||
| 380434f9dd | |||
| 4d137171ec | |||
| 8d3b8b24f8 | |||
| 9e5b667380 | |||
| 61fff3d2b4 | |||
| 94a3d58e49 | |||
| d57777290b | |||
| ffd1d52b4e | |||
| 6a42ac7733 | |||
| 29ba0f3c03 | |||
| 64a9c1f473 | |||
| 3342bd923d | |||
| 95820516cf | |||
| e230270218 | |||
| 3423e6b13d | |||
| 34e699687d | |||
| 63f7315d90 | |||
| a955d2bdee | |||
| a4045a37f1 | |||
| 41128a397b | |||
| 3dbc1fddcb | |||
| 8c5830ddab | |||
| a6a27442fb | |||
| 55e97fd170 | |||
| 3e5a2e5eb2 | |||
| b5e977d7f3 | |||
| 4423e0bb10 | |||
| e54dce5dcf | |||
| 39f4abe243 | |||
| f60abdd18f | |||
| 9f92e4853f | |||
| f8f48796a9 | |||
| 7c2ba4b18a | |||
| ce6505847a | |||
| 8b1ffea91a | |||
| 762f4ff975 | |||
| 9d2a79803a | |||
| 2597fcb463 | |||
| 36b86bbb54 | |||
| 858e8743ba | |||
| 3f09dff79e | |||
| fe130ebb5a | |||
| b8f4149603 | |||
| f2f28f8ba6 | |||
| 12c795a482 | |||
| c2730a81d6 | |||
| 2855e96e53 | |||
| 4b7dfcdae5 | |||
| 67d405ba76 | |||
| d671d6bed1 | |||
| 6483fee792 | |||
| cee034b5b2 | |||
| 0132edd774 | |||
| ed8a31c54b | |||
| 3d30d809d1 | |||
| 5b0503e721 | |||
| 6924012dc8 | |||
| 007c451a77 | |||
| 358395c237 | |||
| e7652a4846 | |||
| 630cb5f2dc | |||
| 117cb1ba5a | |||
| 33f2f2e49e | |||
| 0f6cc23c28 | |||
| ce154cc8d2 | |||
| d7d592de6f | |||
| 2f23f3cdd7 | |||
| 6757026e3e | |||
| 2154200697 | |||
| 6ffcf363a6 | |||
| bf6e02f99d | |||
| 15a31074e8 | |||
| c9eb345491 | |||
| 7e2b9dc7f3 | |||
| 7a2dc5c0c5 | |||
| 51afe03b78 | |||
| be8e96d918 | |||
| 071353eddc | |||
| d1bcd887ca | |||
| a05bae78ff | |||
| 31e9b125e3 | |||
| d0d913a97b | |||
| 50ff363501 | |||
| 1c06d12efc | |||
| eb7550b63e | |||
| 9d83eec674 | |||
| 4cf4628e63 | |||
| a498e75712 | |||
| 8a4739f9c9 | |||
| c6093430c2 | |||
| 57658eb5df | |||
| f25ad2a9cb | |||
| 3b8b4c1566 | |||
| d2939067bd | |||
| 37c05f763e | |||
| e00a865bce | |||
| 4d42512b6a | |||
| 037fd22b51 | |||
| 1ad2c62a22 | |||
| 823eb7d199 | |||
| 7038cbe871 | |||
| 1cb44da76c | |||
| 291b313d79 | |||
| cf13c6fdc1 | |||
| 5bd9a58936 | |||
| ff75875045 | |||
| 4ff0fd56e9 | |||
| 9cc5338602 | |||
| 37a62e3004 | |||
| 9cc1347dd8 | |||
| 716919d052 | |||
| c82a7b708c | |||
| 5acacaa9d8 | |||
| e386c82ada | |||
| 18b2364d52 | |||
| 7cd423d423 | |||
| edd2dcc6c6 | |||
| 486e622f77 | |||
| 5ad566f42d | |||
| aec286d85b | |||
| 38b594d159 | |||
| 5166416c61 | |||
| c700a32b09 | |||
| 0cb6d9de72 | |||
| cdeeab4b31 | |||
| 23944d6060 | |||
| e13ba7627a | |||
| 9e6c0758d5 | |||
| 6932d2a268 | |||
| 638e1ebcec | |||
| 106b02fad1 | |||
| 54522aab6a | |||
| eb5df2f8c3 | |||
| c8dbb72f69 | |||
| e56f09071a | |||
| a824141b90 | |||
| bb56882879 | |||
| ed0e825283 | |||
| c12ed46c49 | |||
| f3c72920de | |||
| 3ef7f1c141 | |||
| 3e0ef46a37 | |||
| 689d8d1644 | |||
| 9bf799e69e | |||
| c27f89a109 | |||
| 651d924759 | |||
| 17d6b8af61 | |||
| 4a2411c05d | |||
| 9f9abaadc8 | |||
| 99d7f5ad7a | |||
| bf7171b7ba | |||
| 1ac5a8d785 | |||
| 9004c5d153 | |||
| 99cdc4da35 | |||
| 3c338db364 | |||
| 7322cc9018 | |||
| dcebd55031 | |||
| 1d789da066 | |||
| dd1c47f9dc | |||
| 98e64c1830 | |||
| e8a4f7947d | |||
| 05a8824019 | |||
| cfd5d2a4eb | |||
| b79256ce32 | |||
| d8ae33dd2a | |||
| b76b05fc71 | |||
| f5b28c0df8 | |||
| a99bfd9294 | |||
| cbdd091d6f | |||
| 82fcdfc160 | |||
| cb724dbfde | |||
| 973b6be2aa | |||
| 7eba094a39 | |||
| 6bfa2aaf10 | |||
| 03e09a97c6 | |||
| 6e9ec4861e | |||
| d3bca858b0 | |||
| ba3e5ae28c | |||
| 516f8cdb15 | |||
| f3efb1601a | |||
| f8a933e094 | |||
| 0a50a3cb24 | |||
| e50215ddce | |||
| f24adcdadb | |||
| 84f6632c13 | |||
| c82191f40b | |||
| 3dde609519 | |||
| ed65c039dc | |||
| dd5d997d47 | |||
| d6a3900f3e | |||
| 64d3951d5e | |||
| 0ef255bc3c | |||
| 8f5905c801 | |||
| 7ac7190d57 | |||
| 06ad541c56 | |||
| b4740b6949 | |||
| d66dd1be18 | |||
| adfe56c777 | |||
| dcf8329b04 | |||
| d49a3486b9 | |||
| 78759a63f2 | |||
| 913076de94 | |||
| e318cbd9e6 | |||
| 2f1de2f104 | |||
| 0e74ac8eea | |||
| 31ec4820fc | |||
| 917e7f9f15 | |||
| 2f0014a489 | |||
| 92340f159d | |||
| a392a1fbfb | |||
| 728f9e2c04 | |||
| 2edfe1e5d8 | |||
| 0b48120a66 | |||
| 577d46eb70 | |||
| 83a3813f53 | |||
| dd14f33c0b | |||
| b305e2354b | |||
| afb2742c2f | |||
| 55d533309e | |||
| 00f7871386 | |||
| f15a23483e | |||
| f14b4c467d | |||
| 4a9cf433d1 | |||
| 866362c6ed | |||
| 0dc17a2141 | |||
| d55e7faf2a | |||
| 52762f828c | |||
| 1d0868eb18 | |||
| 9461c6369e | |||
| 0f3b570800 | |||
| 30cbfad871 | |||
| 262739f37a | |||
| 03fa850ce2 | |||
| 8d2c0d0785 | |||
| 742a9df311 | |||
| c95a0e8ed5 | |||
| a28478c193 | |||
| b521901c90 | |||
| 20f8737fb3 | |||
| f18c93c9ee | |||
| a461281b1e | |||
| 2d3d747d49 | |||
| d165cd2692 | |||
| 3f0e30245b | |||
| c111aafd3a | |||
| 465bd23d6f | |||
| c7d3e00dca | |||
| 72af88a4b3 | |||
| 73b76ba666 | |||
| e93a66be49 | |||
| a4dd1c3463 | |||
| 6d8e513f97 | |||
| baeaa073df | |||
| f07391181d | |||
| 722f71b3b3 | |||
| 76e4ec0069 | |||
| df18124bec | |||
| 370488cd27 | |||
| 4bbc463c8f | |||
| 4a11a117ad | |||
| 19be8107b3 | |||
| e1976d5bfd | |||
| 0fcc47508a | |||
| 76f0b57ff1 | |||
| 1d8ee406af | |||
| 05cb06a28e | |||
| 8a6eb1b60f | |||
| 37d4ee97e7 | |||
| ef5d1448da | |||
| eed42b8fac | |||
| 1f0ac8fdb9 | |||
| 1b1a1f143a | |||
| 9649c23a3b | |||
| 0ad5ace35f | |||
| f7fdb8cc81 | |||
| 41e64781d8 | |||
| 6ea7ee84a6 | |||
| e7b86ae5b0 | |||
| f6035609c9 | |||
| 76feeaccc9 | |||
| af02a6b3a6 | |||
| 832e68cb6f | |||
| a8268e8323 | |||
| 5a4ad69bf7 | |||
| 40f92d15c3 | |||
| ad8d02bfed | |||
| 5866994b84 | |||
| 2ec2ac0e5c | |||
| c7555652d7 | |||
| 5a7ce6d68b | |||
| ad8913a979 | |||
| 188b6a03a8 | |||
| 7f3860b025 | |||
| d08756d1b4 | |||
| 525ef2b77b | |||
| fca2bf7aa8 | |||
| ff82262490 | |||
| c82351476a | |||
| d4f32802aa | |||
| d7668c3e3f | |||
| 1445f91289 | |||
| 44e27f706a | |||
| 416ebde333 | |||
| 40dcd393c6 | |||
| 4fda916c2f | |||
| acb9953ce3 | |||
| 4154356288 | |||
| 54488bddbb | |||
| 8f21866248 | |||
| f3b63aae76 | |||
| 3f05bab4e5 | |||
| 8534333092 | |||
| 5f2b4c96a6 | |||
| 00836ec983 | |||
| fb78f75541 | |||
| 5948282ed0 | |||
| 47b81a6f89 | |||
| 8ae08cb90e | |||
| 7796856119 | |||
| f75045f0c5 | |||
| e52af2baad | |||
| da4ae4c2f2 | |||
| 28043a8e5d | |||
| 0e8fb44301 | |||
| b97dca5d47 | |||
| 207db459a7 | |||
| 026eebf39b | |||
| 5f5c0b41d5 | |||
| 3055dfa1a6 | |||
| b59bdb585b | |||
| ccd97c66ae | |||
| e6747b258c | |||
| 6aa6803490 | |||
| 72fbe3a6d9 | |||
| 68a602573b | |||
| 1e06dfcb76 | |||
| 3a0a912022 | |||
| b16fb7d917 | |||
| 94d2d32d18 | |||
| e743db27f4 | |||
| bb329affb8 | |||
| 6c4e4e5f91 | |||
| a31aa6053e | |||
| b90f146a68 | |||
| f67dca8de0 | |||
| a686d70908 | |||
| 38fe7fea65 | |||
| b26890f74b | |||
| 2c16247943 | |||
| 53fc7bce0c | |||
| f5db905223 | |||
| 3557b11e64 | |||
| c420402334 | |||
| 589c562cba | |||
| d3f2711fe4 | |||
| 8b4305360e | |||
| 1213bbdd73 | |||
| c3380daa19 | |||
| 7aa87907d7 | |||
| 8cfd6b1754 | |||
| fc10915d2b | |||
| c23c21cb88 | |||
| 260bc80a4b | |||
| d7cf803fdf | |||
| 4488044a68 | |||
| 21a4db7823 | |||
| d300454118 | |||
| 553003e664 | |||
| 524561a398 | |||
| 16f82683c8 | |||
| 4dbec59b9c | |||
| dcf69c46e5 | |||
| 611e83ff1e | |||
| bdcebd0f98 | |||
| 9278974d98 | |||
| b3a708c06c | |||
| 0b05c3bcfc | |||
| fef6ef76ed | |||
| e39d62b35a | |||
| f1827d02ca | |||
| 65ccc635d4 | |||
| 2698ba69d5 | |||
| efb3b9d255 | |||
| b0ec185c34 | |||
| 0ca92f6a1d | |||
| 6e4b784652 | |||
| c764da3511 | |||
| c0d97051fd | |||
| 2a199cf8a7 | |||
| b5dacfd621 | |||
| 1a67ce87a1 | |||
| b7db741474 | |||
| 7b1aa207d8 | |||
| 9b58d4aa47 | |||
| bc2d67220e | |||
| 7858cb128e | |||
| 0d859bd4ee | |||
| 360afe68aa | |||
| b6af68bfe6 | |||
| 7440c321b6 | |||
| 73b5c2f1ae | |||
| 485ce22ca7 | |||
| e92520b777 | |||
| 00414a1590 | |||
| 6b6bf248ff | |||
| 03fcd59c5a | |||
| 5e89748ba6 | |||
| c5987bca3d | |||
| 94f98b1e0e | |||
| 38dcdde7ae | |||
| 95f8af01f0 | |||
| 8f0d053049 | |||
| caccfd2516 | |||
| b4c4efcf5b | |||
| fe81dc7f21 | |||
| 4f77c41bd5 | |||
| 47b077b555 | |||
| 845e034614 | |||
| 82d020b20d | |||
| cd73f80894 | |||
| 705a924f41 | |||
| 097e03d6f5 | |||
| 4a69df1f6b | |||
| df69ca27da | |||
| b3778facda | |||
| c333535ae5 | |||
| fcaeb920ec | |||
| 50d3cab1ec | |||
| 4574351817 | |||
| 623997b40d | |||
| 8f7b965a58 | |||
| c1e6e7d736 | |||
| f684a6058c | |||
| 1a80e3f1a1 | |||
| a76af3c2ba | |||
| 28bee52dff | |||
| 58449368fb | |||
| 30c33518f0 | |||
| 3a06afae25 | |||
| 98c479f1bd | |||
| d875baefde | |||
| 9149bb0fdc | |||
| f3c86d7fcc | |||
| 64aeef7103 | |||
| cd1bda5311 | |||
| 67ae9fced0 | |||
| 22fdb324ac | |||
| 263d238f9c | |||
| c6fd02869b | |||
| b4d41790a5 | |||
| e18346d4ea | |||
| c724b2a888 | |||
| d05f320665 | |||
| 6599f74d11 | |||
| 4f03c7e03e | |||
| 946cae4065 | |||
| 98145fbbd9 | |||
| 74598b805b | |||
| 90ab846a18 | |||
| 5ec77b1b1c | |||
| 8d079ce0cb | |||
| fb9edb733b | |||
| 42b777b1f0 | |||
| a968cad4cc | |||
| 7ab03109b9 | |||
| 6782729ed8 | |||
| 9dc75ece44 | |||
| cbd78506ce | |||
| 9af15aeb0b | |||
| 934c9e1485 | |||
| bbebdd56ca | |||
| b1bfe5c3b3 | |||
| a72e1231f2 | |||
| 47da4ef3ae | |||
| 08a8afba5b | |||
| 44e5f89119 | |||
| c68b0c7280 | |||
| 8c5affaae6 | |||
| bd4b80bdc2 | |||
| 5a597d1a84 | |||
| 61c4797b8f | |||
| c935b2fc12 | |||
| 425de7b857 | |||
| 8c466eab00 | |||
| 929611f384 | |||
| 8af4afa781 | |||
| 8a3c979172 | |||
| 15bbb3b779 | |||
| 6cefb2e991 | |||
| 10682fb226 | |||
| df6b7a34f7 | |||
| 67932a22c3 | |||
| 9f49ad5f71 | |||
| ce4407ff3c | |||
| c58c8269af | |||
| c0f148cd3c | |||
| 1d9dcb3ab7 | |||
| a420c4b707 | |||
| 048f77cfed | |||
| b4d0cfe0c8 | |||
| 69139dee5c | |||
| e82cfda6a3 | |||
| 5c9ff09517 | |||
| fb1d78b311 | |||
| da4e86a1d5 |
@@ -11,13 +11,9 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos
|
||||
runner: macos-13
|
||||
runner: macos-14
|
||||
arch: x64
|
||||
oldarch:
|
||||
- os: macos
|
||||
runner: macos-14
|
||||
arch: arm64
|
||||
oldarch: m1
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -34,15 +30,11 @@ jobs:
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install git bison autoconf automake libffi cmake findutils
|
||||
# preinstalled: xz, cmake
|
||||
brew install git bison autoconf automake libffi findutils
|
||||
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install aws cli
|
||||
@@ -51,20 +43,49 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \;
|
||||
rm -rf ./build/*/*/*/install/*gmp* ./build/*/*/*/install/*cgal*
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}-${{ matrix.os }}
|
||||
key: mac-${{ matrix.arch }}
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" == "macos" ]; then
|
||||
DARWIN_C_SOURCE=-D_DARWIN_C_SOURCE
|
||||
fi
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
if [ "${{ matrix.arch }}" == "x64" ]; then
|
||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
MAC_INTEL=-mac-cross-compile-intel
|
||||
# We don't use gmpcxx, but it comes preinstalled on macos runner as arm64 bottle
|
||||
# and CGAL detects it and breaks cross compilation.
|
||||
brew uninstall --ignore-dependencies gmp
|
||||
# Otherwise Python will fallback to use arm64 `pkg-config`,
|
||||
# will pick up arm64 libraries ('zstd' in particular),
|
||||
# and break the build.
|
||||
/usr/local/bin/brew install pkg-config
|
||||
# Required by Python.
|
||||
/usr/local/bin/brew install gettext openssl
|
||||
fi
|
||||
set -o pipefail
|
||||
IFCOS_SCHEMAS=4 CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release \
|
||||
python3 ./nix/build-all.py -v -py-313 --diskcleanup ${MAC_INTEL} \
|
||||
| tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-osx-${{ matrix.arch }}
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/*/logs/*.log
|
||||
build/*/*/*/build/ifcopenshell/**/CMakeCache.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
@@ -85,7 +106,7 @@ jobs:
|
||||
- name: Package .zip archives
|
||||
run: |
|
||||
VERSION=v`cat VERSION`
|
||||
cd ./build/`uname`/*/10.9/install/ifcopenshell
|
||||
cd ./build/`uname`/*/10.15/install/ifcopenshell
|
||||
mkdir ~/output
|
||||
|
||||
ls -d python-* | while read py_version; do
|
||||
|
||||
@@ -14,33 +14,67 @@ jobs:
|
||||
submodules: recursive
|
||||
path: IfcOpenShell
|
||||
|
||||
- name: Checkout Pyodide
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
repository: pyodide/pyodide
|
||||
ref: '0.28.0a3'
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ifcopenshell_build
|
||||
ref: wasm
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
path: pyodide
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
cd ifcopenshell_build
|
||||
python ../IfcOpenShell/pyodide/cache_dependencies.py unpack
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
VERSION=`cat IfcOpenShell/VERSION`
|
||||
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/meta.yaml
|
||||
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/setup.py
|
||||
echo '#!/usr/bin/bash' > script.sh
|
||||
echo 'cd pyodide' >> script.sh
|
||||
echo 'make' >> script.sh
|
||||
echo 'cd ..' >> script.sh
|
||||
echo 'mkdir -p packages/ifcopenshell' >> script.sh
|
||||
echo 'cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell' >> script.sh
|
||||
echo 'PYODIDE_ROOT=/src/pyodide \' >> script.sh
|
||||
echo 'PATH=/src/pyodide/emsdk/emsdk:/src/pyodide/emsdk/emsdk/node/22.16.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> script.sh
|
||||
echo 'pyodide build-recipes ifcopenshell --install' >> script.sh
|
||||
chmod +x script.sh
|
||||
sed -i s/--tty// pyodide/run_docker
|
||||
pyodide/run_docker ./script.sh
|
||||
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp313-cp313-emscripten_4_0_9_wasm32.whl
|
||||
./IfcOpenShell/pyodide/build_pyodide.sh
|
||||
FILE=`echo dist/ifcopenshell-*.whl`
|
||||
NEW_FILE=`echo $FILE | sed "s/-/+${GITHUB_SHA:0:7}-/2"`
|
||||
mv $FILE $NEW_FILE
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-pyodide
|
||||
path: |
|
||||
ifcopenshell_build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Run wheel tests
|
||||
run: |
|
||||
cp -r IfcOpenShell/pyodide/test test
|
||||
# venv set up in build_pyodide.sh.
|
||||
source .venv/bin/activate
|
||||
uv pip install pytest-pyodide
|
||||
PYODIDE_ROOT_DIST=`pyodide config get pyodide_root`/dist
|
||||
# `pytest-pyodide` requires pyodide in 'pyodide' directory in cwd, when running `pytest`.
|
||||
cp -r $PYODIDE_ROOT_DIST test/pyodide
|
||||
cp dist/ifcopenshell-*.whl test/pyodide
|
||||
cd test
|
||||
pytest --capture=no
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
cd ifcopenshell_build
|
||||
python ../IfcOpenShell/pyodide/cache_dependencies.py pack
|
||||
|
||||
- name: Commit and Push Changes to Build Repository
|
||||
run: |
|
||||
cd ifcopenshell_build
|
||||
git config user.name "IfcOpenBot"
|
||||
git config user.email "ifcopenbot@ifcopenshell.org"
|
||||
git add */*/install/cache-*.tar.gz
|
||||
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
|
||||
git push || echo "Push failed"
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
|
||||
@@ -16,7 +16,8 @@ jobs:
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils
|
||||
findutils xz byacc
|
||||
python3 -m pip install typing_extensions
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
- name: Install aws cli
|
||||
@@ -43,18 +44,30 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \;
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
- name: ccache
|
||||
# Latest release (1.2.19) doesn't support Rocky, so using a specific commit.
|
||||
uses: hendrikmuhs/ccache-action@eca11c308176d48942455b9e5b1b70ff6950a778
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-rocky
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -16,7 +16,8 @@ jobs:
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils
|
||||
findutils xz byacc
|
||||
python3 -m pip install typing_extensions
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
- name: Install aws cli
|
||||
@@ -43,18 +44,30 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
- name: ccache
|
||||
# Latest release (1.2.19) doesn't support Rocky, so using a specific commit.
|
||||
uses: hendrikmuhs/ccache-action@eca11c308176d48942455b9e5b1b70ff6950a778
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-rocky-arm64
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -9,7 +9,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: ['3.9.11', '3.10.3', '3.11.8', '3.12.1', '3.13.0']
|
||||
arch: ['x64']
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -30,14 +29,6 @@ jobs:
|
||||
run: |
|
||||
choco install -y sed 7zip.install awscli
|
||||
|
||||
- name: Install Python
|
||||
run: |
|
||||
$installer = "python-${{ matrix.python }}-amd64.exe"
|
||||
$url = "https://www.python.org/ftp/python/${{ matrix.python }}/$installer"
|
||||
Invoke-WebRequest -Uri $url -OutFile $installer
|
||||
Start-Process -Wait -FilePath .\$installer -ArgumentList '/quiet InstallAllUsers=0 PrependPath=0 Include_test=0 TargetDir=C:\Python\${{ matrix.python }}'
|
||||
Remove-Item .\$installer
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2022-x64-installed
|
||||
@@ -45,21 +36,21 @@ jobs:
|
||||
7z x $_.FullName
|
||||
}
|
||||
|
||||
- name: Run Build Script
|
||||
- name: ccache
|
||||
# Use fork to resolve cache misses / duplicated cache entries on Windows.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: win-${{ matrix.arch }}
|
||||
# Windows ccache needs ~1GB
|
||||
# and with default 500MB some cache gets deleted, leading to misses.
|
||||
max-size: 5000MB
|
||||
|
||||
- name: Run Build Script And Pack .zip Archives
|
||||
shell: cmd
|
||||
run: |
|
||||
setlocal EnableDelayedExpansion
|
||||
SET PYTHON_VERSION=${{ matrix.python }}
|
||||
for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do (
|
||||
set PY_VER_MAJOR_MINOR=%%a%%b
|
||||
)
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
SET IFCOS_INSTALL_PYTHON=FALSE
|
||||
cd win
|
||||
echo y | call build-deps.cmd vs2022-x64 Release
|
||||
SET PYTHONHOME=C:\Python\${{ matrix.python }}
|
||||
call run-cmake.bat vs2022-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
|
||||
call install-ifcopenshell.bat vs2022-x64 Release
|
||||
python build-all-win.py
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
@@ -81,35 +72,6 @@ jobs:
|
||||
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
|
||||
git push || echo "Push failed"
|
||||
|
||||
- name: Package .zip Archives
|
||||
run: |
|
||||
$VERSION = 'v' + ((Get-Content VERSION).Trim())
|
||||
$SHA = ${env:GITHUB_SHA}.Substring(0, 7)
|
||||
$OUTPUT_DIR = "$env:USERPROFILE\output"
|
||||
New-Item -ItemType Directory -Force -Path $OUTPUT_DIR
|
||||
|
||||
if ("${{ matrix.python }}" -eq "3.9.11") {
|
||||
# only for the first python version the executables are assembled for upload
|
||||
cd _installed-vs2022-x64/bin
|
||||
Get-ChildItem -Path . | ForEach-Object {
|
||||
echo $_
|
||||
$exe = $_.Name
|
||||
$baseName = $exe.Substring(0, $exe.Length - 4)
|
||||
$zipName = "${baseName}-$VERSION-$SHA-win64.zip"
|
||||
7z a $zipName $exe
|
||||
}
|
||||
mv *.zip $OUTPUT_DIR
|
||||
}
|
||||
|
||||
$pyVersion = "${{ matrix.python }}"
|
||||
$pyVersionMajor = ($pyVersion -split '\.')[0..1] -join ''
|
||||
cd C:\Python\${{ matrix.python }}\Lib\site-packages
|
||||
Remove-Item -Recurse -Force ifcopenshell\__pycache__ -ErrorAction SilentlyContinue
|
||||
Get-ChildItem -Path ifcopenshell -Filter "*.pyc" -Recurse | Remove-Item -Force
|
||||
$zipName = "ifcopenshell-python-$pyVersionMajor-$VERSION-$SHA-win64.zip"
|
||||
7z a $zipName ifcopenshell
|
||||
mv $zipName $OUTPUT_DIR
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
cd src/bcf &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -4,9 +4,6 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.12"
|
||||
|
||||
jobs:
|
||||
lint-formatting:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -17,35 +14,43 @@ jobs:
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install 'black>=24.10.0'
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
uv tool install ruff
|
||||
uv tool install black
|
||||
uv tool install poethepoet
|
||||
|
||||
# black doesn't catch all syntax errors, so we check them explicitly.
|
||||
- name: Check syntax errors
|
||||
id: syntax-errors
|
||||
run: |
|
||||
ERROR=0
|
||||
python3 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||
python3 -W error -m compileall -q src/bonsai || ERROR=1
|
||||
python3.9 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||
python3.11 -W error -m compileall -q src/bonsai || ERROR=1
|
||||
exit $ERROR
|
||||
continue-on-error: true
|
||||
|
||||
- name: Black formatter
|
||||
id: black
|
||||
run: |
|
||||
python3 -m black --diff --check .
|
||||
black --diff --check .
|
||||
continue-on-error: true
|
||||
|
||||
- name: Ruff check
|
||||
id: ruff
|
||||
run: |
|
||||
uvx ruff check
|
||||
ERROR=0
|
||||
poe ruff-main || ERROR=1
|
||||
poe ruff-old || ERROR=1
|
||||
exit $ERROR
|
||||
continue-on-error: true
|
||||
|
||||
- name: Final check
|
||||
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: IfcOpenShell/bonsai_unstable_repo
|
||||
token: ${{ secrets.IOS_TO_BLENDER_REPO }}
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
path: bonsai_unstable_repo
|
||||
|
||||
- name: Download Blender and run critical tests
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
|
||||
|
||||
# Download Blender.
|
||||
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.4/blender-4.4.0-linux-x64.tar.xz
|
||||
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.5/blender-4.5.0-linux-x64.tar.xz
|
||||
tar -xf blender.tar.xz
|
||||
|
||||
# Setup Blender.
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/bsdd &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifc4d &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifc5d &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifccityjson &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifcclash &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifccsv &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifcdiff &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifcfm &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
pyver: [py39, py310, py311, py312, py313, py314]
|
||||
config:
|
||||
- {
|
||||
name: "Windows 64bit",
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
cd src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
|
||||
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
pyver: [py39, py310, py311, py312, py313, py314]
|
||||
config:
|
||||
- {
|
||||
name: "Windows 64bit",
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifcpatch &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: ci-ifctester-org
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- src/ifctester/**
|
||||
|
||||
jobs:
|
||||
publish_website:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout ifctester_org_static_html
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: IfcOpenShell/ifctester_org_static_html
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
path: ifctester_org_static_html
|
||||
- name: Build webapp
|
||||
working-directory: ./src/ifctester
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y nodejs
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
make webapp-build
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cp -r src/ifctester/webapp/dist/* ifctester_org_static_html/
|
||||
git -C ifctester_org_static_html add .
|
||||
git -C ifctester_org_static_html commit --allow-empty -m "$(git log --oneline -1)"
|
||||
git -C ifctester_org_static_html push
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
cd src/ifctester &&
|
||||
make dist IS_STABLE=TRUE
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
+48
-28
@@ -49,7 +49,8 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
pip install git+https://github.com/zdhoward/aud
|
||||
pip install pytest-xdist==3.8.0
|
||||
|
||||
- name: Install C++ dependencies
|
||||
run: |
|
||||
@@ -69,9 +70,32 @@ jobs:
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
# RTTI is only enabled by default in Debug builds of rocksdb.
|
||||
# Distros are using Release builds, so we're compiling it ourselves with RTTI forced on.
|
||||
# https://github.com/facebook/rocksdb/blob/a3aa44a7167b8336f9bc15c8aba063260268ff68/CMakeLists.txt#L433
|
||||
- name: build rocksdb
|
||||
run: |
|
||||
git clone https://github.com/facebook/rocksdb --branch v9.11.2
|
||||
cd rocksdb
|
||||
mkdir build && cd build
|
||||
# rocksdb is using ccache automatically.
|
||||
cmake -DFAIL_ON_WARNINGS=Off \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DWITH_TESTS=OFF \
|
||||
-DWITH_TOOLS=OFF \
|
||||
-DWITH_GFLAGS=OFF \
|
||||
-DWITH_BENCHMARK_TOOLS=OFF \
|
||||
-DWITH_CORE_TOOLS=OFF \
|
||||
-DROCKSDB_BUILD_SHARED=Off \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=On \
|
||||
-DUSE_RTTI=On \
|
||||
..
|
||||
sudo make -j$(nproc) install
|
||||
|
||||
- name: Build ifcopenshell
|
||||
run: |
|
||||
@@ -79,9 +103,9 @@ jobs:
|
||||
echo ${{ env.pythonLocation }}
|
||||
|
||||
mkdir build && cd build
|
||||
# Ubuntu 22.04's libocct-foundation-dev package doesn't have Config.cmake, so we provide OCC paths directly.
|
||||
# In later versions of Ubuntu, this can be simplified and the OCC paths can be removed.
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
@@ -90,36 +114,32 @@ jobs:
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=${{ env.pythonLocation }}/bin/python \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=${{ env.pythonLocation }}/lib/libpython3.11.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
-DUSE_MMAP=On \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DEIGEN_DIR=/usr/include/eigen3 \
|
||||
-DWITH_ROCKSDB=On \
|
||||
../cmake
|
||||
sudo make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
- name: Run IfcConvert on Sample files
|
||||
run: |
|
||||
(find test/input src/bonsai/test/files -name '*.ifc' | while read i; do \
|
||||
echo $i | tee -a log; \
|
||||
timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
|
||||
echo $i $? >> statuses; \
|
||||
done) || true
|
||||
echo Failed
|
||||
grep -v 0$ statuses
|
||||
grep -v 0$ statuses | wc -l
|
||||
echo Succeeded
|
||||
grep 0$ statuses
|
||||
grep 0$ statuses | wc -l
|
||||
# - name: Run IfcConvert on Sample files
|
||||
# run: |
|
||||
# (find test/input src/bonsai/test/files -name '*.ifc' | while read i; do \
|
||||
# echo $i | tee -a log; \
|
||||
# timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
|
||||
# echo $i $? >> statuses; \
|
||||
# done) || true
|
||||
# echo Failed
|
||||
# grep -v 0$ statuses
|
||||
# grep -v 0$ statuses | wc -l
|
||||
# echo Succeeded
|
||||
# grep 0$ statuses
|
||||
# grep 0$ statuses | wc -l
|
||||
|
||||
- name: Run IfcConvert on Sample file
|
||||
run: |
|
||||
IfcConvert test/input/acad2010_walls.ifc test/input/acad2010_walls.obj
|
||||
|
||||
- name: Test ifcopenshell-python
|
||||
run: |
|
||||
@@ -129,7 +149,7 @@ jobs:
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
pip install -e ../ifcpatch --no-deps # Needed for sql.py tests.
|
||||
ERROR=0
|
||||
make test || ERROR=1
|
||||
make test-parallel || ERROR=1
|
||||
cd ../bcf && make test || ERROR=1
|
||||
pip install requests
|
||||
cd ../bsdd && make test || ERROR=1
|
||||
|
||||
@@ -107,4 +107,9 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
bonsaiDecoratorForLoads.code-workspace
|
||||
dev_environment.bat
|
||||
|
||||
.pixi/
|
||||
|
||||
src/ifcopenshell-python/ifcopenshell/express/*.exp
|
||||
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
|
||||
|
||||
@@ -37,25 +37,25 @@ Contents
|
||||
|
||||
| Name | Description | License | Service |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ------------------- | ------- |
|
||||
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) [](https://anaconda.org/conda-forge/bcf-client) |
|
||||
| bonsai | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
|
||||
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
|
||||
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
|
||||
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifc5d/) |
|
||||
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
|
||||
| [bcf](https://docs.ifcopenshell.org/bcf.html) | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) [](https://anaconda.org/conda-forge/bcf-client) |
|
||||
| [bonsai](https://docs.ifcopenshell.org/bonsai.html) | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| [bsdd](https://docs.ifcopenshell.org/bsdd.html) | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
|
||||
| [ifc2ca](https://docs.ifcopenshell.org/ifc2ca.html) | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
|
||||
| [ifc4d](https://docs.ifcopenshell.org/ifc4d.html) | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
|
||||
| [ifc5d](https://docs.ifcopenshell.org/ifc5d.html) | Report and optimise cost information from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifc5d/) |
|
||||
| [ifcbimtester](https://docs.ifcopenshell.org/bimtester.html) | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
|
||||
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccityjson/) |
|
||||
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [](https://pypi.org/project/ifcclash/) |
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||
| [ifccityjson](https://docs.ifcopenshell.org/ifccityjson.html) | Convert CityJSON to IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccityjson/) |
|
||||
| [ifcclash](https://docs.ifcopenshell.org/ifcclash.html) | Clash detection library and CLI app | LGPL-3.0-or-later | [](https://pypi.org/project/ifcclash/) |
|
||||
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||
| [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||
| [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) [Pyodide WASM Wheels](https://github.com/IfcOpenShell/wasm-wheels#pyodide-test-wheels) |
|
||||
| [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||
| [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||
| [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||
|
||||
The IfcOpenShell C++ codebase is split into multiple interal libraries:
|
||||
|
||||
|
||||
+316
-124
@@ -27,20 +27,17 @@ endif()
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
endif()
|
||||
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
cmake_policy(SET CMP0078 NEW)
|
||||
cmake_policy(SET CMP0086 NEW)
|
||||
if (POLICY CMP0144)
|
||||
if(POLICY CMP0141) # 3.25+
|
||||
cmake_policy(SET CMP0141 NEW) # Support for `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT`.
|
||||
endif()
|
||||
if(POLICY CMP0144) # 3.27
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
endif()
|
||||
if(POLICY CMP0167) # 3.30
|
||||
cmake_policy(SET CMP0167 OLD)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
@@ -69,7 +66,7 @@ option(BUILD_IFCPYTHON "Build IfcPython." ON)
|
||||
option(BUILD_CONVERT "Build IfcConvert executable." ON)
|
||||
option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
|
||||
option(BUILD_EXAMPLES "Build example applications." ON)
|
||||
option(BUILD_GEOMSERVER "Build IfcGeomServer executable." ON)
|
||||
option(BUILD_GEOMSERVER "Build IfcGeomServer executable (Open CASCADE is required)." ON)
|
||||
option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||
option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6
|
||||
option(BUILD_PACKAGE "" OFF)
|
||||
@@ -84,10 +81,18 @@ option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." O
|
||||
option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
option(CITYJSON_SUPPORT "Build IfcConvert with CityJSON support (requires CityJSON library)." OFF)
|
||||
option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate geometrical relationships." OFF)
|
||||
option(WITH_ROCKSDB "Support a RocksDB key-value store as a file backend in IfcOpenShell" OFF)
|
||||
option(WITH_ZSTD "Use Zstd compression in RocksDB writes" OFF)
|
||||
|
||||
option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in IfcParse.h with the file VERSION in the repository root" OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in buildinfo.cpp with the file VERSION in the repository root" OFF)
|
||||
|
||||
set(
|
||||
PYTHON_MODULE_INSTALL_DIR
|
||||
"" CACHE PATH
|
||||
"Directory to install IfcPython package to. By default package is installed in found Python's site-packages."
|
||||
)
|
||||
|
||||
if (VERSION_OVERRIDE)
|
||||
file(READ "../VERSION" "RELEASE_VERSION_")
|
||||
@@ -116,6 +121,28 @@ if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND(NOT BUILD_IFCGEOM)
|
||||
set(BUILD_IFCGEOM ON)
|
||||
endif()
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
message(STATUS "`ccache` is found, using it as a compiler launcher.")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}")
|
||||
if(MSVC)
|
||||
# By default Visual Studio generators will use /Zi which is not compatible
|
||||
# with ccache, so tell Visual Studio to use /Z7 instead.
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
|
||||
# Not needed for Ninja.
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
file(COPY_FILE
|
||||
${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe
|
||||
ONLY_IF_DIFFERENT)
|
||||
set(CMAKE_VS_GLOBALS
|
||||
"CLToolExe=cl.exe"
|
||||
"CLToolPath=${CMAKE_BINARY_DIR}"
|
||||
"UseMultiToolTask=true"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
@@ -181,6 +208,8 @@ if(NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES)
|
||||
UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_LIBRARY)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
@@ -222,6 +251,25 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endmacro()
|
||||
|
||||
if(WITH_CGAL)
|
||||
if(NOT CGAL_INCLUDE_DIR)
|
||||
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
|
||||
# and sometiems it's getting in the way.
|
||||
if(NOT DEFINED Boost_USE_STATIC_LIBS)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS OFF)
|
||||
else()
|
||||
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
|
||||
endif()
|
||||
find_package(CGAL REQUIRED)
|
||||
if(NOT CGAL_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"CGAL_SUPPORT enabled, but CGAL_INCLUDE_DIR wasn't provided and CGAL package couldn't be found."
|
||||
)
|
||||
endif()
|
||||
message(STATUS "CGAL: found config at '${CGAL_DIR}'.")
|
||||
link_libraries(CGAL::CGAL)
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFOPSH_WITH_CGAL)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_CGAL)
|
||||
|
||||
@@ -240,22 +288,33 @@ endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
|
||||
restore_wasm_sysroot()
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
if(NOT JSON_INCLUDE_DIR)
|
||||
find_package(nlohmann_json CONFIG)
|
||||
if(nlohmann_json_DIR)
|
||||
link_libraries(nlohmann_json::nlohmann_json)
|
||||
else()
|
||||
message(STATUS "Unable to find nlohmann_json package, trying to find it as a header-only library.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(json_header_path)
|
||||
message(STATUS "JSON for Modern C++ header file found in ${JSON_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
|
||||
if(NOT nlohmann_json_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
|
||||
restore_wasm_sysroot()
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
|
||||
if(json_header_path)
|
||||
message(STATUS "JSON for Modern C++ header file found in '${JSON_INCLUDE_DIR}'.")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file / package, aborting")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_GLTF)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
# Add USD support to serializers
|
||||
if(USD_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
@@ -278,13 +337,13 @@ if(USD_SUPPORT)
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
@@ -302,6 +361,38 @@ if(USD_SUPPORT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
endif(USD_SUPPORT)
|
||||
|
||||
if (WITH_ROCKSDB)
|
||||
# Temporaily mess with CMAKE_FIND_PACKAGE_PREFER_CONFIG to help RocksDB
|
||||
# find it's zstd dependency on Windows.
|
||||
# Only do it on Windows, otherwise it might create problems as
|
||||
# findzstd and zstd-config target names do not match.
|
||||
if(WIN32)
|
||||
set(TEMP CMAKE_FIND_PACKAGE_PREFER_CONFIG)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
endif()
|
||||
find_package(RocksDB CONFIG REQUIRED)
|
||||
if(WIN32)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ${TEMP})
|
||||
endif()
|
||||
|
||||
message(STATUS "RocksDB: found at '${RocksDB_DIR}'.")
|
||||
add_definitions(-DIFOPSH_WITH_ROCKSDB)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB)
|
||||
link_libraries(RocksDB::rocksdb)
|
||||
|
||||
if (WITH_ZSTD)
|
||||
# @todo do we actually need the zstd include dir or rather just pass
|
||||
# the libzstd.a along with the rocksdb library when needed and feature
|
||||
# detect based on rocksdb API?
|
||||
find_package(zstd CONFIG REQUIRED)
|
||||
message(STATUS "zstd: found at '${zstd_DIR}'.")
|
||||
link_libraries(zstd::libzstd_static)
|
||||
|
||||
add_definitions(-DIFOPSH_WITH_ROCKSDB_ZSTD)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB_ZSTD)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find Boost: On win32 the (hardcoded) default is to use static libraries and
|
||||
# runtime, when doing running conda-build we pick what conda prepared for us.
|
||||
if(WIN32 AND("$ENV{CONDA_BUILD}" STREQUAL ""))
|
||||
@@ -335,7 +426,7 @@ if(WASM_BUILD)
|
||||
else()
|
||||
# @todo review this, shouldn't this be all possible header-only now?
|
||||
# ... or rewritten using C++17 features?
|
||||
set(BOOST_COMPONENTS system program_options regex thread date_time)
|
||||
set(BOOST_COMPONENTS system program_options regex thread date_time iostreams)
|
||||
endif()
|
||||
|
||||
if(USE_MMAP)
|
||||
@@ -358,7 +449,34 @@ message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
|
||||
if(NOT MINIMAL_BUILD)
|
||||
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
|
||||
clear_wasm_sysroot()
|
||||
find_package(LibXml2 REQUIRED)
|
||||
if(IFCXML_SUPPORT)
|
||||
if((NOT LIBXML2_INCLUDE_DIR AND NOT LIBXML2_LIBRARIES))
|
||||
# First try config mode (probably works with vcpkg, Conan, macOS brew installs, but not on ubuntu 22.04)
|
||||
# CONFIG is provided using root path, so no need to clear sysroot here.
|
||||
restore_wasm_sysroot()
|
||||
find_package(LibXml2 QUIET CONFIG)
|
||||
clear_wasm_sysroot()
|
||||
|
||||
if(NOT LibXml2_FOUND)
|
||||
# Fallback to CMake's builtin FindLibXml2 module (works on Ubuntu)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
else()
|
||||
message(STATUS "Found LibXml2 config: ${LibXml2_DIR}")
|
||||
endif()
|
||||
|
||||
if(TARGET LibXml2::LibXml2)
|
||||
# Config mode already gives us the target
|
||||
set(LIBXML2_LIBRARIES LibXml2::LibXml2)
|
||||
get_target_property(LIBXML2_INCLUDE_DIR LibXml2::LibXml2 INTERFACE_INCLUDE_DIRECTORIES)
|
||||
else()
|
||||
# Module mode (Ubuntu)
|
||||
set(LIBXML2_LIBRARIES ${LibXml2_LIBRARIES})
|
||||
set(LIBXML2_INCLUDE_DIR ${LibXml2_INCLUDE_DIRS})
|
||||
endif()
|
||||
else()
|
||||
find_package(LibXml2 REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
restore_wasm_sysroot()
|
||||
endif()
|
||||
|
||||
@@ -368,50 +486,73 @@ if(IFCXML_SUPPORT)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
if(MSVC)
|
||||
if(MSVC AND NOT LibXml2_DIR)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
endif()
|
||||
|
||||
# Open CASCADE
|
||||
if(WITH_OPENCASCADE)
|
||||
if("${OCC_INCLUDE_DIR}" STREQUAL "")
|
||||
clear_wasm_sysroot()
|
||||
find_path(OCC_INCLUDE_DIR Standard_Version.hxx
|
||||
PATHS
|
||||
/usr/include/occt
|
||||
/usr/include/oce
|
||||
/usr/include/opencascade
|
||||
REQUIRED
|
||||
)
|
||||
restore_wasm_sysroot()
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
message(STATUS "Found Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find Open CASCADE include directory, specify OCC_INCLUDE_DIR manually.")
|
||||
endif()
|
||||
else()
|
||||
if(OCC_INCLUDE_DIR)
|
||||
set(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
|
||||
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
if(OCC_LIBRARY_DIR)
|
||||
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
||||
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
# No specific paths specified, try to find package.
|
||||
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
|
||||
message(
|
||||
STATUS
|
||||
"Using provided OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
|
||||
"and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}')."
|
||||
)
|
||||
# Parse OCC_VERSION_STRING.
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
|
||||
REGEX "#define OCC_VERSION_MAJOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
|
||||
REGEX "#define OCC_VERSION_MINOR.*"
|
||||
REGEX "#define OCC_VERSION_MINOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
|
||||
REGEX "#define OCC_VERSION_MAINTENANCE.*"
|
||||
REGEX "#define OCC_VERSION_MAINTENANCE.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
|
||||
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
|
||||
endif(OCC_INCLUDE_DIR)
|
||||
elseif(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
|
||||
# OCE is not supported for find_package, because it's using a different name (`oce`)
|
||||
# and also has an odd directory structure (install/lib/oce-0.18/*.cmake).
|
||||
find_package(OpenCASCADE CONFIG REQUIRED)
|
||||
set(OCC_INCLUDE_DIR ${OpenCASCADE_INCLUDE_DIR})
|
||||
# Do not use OpenCASCADE_LIBRARY_DIR for OCC_LIBRARY_DIR - check target property explicitly.
|
||||
# On Windows there is a case with OpenCASCADE_LIBRARY_DIR points to `lib` folder,
|
||||
# while TKernel is actually in `libi`.
|
||||
get_target_property(TKERNEL_LIB_PATH TKernel LOCATION)
|
||||
get_filename_component(OCC_LIBRARY_DIR "${TKERNEL_LIB_PATH}" DIRECTORY)
|
||||
set(OCC_VERSION_STRING ${OpenCASCADE_VERSION})
|
||||
message(
|
||||
STATUS
|
||||
"Found Open CASCADE package at '${OpenCASCADE_DIR}', "
|
||||
"deducing from it OCC_INCLUDE_DIR: '${OCC_INCLUDE_DIR}' "
|
||||
"and OCC_LIBRARY_DIR: '${OCC_LIBRARY_DIR}'."
|
||||
)
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Couldn't find Open CASCADE installation. "
|
||||
"Either both OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}') "
|
||||
"must be specified or OpenCASCADE package should be discoverable. "
|
||||
"If you're using OCE, then providing a package is not available "
|
||||
"and you need to provide OCE_INCLUDE_DIR and OCE_LIBRARY_DIR directly."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(OPENCASCADE_LIBRARY_NAMES
|
||||
set(
|
||||
OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKXSBase TKOffset TKHLR
|
||||
|
||||
@@ -425,24 +566,6 @@ if(BUILD_IFCGEOM)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKDESTEP TKDEIGES)
|
||||
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
|
||||
if("${OCC_LIBRARY_DIR}" STREQUAL "")
|
||||
find_library(OCC_LIBRARY TKernel
|
||||
PATHS
|
||||
/usr/lib
|
||||
REQUIRED
|
||||
)
|
||||
|
||||
if(OCC_LIBRARY)
|
||||
GET_FILENAME_COMPONENT(OCC_LIBRARY_DIR ${OCC_LIBRARY} PATH)
|
||||
message(STATUS "Found Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable find Open CASCADE library directory, specify OCC_LIBRARY_DIR manually.")
|
||||
endif()
|
||||
else()
|
||||
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
||||
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
clear_wasm_sysroot()
|
||||
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
restore_wasm_sysroot()
|
||||
@@ -450,7 +573,10 @@ if(BUILD_IFCGEOM)
|
||||
if(libTKernel)
|
||||
message(STATUS "Required Open Cascade Library files found")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find Open Cascade library files, aborting")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Unable to find Open Cascade library files in OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}'), aborting"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Use the found libTKernel as a template for all other OCC libraries
|
||||
@@ -481,7 +607,7 @@ if(BUILD_IFCGEOM)
|
||||
|
||||
if(OCCT_STATIC)
|
||||
find_package(Threads)
|
||||
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
@@ -493,7 +619,7 @@ if(BUILD_IFCGEOM)
|
||||
set(OPENCASCADE_LIBRARIES -Wl,--start-group ${OPENCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
|
||||
endif()
|
||||
@@ -504,7 +630,18 @@ if(BUILD_IFCGEOM)
|
||||
endif(WITH_OPENCASCADE)
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if(COLLADA_SUPPORT)
|
||||
if(COLLADA_SUPPORT AND (NOT OPENCOLLADA_INCLUDE_DIR AND NOT OPENCOLLADA_LIBRARY_DIR))
|
||||
# If package is found, automatically sets
|
||||
# OPENCOLLADA_INCLUDE_DIRS and OPENCOLLADA_LIBRARIES (list of targets, not paths).
|
||||
find_package(OpenCOLLADA CONFIG)
|
||||
if(OpenCOLLADA_DIR)
|
||||
message(STATUS "Found OpenCOLLADA: '${OpenCOLLADA_DIR}'.")
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
else()
|
||||
message(STATUS "OpenCOLLADA package not found, falling back to manual search.")
|
||||
endif()
|
||||
endif()
|
||||
if(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
|
||||
# Find OpenCOLLADA
|
||||
if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No OpenCOLLADA include directory specified")
|
||||
@@ -537,8 +674,7 @@ if(COLLADA_SUPPORT)
|
||||
|
||||
if(COLLADASWStreamWriter_h)
|
||||
message(STATUS "OpenCOLLADA header files found")
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
|
||||
set(OPENCOLLADA_LIBRARY_NAMES
|
||||
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
|
||||
@@ -586,7 +722,12 @@ if(COLLADA_SUPPORT)
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA headers. "
|
||||
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
|
||||
endif()
|
||||
endif(COLLADA_SUPPORT)
|
||||
endif(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
|
||||
if(COLLADA_SUPPORT AND OPENCOLLADA_FOUND)
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
endif()
|
||||
|
||||
|
||||
if(HDF5_SUPPORT)
|
||||
if("${HDF5_INCLUDE_DIR}" STREQUAL "")
|
||||
@@ -653,15 +794,26 @@ if(HDF5_SUPPORT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_LIBRARIES)
|
||||
# debian default
|
||||
set(HDF5_LIBRARIES
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.so
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.so
|
||||
/usr/lib/x86_64-linux-gnu/libsz.so
|
||||
/usr/lib/x86_64-linux-gnu/libaec.so
|
||||
z dl
|
||||
)
|
||||
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
# First try to find it as a config.
|
||||
find_package(HDF5 CONFIG)
|
||||
if(HDF5_DIR)
|
||||
message(STATUS "HDF5: found config at '${HDF5_DIR}'.")
|
||||
set(HDF5_LIBRARIES hdf5_cpp-static)
|
||||
else()
|
||||
# If it failed, still try to find as a module.
|
||||
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
|
||||
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
|
||||
find_package(HDF5 COMPONENTS CXX)
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
|
||||
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_HDF5)
|
||||
@@ -778,6 +930,23 @@ else()
|
||||
endif()
|
||||
endif(MSVC)
|
||||
|
||||
|
||||
# Ensure other dependencies are provided.
|
||||
if(NOT EXISTS "${EIGEN_DIR}")
|
||||
find_package(Eigen3 CONFIG)
|
||||
if(Eigen3_DIR)
|
||||
message(STATUS "Eigen3: found config at '${Eigen3_DIR}'.")
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"EIGEN_DIR is not provided or provided folder doesn't exist (current value: '${EIGEN_DIR}'). "
|
||||
"Also couldn't find Eigen3 as a package."
|
||||
)
|
||||
endif()
|
||||
link_libraries(Eigen3::Eigen)
|
||||
endif()
|
||||
|
||||
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
|
||||
${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
|
||||
@@ -787,7 +956,7 @@ include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCL
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
if(WASM_BUILD)
|
||||
# super arbitrarily try to keep size down at least a little bit
|
||||
set(SCHEMA_VERSIONS "2x3" "4")
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x3_add2")
|
||||
else()
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3" "4x3_tc1" "4x3_add1" "4x3_add2")
|
||||
endif()
|
||||
@@ -875,11 +1044,7 @@ if(BUILD_IFCGEOM)
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} geometry_mapping_ifc${schema})
|
||||
endforeach()
|
||||
if(WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
@@ -893,12 +1058,14 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
set(GEOM_SERIALIZER_SCHEMA_LIBRARIES ${GEOM_SERIALIZER_SCHEMA_LIBRARIES} GeometrySerializers_ifc${schema})
|
||||
|
||||
add_library(geometry_serializer_ifc${schema} STATIC ../src/ifcgeom/Serialization/schema/Serialization.cpp)
|
||||
set_target_properties(geometry_serializer_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
set_target_properties(geometry_serializer_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
target_link_libraries(geometry_serializer_ifc${schema} ${OPENCASCADE_LIBRARIES})
|
||||
list(APPEND geometry_serializer_libraries geometry_serializer_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
add_library(geometry_serializer STATIC ../src/ifcgeom/Serialization/Serialization.cpp)
|
||||
target_link_libraries(geometry_serializer ${geometry_serializer_libraries})
|
||||
add_library(geometry_serializer STATIC ../src/ifcgeom/Serialization/Serialization.h ../src/ifcgeom/Serialization/Serialization.cpp)
|
||||
set_target_properties(geometry_serializer PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS")
|
||||
target_link_libraries(geometry_serializer ${geometry_serializer_libraries} IfcParse)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} geometry_serializer ${geometry_serializer_libraries})
|
||||
endif()
|
||||
endif()
|
||||
@@ -937,8 +1104,12 @@ endforeach()
|
||||
set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
|
||||
|
||||
add_library(IfcParse ${IFCPARSE_FILES})
|
||||
target_link_libraries(IfcParse ${STDCPPFS})
|
||||
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
if(LibXml2_DIR)
|
||||
target_compile_definitions(IfcParse PRIVATE ${LIBXML2_DEFINITIONS})
|
||||
endif()
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
|
||||
else()
|
||||
@@ -946,7 +1117,11 @@ else()
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
if(WITH_CGAL)
|
||||
# CGAL::CGAL target already has dependencies resolved.
|
||||
if(WITH_CGAL AND CGAL_DIR)
|
||||
set(CGAL_LIBRARIES CGAL::CGAL)
|
||||
message(STATUS "Using found CGAL package at '${CGAL_DIR}'")
|
||||
elseif(WITH_CGAL AND NOT CGAL_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_library(libGMP NAMES gmp mpir PATHS ${GMP_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
find_library(libMPFR NAMES mpfr PATHS ${MPFR_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
@@ -967,24 +1142,24 @@ if(BUILD_IFCGEOM)
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/kernels/${kernel}/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
|
||||
add_library(geometry_kernel_${kernel} ${IFCGEOM_FILES})
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOM_EXPORTS")
|
||||
|
||||
add_library(geometry_kernel_${kernel} ${IFCGEOM_FILES} ../src/ifcgeom/kernels/ifc_geomlibrary_api.h)
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS")
|
||||
# needed?
|
||||
# if(NOT WASM_BUILD)
|
||||
# endif()
|
||||
target_link_libraries(geometry_kernel_${kernel} ${${KERNEL_UPPER}_LIBRARIES})
|
||||
target_link_libraries(geometry_kernel_${kernel} ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel})
|
||||
|
||||
|
||||
if(${kernel} STREQUAL "cgal")
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND_STRING PROPERTY COMPILE_FLAGS " -DCGAL_HAS_THREADS")
|
||||
|
||||
add_library(geometry_kernel_${kernel}_simple ${IFCGEOM_FILES})
|
||||
set_target_properties(geometry_kernel_${kernel}_simple PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
set_target_properties(geometry_kernel_${kernel}_simple PROPERTIES COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
# needed?
|
||||
# if(NOT WASM_BUILD)
|
||||
# endif()
|
||||
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES})
|
||||
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel}_simple)
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -995,10 +1170,13 @@ if(BUILD_IFCGEOM)
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/mapping/*.h)
|
||||
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/mapping/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES} ${IFCGEOM_I_FILES})
|
||||
|
||||
|
||||
add_library(geometry_mapping_ifc${schema} STATIC ${IFCGEOM_FILES})
|
||||
set_target_properties(geometry_mapping_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
target_link_libraries(geometry_mapping_ifc${schema} IfcParse)
|
||||
if (NOT BUILD_SHARED_LIBS)
|
||||
target_link_libraries(geometry_mapping_ifc${schema} IfcGeom)
|
||||
endif()
|
||||
list(APPEND mapping_libraries geometry_mapping_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
@@ -1014,11 +1192,7 @@ if(BUILD_IFCGEOM)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(IfcGeom ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
target_link_libraries(IfcGeom IfcParse ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
@@ -1033,8 +1207,8 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES})
|
||||
else()
|
||||
@@ -1043,7 +1217,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
endforeach()
|
||||
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
if(WITH_PROJ)
|
||||
target_compile_definitions(Serializers PRIVATE "WITH_PROJ")
|
||||
@@ -1054,7 +1228,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
target_link_libraries(Serializers ${PROJ_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} IfcGeom ${OPENCASCADE_LIBRARIES} ${kernel_libraries} IfcParse)
|
||||
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
@@ -1101,7 +1275,7 @@ if(BUILD_CONVERT)
|
||||
set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
add_executable(IfcConvert ${IFCCONVERT_FILES})
|
||||
|
||||
target_link_libraries(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
target_link_libraries(IfcConvert IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
if (WITH_RELATIONSHIP_VALIDATION)
|
||||
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DWITH_RELATIONSHIP_VALIDATION")
|
||||
endif()
|
||||
@@ -1126,11 +1300,16 @@ endif(BUILD_CONVERT)
|
||||
|
||||
# IfcGeomServer
|
||||
if(BUILD_GEOMSERVER)
|
||||
|
||||
if(NOT WITH_OPENCASCADE)
|
||||
message(FATAL_ERROR "Open CASCADE is required to build IfcGeomServer.")
|
||||
endif()
|
||||
|
||||
file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp)
|
||||
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
|
||||
set(SOURCE_FILES ${CPP_FILES} ${H_FILES})
|
||||
add_executable(IfcGeomServer ${SOURCE_FILES})
|
||||
target_link_libraries(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
target_link_libraries(IfcGeomServer IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
|
||||
if((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
|
||||
@@ -1146,6 +1325,10 @@ endif(BUILD_GEOMSERVER)
|
||||
if(ADD_COMMIT_SHA)
|
||||
find_package(Git)
|
||||
|
||||
if(NOT GIT_FOUND)
|
||||
message(FATAL_ERROR "Failed to find Git for ADD_COMMIT_SHA option.")
|
||||
endif()
|
||||
|
||||
if(GIT_FOUND)
|
||||
if (VERSION_OVERRIDE)
|
||||
set (git_branch ${RELEASE_VERSION})
|
||||
@@ -1183,8 +1366,10 @@ if(ADD_COMMIT_SHA)
|
||||
message(FATAL_ERROR "Unable to determine commit sha and/or branch")
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
|
||||
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
|
||||
target_compile_definitions(IfcParse PRIVATE
|
||||
-DIFCOPENSHELL_BRANCH=${git_branch}
|
||||
-DIFCOPENSHELL_COMMIT=${git_sha}
|
||||
)
|
||||
endif()
|
||||
endif(ADD_COMMIT_SHA)
|
||||
|
||||
@@ -1214,7 +1399,7 @@ if(BUILD_IFCMAX)
|
||||
endif()
|
||||
|
||||
if(WITH_CGAL)
|
||||
add_subdirectory(../src/svgfill svgfill)
|
||||
add_subdirectory(../src/svgfill svgfill)
|
||||
endif()
|
||||
|
||||
if(BUILD_QTVIEWER)
|
||||
@@ -1241,6 +1426,11 @@ if(BUILD_IFCGEOM)
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
)
|
||||
|
||||
file(GLOB SERIALIZATION_H_FILES ../src/ifcgeom/serialization/*.h)
|
||||
install(FILES ${SERIALIZATION_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/serialization
|
||||
)
|
||||
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
@@ -1272,11 +1462,13 @@ if(BUILD_CONVERT)
|
||||
endif(BUILD_CONVERT)
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
install(TARGETS geometry_serializer ${geometry_serializer_libraries}
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
if(WITH_OPENCASCADE)
|
||||
install(TARGETS geometry_serializer ${geometry_serializer_libraries}
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
endif()
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
# Cmake uninstall target
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"version": 6,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "shared",
|
||||
"generator": "Ninja",
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"BUILD_IFCPYTHON": "ON",
|
||||
"BUILD_IFCGEOM": "ON",
|
||||
"COLLADA_SUPPORT": "OFF",
|
||||
"BUILD_EXAMPLES": "OFF",
|
||||
"BUILD_GEOMSERVER": "OFF",
|
||||
"GLTF_SUPPORT": "ON",
|
||||
"BUILD_CONVERT": "ON",
|
||||
"BUILD_IFCMAX": "OFF",
|
||||
"IFCXML_SUPPORT": "ON",
|
||||
"HDF5_SUPPORT": "ON",
|
||||
"SCHEMA_VERSIONS": "4x3_add2",
|
||||
"CITYJSON_SUPPORT": "OFF",
|
||||
"CMAKE_GENERATOR_PLATFORM": "",
|
||||
"CMAKE_GENERATOR_TOOLSET": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-shared",
|
||||
"inherits": [
|
||||
"shared"
|
||||
],
|
||||
"hidden": true,
|
||||
"environment": {
|
||||
"CONDA_BUILD": "ON",
|
||||
"CONDA_PY": "312"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"Boost_USE_STATIC_LIBS": "OFF",
|
||||
"CMAKE_INSTALL_PREFIX": "$env{LIBRARY_PREFIX}",
|
||||
"PYTHON_EXECUTABLE": "$env{PREFIX}/python.exe",
|
||||
"PYTHON_INCLUDE_DIR": "$env{PREFIX}/include",
|
||||
"PYTHON_LIBRARY": "$env{PREFIX}/libs/python$env{CONDA_PY}.lib",
|
||||
"CMAKE_FIND_ROOT_PATH": "$env{LIBRARY_PREFIX}",
|
||||
"CMAKE_PREFIX_PATH": "$env{LIBRARY_PREFIX}",
|
||||
"LIBXML2_LIBRARIES": "$env{LIBRARY_PREFIX}/lib/libxml2.lib",
|
||||
"OCC_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include/opencascade",
|
||||
"OCC_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"CGAL_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"EIGEN_DIR": "$env{LIBRARY_PREFIX}/include/eigen3",
|
||||
"JSON_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"GMP_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"GMP_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"MPFR_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"Boost_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"Boost_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"HDF5_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"HDF5_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"ZLIB_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-release",
|
||||
"inherits": [
|
||||
"win-shared"
|
||||
],
|
||||
"hidden": false,
|
||||
"warnings": {
|
||||
"dev": false
|
||||
},
|
||||
"environment": {
|
||||
"PREFIX": "${sourceDir}/../.pixi/envs/prod",
|
||||
"LIBRARY_PREFIX": "${sourceDir}/../.pixi/envs/prod/Library"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-debug",
|
||||
"inherits": [
|
||||
"win-shared"
|
||||
],
|
||||
"hidden": false,
|
||||
"binaryDir": "${sourceDir}/../build/win-debug",
|
||||
"warnings": {
|
||||
"dev": false
|
||||
},
|
||||
"environment": {
|
||||
"PREFIX": "${sourceDir}/../.pixi/envs/dev",
|
||||
"LIBRARY_PREFIX": "${sourceDir}/../.pixi/envs/dev/Library"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-test",
|
||||
"inherits": [
|
||||
"win-shared"
|
||||
],
|
||||
"hidden": false,
|
||||
"binaryDir": "${sourceDir}/../build/win-test",
|
||||
"warnings": {
|
||||
"dev": false
|
||||
},
|
||||
"environment": {
|
||||
"PREFIX": "${sourceDir}/../.pixi/envs/tests",
|
||||
"LIBRARY_PREFIX": "${sourceDir}/../.pixi/envs/tests/Library"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"SCHEMA_VERSIONS": "2x3;4;4x3_add2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -121,4 +121,4 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
endfunction()
|
||||
endfunction()
|
||||
|
||||
+827
-319
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 34300d41ad..09b2e0d45f 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -721,6 +721,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
list (APPEND OCCT_3RDPARTY_CMAKE_LIST "adm/cmake/bison")
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/OpenCOLLADAConfig.cmake.in b/OpenCOLLADAConfig.cmake.in
|
||||
index d765b108..6800c2a7 100644
|
||||
--- a/OpenCOLLADAConfig.cmake.in
|
||||
+++ b/OpenCOLLADAConfig.cmake.in
|
||||
@@ -31,7 +31,7 @@ set(OPENCOLLADA_INCLUDE_DIRS
|
||||
include("@PACKAGE_OPENCOLLADA_INST_CMAKECONFIG@/OpenCOLLADATargets.cmake")
|
||||
|
||||
# Set the library variable
|
||||
-if(UNIX)
|
||||
+if(0)
|
||||
set(OPENCOLLADA_LIBRARIES
|
||||
ftoa_shared
|
||||
buffer_shared
|
||||
@@ -0,0 +1,84 @@
|
||||
[project]
|
||||
name = "IfcOpenShell"
|
||||
version = "0.8.4"
|
||||
description = "IfcOpenShell is a library to support the IFC file format"
|
||||
channels = ["conda-forge"]
|
||||
platforms = ["win-64", "linux-64", "osx-64"]
|
||||
|
||||
[environments]
|
||||
common = { features = ["common"], no-default-feature = true }
|
||||
prod = { features = ["prod", "common"], no-default-feature = true }
|
||||
dev = { features = ["dev", "common"], no-default-feature = true }
|
||||
lint = { features = ["lint"], no-default-feature = true }
|
||||
tests = { features = ["tests", "common"], no-default-feature = true }
|
||||
|
||||
[feature.lint.dependencies]
|
||||
ruff = "*"
|
||||
black = "*"
|
||||
|
||||
[feature.common.dependencies]
|
||||
# Build deps
|
||||
cmake = "==3.30.5"
|
||||
ninja = "*"
|
||||
swig = "*"
|
||||
c-compiler = "*"
|
||||
cxx-compiler = "*"
|
||||
|
||||
# Runtime deps
|
||||
python = "3.12.*"
|
||||
libboost-devel = "*"
|
||||
occt = { version = "*", build = "*novtk*" }
|
||||
cgal-cpp = "*"
|
||||
numpy = "*"
|
||||
lark = "*"
|
||||
hdf5 = "*"
|
||||
eigen = "*"
|
||||
mpfr = "*"
|
||||
gmp = "*"
|
||||
nlohmann_json = "*"
|
||||
zlib = "*"
|
||||
# Not strictly necessary
|
||||
pythonocc-core = "*"
|
||||
|
||||
[feature.tests.dependencies]
|
||||
pytest = "*"
|
||||
shapely = "*"
|
||||
tabulate = "*"
|
||||
isodate = "*"
|
||||
python-dateutil = "*"
|
||||
xmlschema = "*"
|
||||
xsdata = "*"
|
||||
lxml = "*"
|
||||
networkx = "*"
|
||||
|
||||
[feature.common.target.win-64.dependencies]
|
||||
vs2022_win-64 = "*"
|
||||
#vs_win-64 = "*" # Visual Studio
|
||||
#vswhere = "*" # Visual Studio
|
||||
|
||||
[feature.common.tasks]
|
||||
init-submodules = { cmd = "git submodule update --init --recursive", outputs =["$PIXI_PROJECT_ROOT/src/svgfill/3rdparty/svgpp/*"]}
|
||||
|
||||
[feature.prod.target.win-64.tasks]
|
||||
configure-release = { cmd = ["cmake", "--preset", "win-release", "-B", "build/win-release", "cmake"], description = "Configure the project", depends-on=[{ task="init-submodules", environment = "common" }] }
|
||||
build-release = { cmd = ["cmake", "--build", "build/win-release", "--config", "Release"], description = "Build the project" }
|
||||
|
||||
[feature.dev.target.win-64.tasks]
|
||||
configure-debug = { cmd = ["cmake", "--preset", "win-debug", "-B", "build/win-debug", "cmake"], description = "Configure the project", depends-on=[{ task="init-submodules", environment = "common" }], outputs=["build/win-debug/CMakeCache.txt"] }
|
||||
build-debug = { cmd = ["cmake", "--build", "build/win-debug", "--config", "Debug"], description = "Build the project", depends-on = ["configure-debug"] }
|
||||
install-debug = { cmd = ["cmake", "--install", "build/win-debug", "--config", "Debug"], description = "Install the project" } # Optionally Install files to your desired env using --prefix
|
||||
|
||||
vsdebug = { cmd = ["python"], description = "Run a python script with vs debugger attached" }
|
||||
|
||||
[feature.tests.tasks]
|
||||
configure-test = { cmd = ["cmake", "--preset", "win-test", "-B", "build/win-test", "cmake"], description = "Configure the project", depends-on=[{ task="init-submodules", environment = "common" }], outputs=["build/win-test/CMakeCache.txt"] }
|
||||
build-test = { cmd = ["cmake", "--build", "build/win-test", "--config", "Release"], description = "Build the project", depends-on = ["configure-test"], outputs=["build/win-test/IfcGeom.lib"] }
|
||||
install-test = { cmd = ["cmake", "--install", "build/win-test", "--config", "Release"], description = "Install the project", depends-on = ["build-test"] } # Optionally Install files to your desired env using --prefix
|
||||
|
||||
test = { cmd = "pytest .", cwd="src/ifcopenshell-python/test", depends-on=["install-test"], env = {PYTHONPATH="$PIXI_PROJECT_ROOT/src/ifcpatch"}}
|
||||
|
||||
[feature.lint.tasks]
|
||||
lint = { cmd = "ruff check && black --diff --check ."}
|
||||
|
||||
[feature.common.target.win-64.activation]
|
||||
#scripts = ["cmake/activate.bat"]
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
There are two ways to build pyodide ifcopenshell Python wrapper wheel.
|
||||
|
||||
1. Using pyodide build system (`build_pyodide.yml` does it):
|
||||
|
||||
- install prebuilt pyodide build and emscripten environment (see `build_pyodide.sh`)
|
||||
- clone IfcOpenShell to `IfcOpenShell` folder
|
||||
- create `packages/ifcopenshell` folder that will be used by pyodide build system
|
||||
- from `IfcOpenShell` move building recipe `pyodide/meta.yaml` to `packages/ifcopenshell`
|
||||
- run `pyodide build-recipes ifcopenshell --install`, it will
|
||||
- execute `meta.yaml` recipe - it will:
|
||||
- copy IfcOpenShell source to build folder `packages/ifcopenhell/build/ifcopenshell-0.8.0`
|
||||
- build ifcopenshell and its dependencies
|
||||
- note that rerunning `pyodide build-recipes` will remove previous build folder and rebuild all dependencies.
|
||||
The way to avoid it, if build fails, is to use `pyodide build-recipes-no-deps ifcopenshell --continue` instead.
|
||||
- run `setup.py` in `IfcOpenShell` root, producing a wheel in `IfcOpenShell/dist`
|
||||
- copy that wheel to `packages/ifcopenshell/dist`
|
||||
- `--install` it to current build envrionment
|
||||
- copy the wheel next to `dist` folder (in root directory, next to `packages`)
|
||||
- add wheel to `dist/pyodide-lock.json`
|
||||
|
||||
2. Build it outside of pyodide build system.
|
||||
|
||||
Building inside pyodide build system should be preferred, option to build it outside is useful for debugging purposes,
|
||||
since it's pure cmake without any additional moving parts.
|
||||
|
||||
- setup pyodide environment, see above
|
||||
- clone IfcOpenShell repo next to it to `IfcOpenShell` folder
|
||||
- run `python nix/build-all.py -wasm -py-313` in `IfcOpenShell`
|
||||
- it will produce Python package in `IfcOpenShell/ifcopenshell`
|
||||
- run `pyodide build`
|
||||
- it will produce a wheel in `IfcOpenShell/dist`
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/bash
|
||||
set -ex
|
||||
|
||||
# Install uv.
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
uv venv --python 3.13
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install pyodide cross build environment.
|
||||
# Instructions: https://pyodide.org/en/stable/development/building-packages.html
|
||||
uv pip install pyodide-build
|
||||
# `uv run` is required, so xbuildenv would skip using `pip`.
|
||||
uv run pyodide xbuildenv install
|
||||
|
||||
# Emscripten doesn't come with xbuildenv.
|
||||
git clone https://github.com/emscripten-core/emsdk
|
||||
pushd emsdk
|
||||
PYODIDE_EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version)
|
||||
./emsdk install ${PYODIDE_EMSCRIPTEN_VERSION}
|
||||
./emsdk activate ${PYODIDE_EMSCRIPTEN_VERSION}
|
||||
source emsdk_env.sh
|
||||
which emcc
|
||||
popd
|
||||
|
||||
mkdir -p packages/ifcopenshell
|
||||
VERSION=`cat IfcOpenShell/VERSION`
|
||||
cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell
|
||||
sed -i s/0.8.0/$VERSION/g packages/ifcopenshell/meta.yaml
|
||||
|
||||
# Use custom build ifcopenshell directory in build-all to make caching simpler
|
||||
# Otherwise pyodide build path typically includes package version, so cached cmake configs might break.
|
||||
export BUILD_DIR=`readlink -f ifcopenshell_build`
|
||||
|
||||
# Use build-recipes-no-deps first, so logs would be printed to stdout.
|
||||
pyodide build-recipes-no-deps ifcopenshell
|
||||
pyodide build-recipes ifcopenshell --install
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Cache built dependencies for builds.
|
||||
|
||||
This script is finding common install directory and either
|
||||
packs each folder into a tar.gz archive, if it wasn't packed before,
|
||||
or unpacks existing archives.
|
||||
|
||||
Usage: python cache_dependencies.py [pack|unpack]
|
||||
"""
|
||||
|
||||
import tarfile
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
|
||||
CACHE_PREFIX = "cache-"
|
||||
|
||||
|
||||
def get_install_dir() -> Path:
|
||||
for data in Path.cwd().glob("*/*/install"):
|
||||
return data
|
||||
raise Exception("No install dir found")
|
||||
|
||||
|
||||
def pack_dependencies(install_dir: Path) -> None:
|
||||
# Process each install_dir
|
||||
for dependency_path in install_dir.iterdir():
|
||||
if not dependency_path.is_dir():
|
||||
continue
|
||||
dependency_name = dependency_path.name
|
||||
tar_path = install_dir / f"{CACHE_PREFIX}{dependency_name}.tar.gz"
|
||||
if tar_path.exists():
|
||||
print(f"Skipping existing cache: '{tar_path}'")
|
||||
else:
|
||||
with tarfile.open(tar_path, "w:gz") as tar:
|
||||
tar.add(dependency_path, arcname=dependency_path.name)
|
||||
print(f"Created cache: '{tar_path}'")
|
||||
|
||||
|
||||
def unpack_dependencies(install_dir: Path) -> None:
|
||||
# `filter` argument was fully introduced in 3.12
|
||||
# and results in deprecation warnings in 3.12-3.13, if not provided.
|
||||
tar_filter: dict[Literal["filter"], Literal["data"]] = (
|
||||
{"filter": "data"} if bool(sys.version_info >= (3, 12)) else {}
|
||||
)
|
||||
for tar_path in install_dir.glob(f"{CACHE_PREFIX}*.tar.gz"):
|
||||
with tarfile.open(tar_path, "r:gz") as tar:
|
||||
tar.extractall(path=install_dir, **tar_filter)
|
||||
print(f"Extracted cache: '{tar_path.name}'.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2 or (action := sys.argv[1].lower()) not in ("pack", "unpack"):
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
install_dir = get_install_dir()
|
||||
print(f"Found install dir: '{install_dir}'")
|
||||
|
||||
if action == "pack":
|
||||
pack_dependencies(install_dir)
|
||||
else:
|
||||
unpack_dependencies(install_dir)
|
||||
+2
-3
@@ -3,13 +3,12 @@ package:
|
||||
version: 0.8.0
|
||||
|
||||
source:
|
||||
# meta.yaml is placed as `packages/ifcopenshell/meta.yaml`.
|
||||
path: ../../IfcOpenShell
|
||||
|
||||
build:
|
||||
script: |
|
||||
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py313 IfcOpenShell-Python
|
||||
mv package/ifcopenshell .
|
||||
cp pyodide/setup.py .
|
||||
BUILD_CFG=Release python nix/build-all.py -v --wasm --py313
|
||||
|
||||
about:
|
||||
home: http://ifcopenshell.org
|
||||
|
||||
+45
-9
@@ -1,11 +1,47 @@
|
||||
from setuptools import setup, find_packages
|
||||
# setup.py is getting deprecated, but we still use it,
|
||||
# because `tool.setuptools.ext-modules` is still experimental in pyproject.toml
|
||||
# and we need it to get the wheel suffix right.
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
setup(name='ifcopenshell',
|
||||
version='0.8.0',
|
||||
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
|
||||
author='Thomas Krijnen',
|
||||
author_email='thomas@aecgeeks.com',
|
||||
url='http://ifcopenshell.org',
|
||||
packages=find_packages(),
|
||||
package_data={'': ['*.so', '*.json']},
|
||||
import tomllib
|
||||
from setuptools import Extension, find_packages, setup
|
||||
|
||||
REPO_FOLDER = Path(__file__).parent
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
if "PKG_VERSION" in os.environ:
|
||||
# Inside pyodide build environment.
|
||||
return os.environ["PKG_VERSION"]
|
||||
return (REPO_FOLDER / "VERSION").read_text().strip()
|
||||
|
||||
|
||||
# Read dependencies from pyproject.toml
|
||||
def get_dependencies() -> list[str]:
|
||||
pyproject_toml = REPO_FOLDER / "src" / "ifcopenshell-python" / "pyproject.toml"
|
||||
pyproject_data = tomllib.loads(pyproject_toml.read_text())
|
||||
dependencies = pyproject_data["project"]["dependencies"]
|
||||
return dependencies
|
||||
|
||||
|
||||
setup(
|
||||
name="ifcopenshell",
|
||||
version=get_version(),
|
||||
description=(
|
||||
"IfcOpenShell is an open source (LGPL) software library "
|
||||
"for working with the Industry Foundation Classes (IFC) file format."
|
||||
),
|
||||
author="Thomas Krijnen",
|
||||
author_email="thomas@aecgeeks.com",
|
||||
url="https://ifcopenshell.org",
|
||||
install_requires=get_dependencies(),
|
||||
packages=find_packages(include=["ifcopenshell", "ifcopenshell.*"]),
|
||||
package_data={
|
||||
# "*.so" is needed to include prebuilt binary extension. Otherwise it would try to build it and fail.
|
||||
"ifcopenshell": ["util/schema/*.json", "util/schema/*.ifc", "*.so"],
|
||||
"": ["*.json", "*.ifc"],
|
||||
},
|
||||
# Has to provide extension to get the correct wheel suffix.
|
||||
ext_modules=[Extension("ifcopenshell._ifcopenshell_wrapper", sources=[])],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
WHEEL_FILENAME = next(
|
||||
p.name for p in (Path.cwd() / "pyodide").iterdir() if p.name.startswith("ifcopenshell-") and p.suffix == ".whl"
|
||||
)
|
||||
|
||||
|
||||
def test_ifcopenshell_import(selenium):
|
||||
selenium.load_package("micropip")
|
||||
# Important to test it with `micropip.install`
|
||||
# without any dependencies loaded to ensure micropip will load them automatically.
|
||||
selenium.run_async(
|
||||
f"""
|
||||
import micropip
|
||||
await micropip.install(f"./{WHEEL_FILENAME}")
|
||||
import ifcopenshell
|
||||
ifc_file = ifcopenshell.file()
|
||||
wall = ifc_file.create_entity("IfcWall")
|
||||
wall1 = ifc_file.by_type("IfcWall")[0]
|
||||
print(wall, wall1)
|
||||
assert wall == wall1, "Wall entity doesn't match"
|
||||
wall.Name = "Test"
|
||||
assert wall.Name == "Test", f"Entity name wasn't changed: {{wall}}"
|
||||
print(wall)
|
||||
"""
|
||||
)
|
||||
+18
-6
@@ -1,6 +1,9 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = "src/.*.pyi?$"
|
||||
include = '''
|
||||
src/.*.pyi?$
|
||||
|nix/.*.pyi?$
|
||||
'''
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/*
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
@@ -16,8 +19,6 @@ extend-exclude = '''
|
||||
reportInvalidTypeForm = false
|
||||
disableBytesTypePromotions = true
|
||||
reportUnnecessaryTypeIgnoreComment = true
|
||||
# Just to add a quick insert of `# pyright: ignore[xxx]` comments in Pylance.
|
||||
enableTypeIgnoreComments = false
|
||||
|
||||
# Define here general ruff settings,
|
||||
# then they will be inherited projects .toml files.
|
||||
@@ -37,6 +38,7 @@ exclude = [
|
||||
"src/ifc2ca/templates",
|
||||
]
|
||||
[tool.ruff.lint]
|
||||
preview = true
|
||||
select = [
|
||||
# Default Ruff rules.
|
||||
# "E4", # imports
|
||||
@@ -47,17 +49,27 @@ select = [
|
||||
"FA", # future annotations
|
||||
"UP", # pyupgrade
|
||||
"RUF015", # next() > list_comprehension[0]
|
||||
"RUF022", # sort __all__
|
||||
]
|
||||
ignore = [
|
||||
"FA100", # Conflicts with Blender using annotations for props definitions.
|
||||
# Maybe will enable later:
|
||||
"UP007", # Optional to X | Y
|
||||
"UP045", # Optional to X | None
|
||||
"UP015", # Unnecessary mode argument
|
||||
"UP028", # yield for -> yield from
|
||||
"UP030", # implicit references for positional format fields
|
||||
"UP031", # Replace % with .format
|
||||
"UP032", # Replace .format with f-string
|
||||
|
||||
# Deprecated Ruff rules.
|
||||
"UP038", # Use X | Y in isinstance
|
||||
]
|
||||
|
||||
[tool.poe.tasks]
|
||||
|
||||
ruff-main = "ruff check --extend-exclude nix/build-all.py"
|
||||
# It's actually Python 3.6, but ruff only supports 3.7+, but it should do.
|
||||
ruff-old = "ruff check nix/build-all.py --target-version py37"
|
||||
ruff.sequence = ["ruff-main", "ruff-old"]
|
||||
|
||||
black = "black ."
|
||||
|
||||
format.sequence = ["black", "ruff-main", "ruff-old"]
|
||||
|
||||
+3
-12
@@ -6,7 +6,7 @@ from numpy.typing import NDArray
|
||||
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float64],
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
) -> tuple[list[float], list[float], list[float]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to an element.
|
||||
|
||||
@@ -22,7 +22,7 @@ def camera_vectors_from_element_placement(
|
||||
|
||||
def camera_vectors_from_target_position(
|
||||
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
) -> tuple[list[float], list[float], list[float]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
@@ -38,16 +38,7 @@ def camera_vectors_from_target_position(
|
||||
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||
return camera_position, camera_direction, camera_up
|
||||
# rotation_transform = np.eye(4)
|
||||
# rotation_transform[0, :3] = camera_right
|
||||
# rotation_transform[1, :3] = camera_up
|
||||
# rotation_transform[2, :3] = camera_direction
|
||||
# translation_transform = np.eye(4)
|
||||
# translation_transform[:3, -1] = -camera_position
|
||||
# look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
# mat = np.linalg.inv(look_at_transform)
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
return camera_position.tolist(), camera_direction.tolist(), camera_up.tolist()
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
|
||||
|
||||
@@ -36,20 +36,10 @@ from bcf.v2.model.visinfo import (
|
||||
|
||||
__all__ = [
|
||||
"BimSnippet",
|
||||
"Comment",
|
||||
"CommentViewpoint",
|
||||
"Header",
|
||||
"HeaderFile",
|
||||
"Markup",
|
||||
"Topic",
|
||||
"TopicDocumentReference",
|
||||
"TopicRelatedTopic",
|
||||
"ViewPoint",
|
||||
"Project",
|
||||
"ProjectExtension",
|
||||
"Version",
|
||||
"BitmapFormat",
|
||||
"ClippingPlane",
|
||||
"Comment",
|
||||
"CommentViewpoint",
|
||||
"Component",
|
||||
"ComponentColoring",
|
||||
"ComponentColoringColor",
|
||||
@@ -58,10 +48,20 @@ __all__ = [
|
||||
"ComponentVisibilityExceptions",
|
||||
"Components",
|
||||
"Direction",
|
||||
"Header",
|
||||
"HeaderFile",
|
||||
"Line",
|
||||
"Markup",
|
||||
"OrthogonalCamera",
|
||||
"PerspectiveCamera",
|
||||
"Point",
|
||||
"Project",
|
||||
"ProjectExtension",
|
||||
"Topic",
|
||||
"TopicDocumentReference",
|
||||
"TopicRelatedTopic",
|
||||
"Version",
|
||||
"ViewPoint",
|
||||
"ViewSetupHints",
|
||||
"VisualizationInfo",
|
||||
"VisualizationInfoBitmap",
|
||||
|
||||
@@ -192,6 +192,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -55,40 +55,12 @@ from bcf.v3.model.visinfo import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Document",
|
||||
"DocumentInfo",
|
||||
"DocumentInfoDocuments",
|
||||
"Extensions",
|
||||
"ExtensionsPriorities",
|
||||
"ExtensionsSnippetTypes",
|
||||
"ExtensionsStages",
|
||||
"ExtensionsTopicLabels",
|
||||
"ExtensionsTopicStatuses",
|
||||
"ExtensionsTopicTypes",
|
||||
"ExtensionsUsers",
|
||||
"BimSnippet",
|
||||
"Comment",
|
||||
"CommentViewpoint",
|
||||
"DocumentReference",
|
||||
"File",
|
||||
"Header",
|
||||
"HeaderFiles",
|
||||
"Markup",
|
||||
"Topic",
|
||||
"TopicComments",
|
||||
"TopicDocumentReferences",
|
||||
"TopicLabels",
|
||||
"TopicReferenceLinks",
|
||||
"TopicRelatedTopics",
|
||||
"TopicRelatedTopicsRelatedTopic",
|
||||
"TopicViewpoints",
|
||||
"ViewPoint",
|
||||
"Project",
|
||||
"ProjectInfo",
|
||||
"Version",
|
||||
"Bitmap",
|
||||
"BitmapFormat",
|
||||
"ClippingPlane",
|
||||
"Comment",
|
||||
"CommentViewpoint",
|
||||
"Component",
|
||||
"ComponentColoring",
|
||||
"ComponentColoringColor",
|
||||
@@ -98,10 +70,38 @@ __all__ = [
|
||||
"ComponentVisibilityExceptions",
|
||||
"Components",
|
||||
"Direction",
|
||||
"Document",
|
||||
"DocumentInfo",
|
||||
"DocumentInfoDocuments",
|
||||
"DocumentReference",
|
||||
"Extensions",
|
||||
"ExtensionsPriorities",
|
||||
"ExtensionsSnippetTypes",
|
||||
"ExtensionsStages",
|
||||
"ExtensionsTopicLabels",
|
||||
"ExtensionsTopicStatuses",
|
||||
"ExtensionsTopicTypes",
|
||||
"ExtensionsUsers",
|
||||
"File",
|
||||
"Header",
|
||||
"HeaderFiles",
|
||||
"Line",
|
||||
"Markup",
|
||||
"OrthogonalCamera",
|
||||
"PerspectiveCamera",
|
||||
"Point",
|
||||
"Project",
|
||||
"ProjectInfo",
|
||||
"Topic",
|
||||
"TopicComments",
|
||||
"TopicDocumentReferences",
|
||||
"TopicLabels",
|
||||
"TopicReferenceLinks",
|
||||
"TopicRelatedTopics",
|
||||
"TopicRelatedTopicsRelatedTopic",
|
||||
"TopicViewpoints",
|
||||
"Version",
|
||||
"ViewPoint",
|
||||
"ViewSetupHints",
|
||||
"VisualizationInfo",
|
||||
"VisualizationInfoBitmaps",
|
||||
|
||||
@@ -179,6 +179,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
+4
-2
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=ec13294
|
||||
OLD:=6924012
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -127,7 +127,7 @@ endif
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download GitPython --dest=./wheels
|
||||
# Provides audio playback for costing
|
||||
# This is a REALLY IMPORTANT feature
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/Andrej730/aud.git --wheel-dir=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/zdhoward/aud --wheel-dir=./wheels
|
||||
# IfcOpenShell dependency - support for new typing features
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download typing_extensions --dest=./wheels
|
||||
# Required by IfcCSV
|
||||
@@ -192,6 +192,8 @@ else ifeq ($(PLATFORM), macos)
|
||||
else
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
endif
|
||||
# Required by ifctester web ui.
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download flask $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
|
||||
# jQuery is required for web UI functionality
|
||||
cd build/bonsai/bim/data/webui/static/js/ && wget https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
|
||||
|
||||
@@ -97,7 +97,6 @@ for name in modules.keys():
|
||||
|
||||
|
||||
classes = [
|
||||
operator.AddIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_delete_object,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
@@ -106,9 +105,12 @@ classes = [
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.BIM_OT_multiple_file_selector,
|
||||
operator.BIM_OT_attribute_add_subitem,
|
||||
operator.BIM_OT_attribute_remove_subitem,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.CloseBlendWarning,
|
||||
operator.CloseError,
|
||||
operator.CreateMacBonsaiApp,
|
||||
operator.CopyTextToClipboard,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
@@ -117,7 +119,6 @@ classes = [
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.RevertClippingPlaneCut,
|
||||
operator.SelectDir,
|
||||
operator.SelectIfcFile,
|
||||
@@ -127,15 +128,16 @@ classes = [
|
||||
operator.ShowSystemInfo,
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
operator.BIM_OT_attribute_search_values,
|
||||
prop.ObjProperty,
|
||||
prop.MultipleFileSelect,
|
||||
prop.Attribute,
|
||||
prop.ISODuration,
|
||||
prop.BIMAreaProperties,
|
||||
prop.BIMTabProperties,
|
||||
prop.BIMProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
prop.GlobalId,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMCollectionProperties,
|
||||
prop.BIMMeshProperties,
|
||||
@@ -145,7 +147,7 @@ classes = [
|
||||
prop.BIMSnapGroups,
|
||||
ui.BIM_UL_clipping_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.DocPreferences,
|
||||
ui.BIM_ADDON_preferences,
|
||||
# Tabs panel
|
||||
ui.BIM_PT_tabs,
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
@@ -499,6 +499,10 @@
|
||||
0.05087608844041824,
|
||||
0.05087608844041824
|
||||
],
|
||||
"scene.eevee.use_shadows": true,
|
||||
"scene.eevee.shadow_ray_count": 1,
|
||||
"scene.eevee.shadow_step_count": 6,
|
||||
"scene.eevee.shadow_resolution_scale": 1.0,
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 301 B |
Binary file not shown.
|
After Width: | Height: | Size: 215 B |
Binary file not shown.
|
After Width: | Height: | Size: 336 B |
Binary file not shown.
|
After Width: | Height: | Size: 217 B |
@@ -13,10 +13,10 @@ DATA;
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','Whether or not this drawing can be referenced in other drawings.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','Comma separated list of selector expressions to evaluate for each drawing elementand add results to their ''class'' attribute.\X2\000A\X0\E.g. ''Name, id'' would add to ''class'' value similar to ''Name-Wall id-1220''.\X2\000A\X0\Then it can be used to applied css styles based on the resulting class.\X2\000A\X0\If attribute is not present on the element, then it won''t be added to it''s ''class''.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
@@ -5,10 +5,10 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Anno
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4,#29));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
@@ -36,5 +36,7 @@ DATA;
|
||||
#29=IFCSIMPLEPROPERTYTEMPLATE('2pJmUDpB50VBdCOib1zcJJ',$,'Newline_At','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#30=IFCSIMPLEPROPERTYTEMPLATE('2TJn72t_v2cvBUG916Dpev',$,'CustomUnit','Dimension''s custom unit',.P_ENUMERATEDVALUE.,'IfcText',$,#31,$,$,$,.READWRITE.);
|
||||
#31=IFCPROPERTYENUMERATION('CustomUnit',(IFCTEXT('Feet and Inches - Fractional'),IFCTEXT('Feet - Decimal'),IFCTEXT('Inches - Fractional'),IFCTEXT('Inches - Decimal'),IFCTEXT('Meters'),IFCTEXT('Decimeters'),IFCTEXT('Centimeters'),IFCTEXT('Millimeters')),$);
|
||||
#32=IFCSIMPLEPROPERTYTEMPLATE('0gjJzDYBX8P85qn1xcAOOo',$,'Reverse_List','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#33=IFCSIMPLEPROPERTYTEMPLATE('22TrcxF8jFNB4buSmzjGEF',$,'List_Separator','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
SVG [mustache](https://mustache.github.io/) templates that will be used for sheets and fill be filled with infromation from the sheet's IfcDocumentInformation attributes (e.g. Identification, Name, Revision, etc).
|
||||
@@ -36,6 +36,7 @@ from bonsai.bim.ifc import IfcStore
|
||||
from mathutils import Vector
|
||||
from typing import Union
|
||||
from logging import Logger
|
||||
from math import radians
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
@@ -74,12 +75,12 @@ class IfcExporter:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
|
||||
def set_header(self):
|
||||
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.wrapped_data.header.file_name.time_stamp = (
|
||||
self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.header.file_name.time_stamp = (
|
||||
datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat()
|
||||
)
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.header.file_name.originating_system = "{} {}".format(
|
||||
self.get_application_name(), tool.Blender.get_bonsai_version()
|
||||
)
|
||||
|
||||
@@ -104,7 +105,16 @@ class IfcExporter:
|
||||
|
||||
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = self.file.by_id(tool.Blender.get_object_bim_props(obj).ifc_definition_id)
|
||||
if tool.Geometry.is_scaled(obj):
|
||||
# Handle camera scales specially
|
||||
if obj.type == "CAMERA":
|
||||
# Check if this is a reflected ceiling plan camera
|
||||
camera = tool.Ifc.get_entity(obj)
|
||||
if ifcopenshell.util.element.get_pset(camera, "EPset_Drawing", "TargetView") == "REFLECTED_PLAN_VIEW":
|
||||
# Ensure reflected ceiling cameras have the correct scale
|
||||
if obj.scale != (-1, -1, -1):
|
||||
obj.scale = (-1, -1, -1)
|
||||
# Skip all other scale handling for cameras
|
||||
elif tool.Geometry.is_scaled(obj):
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
# update_representation might not apply scale if the object has openings
|
||||
# reset it, so let user know that the scale wasn't saved.
|
||||
@@ -139,6 +149,10 @@ class IfcExporter:
|
||||
|
||||
|
||||
class IfcExportSettings:
|
||||
"""
|
||||
Initialize only using `IfcExportSettings.factory()`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.logger: Logger = None
|
||||
self.output_file: str = None
|
||||
|
||||
@@ -30,7 +30,7 @@ from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
from bonsai.bim.module.aggregate.decorator import AggregateDecorator
|
||||
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
|
||||
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
|
||||
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator, BoundingBoxDecorator
|
||||
from bonsai.bim.module.nest.decorator import NestDecorator
|
||||
from mathutils import Vector
|
||||
from math import cos
|
||||
@@ -219,12 +219,10 @@ def refresh_ui_data():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
if isinstance(ifc_file := tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
ifc_file.clear_cache()
|
||||
|
||||
props = tool.Drawing.get_document_props()
|
||||
props.should_draw_decorations = props.should_draw_decorations
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
if tool.Web.get_web_props().is_connected:
|
||||
tool.Web.send_webui_data()
|
||||
|
||||
|
||||
@@ -287,14 +285,17 @@ def get_application(ifc: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
|
||||
# Use only main part from the version to avoid flooding advanced users projects with IfcApplications.
|
||||
version = tool.Blender.get_bonsai_version().split("-")[0]
|
||||
identifier = f"Bonsai-{version}"
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
if element.ApplicationIdentifier == "Bonsai" and element.Version == version:
|
||||
if element.ApplicationIdentifier == identifier:
|
||||
return element
|
||||
application_developer = next((org for org in ifc.by_type("IfcOrganization") if org.Name == "IfcOpenShell"), None)
|
||||
application = ifcopenshell.api.owner.add_application(
|
||||
ifc,
|
||||
application_developer=application_developer,
|
||||
version=version,
|
||||
application_full_name="Bonsai",
|
||||
application_identifier="Bonsai",
|
||||
application_identifier=identifier,
|
||||
)
|
||||
SettingsCache.APPLICATION_ID = application.id()
|
||||
SettingsCache.set_file(ifc)
|
||||
@@ -406,9 +407,13 @@ def load_post(scene):
|
||||
WallAxisDecorator.install(bpy.context)
|
||||
if model_props.show_slab_direction:
|
||||
SlabDirectionDecorator.install(bpy.context)
|
||||
if model_props.show_bounding_box:
|
||||
BoundingBoxDecorator.install(bpy.context)
|
||||
|
||||
if preferences.should_use_snap and (scene := bpy.context.scene):
|
||||
# Snapping is off by default in Blender, but in BIM, it's more useful to be on
|
||||
scene.tool_settings.use_snap = True
|
||||
# Match default Bonsai snaps
|
||||
scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"}
|
||||
|
||||
tool.Blender.sync_old_preferences()
|
||||
|
||||
+110
-26
@@ -27,8 +27,9 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
import bonsai.tool as tool
|
||||
from types import EllipsisType
|
||||
from typing import Optional, Any, Union, TYPE_CHECKING
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bonsai.bim.prop
|
||||
@@ -39,6 +40,9 @@ if TYPE_CHECKING:
|
||||
# - None - property should be imported by default workflow
|
||||
# - True - setting value for imported attribute should be skipped
|
||||
# - False - property should be skipped entirely from import
|
||||
# Second argument is optional,
|
||||
# because ImportCallback might be called for attributes that are not created by default
|
||||
# (e.g. IFC entity attributes).
|
||||
ImportCallback = Callable[[str, Optional[bonsai.bim.prop.Attribute], dict[str, Any]], Union[bool, None]]
|
||||
# ExportCallback return values:
|
||||
# - True - property should be skipped entirely from export
|
||||
@@ -47,36 +51,67 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
def draw_attributes(
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: Union[bpy.types.bpy_prop_collection_idprop[Attribute], Sequence[Attribute]],
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
|
||||
callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None,
|
||||
*,
|
||||
enable_search: Union[bool, EllipsisType] = ...,
|
||||
) -> None:
|
||||
"""Draw editable UI for prop.Attributes.
|
||||
|
||||
You can set attribute active in popup with `active_attribute`
|
||||
meaning you will be able to type into attribute's field without having to click
|
||||
on it first
|
||||
|
||||
:param enable_search: Add search button to string, integer, and float attributes
|
||||
Possible values:
|
||||
|
||||
- ``...`` (default value) -
|
||||
add search if possible. If it's not possible, there will be no warnings or errors.
|
||||
- ``True`` - always add search, if it's not possible it will result in errors.
|
||||
- ``False`` - never add search.
|
||||
|
||||
"""
|
||||
for attribute in props:
|
||||
row = layout.row(align=True)
|
||||
if attribute == popup_active_attribute:
|
||||
row.activate_init = True
|
||||
draw_attribute(attribute, row, copy_operator)
|
||||
draw_attribute(attribute, row, copy_operator, enable_search=enable_search)
|
||||
if callback:
|
||||
callback(attribute, row)
|
||||
|
||||
|
||||
def draw_attribute(
|
||||
attribute: bonsai.bim.prop.Attribute, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
attribute: bonsai.bim.prop.Attribute,
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
enable_search: Union[bool, EllipsisType] = ...,
|
||||
) -> None:
|
||||
value_name = attribute.get_value_name(display_only=True)
|
||||
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
elif value_name == "filepath_value":
|
||||
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
|
||||
elif attribute.name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"):
|
||||
|
||||
elif value_name == "subitems_values":
|
||||
col = layout.column()
|
||||
layout = col.row(align=True)
|
||||
layout.label(text=f"{attribute.name}:")
|
||||
data_path = tool.Blender.get_full_data_path(attribute, value_name)
|
||||
for i, item in enumerate(attribute.subitems_values, 1):
|
||||
row = col.row(align=True)
|
||||
row.alignment = "EXPAND"
|
||||
row.prop(item, "name", text=f"# {i}")
|
||||
op = row.operator("bim.attribute_remove_subitem", text="", icon="X")
|
||||
op.data_path = data_path
|
||||
op.index = i - 1
|
||||
op = layout.operator("bim.attribute_add_subitem", icon="ADD", text="")
|
||||
op.data_path = data_path
|
||||
|
||||
elif attribute.special_type == "DURATION":
|
||||
props = tool.Sequence.get_work_schedule_props()
|
||||
for item in props.durations_attributes:
|
||||
if item.name == attribute.name:
|
||||
@@ -96,17 +131,34 @@ def draw_attribute(
|
||||
text=attribute.display_name,
|
||||
)
|
||||
|
||||
if attribute.is_uri:
|
||||
if attribute.special_type == "URI":
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.data_path = attribute.path_from_id("string_value")
|
||||
op.attribute_data_path = tool.Blender.get_full_data_path(attribute)
|
||||
elif attribute.special_type in ("DATE", "DATETIME"):
|
||||
op = layout.operator("bim.datepicker", text="", icon="TIME")
|
||||
op.target_prop = attribute.path_from_id("string_value")
|
||||
op.include_time = attribute.special_type == "DATETIME"
|
||||
|
||||
if attribute.data_type in ("string", "integer", "float") and (
|
||||
enable_search is True or (enable_search is ... and attribute.ifc_class)
|
||||
):
|
||||
op = layout.operator("bim.attribute_search_values", text="", icon="VIEWZOOM")
|
||||
op.attribute_name = attribute.name
|
||||
op.attribute_ifc_class = attribute.ifc_class
|
||||
op.data_path = tool.Blender.get_full_data_path(attribute, value_name)
|
||||
op.data_type = attribute.data_type
|
||||
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
if attribute.use_explorer_ui:
|
||||
op = layout.operator("bim.explorer_show_ui_popup", text="", icon="ZOOM_SELECTED")
|
||||
op.ifc_class = attribute.ifc_class
|
||||
op.attribute_name = attribute.name
|
||||
op.data_path = tool.Blender.get_full_data_path(attribute, value_name)
|
||||
if ifc_id := attribute.get_value():
|
||||
op.preselect_ifc_id = int(ifc_id)
|
||||
|
||||
if attribute.name == "GlobalId":
|
||||
layout.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
|
||||
elif copy_operator:
|
||||
@@ -115,23 +167,14 @@ def draw_attribute(
|
||||
|
||||
|
||||
def import_attributes(
|
||||
ifc_class: str,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
data: dict[str, Any],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
schema = tool.Ifc.schema()
|
||||
assert (entity := schema.declaration_by_name(ifc_class).as_entity())
|
||||
for attribute in entity.all_attributes():
|
||||
import_attribute(attribute, props, data, callback=callback)
|
||||
|
||||
|
||||
# A more elegant attribute importer signature, intended to supersede import_attributes
|
||||
def import_attributes2(
|
||||
element: Union[str, ifcopenshell.entity_instance],
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param element: Entity or IFC class string.
|
||||
"""
|
||||
info: dict[str, Any]
|
||||
if isinstance(element, str):
|
||||
assert (entity := tool.Ifc.schema().declaration_by_name(element).as_entity())
|
||||
attributes = entity.all_attributes()
|
||||
@@ -153,15 +196,19 @@ def import_attribute(
|
||||
) -> None:
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
# Complex data types (aggregates and entities) are handled only by callback.
|
||||
if data_type == ("list", "string"):
|
||||
data_type = "list[string]"
|
||||
if isinstance(data_type, tuple) or data_type == "entity":
|
||||
callback(attribute.name(), None, data) if callback else None
|
||||
return
|
||||
new: bonsai.bim.prop.Attribute = props.add()
|
||||
|
||||
new = props.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type if isinstance(data_type, str) else ""
|
||||
new.ifc_class = data["type"]
|
||||
|
||||
is_handled_by_callback = callback(attribute.name(), new, data) if callback else None
|
||||
data_type = new.data_type # Allow callback to override data type.
|
||||
|
||||
@@ -171,8 +218,9 @@ def import_attribute(
|
||||
props.remove(len(props) - 1)
|
||||
elif data_type == "string":
|
||||
new.string_value = "" if new.is_null else str(data[attribute.name()]).replace("\n", "\\n")
|
||||
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
|
||||
new.is_uri = True
|
||||
attribute_type = attribute.type_of_attribute()
|
||||
if attribute_type._is("IfcURIReference"):
|
||||
new.special_type = "URI"
|
||||
elif attribute.type_of_attribute()._is("IfcDate"):
|
||||
new.special_type = "DATE"
|
||||
elif attribute.type_of_attribute()._is("IfcDateTime"):
|
||||
@@ -206,6 +254,12 @@ def import_attribute(
|
||||
|
||||
if enum_value is not None:
|
||||
new.enum_value = enum_value
|
||||
elif data_type == "list[string]":
|
||||
value: Union[list[str], None] = data[attribute.name()]
|
||||
if value:
|
||||
for item in value:
|
||||
new.subitems_values.add().name = str(item).replace("\n", "\\n")
|
||||
|
||||
add_attribute_description(new, data)
|
||||
add_attribute_min_max(attribute, new)
|
||||
|
||||
@@ -242,7 +296,13 @@ def add_attribute_enum_items_descriptions(
|
||||
new_enum_description.name = description
|
||||
|
||||
|
||||
def add_attribute_description(attribute_blender: bonsai.bim.prop.Attribute, attribute_ifc=None):
|
||||
def add_attribute_description(
|
||||
attribute_blender: bonsai.bim.prop.Attribute,
|
||||
attribute_ifc: Union[ifcopenshell.entity_instance, None] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param attribute_ifc: IFC Entity to use as a fallback source of description (using "Description" attribute).
|
||||
"""
|
||||
if not attribute_blender.name:
|
||||
return
|
||||
version = tool.Ifc.get_schema()
|
||||
@@ -273,6 +333,16 @@ def export_attributes(
|
||||
return attributes
|
||||
|
||||
|
||||
def process_exported_entity_attribute(attributes: dict[str, Any], attribute_names: list[str]) -> None:
|
||||
for attribute_name in attribute_names:
|
||||
entity_id = attributes[attribute_name]
|
||||
if entity_id is None:
|
||||
# Maybe it was removed by now and enum is invalid.
|
||||
del attributes[attribute_name]
|
||||
else:
|
||||
attributes[attribute_name] = tool.Ifc.get().by_id(int(entity_id))
|
||||
|
||||
|
||||
ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties]
|
||||
|
||||
|
||||
@@ -296,22 +366,35 @@ def prop_with_search(
|
||||
prop_name: str,
|
||||
should_click_ok: bool = False,
|
||||
original_operator_path: Optional[str] = None,
|
||||
*,
|
||||
enable_relating_type_suggestions: bool = False,
|
||||
search_threshold: int = 10,
|
||||
button_kwargs: Union[dict[str, Any], None] = None,
|
||||
**kwargs: Any,
|
||||
) -> bpy.types.UILayout:
|
||||
"""
|
||||
Draw a row with enum prop and enum search operator.
|
||||
|
||||
Search operator appears only in case if there's more than `search_threshold` items in enum.
|
||||
|
||||
:arg button_kwargs: kwargs to pass to ``UILayout.operator()``.
|
||||
:arg kwargs: kwargs to pass to ``UILayout.prop()``.
|
||||
:arg enable_relating_type_suggestions: Enable additional suggestions for relating type properties.
|
||||
:arg search_threshold: Minimum number of enum items required to show search button.
|
||||
:return: Added row.
|
||||
"""
|
||||
# kwargs are layout.prop arguments (text, icon, etc.)
|
||||
row = layout.row(align=True)
|
||||
row.prop(data, prop_name, **kwargs)
|
||||
try:
|
||||
if len(get_enum_items(data, prop_name, original_operator_path=original_operator_path)) > 10:
|
||||
if len(get_enum_items(data, prop_name, original_operator_path=original_operator_path)) > search_threshold:
|
||||
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
|
||||
row.context_pointer_set(name="data", data=data)
|
||||
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
|
||||
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM", **(button_kwargs or {}))
|
||||
op.prop_name = prop_name
|
||||
op.should_click_ok = should_click_ok
|
||||
op.original_operator_path = original_operator_path or ""
|
||||
op.enable_relating_type_suggestions = enable_relating_type_suggestions
|
||||
except TypeError: # Prop is not iterable
|
||||
pass
|
||||
return row
|
||||
@@ -473,6 +556,7 @@ def draw_filter(
|
||||
elif ifc_filter.type == "query":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
|
||||
row.prop(ifc_filter, "comparison", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
|
||||
@@ -64,7 +64,11 @@ class TransactionStep(TypedDict):
|
||||
|
||||
class IfcStore:
|
||||
path: str = ""
|
||||
"""Should be set only using ``tool.Ifc.set_path``."""
|
||||
|
||||
file: Optional[ifcopenshell.file] = None
|
||||
"""Should be set only using ``tool.Ifc.set``."""
|
||||
|
||||
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
|
||||
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
|
||||
cache_path: Optional[str] = None
|
||||
@@ -105,32 +109,45 @@ class IfcStore:
|
||||
IfcStore.session_files = {}
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
def get_file() -> ifcopenshell.file | None:
|
||||
if IfcStore.file is None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
IfcStore.set_path(props.ifc_file)
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
tool.Ifc.after_file_loaded()
|
||||
except Exception as e:
|
||||
print(f"Failed to load file {IfcStore.path}. Error details: {e}")
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
def set_path(value):
|
||||
def set_path(value: str) -> None:
|
||||
IfcStore.path = value
|
||||
# Interpret relative paths as relative to .blend file.
|
||||
if IfcStore.path and not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
|
||||
@staticmethod
|
||||
def get_cache():
|
||||
def generate_cache_path() -> str:
|
||||
"""Generate cache path based on the active file and it's path."""
|
||||
assert IfcStore.file
|
||||
ifc_key = IfcStore.path + IfcStore.file.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
|
||||
return cache_path
|
||||
|
||||
@staticmethod
|
||||
def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None:
|
||||
"""Get existing cache for the current file or create a new one.
|
||||
|
||||
.h5 cache name reflects IFC filepath and it's current header's timestamp.
|
||||
"""
|
||||
if IfcStore.cache is None and IfcStore.path:
|
||||
props = tool.Blender.get_bim_props()
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
os.makedirs(props.cache_dir, exist_ok=True)
|
||||
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
|
||||
cache_path = IfcStore.generate_cache_path()
|
||||
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
|
||||
IfcStore.cache_path = cache_path
|
||||
cache_path = Path(IfcStore.cache_path)
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
serializer_settings = ifcopenshell.geom.serializer_settings()
|
||||
@@ -163,15 +180,12 @@ class IfcStore:
|
||||
return IfcStore.cache
|
||||
|
||||
@staticmethod
|
||||
def update_cache():
|
||||
def update_cache() -> None:
|
||||
"""Update cache filename after timestamp was updated."""
|
||||
if not IfcStore.cache:
|
||||
return
|
||||
assert IfcStore.cache_path
|
||||
assert IfcStore.file
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
props = tool.Blender.get_bim_props()
|
||||
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
|
||||
new_cache_path = IfcStore.generate_cache_path()
|
||||
IfcStore.cache = None
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
@@ -183,11 +197,11 @@ class IfcStore:
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path) -> None:
|
||||
def load_file(path: str) -> None:
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
props = tool.Project.get_project_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if extension.lower() == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
with zipfile.ZipFile(path, "r") as zip_ref:
|
||||
@@ -197,10 +211,11 @@ class IfcStore:
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif props.should_stream:
|
||||
elif prefs.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
tool.Ifc.after_file_loaded()
|
||||
|
||||
@staticmethod
|
||||
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
|
||||
@@ -445,8 +460,11 @@ class IfcStore:
|
||||
|
||||
return callback
|
||||
|
||||
@staticmethod
|
||||
modal_in_progress = False
|
||||
|
||||
@classmethod
|
||||
def execute_ifc_operator(
|
||||
cls,
|
||||
operator: tool.Ifc.Operator,
|
||||
context: bpy.types.Context,
|
||||
event=None,
|
||||
@@ -456,16 +474,33 @@ class IfcStore:
|
||||
props = tool.Blender.get_bim_props()
|
||||
props.is_dirty = True
|
||||
# Modals don't nest, and Blender handles the loop that continuously calls modal()
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction) or (method == "MODAL")
|
||||
# So for modal operators we emulate nesting - first modal call is starting transaction
|
||||
# and modal call with FINISHED/CANCELLED result finishes it.
|
||||
|
||||
# Example call chain for modal operators:
|
||||
# Operator1.execute called
|
||||
# -> Operator.execute called nested Operator2
|
||||
# -> Operator2.execute returned RUNNING_MODAL
|
||||
# -> Operator2.execute is finished
|
||||
# -> Operator1.execute is finished
|
||||
# -> Operator2.modal is called and returned PASS_THROUGH (going to another modal loop)
|
||||
# -> Operator2.modal is finished
|
||||
# -> Operator2.modal called again and last two steps repeat until FINISHED or CANCELLED
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction) and not cls.modal_in_progress
|
||||
|
||||
if is_top_level_operator:
|
||||
IfcStore.begin_transaction(operator)
|
||||
if tool.Ifc.get():
|
||||
tool.Ifc.get().begin_transaction()
|
||||
if ifc_file := tool.Ifc.get():
|
||||
assert (
|
||||
ifc_file.transaction is None
|
||||
), "Trying to override existing transaction, possible IFC undo data loss."
|
||||
ifc_file.begin_transaction()
|
||||
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
|
||||
BrickStore.begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
|
||||
if method == "MODAL":
|
||||
cls.modal_in_progress = True
|
||||
else:
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@@ -483,6 +518,9 @@ class IfcStore:
|
||||
IfcStore.end_transaction(operator)
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
if method == "MODAL":
|
||||
cls.modal_in_progress = False
|
||||
|
||||
try:
|
||||
if method == "EXECUTE":
|
||||
result = getattr(operator, "_execute")(context)
|
||||
@@ -503,8 +541,10 @@ class IfcStore:
|
||||
|
||||
if method == "MODAL":
|
||||
if result == {"FINISHED"}:
|
||||
is_top_level_operator = True
|
||||
end_top_level_operator()
|
||||
elif result == {"CANCELLED"}:
|
||||
is_top_level_operator = True
|
||||
# Please read the docs: https://docs.blender.org/api/current/bpy.types.Operator.html
|
||||
# > "when an operator returns {'CANCELLED'}, no undo step will be created".
|
||||
# This means that if your modal edits IFC data, then the user
|
||||
|
||||
@@ -29,6 +29,7 @@ import numpy.typing as npt
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
@@ -218,9 +219,7 @@ class IfcImporter:
|
||||
self.gross_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.element_types: set[ifcopenshell.entity_instance] = set()
|
||||
self.spatial_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.type_products = {}
|
||||
self.meshes: dict[str, OBJECT_DATA_TYPE] = {}
|
||||
self.mesh_shapes = {}
|
||||
self.time = 0
|
||||
self.unit_scale = 1.0
|
||||
# ifc definition ids to blender elements mapping
|
||||
@@ -230,7 +229,8 @@ class IfcImporter:
|
||||
self.progress = 0
|
||||
|
||||
self.material_creator = MaterialCreator(ifc_import_settings, self)
|
||||
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
classes_to_wireframe_str = prefs.doc.classes_to_wireframe
|
||||
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
|
||||
|
||||
def profile_code(self, message: str) -> None:
|
||||
@@ -283,6 +283,7 @@ class IfcImporter:
|
||||
self.place_objects_in_collections()
|
||||
self.profile_code("Place objects in collections")
|
||||
self.setup_arrays()
|
||||
self.update_linked_aggregates()
|
||||
self.profile_code("Setup arrays")
|
||||
tool.Project.load_linked_models_from_ifc()
|
||||
self.profile_code("Load linked models")
|
||||
@@ -461,6 +462,10 @@ class IfcImporter:
|
||||
return False
|
||||
|
||||
def calculate_model_offset(self) -> None:
|
||||
# TODO:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
print("WARNING. Calculating model offset for IFCSQLite is not supported.")
|
||||
return
|
||||
props = tool.Georeference.get_georeference_props()
|
||||
if self.ifc_import_settings.false_origin_mode == "MANUAL":
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
@@ -514,11 +519,52 @@ class IfcImporter:
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement))
|
||||
|
||||
def create_element_types(self):
|
||||
# TODO:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
self.create_element_types_sqlite(self.element_types)
|
||||
return
|
||||
for element_type in self.element_types:
|
||||
if not element_type:
|
||||
continue
|
||||
self.create_element_type(element_type)
|
||||
|
||||
def create_element_types_sqlite(self, element_types: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sqlite)
|
||||
geometry_cache = self.file.get_geometry([e.id() for e in element_types])
|
||||
geometry_meshes: dict[str, bpy.types.Mesh] = {}
|
||||
for geometry_id, geometry in geometry_cache["geometry"].items():
|
||||
verts = geometry["verts"]
|
||||
fake_geometry = type("Geometry", (), {"id": geometry_id})
|
||||
mesh_name = tool.Loader.get_mesh_name_from_shape(fake_geometry) # pyright: ignore[reportArgumentType]
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
if geometry["faces"].size:
|
||||
mesh = tool.Loader.create_mesh_from_shape(
|
||||
mesh=mesh, faces=geometry["faces"].reshape(-1, 3), verts=verts.reshape(-1, 3)
|
||||
)
|
||||
else:
|
||||
vertices = verts.reshape(-1, 3).tolist()
|
||||
edges = geometry["edges"].reshape(-1, 2).tolist()
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
tool.Loader.link_mesh(fake_geometry, mesh) # pyright: ignore[reportArgumentType]
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
mesh["ios_material_ids"] = geometry["material_ids"]
|
||||
self.meshes[mesh_name] = mesh
|
||||
geometry_meshes[geometry_id] = mesh
|
||||
|
||||
shapes = geometry_cache["shapes"]
|
||||
for element in element_types:
|
||||
# Allow missing element types to accomodate older ifcsqlite files
|
||||
# that didn't store element types geometry.
|
||||
shape = shapes.get(element.id())
|
||||
if shape:
|
||||
geometry_id = shapes[element.id()]["geometry"]
|
||||
else:
|
||||
geometry_id = None
|
||||
mesh = None if geometry_id is None else geometry_meshes[geometry_id]
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh, False)
|
||||
|
||||
def create_element_type(self, element: ifcopenshell.entity_instance) -> None:
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
mesh = None
|
||||
@@ -545,7 +591,6 @@ class IfcImporter:
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh, False)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def create_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
@@ -580,18 +625,13 @@ class IfcImporter:
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self) -> None:
|
||||
if tool.Blender.get_addon_preferences().spatial_elements_unselectable:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=True)
|
||||
else:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=False)
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
self.create_generic_elements(self.gross_elements, is_gross=True)
|
||||
|
||||
def create_generic_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance], unselectable=False, is_gross=False
|
||||
) -> None:
|
||||
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], is_gross=False) -> None:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
@@ -615,10 +655,6 @@ class IfcImporter:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
objects.add(self.create_product(element))
|
||||
|
||||
if unselectable:
|
||||
for obj in objects:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sql.sqlite)
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
@@ -629,15 +665,13 @@ class IfcImporter:
|
||||
verts = geometry["verts"]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry["faces"]:
|
||||
if geometry["faces"].size:
|
||||
mesh = tool.Loader.create_mesh_from_shape(
|
||||
mesh=mesh, faces=geometry["faces"].reshape(-1, 3), verts=verts.reshape(-1, 3)
|
||||
)
|
||||
else:
|
||||
e = geometry["edges"]
|
||||
v = verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
vertices = verts.reshape(-1, 3).tolist()
|
||||
edges = geometry["edges"].reshape(-1, 2).tolist()
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
@@ -661,7 +695,7 @@ class IfcImporter:
|
||||
settings: Optional[ifcopenshell.geom.main.settings] = None,
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
checkpoint = time.time()
|
||||
results = set()
|
||||
results: set[ifcopenshell.entity_instance] = set()
|
||||
if not products:
|
||||
return results
|
||||
|
||||
@@ -882,7 +916,8 @@ class IfcImporter:
|
||||
for material in self.material_creator.materials.values():
|
||||
bpy.data.materials.remove(material)
|
||||
|
||||
def add_project_to_scene(self):
|
||||
def add_project_to_scene(self) -> None:
|
||||
assert bpy.context.scene
|
||||
try:
|
||||
bpy.context.scene.collection.children.link(self.project["blender"])
|
||||
except:
|
||||
@@ -907,6 +942,7 @@ class IfcImporter:
|
||||
bpy.ops.mesh.normals_make_consistent()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
assert bpy.context.view_layer
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
@@ -916,6 +952,9 @@ class IfcImporter:
|
||||
if not props.ifc_file:
|
||||
props.ifc_file = self.ifc_import_settings.input_file
|
||||
self.file = tool.Ifc.get()
|
||||
# IFC4 Reference View shall have no booleans https://github.com/BuildingSMART/IFC4-CV/issues/14
|
||||
if self.file.schema == "IFC4" and "ReferenceView" in str(self.file.header.file_description.description):
|
||||
self.ifc_import_settings.void_limit = 0
|
||||
|
||||
def calculate_unit_scale(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
@@ -925,6 +964,7 @@ class IfcImporter:
|
||||
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
|
||||
return # Geometry is optional in IFC
|
||||
props = tool.Blender.get_bim_props()
|
||||
assert bpy.context.scene
|
||||
for unit in assignment.Units:
|
||||
if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT":
|
||||
if unit.is_a("IfcSIUnit"):
|
||||
@@ -972,6 +1012,7 @@ class IfcImporter:
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
obj.hide_select = True
|
||||
obj.hide_viewport = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
@@ -1032,9 +1073,9 @@ class IfcImporter:
|
||||
def create_curve(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
||||
shape: Union[W.Triangulation, W.TriangulationElement],
|
||||
) -> bpy.types.Curve:
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
if isinstance(shape, W.TriangulationElement):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
@@ -1063,11 +1104,11 @@ class IfcImporter:
|
||||
def create_mesh(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
||||
shape: Union[W.Triangulation, W.TriangulationElement],
|
||||
cartesian_point_offset: Union[npt.NDArray[np.float64], Literal[False]] = None,
|
||||
) -> Union[bpy.types.Mesh, None]:
|
||||
try:
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
if isinstance(shape, W.TriangulationElement):
|
||||
# shape is ShapeElementType
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
@@ -1173,10 +1214,44 @@ class IfcImporter:
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
def update_linked_aggregates(self):
|
||||
# TODO Remove this after a while. See commit 17d6b8a
|
||||
# https://github.com/IfcOpenShell/IfcOpenShell/commit/17d6b8af61c8dd7cd82f6e72b2fb3831d851a33d#commitcomment-163151609
|
||||
|
||||
groups = self.file.by_type("IfcGroup")
|
||||
target_groups = [group for group in groups if group.Name == "BBIM_Linked_Aggregate"]
|
||||
if not target_groups:
|
||||
return
|
||||
|
||||
for i, group in enumerate(target_groups):
|
||||
name = "Default"
|
||||
elements = ifcopenshell.util.element.get_grouped_by(self.file.by_id(group.id()), is_recursive=True)
|
||||
for j, element in enumerate(elements):
|
||||
split = element.Name.rsplit("_")
|
||||
name = split[0]
|
||||
try:
|
||||
aggregate_index = int(split[1])
|
||||
except:
|
||||
aggregate_index = 0
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate", should_inherit=False)
|
||||
if not pset:
|
||||
return
|
||||
if "Aggregate_Index" not in pset.keys():
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
self.file,
|
||||
pset=self.file.by_id(pset["id"]),
|
||||
properties={"Aggregate_Index": aggregate_index, "Name": name},
|
||||
)
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
"""
|
||||
Initialize only using `IfcImportSettings.factory()`.
|
||||
"""
|
||||
|
||||
input_file: Union[str, None] = None
|
||||
logger: Union[logging.Logger, None] = None
|
||||
logger: logging.Logger
|
||||
|
||||
def __init__(self):
|
||||
self.diff_file = None
|
||||
@@ -1213,6 +1288,7 @@ class IfcImportSettings:
|
||||
context=None, input_file: Optional[str] = None, logger: Optional[logging.Logger] = None
|
||||
) -> IfcImportSettings:
|
||||
scene_diff = tool.Blender.get_diff_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
props = tool.Project.get_project_props()
|
||||
settings = IfcImportSettings()
|
||||
settings.input_file = input_file
|
||||
@@ -1225,7 +1301,7 @@ class IfcImportSettings:
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_load_geometry = props.should_load_geometry
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.should_cache = props.should_cache
|
||||
settings.should_cache = prefs.should_always_cache or props.should_cache
|
||||
settings.deflection_tolerance = props.deflection_tolerance
|
||||
settings.angular_tolerance = props.angular_tolerance
|
||||
settings.void_limit = props.void_limit
|
||||
|
||||
@@ -71,11 +71,14 @@ class AggregateData:
|
||||
@classmethod
|
||||
def total_linked_aggregate(cls) -> Union[int, None]:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return
|
||||
if not element:
|
||||
return
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if not aggregate and not parts:
|
||||
return
|
||||
if parts:
|
||||
aggregate = element
|
||||
|
||||
product_linked_agg_group = next(
|
||||
(
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.group
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
@@ -93,6 +94,9 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
aggregates_to_check = set()
|
||||
|
||||
# First pass: unassign all parts and track their aggregates
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -100,6 +104,10 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
continue
|
||||
|
||||
# Track this aggregate for later checking
|
||||
aggregates_to_check.add(aggregate)
|
||||
|
||||
core.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
@@ -115,6 +123,29 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
# Second pass: delete aggregates that now have no parts
|
||||
deleted_aggregates = []
|
||||
for aggregate in aggregates_to_check:
|
||||
related_objects = ifcopenshell.util.element.get_parts(aggregate)
|
||||
if len(related_objects) == 0:
|
||||
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
|
||||
deleted_aggregates.append(aggregate_name)
|
||||
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj:
|
||||
ifcopenshell.api.root.remove_product(tool.Ifc.get(), product=aggregate)
|
||||
bpy.data.objects.remove(aggregate_obj, do_unlink=True)
|
||||
|
||||
# Show info message if aggregates were deleted
|
||||
if deleted_aggregates:
|
||||
if len(deleted_aggregates) == 1:
|
||||
self.report(
|
||||
{"INFO"}, f"Aggregate '{deleted_aggregates[0]}' was deleted because it had no remaining parts"
|
||||
)
|
||||
else:
|
||||
aggregate_list = ", ".join(f"'{name}'" for name in deleted_aggregates)
|
||||
self.report({"INFO"}, f"Aggregates {aggregate_list} were deleted because they had no remaining parts")
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator):
|
||||
"""Enable editing aggregation relationship"""
|
||||
@@ -224,16 +255,25 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.select_aggregate"
|
||||
bl_label = "Select Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
obj: bpy.props.StringProperty()
|
||||
select_parts: bpy.props.BoolProperty(default=False)
|
||||
one_level_deep: bpy.props.BoolProperty(
|
||||
name="One Level Deep", description="Select only immediate children, not recursively", default=False
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.select_parts:
|
||||
return "Select Aggregate and Parts"
|
||||
return "Select Aggregate and Parts.\n\nCtrl+click to select only one level deep"
|
||||
else:
|
||||
return "Select Aggregate"
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.type == "LEFTMOUSE" and event.ctrl:
|
||||
self.one_level_deep = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
all_parts = []
|
||||
for obj in context.selected_objects:
|
||||
@@ -249,14 +289,27 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
obj.select_set(False)
|
||||
|
||||
if self.select_parts:
|
||||
all_objs = []
|
||||
selected_parts = []
|
||||
|
||||
for part in all_parts:
|
||||
if part.IsDecomposedBy:
|
||||
for subpart in part.IsDecomposedBy[0].RelatedObjects:
|
||||
all_parts.append(subpart)
|
||||
all_objs.append(part)
|
||||
for rel in part.IsDecomposedBy:
|
||||
for subpart in rel.RelatedObjects:
|
||||
selected_parts.append(subpart)
|
||||
|
||||
for element in all_objs:
|
||||
# If not limited to one level, traverse deeper
|
||||
if not self.one_level_deep:
|
||||
|
||||
def add_descendants(elem):
|
||||
if elem.IsDecomposedBy:
|
||||
for rel in elem.IsDecomposedBy:
|
||||
for deeper in rel.RelatedObjects:
|
||||
selected_parts.append(deeper)
|
||||
add_descendants(deeper)
|
||||
|
||||
add_descendants(subpart)
|
||||
|
||||
for element in set(selected_parts + all_parts):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
@@ -264,9 +317,11 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
else:
|
||||
for aggregate_element in all_parts:
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate_element)
|
||||
aggregate_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = aggregate_obj
|
||||
if aggregate_obj:
|
||||
aggregate_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = aggregate_obj
|
||||
|
||||
self.one_level_deep = False # <-- forcibly reset
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -301,6 +356,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
@@ -312,8 +368,8 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
|
||||
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Linked_Aggregate")
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=part, pset=pset)
|
||||
pset = ifc_file.by_id(pset["id"])
|
||||
ifcopenshell.api.pset.remove_pset(ifc_file, product=part, pset=pset)
|
||||
|
||||
linked_aggregate_group = next(
|
||||
r.RelatingGroup
|
||||
@@ -321,7 +377,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
)
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group, products=[aggregate])
|
||||
ifcopenshell.api.group.unassign_group(ifc_file, group=linked_aggregate_group, products=[aggregate])
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class BIM_PT_linked_aggregate(Panel):
|
||||
assert (element := tool.Ifc.get_entity(obj))
|
||||
row = layout.row(align=True)
|
||||
|
||||
if element.Decomposes:
|
||||
if element.Decomposes or element.IsDecomposedBy:
|
||||
Number_Linked_Aggregates = AggregateData.data["total_linked_aggregate"]
|
||||
if not Number_Linked_Aggregates:
|
||||
row.label(text="Not a Linked Aggregate")
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
import os
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.alignment.add_stationing_to_alignment
|
||||
|
||||
import bpy
|
||||
import json
|
||||
@@ -62,9 +61,7 @@ class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
start = time.time()
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(self.file, alignment)
|
||||
ifcopenshell.api.alignment.add_stationing_to_alignment(self.file, alignment=alignment, start_station=0.0)
|
||||
alignment = ifcopenshell.api.alignment.create_from_csv(self.file, self.filepath)
|
||||
|
||||
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
|
||||
sites = self.file.by_type("IfcSite")
|
||||
|
||||
@@ -25,13 +25,23 @@ classes = (
|
||||
operator.EditAttributes,
|
||||
operator.GenerateGlobalId,
|
||||
operator.CopyAttributeToSelection,
|
||||
operator.ExplorerAddEntity,
|
||||
operator.ExplorerEnableEditingEntity,
|
||||
operator.ExplorerDisableEditingEntity,
|
||||
operator.ExplorerEditEntity,
|
||||
operator.ExplorerShowUIPopup,
|
||||
prop.BIMAttributeProperties,
|
||||
prop.ExplorerEntity,
|
||||
prop.BIMExplorerProperties,
|
||||
ui.BIM_PT_object_attributes,
|
||||
ui.BIM_PT_explorer,
|
||||
ui.BIM_UL_explorer,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
bpy.types.Scene.BIMExplorerProperties = bpy.props.PointerProperty(type=prop.BIMExplorerProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
@@ -26,7 +26,11 @@ import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.attribute as core
|
||||
import bonsai.core.spatial
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, Union, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import Attribute
|
||||
import bpy.stub_internal.rna_enums as rna_enums
|
||||
|
||||
|
||||
def get_objs_for_operation(
|
||||
@@ -75,7 +79,9 @@ class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
None,
|
||||
)
|
||||
|
||||
def callback(name, prop, data):
|
||||
lookup_attrs = tool.Attribute.does_ifc_class_support_explorer_lookup(element.is_a())
|
||||
|
||||
def callback(name: str, prop: Union["Attribute", None], data: dict[str, Any]) -> None | Literal[True]:
|
||||
if name in ("RefLatitude", "RefLongitude"):
|
||||
new = props.attributes.add()
|
||||
new.name = name
|
||||
@@ -89,8 +95,19 @@ class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
if name in ("PredefinedType", "ObjectType") and has_inherited_predefined_type:
|
||||
props.attributes.remove(len(props.attributes) - 1)
|
||||
return True
|
||||
if lookup_attrs and (name in lookup_attrs):
|
||||
new = props.attributes.add()
|
||||
new.name = name
|
||||
new.ifc_class = data["type"]
|
||||
new.data_type = "enum"
|
||||
new.is_optional = True
|
||||
new.enum_items_dynamic = lookup_attrs[name]
|
||||
new.use_explorer_ui = True
|
||||
value: Union[ifcopenshell.entity_instance, None] = data[name]
|
||||
if value is not None:
|
||||
new.enum_value = str(value.id())
|
||||
|
||||
bonsai.bim.helper.import_attributes2(element, props.attributes, callback=callback)
|
||||
bonsai.bim.helper.import_attributes(element, props.attributes, callback=callback)
|
||||
props.is_editing_attributes = True
|
||||
|
||||
def execute(self, context):
|
||||
@@ -107,7 +124,8 @@ class DisableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
|
||||
def disable_editing_attributes_on_obj(self, obj: bpy.types.Object) -> None:
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
props.is_editing_attributes = False
|
||||
props.attributes.clear()
|
||||
props.property_unset("is_editing_attributes")
|
||||
|
||||
def execute(self, context):
|
||||
for obj in get_objs_for_operation(self, context):
|
||||
@@ -127,7 +145,7 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not obj or not (element := tool.Ifc.get_entity(obj)):
|
||||
return
|
||||
|
||||
def callback(attributes, prop):
|
||||
def callback(attributes: dict[str, Any], prop: "Attribute") -> None | Literal[True]:
|
||||
if prop.name in ("RefLatitude", "RefLongitude"):
|
||||
if not prop.is_null:
|
||||
try:
|
||||
@@ -138,6 +156,9 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
lookup_attrs = tool.Attribute.does_ifc_class_support_explorer_lookup(element.is_a())
|
||||
if lookup_attrs:
|
||||
bonsai.bim.helper.process_exported_entity_attribute(attributes, list(lookup_attrs))
|
||||
ifcopenshell.api.attribute.edit_attributes(self.file, product=element, attributes=attributes)
|
||||
|
||||
tool.Root.set_object_name(obj, element)
|
||||
@@ -204,3 +225,139 @@ class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value
|
||||
)
|
||||
self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.")
|
||||
|
||||
|
||||
class ExplorerAddEntity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.explorer_add_entity"
|
||||
bl_label = "Add Entity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context) -> None:
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
entity = ifc_file.create_entity(props.ifc_class)
|
||||
tool.Attribute.refresh_uilist_entities()
|
||||
tool.Attribute.enable_editing_entity(entity)
|
||||
tool.Attribute.import_entity_attributes(entity)
|
||||
|
||||
|
||||
class ExplorerEnableEditingEntity(bpy.types.Operator):
|
||||
bl_idname = "bim.explorer_enable_editing_entity"
|
||||
bl_label = "Enable Editing Entity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
ifc_file = tool.Ifc.get()
|
||||
assert (active_entity := props.active_entity)
|
||||
entity = ifc_file.by_id(active_entity.ifc_definition_id)
|
||||
|
||||
tool.Attribute.disable_editing_entity()
|
||||
tool.Attribute.enable_editing_entity(entity)
|
||||
tool.Attribute.import_entity_attributes(entity)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExplorerDisableEditingEntity(bpy.types.Operator):
|
||||
bl_idname = "bim.explorer_disable_editing_entity"
|
||||
bl_label = "Disable Editing Entity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||
tool.Attribute.disable_editing_entity()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExplorerEditEntity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.explorer_edit_entity"
|
||||
bl_label = "Edit Entity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
entity = ifc_file.by_id(props.editing_entity_id)
|
||||
|
||||
attrs = tool.Attribute.export_entity_attributes()
|
||||
for attr, value in attrs.items():
|
||||
setattr(entity, attr, value)
|
||||
tool.Attribute.refresh_uilist_entities()
|
||||
tool.Attribute.disable_editing_entity()
|
||||
|
||||
|
||||
class ExplorerShowUIPopup(bpy.types.Operator):
|
||||
bl_idname = "bim.explorer_show_ui_popup"
|
||||
bl_label = "Show Explorer UI"
|
||||
bl_description = "Show Explorer UI to select element as attribute value or edit it."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
ifc_class: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
"""Element IFC class."""
|
||||
attribute_name: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
"""IFC class attribute name."""
|
||||
data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
"""Full data path"""
|
||||
preselect_ifc_id: bpy.props.IntProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||
"""IFC id to preselect in the popup."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifc_class: str
|
||||
attribute_name: str
|
||||
data_path: str
|
||||
preselect_ifc_id: int
|
||||
|
||||
def invoke(self, context, event) -> "set[rna_enums.OperatorReturnItems]":
|
||||
assert context.window_manager
|
||||
assert self.ifc_class and self.attribute_name and self.data_path
|
||||
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
props.is_loaded = True
|
||||
props.ifc_class = self.get_attribute_type()
|
||||
|
||||
if self.preselect_ifc_id:
|
||||
props.active_entity_index = next(
|
||||
i for i, e in enumerate(props.entities) if e.ifc_definition_id == self.preselect_ifc_id
|
||||
)
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||
|
||||
def get_attribute_type(self) -> str:
|
||||
schema = tool.Ifc.schema()
|
||||
entity = schema.declaration_by_name(self.ifc_class).as_entity()
|
||||
assert entity
|
||||
i = entity.attribute_index(self.attribute_name)
|
||||
attr = entity.all_attributes()[i]
|
||||
named_type = attr.type_of_attribute().as_named_type()
|
||||
assert named_type
|
||||
declared = named_type.declared_type()
|
||||
return declared.name()
|
||||
|
||||
def draw(self, context) -> None:
|
||||
from bonsai.bim.module.attribute.ui import BIM_PT_explorer
|
||||
|
||||
BIM_PT_explorer.draw(self, context, is_popup=True)
|
||||
|
||||
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
active_entity = props.active_entity
|
||||
if active_entity is None:
|
||||
self.report({"WARNING"}, "No entity selected.")
|
||||
return {"FINISHED"}
|
||||
|
||||
# Apply pending changes for convenience.
|
||||
if props.editing_entity_id:
|
||||
if props.editing_entity_id == active_entity.ifc_definition_id:
|
||||
bpy.ops.bim.explorer_edit_entity()
|
||||
else:
|
||||
bpy.ops.bim.explorer_disable_editing_entity()
|
||||
|
||||
# Very important to do it after changes applied, otherwise enum might update
|
||||
# and index will be pointing to a different element.
|
||||
exec(f"{self.data_path} = '{active_entity.ifc_definition_id}'")
|
||||
return {"FINISHED"}
|
||||
|
||||
def cancel(self, context: bpy.types.Context) -> None:
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
if props.editing_entity_id:
|
||||
bpy.ops.bim.explorer_disable_editing_entity()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -29,7 +30,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
|
||||
class BIMAttributeProperties(PropertyGroup):
|
||||
@@ -39,3 +40,62 @@ class BIMAttributeProperties(PropertyGroup):
|
||||
if TYPE_CHECKING:
|
||||
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
is_editing_attributes: bool
|
||||
|
||||
|
||||
class ExplorerEntity(PropertyGroup):
|
||||
ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifc_definition_id: int
|
||||
|
||||
|
||||
class BIMExplorerProperties(PropertyGroup):
|
||||
def update_is_loaded(self, context: object) -> None:
|
||||
if self.is_loaded:
|
||||
# Trigger refresh.
|
||||
self.ifc_class = self.ifc_class
|
||||
else:
|
||||
self.property_unset("is_loaded")
|
||||
self.property_unset("ifc_class")
|
||||
self.entities.clear()
|
||||
self.property_unset("active_entity_index")
|
||||
self.property_unset("editing_entity_id")
|
||||
self.entity_attributes.clear()
|
||||
|
||||
is_loaded: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Toggle Explorer UI",
|
||||
update=update_is_loaded,
|
||||
)
|
||||
|
||||
def get_ifc_class(self, context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
# TODO: Add more entities.
|
||||
classes = [
|
||||
"IfcPostalAddress",
|
||||
"IfcTelecomAddress",
|
||||
]
|
||||
return [(c, c, "") for c in classes]
|
||||
|
||||
def update_ifc_class(self, context: object) -> None:
|
||||
tool.Attribute.refresh_uilist_entities()
|
||||
|
||||
ifc_class: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="IFC Class To Search",
|
||||
items=get_ifc_class,
|
||||
update=update_ifc_class,
|
||||
)
|
||||
entities: CollectionProperty(type=ExplorerEntity) # pyright: ignore[reportRedeclaration]
|
||||
active_entity_index: IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
editing_entity_id: IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
entity_attributes: CollectionProperty(type=Attribute) # pyright: ignore[reportRedeclaration]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_loaded: bool
|
||||
ifc_class: str
|
||||
entities: bpy.types.bpy_prop_collection_idprop[ExplorerEntity]
|
||||
active_entity_index: int
|
||||
editing_entity_id: int
|
||||
entity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
|
||||
@property
|
||||
def active_entity(self) -> Union[ExplorerEntity, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.entities, self.active_entity_index)
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bonsai.bim.helper
|
||||
import bpy.types
|
||||
from bpy.types import Panel
|
||||
from bonsai.bim.module.attribute.data import AttributesData
|
||||
import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.attribute.prop import BIMExplorerProperties, ExplorerEntity
|
||||
|
||||
|
||||
def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes) -> None:
|
||||
@@ -33,7 +38,9 @@ def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes)
|
||||
row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
|
||||
|
||||
bonsai.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
|
||||
bonsai.bim.helper.draw_attributes(
|
||||
props.attributes, layout, copy_operator="bim.copy_attribute_to_selection", enable_search=True
|
||||
)
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
@@ -66,3 +73,73 @@ class BIM_PT_object_attributes(Panel):
|
||||
if not AttributesData.is_loaded:
|
||||
AttributesData.load()
|
||||
draw_ui(context, self.layout, AttributesData.data["attributes"])
|
||||
|
||||
|
||||
class BIM_PT_explorer(Panel):
|
||||
bl_label = "Explorer"
|
||||
bl_idname = "BIM_PT_explorer"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_project_setup"
|
||||
|
||||
def draw(self, context, *, is_popup=False):
|
||||
assert (layout := self.layout)
|
||||
props = tool.Attribute.get_explorer_props()
|
||||
|
||||
if is_popup:
|
||||
layout.label(text=props.ifc_class)
|
||||
else:
|
||||
if not props.is_loaded:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Explorer UI is not Loaded.")
|
||||
row.prop(props, "is_loaded", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_class", text="")
|
||||
row.prop(props, "is_loaded", text="", icon="CANCEL")
|
||||
|
||||
active_entity = props.active_entity
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{len(props.entities)} entities found")
|
||||
row.operator("bim.explorer_add_entity", text="", icon="ADD")
|
||||
|
||||
if active_entity:
|
||||
if active_entity.ifc_definition_id == props.editing_entity_id:
|
||||
row.operator("bim.explorer_disable_editing_entity", icon="CANCEL", text="")
|
||||
else:
|
||||
row.operator("bim.explorer_enable_editing_entity", icon="GREASEPENCIL", text="")
|
||||
# TODO: 'Remove' button?
|
||||
|
||||
layout.template_list("BIM_UL_explorer", "", props, "entities", props, "active_entity_index")
|
||||
|
||||
if props.editing_entity_id:
|
||||
box = self.layout.box()
|
||||
# In popup we accept edits automatically for the convenience.
|
||||
# Othrewise it seems very unintuitive, when you need to click confirmation twice.
|
||||
if not is_popup:
|
||||
row = box.row(align=True)
|
||||
row.operator("bim.explorer_edit_entity", icon="CHECKMARK")
|
||||
row.operator("bim.explorer_disable_editing_entity", icon="CANCEL", text="")
|
||||
bonsai.bim.helper.draw_attributes(props.entity_attributes, box, enable_search=True)
|
||||
|
||||
|
||||
class BIM_UL_explorer(bpy.types.UIList):
|
||||
def draw_item(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMExplorerProperties,
|
||||
item: ExplorerEntity,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
row = layout.row(align=True)
|
||||
|
||||
if item.ifc_definition_id == data.editing_entity_id:
|
||||
row.label(text=item.name, icon="GREASEPENCIL")
|
||||
else:
|
||||
row.label(text=item.name)
|
||||
|
||||
@@ -71,6 +71,7 @@ classes = (
|
||||
ui.BIM_PT_bcf,
|
||||
ui.BIM_PT_bcf_metadata,
|
||||
ui.BIM_PT_bcf_comments,
|
||||
ui.BIM_UL_topics,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1208,12 +1208,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
self.file = tool.Ifc.get()
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
assert context.scene
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
blender_topic = props.active_topic
|
||||
assert blender_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if self.viewpoint_guid:
|
||||
viewpoint_guid = self.viewpoint_guid
|
||||
viewpoint_guid = self.viewpoint_guid + ".bcfv"
|
||||
if viewpoint_guid not in topic.viewpoints:
|
||||
self.report({"ERROR"}, f"No such viewpoint in the active topic: '{viewpoint_guid}'.")
|
||||
return {"CANCELLED"}
|
||||
@@ -1234,6 +1236,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
cam_height = context.scene.render.resolution_y
|
||||
cam_aspect = cam_width / cam_height
|
||||
|
||||
assert isinstance(obj.data, bpy.types.Camera)
|
||||
obj.data.background_images.clear()
|
||||
if viewpoint.snapshot:
|
||||
obj.data.show_background_images = True
|
||||
@@ -1273,7 +1276,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if tool.Bcf.get_viewpoint_bitmaps(viewpoint):
|
||||
self.create_bitmaps(bcfxml, viewpoint, topic)
|
||||
|
||||
self.setup_camera(viewpoint, obj, cam_aspect, context)
|
||||
self.setup_camera(viewpoint, obj, cam_aspect, context, cam_height, cam_width)
|
||||
return {"FINISHED"}
|
||||
|
||||
def setup_camera(
|
||||
@@ -1282,6 +1285,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj: bpy.types.Object,
|
||||
cam_aspect: float,
|
||||
context: bpy.types.Context,
|
||||
cam_height: float,
|
||||
cam_width: float,
|
||||
) -> None:
|
||||
assert isinstance(obj.data, bpy.types.Camera)
|
||||
if viewpoint.visualization_info.orthogonal_camera:
|
||||
@@ -1308,7 +1313,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
rotation = Matrix((x_axis, y_axis, z_axis))
|
||||
rotation.invert()
|
||||
matrix = np.matrix(
|
||||
matrix = np.array(
|
||||
(
|
||||
[x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x],
|
||||
[x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y],
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from . import bcfstore
|
||||
from bpy.types import Panel
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.bcf.prop import BcfTopic, BCFProperties
|
||||
|
||||
|
||||
class BIM_PT_bcf(Panel):
|
||||
@@ -33,6 +38,7 @@ class BIM_PT_bcf(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
@@ -125,6 +131,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
# without adding them to specific topic.
|
||||
# Currently we just handle it the same way as in v2
|
||||
# documents are accessed and managed from topics.
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
@@ -139,6 +146,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
bcf_v3 = bcf_verison.startswith("3")
|
||||
|
||||
topic = props.active_topic
|
||||
assert topic
|
||||
bcf_topic = bcfxml.topics[topic.name]
|
||||
|
||||
layout.label(text="Header Files:")
|
||||
@@ -303,6 +311,7 @@ class BIM_PT_bcf_comments(Panel):
|
||||
row.prop(props, "comment_text_width")
|
||||
|
||||
topic = props.active_topic
|
||||
assert topic
|
||||
for comment in topic.comments:
|
||||
box = self.layout.box()
|
||||
|
||||
@@ -341,3 +350,20 @@ class BIM_PT_bcf_comments(Panel):
|
||||
row.prop(props, "has_related_viewpoint")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_comment")
|
||||
|
||||
|
||||
class BIM_UL_topics(bpy.types.UIList):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BCFProperties,
|
||||
item: BcfTopic,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.prop(item, "title", text="", emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
@@ -40,6 +40,7 @@ from mathutils import Vector, Matrix
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||
from bonsai.bim.module.boundary.decorator import BoundaryDecorator
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
import bonsai.core
|
||||
import bonsai.core.geometry
|
||||
from typing import Union, Optional
|
||||
@@ -984,7 +985,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
continue
|
||||
|
||||
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
|
||||
boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class)
|
||||
boundary = ifcopenshell.api.root.create_entity(ifc_file, ifc_class=props.boundary_class)
|
||||
boundary.RelatingSpace = space
|
||||
boundary.RelatedBuildingElement = filling
|
||||
boundary.ConnectionGeometry = connection_geometry
|
||||
@@ -1058,15 +1059,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
tool.Model.unit_scale = self.unit_scale
|
||||
builder = ShapeBuilder(tool.Ifc.get())
|
||||
|
||||
surface = tool.Ifc.get().createIfcCurveBoundedPlane()
|
||||
surface.BasisSurface = tool.Ifc.get().createIfcPlane(
|
||||
tool.Ifc.get().createIfcAxis2Placement3D(
|
||||
tool.Ifc.get().createIfcCartesianPoint([o / self.unit_scale for o in p1]),
|
||||
tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]),
|
||||
tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]),
|
||||
)
|
||||
)
|
||||
placement = builder.create_axis2_placement_3d([o / self.unit_scale for o in p1], z_axis, x_axis)
|
||||
surface.BasisSurface = tool.Ifc.get().create_entity("IfcPlane", placement)
|
||||
|
||||
if tool.Ifc.get().schema != "IFC2X3":
|
||||
points = [tool.Model.convert_si_to_unit(list(co)) for co in polygon.exterior.coords]
|
||||
|
||||
@@ -25,6 +25,7 @@ classes = (
|
||||
operator.LoadBSDDDictionaries,
|
||||
operator.SearchBSDDClassifications,
|
||||
operator.SearchBSDDProperties,
|
||||
operator.BIM_OT_show_bsdd_description,
|
||||
prop.BSDDDictionary,
|
||||
prop.BSDDClassification,
|
||||
prop.BSDDProperty,
|
||||
|
||||
@@ -38,8 +38,7 @@ class BSDDData:
|
||||
cls.data["active_dictionary"] = cls.active_dictionary()
|
||||
|
||||
@classmethod
|
||||
def active_dictionary(cls):
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
def active_dictionary(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
results = [("ALL", "All Dictionaries", "All active dictionaries")]
|
||||
results.extend([(d.uri, d.name, f"{d.status} - {d.version}") for d in props.dictionaries if d.is_active])
|
||||
results.extend(tool.Bsdd.get_active_bsdd_enum_items())
|
||||
return results
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import textwrap
|
||||
import bpy
|
||||
import bsdd
|
||||
import bonsai.tool as tool
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
from bonsai.core import bsdd as core
|
||||
from typing import Any
|
||||
|
||||
|
||||
class LoadBSDDDictionaries(bpy.types.Operator):
|
||||
@@ -41,7 +42,7 @@ class SearchBSDDClassifications(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
total = core.search_bsdd_class(tool.Bsdd, context.scene.BIMBSDDProperties.keyword)
|
||||
total = core.search_bsdd_class(tool.Bsdd, tool.Bsdd.get_bsdd_props().keyword)
|
||||
self.report({"INFO"}, f"{total} bSDD classes found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -71,7 +72,7 @@ class SearchBSDDProperties(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
core.search_bsdd_properties(tool.Bsdd, context.scene.BIMBSDDProperties.keyword, self.obj, self.obj_type)
|
||||
core.search_bsdd_properties(tool.Bsdd, tool.Bsdd.get_bsdd_props().keyword, self.obj, self.obj_type)
|
||||
self.report({"INFO"}, f"{len(props.properties)} bSDD properties found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -79,23 +80,30 @@ class SearchBSDDProperties(bpy.types.Operator):
|
||||
class AddBSDDProperties(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_bsdd_properties"
|
||||
bl_label = "Add bSDD Properties"
|
||||
bl_description = "Add selected bSDD properties"
|
||||
bl_description = "Add selected bSDD properties to the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
if not props.selected_properties:
|
||||
cls.poll_message_set("No properties selected to add.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
|
||||
psets = {}
|
||||
psets: dict[str, dict[str, Any]] = {}
|
||||
for selected_property in bprops.selected_properties:
|
||||
psets.setdefault(selected_property.metadata, {})[selected_property.name] = selected_property.get_value()
|
||||
|
||||
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
assert ifc_definition_id
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
properties = {}
|
||||
|
||||
current_psets = ifcopenshell.util.element.get_psets(element, verbose=True)
|
||||
for pset_name, properties in psets.items():
|
||||
@@ -104,3 +112,35 @@ class AddBSDDProperties(bpy.types.Operator, tool.Ifc.Operator):
|
||||
else:
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name=pset_name)
|
||||
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties=properties)
|
||||
|
||||
|
||||
class BIM_OT_show_bsdd_description(bpy.types.Operator):
|
||||
bl_idname = "bim.show_bsdd_description"
|
||||
bl_label = "bSDD Description"
|
||||
url: bpy.props.StringProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self, width=450)
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
WIDTH = 80
|
||||
layout = self.layout
|
||||
wrapper = textwrap.TextWrapper(width=WIDTH)
|
||||
result = tool.Bsdd.get_bsdd_property(self.url)
|
||||
description = result.get("description") or ""
|
||||
definition = result.get("definition") or ""
|
||||
if description != definition:
|
||||
text = wrapper.wrap(f"Description : {description}")
|
||||
text.append("-" * (WIDTH - 15))
|
||||
text += wrapper.wrap(f"Definition : {definition}")
|
||||
else:
|
||||
text = wrapper.wrap(f"Description : {description}")
|
||||
for line in text:
|
||||
layout.label(text=line)
|
||||
if self.url:
|
||||
url_op = layout.operator("bim.open_uri", icon="URL", text="Online bSDD Documentation")
|
||||
url_op.uri = self.url
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import PropertyGroup
|
||||
from bonsai.bim.module.bsdd.data import BSDDData
|
||||
from bonsai.bim.module.classification.data import ClassificationsData
|
||||
from bonsai.bim.prop import Attribute, StrProperty
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -31,17 +32,34 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import Union
|
||||
from typing import Union, TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
def get_active_dictionary(self, context):
|
||||
def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not BSDDData.is_loaded:
|
||||
BSDDData.load()
|
||||
return BSDDData.data["active_dictionary"]
|
||||
|
||||
|
||||
def update_is_active(self: "BSDDDictionary", context: bpy.types.Context) -> None:
|
||||
tool.Bsdd.save_active_bsdd_to_ifc()
|
||||
|
||||
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
|
||||
if ClassificationsData.is_loaded:
|
||||
props = tool.Classification.get_classification_props()
|
||||
# Preserve original enum value.
|
||||
classification_source = props.classification_source
|
||||
ClassificationsData.data["classification_source"] = ClassificationsData.classification_source()
|
||||
|
||||
# Try to restore enum value.
|
||||
if "classification_source" not in props:
|
||||
# It's already on the default value, nothing to restore.
|
||||
return
|
||||
try:
|
||||
props.classification_source = classification_source
|
||||
except TypeError:
|
||||
# Item is no longer active and not present in enum, fallback to the default.
|
||||
del props["classification_source"]
|
||||
|
||||
|
||||
def update_is_selected(self: "BSDDProperty", context: bpy.types.Context) -> None:
|
||||
@@ -54,27 +72,41 @@ def update_active_class_index(self: "BIMBSDDProperties", context: bpy.types.Cont
|
||||
|
||||
|
||||
class BSDDDictionary(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
uri: StringProperty(name="URI")
|
||||
default_language_code: StringProperty(name="Language")
|
||||
organization_name_owner: StringProperty(name="Organization")
|
||||
status: StringProperty(name="Status")
|
||||
version: StringProperty(name="Version")
|
||||
is_active: BoolProperty(
|
||||
name="Is Active", description="Enable to search with this dictionary", default=False, update=update_is_active
|
||||
name="Is Active",
|
||||
description="Enable to search with this dictionary",
|
||||
default=False,
|
||||
update=update_is_active,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
uri: str
|
||||
default_language_code: str
|
||||
organization_name_owner: str
|
||||
status: str
|
||||
version: str
|
||||
is_active: bool
|
||||
|
||||
|
||||
class BSDDClassification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
reference_code: StringProperty(name="Reference Code")
|
||||
uri: StringProperty(name="URI")
|
||||
dictionary_name: StringProperty(name="Dictionary Name")
|
||||
dictionary_namespace_uri: StringProperty(name="Dictionary Namespace URI")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
reference_code: str
|
||||
uri: str
|
||||
dictionary_name: str
|
||||
dictionary_namespace_uri: str
|
||||
|
||||
|
||||
class BSDDProperty(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
code: StringProperty(name="Code")
|
||||
uri: StringProperty(name="URI")
|
||||
pset: StringProperty(name="Pset")
|
||||
@@ -82,14 +114,22 @@ class BSDDProperty(PropertyGroup):
|
||||
name="Is Selected", description="Select to add or edit this property", default=False, update=update_is_selected
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
code: str
|
||||
uri: str
|
||||
pset: str
|
||||
is_selected: bool
|
||||
|
||||
|
||||
class BSDDPset(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
properties: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
|
||||
|
||||
class BIMBSDDProperties(PropertyGroup):
|
||||
active_dictionary: StringProperty(name="Active Dictionary")
|
||||
# TODO: `active_dictionary` is not used anywhere?
|
||||
active_dictionary: EnumProperty(items=get_active_dictionary, name="Active Dictionary")
|
||||
active_uri: StringProperty(name="Active URI")
|
||||
dictionaries: CollectionProperty(name="Dictionaries", type=BSDDDictionary)
|
||||
@@ -120,17 +160,27 @@ class BIMBSDDProperties(PropertyGroup):
|
||||
description="Whether to display and assign only properties from IFC dictionary",
|
||||
default=False,
|
||||
)
|
||||
load_preview_dictionaries: BoolProperty(
|
||||
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
|
||||
)
|
||||
load_inactive_dictionaries: BoolProperty(
|
||||
name="Load Inactive Dictionaries", description="Load dictionaries marked as Inactive status", default=False
|
||||
)
|
||||
load_test_dictionaries: BoolProperty(
|
||||
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
|
||||
)
|
||||
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
active_dictionary: str
|
||||
active_dictionary: str
|
||||
active_uri: str
|
||||
dictionaries: bpy.types.bpy_prop_collection_idprop[BSDDDictionary]
|
||||
active_dictionary_index: int
|
||||
classifications: bpy.types.bpy_prop_collection_idprop[BSDDClassification]
|
||||
active_classification_index: int
|
||||
property_filter_mode: Literal["CLASS", "KEYWORD"]
|
||||
classes: bpy.types.bpy_prop_collection_idprop[BSDDClassification]
|
||||
active_class_index: int
|
||||
properties: bpy.types.bpy_prop_collection_idprop[BSDDProperty]
|
||||
active_property_index: int
|
||||
selected_properties: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
keyword: str
|
||||
should_filter_ifc_class: bool
|
||||
use_only_ifc_properties: bool
|
||||
classification_psets: bpy.types.bpy_prop_collection_idprop[BSDDPset]
|
||||
|
||||
@property
|
||||
def active_class(self) -> Union[BSDDClassification, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.classes, self.active_class_index)
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.bsdd.data import BSDDData
|
||||
from bpy.types import Panel, UIList
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDDictionary, BSDDClassification, BSDDProperty
|
||||
|
||||
|
||||
class BIM_PT_bsdd(Panel):
|
||||
@@ -33,7 +39,8 @@ class BIM_PT_bsdd(Panel):
|
||||
def draw(self, context):
|
||||
if not BSDDData.is_loaded:
|
||||
BSDDData.load()
|
||||
props = context.scene.BIMBSDDProperties
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
if len(props.dictionaries):
|
||||
row = self.layout.row()
|
||||
@@ -70,9 +77,17 @@ class BIM_PT_bsdd(Panel):
|
||||
|
||||
|
||||
class BIM_UL_bsdd_dictionaries(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMBSDDProperties,
|
||||
item: BSDDDictionary,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
props = context.scene.BIMBSDDProperties
|
||||
row = layout.row(align=True)
|
||||
if item.status != "Active":
|
||||
row.label(
|
||||
@@ -86,7 +101,16 @@ class BIM_UL_bsdd_dictionaries(UIList):
|
||||
|
||||
|
||||
class BIM_UL_bsdd_classifications(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMBSDDProperties,
|
||||
item: BSDDClassification,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.reference_code)
|
||||
@@ -95,7 +119,16 @@ class BIM_UL_bsdd_classifications(UIList):
|
||||
|
||||
|
||||
class BIM_UL_bsdd_classes(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMBSDDProperties,
|
||||
item: BSDDClassification,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
@@ -103,8 +136,18 @@ class BIM_UL_bsdd_classes(UIList):
|
||||
|
||||
|
||||
class BIM_UL_bsdd_properties(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMBSDDProperties,
|
||||
item: BSDDProperty,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.context_pointer_set("active_bsdd_property", item) # used for context menu
|
||||
row = layout.row(align=True)
|
||||
name = item.name
|
||||
if name != item.code:
|
||||
|
||||
@@ -48,6 +48,7 @@ classes = (
|
||||
ui.BIM_PT_classification_references,
|
||||
ui.BIM_PT_material_classifications,
|
||||
ui.BIM_PT_cost_classifications,
|
||||
ui.BIM_PT_zone_classifications,
|
||||
ui.BIM_UL_classifications,
|
||||
)
|
||||
|
||||
|
||||
@@ -21,19 +21,20 @@ import ifcopenshell
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.classification
|
||||
import bonsai.tool as tool
|
||||
from typing import Any
|
||||
from typing import Any, Literal, Union
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def refresh():
|
||||
ClassificationsData.is_loaded = False
|
||||
ClassificationReferencesData.is_loaded = False
|
||||
ObjectClassificationsData.is_loaded = False
|
||||
MaterialClassificationsData.is_loaded = False
|
||||
CostClassificationsData.is_loaded = False
|
||||
ZoneClassificationsData.is_loaded = False
|
||||
|
||||
|
||||
class ClassificationsData:
|
||||
data = {}
|
||||
data: dict[str, Any] = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
@@ -55,6 +56,7 @@ class ClassificationsData:
|
||||
data = element.get_info()
|
||||
if tool.Ifc.get().schema == "IFC2X3" and element.EditionDate:
|
||||
data["EditionDate"] = ifcopenshell.util.date.ifc2datetime(data["EditionDate"])
|
||||
data["Name"] = data["Name"] or "Unnamed"
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
@@ -65,13 +67,12 @@ class ClassificationsData:
|
||||
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
|
||||
|
||||
@classmethod
|
||||
def classification_source(cls):
|
||||
def classification_source(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
items = [
|
||||
("FILE", "IFC File", ""),
|
||||
("MANUAL", "Manual Entry", ""),
|
||||
]
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
dictionaries = [(d.uri, f"bSDD: {d.name}", "") for d in bprops.dictionaries if d.is_active]
|
||||
dictionaries = tool.Bsdd.get_active_bsdd_enum_items()
|
||||
if dictionaries:
|
||||
items.append(("BSDD", "All Active bSDDs", ""))
|
||||
items.extend(dictionaries)
|
||||
@@ -79,11 +80,39 @@ class ClassificationsData:
|
||||
|
||||
|
||||
class ReferencesData:
|
||||
data: dict[str, Any]
|
||||
is_loaded = False
|
||||
obj_type: Literal["Object", "Material", "Cost", "Zone"]
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {}
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
|
||||
@classmethod
|
||||
def get_element(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get element to get classification references from."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def references(cls) -> list[dict[str, Any]]:
|
||||
results = []
|
||||
element = cls.get_element()
|
||||
if element:
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def active_classification_library(cls):
|
||||
if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"):
|
||||
return False
|
||||
props = bpy.context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
|
||||
if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]:
|
||||
return name
|
||||
@@ -93,81 +122,44 @@ class ReferencesData:
|
||||
return [(str(e.id()), e.Name, "") for e in tool.Ifc.get().by_type("IfcClassification")]
|
||||
|
||||
|
||||
class ClassificationReferencesData(ReferencesData):
|
||||
data = {}
|
||||
is_loaded = False
|
||||
class ObjectClassificationsData(ReferencesData):
|
||||
obj_type = "Object"
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["object_type"] = "Object"
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
results.append(data)
|
||||
return results
|
||||
def get_element(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if obj := bpy.context.active_object:
|
||||
return tool.Ifc.get_entity(obj)
|
||||
return None
|
||||
|
||||
|
||||
class MaterialClassificationsData(ReferencesData):
|
||||
data = {}
|
||||
is_loaded = False
|
||||
obj_type = "Material"
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["object_type"] = "Material"
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
|
||||
def get_element(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
props = tool.Material.get_material_props()
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
element = tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
results.append(data)
|
||||
return results
|
||||
if material := props.active_material:
|
||||
return tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
return None
|
||||
|
||||
|
||||
class CostClassificationsData(ReferencesData):
|
||||
data = {}
|
||||
is_loaded = False
|
||||
obj_type = "Cost"
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["object_type"] = "Cost"
|
||||
|
||||
@classmethod
|
||||
def references(cls) -> list[dict[str, Any]]:
|
||||
results: list[dict[str, Any]] = []
|
||||
def get_element(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
props = tool.Cost.get_cost_props()
|
||||
active_cost_item = props.active_cost_item
|
||||
if not active_cost_item:
|
||||
return results
|
||||
element = tool.Ifc.get().by_id(active_cost_item.ifc_definition_id)
|
||||
if element:
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
results.append(data)
|
||||
return results
|
||||
if cost_item := props.active_cost_item:
|
||||
return tool.Ifc.get().by_id(cost_item.ifc_definition_id)
|
||||
return None
|
||||
|
||||
|
||||
class ZoneClassificationsData(ReferencesData):
|
||||
obj_type = "Zone"
|
||||
|
||||
@classmethod
|
||||
def get_element(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
props = tool.System.get_zone_props()
|
||||
if active_zone := props.active_zone:
|
||||
return tool.Ifc.get().by_id(active_zone.ifc_definition_id)
|
||||
return None
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
@@ -47,7 +46,7 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
ifcopenshell.api.classification.add_classification(
|
||||
tool.Ifc.get(),
|
||||
classification=IfcStore.classification_file.by_id(int(props.available_classifications)),
|
||||
@@ -60,7 +59,7 @@ class AddManualClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes)
|
||||
classification = ifcopenshell.api.classification.add_classification(tool.Ifc.get(), classification="Unnamed")
|
||||
ifcopenshell.api.classification.edit_classification(
|
||||
@@ -84,7 +83,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
objects = [context.active_object.name]
|
||||
else:
|
||||
objects = [self.obj]
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.reference_attributes)
|
||||
products = [
|
||||
tool.Ifc.get().by_id(ifc_definition_id)
|
||||
@@ -110,7 +109,7 @@ class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
cprops = context.scene.BIMClassificationProperties
|
||||
cprops = tool.Classification.get_classification_props()
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
if cprops.classification_source == "BSDD":
|
||||
dictionaries = [d.uri for d in bprops.dictionaries if d.is_active]
|
||||
@@ -142,11 +141,11 @@ class EnableAddingManualClassification(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.is_adding = True
|
||||
props.active_classification_id = 0
|
||||
props.classification_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2("IfcClassification", props.classification_attributes)
|
||||
bonsai.bim.helper.import_attributes("IfcClassification", props.classification_attributes)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -156,7 +155,7 @@ class DisableAddingManualClassification(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.is_adding = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -167,10 +166,10 @@ class EnableAddingManualClassificationReference(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
props.is_adding = True
|
||||
props.reference_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2("IfcClassificationReference", props.reference_attributes)
|
||||
bonsai.bim.helper.import_attributes("IfcClassificationReference", props.reference_attributes)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -180,7 +179,7 @@ class DisableAddingManualClassificationReference(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
props.is_adding = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -192,21 +191,11 @@ class EnableEditingClassification(bpy.types.Operator):
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
def callback(name, prop, data):
|
||||
if name == "ReferenceTokens":
|
||||
geo_props = tool.Georeference.get_georeference_props()
|
||||
new = geo_props.projected_crs.add()
|
||||
new.name = name
|
||||
new.data_type = "string"
|
||||
new.is_null = data[name] is None
|
||||
new.is_optional = True
|
||||
new.string_value = "" if new.is_null else json.dumps(data[name])
|
||||
return True
|
||||
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.classification_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
|
||||
bonsai.bim.helper.import_attributes(
|
||||
tool.Ifc.get().by_id(self.classification),
|
||||
props.classification_attributes,
|
||||
)
|
||||
props.active_classification_id = self.classification
|
||||
return {"FINISHED"}
|
||||
@@ -218,7 +207,8 @@ class DisableEditingClassification(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMClassificationProperties.active_classification_id = 0
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.active_classification_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -246,14 +236,8 @@ class EditClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
|
||||
def callback(attributes, prop):
|
||||
if prop.name == "ReferenceTokens":
|
||||
attributes[prop.name] = json.loads(prop.string_value)
|
||||
return True
|
||||
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes, callback=callback)
|
||||
props = tool.Classification.get_classification_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.classification.edit_classification(
|
||||
ifc_file,
|
||||
@@ -271,9 +255,9 @@ class EnableEditingClassificationReference(bpy.types.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
props.reference_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
|
||||
props.active_reference_id = self.reference
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -285,7 +269,7 @@ class DisableEditingClassificationReference(bpy.types.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMClassificationReferenceProperties.active_reference_id = 0
|
||||
tool.Classification.get_classification_reference_props().active_reference_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -337,7 +321,7 @@ class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.reference_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.classification.edit_reference(
|
||||
@@ -364,7 +348,7 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
objects = [context.active_object.name]
|
||||
else:
|
||||
objects = [self.obj]
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
classification = None
|
||||
classification_name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
|
||||
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||
@@ -405,8 +389,7 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
objects = [context.active_object.name]
|
||||
else:
|
||||
objects = [self.obj]
|
||||
props = context.scene.BIMClassificationProperties
|
||||
bprops = context.scene.BIMBSDDProperties
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
use_only_ifc_properties: bool = bprops.use_only_ifc_properties
|
||||
|
||||
bsdd_classification = bprops.classifications[bprops.active_classification_index]
|
||||
@@ -487,7 +470,7 @@ class ChangeClassificationLevel(bpy.types.Operator):
|
||||
parent_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.available_library_references.clear()
|
||||
for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences:
|
||||
new = props.available_library_references.add()
|
||||
@@ -509,6 +492,6 @@ class DisableEditingClassificationReferences(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.available_library_references.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.prop import Attribute
|
||||
from bonsai.bim.module.classification.data import ClassificationsData, ClassificationReferencesData
|
||||
from bonsai.bim.module.classification.data import ClassificationsData, ObjectClassificationsData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -30,33 +31,43 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
def get_available_classifications(self, context):
|
||||
def get_available_classifications(
|
||||
self: "BIMClassificationProperties", context: bpy.types.Context
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not ClassificationsData.is_loaded:
|
||||
ClassificationsData.load()
|
||||
return ClassificationsData.data["available_classifications"]
|
||||
|
||||
|
||||
def get_classifications(self, context):
|
||||
if not ClassificationReferencesData.is_loaded:
|
||||
ClassificationReferencesData.load()
|
||||
return ClassificationReferencesData.data["classifications"]
|
||||
if not ObjectClassificationsData.is_loaded:
|
||||
ObjectClassificationsData.load()
|
||||
return ObjectClassificationsData.data["classifications"]
|
||||
|
||||
|
||||
def get_classification_source(self, context):
|
||||
def get_classification_source(
|
||||
self: "BIMClassificationProperties", context: bpy.types.Context
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not ClassificationsData.is_loaded:
|
||||
ClassificationsData.load()
|
||||
return ClassificationsData.data["classification_source"]
|
||||
|
||||
|
||||
class ClassificationReference(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
has_references: BoolProperty(name="Has References")
|
||||
referenced_source: IntProperty(name="IFC Definition ID")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
identification: str
|
||||
ifc_definition_id: int
|
||||
has_references: bool
|
||||
referenced_source: int
|
||||
|
||||
|
||||
class BIMClassificationProperties(PropertyGroup):
|
||||
is_adding: BoolProperty(name="Is Adding", default=False)
|
||||
@@ -68,9 +79,25 @@ class BIMClassificationProperties(PropertyGroup):
|
||||
active_library_referenced_source: IntProperty(name="Active Library Referenced Source")
|
||||
active_library_reference_index: IntProperty(name="Active Library Reference Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_adding: bool
|
||||
classification_source: str
|
||||
available_classifications: str
|
||||
classification_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
active_classification_id: int
|
||||
available_library_references: bpy.types.bpy_prop_collection_idprop[ClassificationReference]
|
||||
active_library_referenced_source: int
|
||||
active_library_reference_index: int
|
||||
|
||||
|
||||
class BIMClassificationReferenceProperties(PropertyGroup):
|
||||
is_adding: BoolProperty(name="Is Adding", default=False)
|
||||
classifications: EnumProperty(items=get_classifications, name="Classifications")
|
||||
reference_attributes: CollectionProperty(name="Reference Attributes", type=Attribute)
|
||||
active_reference_id: IntProperty(name="Active Reference Id")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_adding: bool
|
||||
classifications: str
|
||||
reference_attributes: bpy.types.bpy_prop_collection_idprop
|
||||
active_reference_id: int
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
@@ -23,10 +24,15 @@ import bonsai.bim.module.classification.prop as classification_prop
|
||||
from bpy.types import Panel, UIList
|
||||
from bonsai.bim.module.classification.data import (
|
||||
ClassificationsData,
|
||||
ClassificationReferencesData,
|
||||
ObjectClassificationsData,
|
||||
MaterialClassificationsData,
|
||||
CostClassificationsData,
|
||||
ZoneClassificationsData,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.classification.prop import BIMClassificationProperties, ClassificationReference
|
||||
|
||||
|
||||
class BIM_PT_classifications(Panel):
|
||||
@@ -46,7 +52,8 @@ class BIM_PT_classifications(Panel):
|
||||
if not ClassificationsData.is_loaded:
|
||||
ClassificationsData.load()
|
||||
|
||||
self.props = context.scene.BIMClassificationProperties
|
||||
self.props = tool.Classification.get_classification_props()
|
||||
assert self.layout
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Source", icon="OUTLINER")
|
||||
@@ -66,6 +73,7 @@ class BIM_PT_classifications(Panel):
|
||||
self.draw_ui(classification)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
assert self.layout
|
||||
if self.props.is_adding:
|
||||
bonsai.bim.helper.draw_attributes(self.props.classification_attributes, self.layout)
|
||||
row = self.layout.row(align=True)
|
||||
@@ -76,10 +84,12 @@ class BIM_PT_classifications(Panel):
|
||||
row.operator("bim.enable_adding_manual_classification", text="Add Classification", icon="ADD")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
assert self.layout
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_classification_from_bsdd", icon="ADD")
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
assert self.layout
|
||||
if ClassificationsData.data["has_classification_file"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_classifications", text="")
|
||||
@@ -90,13 +100,15 @@ class BIM_PT_classifications(Panel):
|
||||
row.label(text="No Active Classification Library")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
|
||||
def draw_editable_ui(self):
|
||||
def draw_editable_ui(self) -> None:
|
||||
assert self.layout
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_classification", text="", icon="CANCEL")
|
||||
bonsai.bim.helper.draw_attributes(self.props.classification_attributes, self.layout)
|
||||
|
||||
def draw_ui(self, classification):
|
||||
def draw_ui(self, classification: dict[str, Any]) -> None:
|
||||
assert self.layout
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=classification["Name"], icon="ASSET_MANAGER")
|
||||
if not self.props.active_classification_id:
|
||||
@@ -106,13 +118,32 @@ class BIM_PT_classifications(Panel):
|
||||
|
||||
|
||||
class ReferenceUI:
|
||||
layout: bpy.types.UILayout
|
||||
layout: Union[bpy.types.UILayout, None]
|
||||
data: type[
|
||||
Union[
|
||||
ObjectClassificationsData,
|
||||
MaterialClassificationsData,
|
||||
CostClassificationsData,
|
||||
ZoneClassificationsData,
|
||||
]
|
||||
]
|
||||
|
||||
def draw_ui(self, context):
|
||||
obj = context.active_object
|
||||
self.sprops = context.scene.BIMClassificationProperties
|
||||
self.bprops = context.scene.BIMBSDDProperties
|
||||
self.props = context.scene.BIMClassificationReferenceProperties
|
||||
obj: str
|
||||
"""Object name."""
|
||||
|
||||
def get_object_name(self, context: bpy.types.Context) -> str:
|
||||
return ""
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
if not self.data.is_loaded:
|
||||
self.data.load()
|
||||
self.obj = self.get_object_name(context)
|
||||
self.draw_ui(context)
|
||||
|
||||
def draw_ui(self, context: bpy.types.Context) -> None:
|
||||
self.sprops = tool.Classification.get_classification_props()
|
||||
self.bprops = tool.Bsdd.get_bsdd_props()
|
||||
self.props = tool.Classification.get_classification_reference_props()
|
||||
self.file = tool.Ifc.get()
|
||||
|
||||
self.draw_add_ui(context)
|
||||
@@ -127,7 +158,7 @@ class ReferenceUI:
|
||||
else:
|
||||
self.draw_reference_ui(reference)
|
||||
|
||||
def draw_add_ui(self, context):
|
||||
def draw_add_ui(self, context: bpy.types.Context) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Source", icon="OUTLINER")
|
||||
row.prop(self.sprops, "classification_source", text="")
|
||||
@@ -139,20 +170,20 @@ class ReferenceUI:
|
||||
else:
|
||||
self.draw_add_bsdd_ui(context)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
def draw_add_manual_ui(self, context: object) -> None:
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "classifications", text="")
|
||||
if self.props.is_adding:
|
||||
bonsai.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.add_manual_classification_reference", text="Save", icon="CHECKMARK")
|
||||
op.obj_type = self.data.data["object_type"]
|
||||
op.obj_type = self.data.obj_type
|
||||
row.operator("bim.disable_adding_manual_classification_reference", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_adding_manual_classification_reference", text="Add Reference", icon="ADD")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
def draw_add_bsdd_ui(self, context: object) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.bprops, "keyword", text="")
|
||||
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
|
||||
@@ -177,9 +208,9 @@ class ReferenceUI:
|
||||
"bim.add_classification_reference_from_bsdd", text="Add Classification Reference", icon="ADD"
|
||||
)
|
||||
op.obj = self.obj
|
||||
op.obj_type = self.obj_type
|
||||
op.obj_type = self.data.obj_type
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
def draw_add_file_ui(self, context: object) -> None:
|
||||
if not self.data.data["active_classification_library"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Active Classification Library", icon="ERROR")
|
||||
@@ -199,7 +230,7 @@ class ReferenceUI:
|
||||
if self.sprops.active_library_reference_index < len(self.sprops.available_library_references):
|
||||
op = row.operator("bim.add_classification_reference", text="", icon="ADD")
|
||||
op.obj = self.obj
|
||||
op.obj_type = self.obj_type
|
||||
op.obj_type = self.data.obj_type
|
||||
op.reference = self.sprops.available_library_references[
|
||||
self.sprops.active_library_reference_index
|
||||
].ifc_definition_id
|
||||
@@ -213,13 +244,13 @@ class ReferenceUI:
|
||||
"active_library_reference_index",
|
||||
)
|
||||
|
||||
def draw_editable_ui(self):
|
||||
def draw_editable_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_classification_reference", text="Save changes", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
|
||||
bonsai.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
|
||||
|
||||
def draw_reference_ui(self, reference):
|
||||
def draw_reference_ui(self, reference: dict[str, Any]) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
if self.file.schema == "IFC2X3":
|
||||
name = reference["ItemReference"] or "No Identification"
|
||||
@@ -235,7 +266,7 @@ class ReferenceUI:
|
||||
op = row.operator("bim.remove_classification_reference", text="", icon="X")
|
||||
op.reference = reference["id"]
|
||||
op.obj = self.obj
|
||||
op.obj_type = self.obj_type
|
||||
op.obj_type = self.data.obj_type
|
||||
|
||||
|
||||
class BIM_PT_classification_references(Panel, ReferenceUI):
|
||||
@@ -247,19 +278,19 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_tab_object_metadata"
|
||||
|
||||
data = ObjectClassificationsData
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
return bool(tool.Ifc.get_entity(context.active_object))
|
||||
return (
|
||||
(obj := tool.Blender.get_active_object())
|
||||
and (element := tool.Ifc.get_entity(obj))
|
||||
and element.is_a("IfcObjectDefinition")
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
if not ClassificationReferencesData.is_loaded:
|
||||
ClassificationReferencesData.load()
|
||||
self.data = ClassificationReferencesData
|
||||
self.obj = context.active_object.name
|
||||
self.obj_type = "Object"
|
||||
self.draw_ui(context)
|
||||
def get_object_name(self, context: bpy.types.Context) -> str:
|
||||
assert (obj := context.active_object)
|
||||
return obj.name
|
||||
|
||||
|
||||
class BIM_PT_material_classifications(Panel, ReferenceUI):
|
||||
@@ -271,6 +302,8 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_materials"
|
||||
|
||||
data = MaterialClassificationsData
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get():
|
||||
@@ -280,14 +313,6 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialClassificationsData.is_loaded:
|
||||
MaterialClassificationsData.load()
|
||||
self.data = MaterialClassificationsData
|
||||
self.obj = ""
|
||||
self.obj_type = "Material"
|
||||
self.draw_ui(context)
|
||||
|
||||
|
||||
class BIM_PT_cost_classifications(Panel, ReferenceUI):
|
||||
bl_label = "Cost Item Classifications"
|
||||
@@ -298,6 +323,8 @@ class BIM_PT_cost_classifications(Panel, ReferenceUI):
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_cost_schedules"
|
||||
|
||||
data = CostClassificationsData
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get():
|
||||
@@ -305,17 +332,35 @@ class BIM_PT_cost_classifications(Panel, ReferenceUI):
|
||||
props = tool.Cost.get_cost_props()
|
||||
return bool(props.cost_items)
|
||||
|
||||
def draw(self, context):
|
||||
if not CostClassificationsData.is_loaded:
|
||||
CostClassificationsData.load()
|
||||
self.data = CostClassificationsData
|
||||
self.obj = ""
|
||||
self.obj_type = "Cost"
|
||||
self.draw_ui(context)
|
||||
|
||||
class BIM_PT_zone_classifications(Panel, ReferenceUI):
|
||||
bl_label = "Zone Classifications"
|
||||
bl_idname = "BIM_PT_zone_classifications"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_zones"
|
||||
|
||||
data = ZoneClassificationsData
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.System.get_zone_props()
|
||||
return bool(props.active_zone)
|
||||
|
||||
|
||||
class BIM_UL_classifications(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClassificationProperties,
|
||||
item: ClassificationReference,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
):
|
||||
if item:
|
||||
if item.has_references:
|
||||
op = layout.operator("bim.change_classification_level", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
|
||||
@@ -81,7 +81,7 @@ class EnableEditingConstraint(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Blender.get_constraint_props()
|
||||
props.constraint_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
|
||||
props.active_constraint_id = self.constraint
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -52,12 +52,19 @@ class ContextData:
|
||||
def get_subcontexts(cls, context: ifcopenshell.entity_instance) -> list[dict[str, Any]]:
|
||||
results = []
|
||||
for subcontext in context.HasSubContexts:
|
||||
target_scale_denominator = None
|
||||
if subcontext.TargetScale is not None:
|
||||
scale_value = float(subcontext.TargetScale)
|
||||
if scale_value > 0:
|
||||
target_scale_denominator = 1.0 / scale_value
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": subcontext.id(),
|
||||
"context_type": subcontext.ContextType,
|
||||
"context_identifier": subcontext.ContextIdentifier,
|
||||
"target_view": subcontext.TargetView,
|
||||
"target_scale_denominator": target_scale_denominator,
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
@@ -84,5 +84,26 @@ class BIM_PT_context(bpy.types.Panel):
|
||||
row.label(text=subcontext["context_type"])
|
||||
row.label(text=subcontext["context_identifier"])
|
||||
row.label(text=subcontext["target_view"])
|
||||
|
||||
scale_col = row.column()
|
||||
scale_col.ui_units_x = 6
|
||||
|
||||
if subcontext["target_scale_denominator"] is not None:
|
||||
scale_value = subcontext["target_scale_denominator"]
|
||||
|
||||
if scale_value == int(scale_value):
|
||||
scale_str = str(int(scale_value))
|
||||
if len(scale_str) > 4:
|
||||
scale_str = f"{float(scale_value):.3g}"
|
||||
else:
|
||||
scale_str = f"{scale_value:.4g}"
|
||||
|
||||
if len(scale_str) > 4:
|
||||
scale_str = f"{float(scale_value):.3g}"
|
||||
|
||||
scale_col.label(text=f"1:{scale_str}")
|
||||
else:
|
||||
scale_col.label(text="")
|
||||
|
||||
row.operator("bim.enable_editing_context", icon="GREASEPENCIL", text="").context = subcontext["id"]
|
||||
row.operator("bim.remove_context", icon="X", text="").context = subcontext["id"]
|
||||
|
||||
@@ -37,6 +37,7 @@ classes = (
|
||||
operator.ContractCostItemRate,
|
||||
operator.ContractCostItems,
|
||||
operator.CopyCostItem,
|
||||
operator.CopyCostSchedule,
|
||||
operator.CopyCostItemValues,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
@@ -59,9 +60,11 @@ classes = (
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ExpandCostItems,
|
||||
operator.ExportCostSchedules,
|
||||
operator.ExportCostSchedulesToPDF,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.RefreshCostScheduleCsv,
|
||||
operator.RemoveCostScheduleCsvLink,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.ShowAssignedCostRate,
|
||||
@@ -84,7 +87,6 @@ classes = (
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
prop.CostSchedulesMapping,
|
||||
prop.ScheduleColumn,
|
||||
prop.BIMCostProperties,
|
||||
ui.BIM_PT_cost_schedules,
|
||||
|
||||
@@ -56,9 +56,29 @@ class CostSchedulesData:
|
||||
"cost_values": cls.cost_values(),
|
||||
"quantity_types": cls.quantity_types(),
|
||||
"currency": cls.currency(),
|
||||
"csv_filepaths": cls.get_csv_filepaths(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def get_csv_filepaths(cls) -> dict[int, str]:
|
||||
"""Get CSV filepaths for cost schedules from document references."""
|
||||
filepaths = {}
|
||||
cost_docs_document = tool.Cost.get_or_create_cost_documents()
|
||||
|
||||
if cost_docs_document:
|
||||
references = tool.Document.get_document_references(cost_docs_document)
|
||||
for reference in references:
|
||||
if reference.Description and "Cost Schedule ID:" in reference.Description:
|
||||
schedule_id_str = reference.Description.split("Cost Schedule ID:")[1].strip()
|
||||
try:
|
||||
schedule_id = int(schedule_id_str)
|
||||
filepaths[schedule_id] = reference.Location
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return filepaths
|
||||
|
||||
@classmethod
|
||||
def currency(cls) -> Union[Currency, None]:
|
||||
unit = tool.Unit.get_project_currency_unit()
|
||||
@@ -111,6 +131,7 @@ class CostSchedulesData:
|
||||
cls._load_cost_values(cost_item, data)
|
||||
cls._load_nesting_index(cost_item, data)
|
||||
cls._load_assigned_cost_rate(cost_item, data)
|
||||
cls._load_parent_cost_schedule(cost_item, data)
|
||||
results[cost_item.id()] = data
|
||||
return results
|
||||
|
||||
@@ -162,6 +183,10 @@ class CostSchedulesData:
|
||||
def _load_assigned_cost_rate(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None:
|
||||
data["AssignedCostRate"] = tool.Cost.get_assigned_rate_cost_item(cost_item)
|
||||
|
||||
@classmethod
|
||||
def _load_parent_cost_schedule(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None:
|
||||
data["ParentCostSchedule"] = tool.Cost.get_cost_schedule(cost_item)
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None:
|
||||
# parametric_quantities = []
|
||||
@@ -171,6 +196,7 @@ class CostSchedulesData:
|
||||
# parametric_quantities.extend(quantities)
|
||||
data["TotalCostQuantity"] = ifcopenshell.util.cost.get_total_quantity(cost_item)
|
||||
data["UnitSymbol"] = "-"
|
||||
data["QuantityType"] = None
|
||||
quantities: list[ifcopenshell.entity_instance] = cost_item.CostQuantities
|
||||
if quantities:
|
||||
quantity = quantities[0]
|
||||
@@ -300,10 +326,8 @@ class CostSchedulesData:
|
||||
|
||||
@classmethod
|
||||
def quantity_types(cls) -> list[tuple[str, str, str]]:
|
||||
return [
|
||||
(t.name(), t.name(), "")
|
||||
for t in tool.Ifc.schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
|
||||
]
|
||||
assert (entity := tool.Ifc.schema().declaration_by_name("IfcPhysicalSimpleQuantity").as_entity())
|
||||
return [(t.name(), t.name(), "") for t in entity.subtypes()]
|
||||
|
||||
@classmethod
|
||||
def get_cost_schedule_types(cls) -> list[tuple[str, str, str]]:
|
||||
@@ -350,13 +374,13 @@ class CostItemQuantitiesData:
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def product_quantity_names(cls):
|
||||
elements = tool.Spatial.get_selected_products()
|
||||
def product_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
elements = list(tool.Spatial.get_selected_products())
|
||||
names = ifcopenshell.util.cost.get_product_quantity_names(elements)
|
||||
return [(n, n, "") for n in names]
|
||||
|
||||
@classmethod
|
||||
def process_quantity_names(cls):
|
||||
def process_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
props = tool.Sequence.get_work_schedule_props()
|
||||
active_task_index = props.active_task_index
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
@@ -372,14 +396,11 @@ class CostItemQuantitiesData:
|
||||
return [(n, n, "") for n in names if n != "id"]
|
||||
|
||||
@classmethod
|
||||
def resource_quantity_names(cls):
|
||||
active_resource_index = bpy.context.scene.BIMResourceProperties.active_resource_index
|
||||
total_resources = len(bpy.context.scene.BIMResourceTreeProperties.resources)
|
||||
if not total_resources or active_resource_index >= total_resources:
|
||||
def resource_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
if not active_resource:
|
||||
return []
|
||||
ifc_definition_id = bpy.context.scene.BIMResourceTreeProperties.resources[
|
||||
active_resource_index
|
||||
].ifc_definition_id
|
||||
ifc_definition_id = active_resource.ifc_definition_id
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
names = set()
|
||||
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
|
||||
|
||||
@@ -25,6 +25,8 @@ import bonsai.tool as tool
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.cost as core
|
||||
from pathlib import Path
|
||||
|
||||
from typing import get_args, TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
@@ -80,6 +82,20 @@ class RemoveCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.remove_cost_schedule(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||
|
||||
|
||||
class CopyCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.copy_cost_schedule"
|
||||
bl_label = "Copy Cost Schedule"
|
||||
bl_description = "Create a duplicate of the provided cost schedule."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_schedule: int
|
||||
|
||||
def _execute(self, context):
|
||||
core.copy_cost_schedule(tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||
|
||||
|
||||
class EnableEditingCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_schedule_attributes"
|
||||
bl_label = "Enable Editing Cost Schedule"
|
||||
@@ -545,11 +561,16 @@ class SelectCostScheduleProducts(bpy.types.Operator):
|
||||
class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
bl_idname = "bim.import_cost_schedule_csv"
|
||||
bl_label = "Import Cost Schedule CSV"
|
||||
bl_description = "Import cost schdule from the provided .csv file."
|
||||
bl_description = "Import cost schedule from the provided .csv file."
|
||||
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)
|
||||
use_relative_path: bpy.props.BoolProperty(
|
||||
name="Use Relative Path",
|
||||
description="Store the CSV filepath relative to the currently opened IFC file",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -560,22 +581,82 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator)
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
cost_schedule = core.import_cost_schedule_csv(tool.Cost, self.filepath, self.is_schedule_of_rates)
|
||||
core.add_csv_filepath(tool.Cost, self.filepath, self.is_schedule_of_rates, cost_schedule)
|
||||
return {"FINISHED"}
|
||||
|
||||
store_path = self.filepath
|
||||
if self.use_relative_path:
|
||||
store_path = tool.Ifc.get_uri(self.filepath, use_relative_path=True)
|
||||
|
||||
resolved_path = Path(tool.Ifc.resolve_uri(self.filepath))
|
||||
if not resolved_path.exists():
|
||||
self.report({"ERROR"}, f"File does not exist: '{store_path}' (resolved to '{resolved_path}')")
|
||||
return {"CANCELLED"}
|
||||
|
||||
cost_schedule = core.import_cost_schedule_csv(tool.Cost, str(resolved_path), self.is_schedule_of_rates)
|
||||
if cost_schedule:
|
||||
core.add_csv_filepath(tool.Cost, store_path, self.is_schedule_of_rates, cost_schedule)
|
||||
return {"FINISHED"}
|
||||
return {"CANCELLED"}
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "is_schedule_of_rates")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "use_relative_path")
|
||||
|
||||
|
||||
class RefreshCostScheduleCsv(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.refresh_cost_schedule_csv"
|
||||
bl_label = "Refresh Cost Schedule CSV"
|
||||
bl_description = "Refresh cost schedule data from the associated CSV file"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Cost.get_cost_props()
|
||||
if not props.active_cost_schedule_id:
|
||||
cls.poll_message_set("No active cost schedule")
|
||||
return False
|
||||
|
||||
file_path = tool.Cost.get_cost_schedule_csv_filepath(props.active_cost_schedule_id)
|
||||
if not file_path:
|
||||
cls.poll_message_set("No CSV file associated with this cost schedule")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_cost_schedule_csv(tool.Ifc, tool.Cost)
|
||||
core.refresh_cost_schedule_csv(tool.Cost)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCostScheduleCsvLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_cost_schedule_csv_link"
|
||||
bl_label = "Remove CSV Link"
|
||||
bl_description = "Remove the link to the CSV file and delete the related document reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Cost.get_cost_props()
|
||||
if not props.active_cost_schedule_id:
|
||||
cls.poll_message_set("No active cost schedule")
|
||||
return False
|
||||
|
||||
file_path = tool.Cost.get_cost_schedule_csv_filepath(props.active_cost_schedule_id)
|
||||
if not file_path:
|
||||
cls.poll_message_set("No CSV file associated with this cost schedule")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_cost_schedule_csv_link(tool.Cost)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
|
||||
|
||||
class AddCostColumn(bpy.types.Operator):
|
||||
bl_idname = "bim.add_cost_column"
|
||||
bl_label = "Add Cost Column"
|
||||
@@ -620,6 +701,7 @@ class ShowAssignedCostRate(bpy.types.Operator):
|
||||
bl_idname = "bim.show_assigned_cost_rate"
|
||||
bl_label = "Info about the assigned cost item rate"
|
||||
bl_options = {"REGISTER"}
|
||||
parent_cost_schedule_name: bpy.props.StringProperty()
|
||||
assigned_rate_identification: bpy.props.StringProperty()
|
||||
assigned_rate_name: bpy.props.StringProperty()
|
||||
assigned_rate_description: bpy.props.StringProperty()
|
||||
@@ -636,6 +718,7 @@ class ShowAssignedCostRate(bpy.types.Operator):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
wrapper = textwrap.TextWrapper(width=80)
|
||||
layout.label(text=f"COST SCHEDULE: {self.parent_cost_schedule_name}")
|
||||
layout.label(text=f"ID: {self.assigned_rate_identification}")
|
||||
layout.label(text=f"Name: {self.assigned_rate_name}")
|
||||
layout.label(text="Description:")
|
||||
@@ -785,6 +868,178 @@ class ExportCostSchedules(bpy.types.Operator, ExportHelper):
|
||||
self.layout.label(text="Select a directory.")
|
||||
|
||||
|
||||
class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
|
||||
bl_idname = "bim.export_cost_schedules_to_pdf"
|
||||
bl_label = "Export Cost Schedule to PDF"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Print chosen cost schedule to pdf."
|
||||
filename_ext = ".pdf"
|
||||
filter_glob: bpy.props.StringProperty(default="*.pdf", options={"HIDDEN"}, maxlen=255)
|
||||
|
||||
cost_schedules_items = []
|
||||
|
||||
def get_cost_schedules_enum_items(self, context):
|
||||
return ExportCostSchedulesToPDF.cost_schedules_items
|
||||
|
||||
cost_schedules_enum: bpy.props.EnumProperty(
|
||||
name="",
|
||||
description="Choose IfcCostSchedule to print",
|
||||
items=get_cost_schedules_enum_items,
|
||||
)
|
||||
|
||||
nested_structure_depth: bpy.props.IntProperty(
|
||||
name="Nested structure depth: ",
|
||||
description="Define till which level of the structure the parent cost items are displayed.\n0: display the full structure.",
|
||||
default=0,
|
||||
min=0,
|
||||
max=9,
|
||||
)
|
||||
parent_to_new_page_up_to_depth: bpy.props.IntProperty(
|
||||
name="Parents to new page up to depth: ",
|
||||
description="Define till which level of the structure the parent is printed to a new page.\n0: no parent is split to a new page.",
|
||||
default=0,
|
||||
min=0,
|
||||
max=9,
|
||||
)
|
||||
show_only_parents: bpy.props.BoolProperty(
|
||||
name="Show only parent cost items",
|
||||
description="Hide cost items and show only container costs",
|
||||
default=False,
|
||||
)
|
||||
should_print_cover: bpy.props.BoolProperty(
|
||||
name="Cover page",
|
||||
description="Create a cover page with project data",
|
||||
default=False,
|
||||
)
|
||||
should_print_description: bpy.props.BoolProperty(
|
||||
name="Full Cost Items Description",
|
||||
description="Export the full description if present",
|
||||
default=True,
|
||||
)
|
||||
should_print_cost_ids: bpy.props.BoolProperty(
|
||||
name="Print Cost Identification",
|
||||
description="Print Cost Identification under Cost Name if present",
|
||||
default=True,
|
||||
)
|
||||
should_print_each_quantity: bpy.props.BoolProperty(
|
||||
name="Show each quantity",
|
||||
description="Export the full list of quantities",
|
||||
default=False,
|
||||
)
|
||||
should_print_each_cost_value: bpy.props.BoolProperty(
|
||||
name="Show each cost value",
|
||||
description="Export the full list of cost values\nassociated with each cost item\nin the schedule of rates",
|
||||
default=False,
|
||||
)
|
||||
should_print_rates: bpy.props.BoolProperty(
|
||||
name="Rates and totals",
|
||||
description="Print rates and totals for each voice",
|
||||
default=True,
|
||||
)
|
||||
should_print_summary: bpy.props.BoolProperty(
|
||||
name="Final Summary",
|
||||
description="Print summary at the end of the document",
|
||||
default=True,
|
||||
)
|
||||
force_schedule_type: bpy.props.EnumProperty(
|
||||
name="Output type",
|
||||
description='Force the output to this type.\n"Auto" defaults to selected cost schedule Predefined Type',
|
||||
items=[
|
||||
(
|
||||
"AUTO",
|
||||
"By PredefinedType",
|
||||
"Uses Cost Schedule Predefined Type",
|
||||
),
|
||||
(
|
||||
"PRICEDBILLOFQUANTITIES",
|
||||
"Priced Bill of Quantities",
|
||||
"Forces the output as a priced bill of quantities",
|
||||
),
|
||||
(
|
||||
"SCHEDULEOFRATES",
|
||||
"Schedule of Rates",
|
||||
"Forces the output as a schedule of rates",
|
||||
),
|
||||
],
|
||||
default="AUTO",
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Select Ifc Cost Schedule:")
|
||||
box.prop(self, "cost_schedules_enum", text="")
|
||||
layout.separator()
|
||||
box = layout.box()
|
||||
box.label(text="Nested cost structure:")
|
||||
box.prop(self, "nested_structure_depth")
|
||||
layout.separator()
|
||||
box = layout.box()
|
||||
box.label(text="PDF Document properties:")
|
||||
box.prop(self, "should_print_cover")
|
||||
box.prop(self, "should_print_cost_ids")
|
||||
box.prop(self, "should_print_description")
|
||||
box.prop(self, "should_print_each_quantity")
|
||||
box.prop(self, "should_print_rates")
|
||||
box.prop(self, "should_print_summary")
|
||||
box.prop(self, "force_schedule_type")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
import typst
|
||||
|
||||
return True
|
||||
except:
|
||||
cls.poll_message_set(
|
||||
"Typst not available.\nIt can be installed from Quality and\nControl -> Debug and using 'typst' with Pip Install.\n(Run Blender as Administrator)"
|
||||
)
|
||||
return False
|
||||
|
||||
def invoke(self, context, event):
|
||||
ExportCostSchedulesToPDF.cost_schedules_items.clear()
|
||||
file = tool.Ifc.get()
|
||||
schedules = file.by_type("IfcCostSchedule")
|
||||
for schedule in schedules:
|
||||
ExportCostSchedulesToPDF.cost_schedules_items.append(
|
||||
(
|
||||
str(schedule.id()),
|
||||
"{} ({})".format(
|
||||
schedule.Name if schedule.Name is not None else "Unnamed",
|
||||
schedule.PredefinedType if schedule.PredefinedType is not None else "UNTYPED",
|
||||
),
|
||||
"",
|
||||
)
|
||||
)
|
||||
return ExportHelper.invoke(self, context, event)
|
||||
|
||||
def execute(self, context):
|
||||
file = tool.Ifc.get()
|
||||
self.props = tool.Cost.get_cost_props()
|
||||
cost_schedule = file.by_id(int(self.cost_schedules_enum))
|
||||
options = {
|
||||
"nested_structure_depth": self.nested_structure_depth,
|
||||
"parent_to_new_page_up_to_depth": self.parent_to_new_page_up_to_depth,
|
||||
"show_only_parents": self.show_only_parents,
|
||||
"should_print_cover": self.should_print_cover,
|
||||
"should_print_cost_ids": self.should_print_cost_ids,
|
||||
"should_print_description": self.should_print_description,
|
||||
"should_print_each_quantity": self.should_print_each_quantity,
|
||||
"should_print_each_cost_value": self.should_print_each_cost_value,
|
||||
"should_print_rates": self.should_print_rates,
|
||||
"should_print_summary": self.should_print_summary,
|
||||
}
|
||||
|
||||
core.export_cost_schedules_to_pdf(
|
||||
tool.Cost,
|
||||
filepath=self.filepath,
|
||||
cost_schedule=cost_schedule,
|
||||
options=options,
|
||||
force_schedule_type=self.force_schedule_type,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ClearCostItemAssignments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.clear_cost_item_assignments"
|
||||
bl_label = "Clear Cost Item Product Assignments"
|
||||
|
||||
@@ -175,15 +175,6 @@ class CostItemQuantity(PropertyGroup):
|
||||
total_cost_quantity: float
|
||||
|
||||
|
||||
class CostSchedulesMapping(PropertyGroup):
|
||||
cost_schedule_id: IntProperty(name="cost_schedule_id")
|
||||
csv_filepath: StringProperty(name="filepath")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_schedule_id: int
|
||||
csv_filepath: str
|
||||
|
||||
|
||||
class CostItemType(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
@@ -289,7 +280,6 @@ class BIMCostProperties(PropertyGroup):
|
||||
custom_currency: StringProperty(
|
||||
name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format"
|
||||
)
|
||||
cost_schedule_files: CollectionProperty(name="Cost Schedule Files", type=CostSchedulesMapping)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_schedule_predefined_types: str
|
||||
@@ -344,7 +334,6 @@ class BIMCostProperties(PropertyGroup):
|
||||
show_cost_item_operators: bool
|
||||
currency: str
|
||||
custom_currency: str
|
||||
cost_schedule_files: bpy.types.bpy_prop_collection_idprop[CostSchedulesMapping]
|
||||
|
||||
@property
|
||||
def active_cost_item(self) -> Union[CostItem, None]:
|
||||
|
||||
@@ -38,6 +38,8 @@ class BIM_PT_cost_schedules(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_cost"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
layout: bpy.types.UILayout
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
@@ -53,6 +55,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.export_cost_schedules", icon="EXPORT", text="Export All Schedules")
|
||||
row.operator("bim.export_cost_schedules_to_pdf", icon="OUTPUT", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
else:
|
||||
@@ -76,17 +79,14 @@ class BIM_PT_cost_schedules(Panel):
|
||||
col = row0.column()
|
||||
col.label(text="Linked CSV:")
|
||||
row_1 = col.row(align=True)
|
||||
if self.props.active_cost_schedule_id in [item.cost_schedule_id for item in self.props.cost_schedule_files]:
|
||||
file = next(
|
||||
(
|
||||
item.csv_filepath
|
||||
for item in self.props.cost_schedule_files
|
||||
if item.cost_schedule_id == self.props.active_cost_schedule_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
row_1.label(text=file)
|
||||
|
||||
csv_filepaths = CostSchedulesData.data["csv_filepaths"]
|
||||
file_path = csv_filepaths.get(self.props.active_cost_schedule_id)
|
||||
|
||||
if file_path:
|
||||
row_1.label(text=file_path)
|
||||
row_1.operator("bim.refresh_cost_schedule_csv", icon="FILE_REFRESH", text="")
|
||||
row_1.operator("bim.remove_cost_schedule_csv_link", icon="X", text="")
|
||||
else:
|
||||
row_1.label(text="No CSV file found")
|
||||
row_1.operator("bim.import_cost_schedule_csv", icon="IMPORT", text="")
|
||||
@@ -129,6 +129,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.operator("bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL").cost_schedule = (
|
||||
cost_schedule["id"]
|
||||
)
|
||||
row.operator("bim.copy_cost_schedule", text="", icon="DUPLICATE").cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
|
||||
if self.props.active_cost_schedule_id == cost_schedule["id"]:
|
||||
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
||||
@@ -140,13 +141,13 @@ class BIM_PT_cost_schedules(Panel):
|
||||
self.draw_currency_ui()
|
||||
self.draw_editable_cost_item_ui()
|
||||
|
||||
def draw_column_ui(self):
|
||||
def draw_column_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "cost_column", text="")
|
||||
row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column
|
||||
self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
|
||||
|
||||
def draw_currency_ui(self):
|
||||
def draw_currency_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
if CostSchedulesData.data["currency"]:
|
||||
text = "Currency used: {}".format(CostSchedulesData.data["currency"]["name"])
|
||||
@@ -163,7 +164,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
def draw_editable_cost_schedule_ui(self):
|
||||
bonsai.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
|
||||
|
||||
def draw_editable_cost_item_ui(self):
|
||||
def draw_editable_cost_item_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
ifc_definition_id = None
|
||||
@@ -219,14 +220,14 @@ class BIM_PT_cost_schedules(Panel):
|
||||
elif self.props.cost_item_editing_type == "VALUES":
|
||||
self.draw_editable_cost_item_values_ui()
|
||||
|
||||
def draw_editable_cost_item_attributes_ui(self):
|
||||
def draw_editable_cost_item_attributes_ui(self) -> None:
|
||||
bonsai.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
|
||||
|
||||
def draw_editable_cost_item_quantities_ui(self, cost_item: dict[str, Any]):
|
||||
quantities = CostSchedulesData.data["cost_quantities"]
|
||||
row = self.layout.row(align=True)
|
||||
# In IFC, all quantities of IfcCostTime should have 1 type.
|
||||
if quantities:
|
||||
if quantities and cost_item.get("QuantityType"):
|
||||
quantity_class = cost_item["QuantityType"]
|
||||
row.label(text=quantity_class)
|
||||
else:
|
||||
@@ -395,7 +396,7 @@ class BIM_PT_cost_item_types(Panel):
|
||||
op.cost_item = cost_item.ifc_definition_id
|
||||
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
rprops = context.scene.BIMResourceProperties
|
||||
rprops = tool.Resource.get_resource_props()
|
||||
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
||||
if has_quantity_names:
|
||||
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
||||
@@ -554,9 +555,8 @@ class BIM_PT_cost_item_quantities(Panel):
|
||||
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
|
||||
op.cost_item = cost_item.ifc_definition_id
|
||||
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
rprops = context.scene.BIMResourceProperties
|
||||
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
if active_resource:
|
||||
if has_quantity_names:
|
||||
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
||||
op.related_object_type = "RESOURCE"
|
||||
@@ -734,6 +734,9 @@ class BIM_UL_cost_items_trait:
|
||||
if cost_item["AssignedCostRate"] is not None:
|
||||
op = row.operator("bim.show_assigned_cost_rate", text="", emboss=False, icon="ZOOM_IN")
|
||||
identification = cost_item["AssignedCostRate"].Identification
|
||||
op.parent_cost_schedule_name = CostSchedulesData.data["cost_items"][cost_item["AssignedCostRate"].id()][
|
||||
"ParentCostSchedule"
|
||||
].Name
|
||||
op.assigned_rate_identification = identification if identification is not None else "XXX"
|
||||
op.assigned_rate_name = cost_item["AssignedCostRate"].Name or ""
|
||||
op.assigned_rate_description = cost_item["AssignedCostRate"].Description or ""
|
||||
|
||||
@@ -121,7 +121,14 @@ class CoveringToolUI:
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
prop_with_search(
|
||||
row,
|
||||
cls.props,
|
||||
"relating_type_id",
|
||||
text="",
|
||||
enable_relating_type_suggestions=True,
|
||||
search_threshold=0,
|
||||
)
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
|
||||
@@ -34,13 +34,14 @@ class Login(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
token = api.login(context.scene.CoveToolProperties.username, context.scene.CoveToolProperties.password)
|
||||
props = tool.Blender.get_covetool_props()
|
||||
token = api.login(props.username, props.password)
|
||||
if token:
|
||||
context.scene.CoveToolProperties.token = token
|
||||
props.token = token
|
||||
|
||||
projects = api.get_request("projects")
|
||||
for project in projects:
|
||||
new_project = context.scene.CoveToolProperties.projects.add()
|
||||
new_project = props.projects.add()
|
||||
new_project.name = project["name"]
|
||||
new_project.run_set = project["run_set"][0]
|
||||
new_project.url = project["url"]
|
||||
@@ -54,11 +55,10 @@ class RunSimpleAnalysis(bpy.types.Operator):
|
||||
bl_label = "Run Simple Analysis"
|
||||
|
||||
def execute(self, context):
|
||||
simple_analysis = context.scene.CoveToolProperties.simple_analysis
|
||||
props = tool.Blender.get_covetool_props()
|
||||
simple_analysis = props.simple_analysis
|
||||
data = {
|
||||
"run": context.scene.CoveToolProperties.projects[
|
||||
context.scene.CoveToolProperties.active_project_index
|
||||
].run_set,
|
||||
"run": props.projects[props.active_project_index].run_set,
|
||||
"si_units": simple_analysis.si_units,
|
||||
"building_height": simple_analysis.building_height,
|
||||
"roof_area": simple_analysis.roof_area,
|
||||
@@ -92,6 +92,7 @@ class RunAnalysis(bpy.types.Operator):
|
||||
bl_label = "Run Analysis"
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Blender.get_covetool_props()
|
||||
self.file = tool.Ifc.get()
|
||||
self.inputs = {
|
||||
"floors": [],
|
||||
@@ -104,9 +105,7 @@ class RunAnalysis(bpy.types.Operator):
|
||||
}
|
||||
self.parse_objects(context)
|
||||
data = {
|
||||
"run": context.scene.CoveToolProperties.projects[
|
||||
context.scene.CoveToolProperties.active_project_index
|
||||
].run_set,
|
||||
"run": props.projects[props.active_project_index].run_set,
|
||||
"source": "Bonsai",
|
||||
"rotation_angle": self.get_rotation_angle(),
|
||||
**self.inputs,
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy.props
|
||||
import bpy.types
|
||||
import bpy
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class CoveToolProject(bpy.types.PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="Name")
|
||||
run_set: bpy.props.StringProperty(name="Run Set")
|
||||
url: bpy.props.StringProperty(name="URL")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
run_set: str
|
||||
url: set
|
||||
|
||||
|
||||
class CoveToolSimpleAnalysis(bpy.types.PropertyGroup):
|
||||
si_units: bpy.props.BoolProperty(name="SI Units")
|
||||
@@ -49,6 +52,29 @@ class CoveToolSimpleAnalysis(bpy.types.PropertyGroup):
|
||||
window_area_s: bpy.props.StringProperty(name="Window Area S")
|
||||
window_area_se: bpy.props.StringProperty(name="Window Area SE")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
si_units: bool
|
||||
building_height: str
|
||||
roof_area: str
|
||||
floor_area: str
|
||||
skylight_area: str
|
||||
wall_area_e: str
|
||||
wall_area_ne: str
|
||||
wall_area_n: str
|
||||
wall_area_nw: str
|
||||
wall_area_w: str
|
||||
wall_area_sw: str
|
||||
wall_area_s: str
|
||||
wall_area_se: str
|
||||
window_area_e: str
|
||||
window_area_ne: str
|
||||
window_area_n: str
|
||||
window_area_nw: str
|
||||
window_area_w: str
|
||||
window_area_sw: str
|
||||
window_area_s: str
|
||||
window_area_se: str
|
||||
|
||||
|
||||
class CoveToolProperties(bpy.types.PropertyGroup):
|
||||
username: bpy.props.StringProperty(name="Username")
|
||||
@@ -57,3 +83,11 @@ class CoveToolProperties(bpy.types.PropertyGroup):
|
||||
projects: bpy.props.CollectionProperty(name="Projects", type=CoveToolProject)
|
||||
active_project_index: bpy.props.IntProperty(name="Active Project Index")
|
||||
simple_analysis: bpy.props.PointerProperty(type=CoveToolSimpleAnalysis)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
username: str
|
||||
password: str
|
||||
token: str
|
||||
projects: bpy.types.bpy_prop_collection_idprop[CoveToolProject]
|
||||
active_project_index: int
|
||||
simple_analysis: CoveToolSimpleAnalysis
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy.types
|
||||
import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.covetool.prop import CoveToolProperties, CoveToolProject
|
||||
|
||||
|
||||
class BIM_PT_covetool(bpy.types.Panel):
|
||||
@@ -32,8 +38,7 @@ class BIM_PT_covetool(bpy.types.Panel):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
props = scene.CoveToolProperties
|
||||
props = tool.Blender.get_covetool_props()
|
||||
|
||||
if not props.token:
|
||||
row = layout.row()
|
||||
@@ -81,7 +86,16 @@ class BIM_PT_covetool(bpy.types.Panel):
|
||||
|
||||
|
||||
class BIM_UL_covetool_projects(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: CoveToolProperties,
|
||||
item: CoveToolProject,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
ob = data
|
||||
if item:
|
||||
layout.prop(item, "name", text="", emboss=False)
|
||||
|
||||
@@ -269,7 +269,7 @@ class ExportIfcCsv(bpy.types.Operator, ExportHelper):
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
|
||||
if props.format == "web":
|
||||
if not context.scene.WebProperties.is_connected:
|
||||
if not tool.Web.get_web_props().is_connected:
|
||||
bpy.ops.bim.connect_websocket_server()
|
||||
df = ifc_csv.dataframe
|
||||
assert df is not None
|
||||
|
||||
@@ -32,6 +32,7 @@ class BIM_PT_ifccsv(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
props = tool.Blender.get_csv_props()
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ChangeLogLevel,
|
||||
operator.ConvertToBlender,
|
||||
operator.CopyDebugInformation,
|
||||
operator.CreateAllShapes,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user