Compare commits
1286 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc90340b3b | |||
| db075c7160 | |||
| ea48996f0d | |||
| 6b55e5e6ee | |||
| e270583451 | |||
| 99dc4795c7 | |||
| 1642f231fb | |||
| 9998442eb4 | |||
| 4040c31583 | |||
| 6614245fc1 | |||
| c7a41b46fd | |||
| dfd11fb723 | |||
| 8d3aa98b58 | |||
| b08acd1625 | |||
| 181b0cefc3 | |||
| e85043ae31 | |||
| ca0e4f9cd6 | |||
| 3ff0ff66a4 | |||
| 0ea71b0795 | |||
| 932a718d2d | |||
| 106b6f74e1 | |||
| 88de650660 | |||
| 72ea93bc0f | |||
| 6f347bcb81 | |||
| fa29c42887 | |||
| 86edd52894 | |||
| db0a72c794 | |||
| d68dee148b | |||
| a1134e83a4 | |||
| b61927b460 | |||
| 863633f2dc | |||
| b7dfeaee37 | |||
| 2771465996 | |||
| f62b1ed326 | |||
| 0c0225543b | |||
| ffe8d4fad2 | |||
| c18d2e8c17 | |||
| 70521b1020 | |||
| a777d67252 | |||
| 7544053f0b | |||
| 7bebd2f7ce | |||
| 64fe2240e6 | |||
| 54b104b822 | |||
| abd7c93c35 | |||
| 5c56ea6343 | |||
| e8a2f3aed2 | |||
| 931e945044 | |||
| 5eeea23774 | |||
| 2a44af57d2 | |||
| 035b6d00c5 | |||
| 8372e12f88 | |||
| 76869b0b02 | |||
| 5c08b8f22d | |||
| bcf54b643d | |||
| 71c5e83fa0 | |||
| 9eaecdb0a4 | |||
| 3077687c59 | |||
| 71997d37c0 | |||
| acc9e7f706 | |||
| 5fa5b28484 | |||
| 745c8e173a | |||
| 14917d2c31 | |||
| 340916a5a4 | |||
| 734ac2ba70 | |||
| dbfe3ea3c4 | |||
| c581946fa8 | |||
| 492df2310d | |||
| e8eb3972a1 | |||
| 45b3008b99 | |||
| ce6c56cab4 | |||
| 1d90665c3a | |||
| 6bfbcb8f92 | |||
| 4acdd7c960 | |||
| 647e379c69 | |||
| 18d5957424 | |||
| 65ecb92c03 | |||
| b8d98b7512 | |||
| 97ed3bb0d5 | |||
| 217ada0993 | |||
| dcd6acb1f4 | |||
| 122ec6fb0a | |||
| 60837b9512 | |||
| ada4a8884b | |||
| a27bdc4e3e | |||
| 43cecf6c5e | |||
| 4666589fcd | |||
| e3ef64d0b3 | |||
| 828160c1b1 | |||
| 832ccff139 | |||
| 17e67fbdaa | |||
| 01b28fde6f | |||
| 9130659f71 | |||
| e073f11e25 | |||
| dbd64ebb0e | |||
| ecba1c3e7b | |||
| 5837a143da | |||
| 01c67eea48 | |||
| 0e5008d21e | |||
| b17eacfa6a | |||
| a04912e174 | |||
| ee7fd0cadf | |||
| 5ae75d32bd | |||
| a0e6605834 | |||
| 903e673984 | |||
| a7efdb0bff | |||
| 695ab0bfab | |||
| e9e89c1280 | |||
| e29f8ce18a | |||
| ee892f63bf | |||
| bb8e1866b0 | |||
| 3a5856511d | |||
| 725df97ac0 | |||
| 98888d5096 | |||
| 14d44dcd3e | |||
| 7f1f249759 | |||
| 3113d62c85 | |||
| 58b46da04d | |||
| db77d06a8a | |||
| 4684aa4a55 | |||
| b9c6508aa7 | |||
| d7bf8eba02 | |||
| e7a725dca3 | |||
| f763e4b0a7 | |||
| 12742bd08c | |||
| 8940ec7b80 | |||
| cb2895cc41 | |||
| 1facba797d | |||
| a76e802ab4 | |||
| afeec29261 | |||
| 6c573445dd | |||
| f17c2eb80e | |||
| 0888cbb675 | |||
| 2aeb87b5fe | |||
| 79c09ae225 | |||
| 686514b88a | |||
| a014cd58e3 | |||
| 090c670f04 | |||
| d1b9d9697e | |||
| fe316e2741 | |||
| 16553417c1 | |||
| fac9444cfd | |||
| cd8f6e2c78 | |||
| 9e2189fea4 | |||
| 5ffef568f7 | |||
| 9ccbd332cf | |||
| a1fb4d6b81 | |||
| 2f0500a14b | |||
| 6f6700db06 | |||
| 0235d84f25 | |||
| 1225073bef | |||
| fb714e8fd8 | |||
| a0aa5396c9 | |||
| 017de3bd59 | |||
| fe5dada256 | |||
| fe0aa4b5b7 | |||
| 3a2f29aefc | |||
| cdaa782c18 | |||
| b4d652d12e | |||
| 014edcdadc | |||
| 3f2efcc846 | |||
| 095c854d41 | |||
| dd646d2763 | |||
| a32c4ae694 | |||
| 2e21ec749b | |||
| 189bcd5ad6 | |||
| dc1842dbe8 | |||
| c75e2e3fc0 | |||
| 10e76a09de | |||
| 766f8adce7 | |||
| 47f013fc18 | |||
| bd42fc9cef | |||
| 0eb713a7ff | |||
| 50c6cbcb2d | |||
| 304db50692 | |||
| 1a55212099 | |||
| ddd17d519e | |||
| 481e6b8a8d | |||
| d01438e7f1 | |||
| da0d383666 | |||
| a38a3ad2fb | |||
| bba862e3ae | |||
| 11eb1c26f0 | |||
| d09ab54869 | |||
| aeb00e518b | |||
| 844be6bd1c | |||
| 4f6ca68195 | |||
| c33c0b7780 | |||
| 7137d95c63 | |||
| 48784ba834 | |||
| fa642e6203 | |||
| ee54b3a6f4 | |||
| e3223db059 | |||
| b34157e257 | |||
| bfe3601fd1 | |||
| cc6249d6cc | |||
| 4c2fc55cfe | |||
| 36a0a0f2dd | |||
| 89b1f9c2d7 | |||
| 3c51c6325c | |||
| fdf4749dd7 | |||
| 742b3ca120 | |||
| c85eb79c50 | |||
| 1d6b0f781b | |||
| 32899ebffb | |||
| 1b321f0747 | |||
| 774791cf97 | |||
| c5f27e908a | |||
| 0f4e4730e2 | |||
| 6fb4ce202e | |||
| f66d1bed73 | |||
| af3fb78e1b | |||
| 29d6d2ba97 | |||
| a4226369bd | |||
| 250fad696e | |||
| 461e3d478a | |||
| f3df7b8475 | |||
| aa9b2d7428 | |||
| 8e8136ffb6 | |||
| b595eb043e | |||
| 2e6ea4fffb | |||
| eef0eb5d43 | |||
| 9cad7269fd | |||
| 0979c37ba8 | |||
| f9fc563de9 | |||
| ad74d02165 | |||
| 93457051da | |||
| ce1f1bc394 | |||
| 0068023c6f | |||
| 2ea4fc6491 | |||
| b057f89459 | |||
| 1a8c11c715 | |||
| cf2a156872 | |||
| 79a8fb1f7f | |||
| d4848f26f6 | |||
| 9b5f20e1dc | |||
| 864cc10b24 | |||
| 2e7e2d24e4 | |||
| c10056611b | |||
| 89a078bc50 | |||
| 07fda60794 | |||
| 0935159c42 | |||
| ca9681568c | |||
| 13a6d5b64a | |||
| 8aa1b68d3e | |||
| dc63e989d6 | |||
| 0cef788a09 | |||
| 38d450e111 | |||
| e5c83b1538 | |||
| 9177e19efa | |||
| bf9d84e738 | |||
| 2994f5c170 | |||
| 3107fd91ee | |||
| 69781daa6b | |||
| e810dc76e0 | |||
| cab3fd266d | |||
| 62aef09bc7 | |||
| 2626968a05 | |||
| 0e6479b090 | |||
| 7b0a792ff7 | |||
| 944e9c45ac | |||
| 99b1f5ec45 | |||
| 3b71d00508 | |||
| c3e4ebd996 | |||
| be9c9b2caa | |||
| 306a269061 | |||
| 392b458563 | |||
| 8ae951d27a | |||
| 770e25dbc2 | |||
| 2c08cff67b | |||
| 83c40c7fb4 | |||
| 3b9c1d88c8 | |||
| 040aec3e52 | |||
| 8f443a79f3 | |||
| 3a9598a9d9 | |||
| 104b6f5564 | |||
| d5fddd5f1b | |||
| 99ae69f1e4 | |||
| b700c5e448 | |||
| 2a9301ead2 | |||
| 315d3c6437 | |||
| 35f53232b6 | |||
| 3978226b65 | |||
| c3a6d28927 | |||
| 0ca3837fc2 | |||
| c96c661394 | |||
| a4ac71f9f7 | |||
| f63a7d420e | |||
| 19f1c87437 | |||
| 4b39631b98 | |||
| e040dc4562 | |||
| 55726b3d50 | |||
| f4165d4149 | |||
| b7c390585b | |||
| 7133b01b76 | |||
| 616a8882d9 | |||
| e6e6bd841c | |||
| 45ae7634a8 | |||
| b67be01590 | |||
| 06a2c5bf0b | |||
| 79b1388e44 | |||
| 53765222d6 | |||
| 665afc0375 | |||
| 5129d2016e | |||
| 5d83c49fe0 | |||
| 3ad58aba7a | |||
| 9131804e4e | |||
| 6e2e01b460 | |||
| 5d1dee4480 | |||
| 62f696da51 | |||
| af7bdf6b9f | |||
| 2c22421889 | |||
| 8a0e87b8f8 | |||
| 1bcc7199f6 | |||
| 275d615d42 | |||
| 905044f0d7 | |||
| e2ae2a4a89 | |||
| abe3d7e5db | |||
| dd01cb0e51 | |||
| 2eecda17e4 | |||
| c45b4372a8 | |||
| 5bb40111c1 | |||
| faa94eb636 | |||
| 2a6616707c | |||
| d1c430cd4d | |||
| a5e6539ddf | |||
| 7bc4b5100b | |||
| 8d08d358af | |||
| 314534436d | |||
| be68a8fdde | |||
| 400ebf2efb | |||
| 0dff19ba6a | |||
| 24a6ed0e5c | |||
| 63ffa1aadb | |||
| f7bf440848 | |||
| 82fd49e4c4 | |||
| 4261fdbed6 | |||
| 9f02737a2e | |||
| fe3b66c044 | |||
| 14cea7453b | |||
| cfa2dc5d97 | |||
| 123d023bb3 | |||
| 8ff94d3f61 | |||
| 3b64350a3f | |||
| 78302d1583 | |||
| d93ffb1fe6 | |||
| ceeba53c18 | |||
| ecd147675e | |||
| 1cf7f65902 | |||
| 7d8ef3ffd0 | |||
| ca5f083481 | |||
| a35304f1d5 | |||
| c80c52c248 | |||
| dd516fb873 | |||
| f8680503ef | |||
| 23389dcac0 | |||
| 8814d3a22b | |||
| 340e8515f5 | |||
| 22b9a8804d | |||
| 341b97bce2 | |||
| 24f66bbcb4 | |||
| a273c87e41 | |||
| b01471aa55 | |||
| ebb7f0c8a9 | |||
| d581174513 | |||
| 41d5c391e1 | |||
| a8157e7dd6 | |||
| 4e33569851 | |||
| 761795ccbf | |||
| 95e9b8aeef | |||
| dd9de98290 | |||
| aad66f94a3 | |||
| 6ac40b4ab3 | |||
| fce7b4416e | |||
| 42f32e3418 | |||
| 47581f2316 | |||
| c68c621400 | |||
| 61151442cf | |||
| 7b36f93973 | |||
| 5952ce8ed3 | |||
| 3c309bbac8 | |||
| 3e3b133c7b | |||
| 0af2ab53a0 | |||
| 700a453501 | |||
| e45b41c04b | |||
| 86367e9c1f | |||
| 9af9120dff | |||
| dc5e3d86b4 | |||
| 4472578915 | |||
| 9ede6a693d | |||
| 7584a40219 | |||
| 1d4f5de4a0 | |||
| fa92d3bd59 | |||
| d868eeb77d | |||
| 083a1e4074 | |||
| ef5dc58b91 | |||
| c4232e7407 | |||
| 209c0fef57 | |||
| d4bc34dea7 | |||
| 0ce92587bf | |||
| 56de208470 | |||
| 266416226b | |||
| 810f42ea24 | |||
| 21dd5054e6 | |||
| 08e221fe93 | |||
| c7e97d06bd | |||
| 46cc3a449a | |||
| 9e86d83616 | |||
| 2829b7daf6 | |||
| 86ec618327 | |||
| 9a23daf7b3 | |||
| 4e3d6f1773 | |||
| 7045fba9c0 | |||
| 36b4225b18 | |||
| b05e6cf71a | |||
| 7692b910f4 | |||
| 0c70d4c828 | |||
| 11deb955f8 | |||
| 036754ea20 | |||
| 2557fcf628 | |||
| 4bee80edc5 | |||
| d1c4a0a415 | |||
| f92a02cfcb | |||
| ff5c402750 | |||
| 2151035afd | |||
| cc11c43240 | |||
| 7339325cc9 | |||
| 3c2a0c9bf3 | |||
| 98d767988e | |||
| 7b2a30e5b7 | |||
| 455012de4c | |||
| ab8a6b80e4 | |||
| acd1b165f8 | |||
| fe1dff0191 | |||
| 08bba92b2e | |||
| 7e6be53237 | |||
| c6c9f400d3 | |||
| 2b35e5fb91 | |||
| e7dd186349 | |||
| 0091f5c09b | |||
| c33e2fc689 | |||
| df055cefd4 | |||
| 65f9a745a3 | |||
| a51ed6e6b3 | |||
| aaf5fde92e | |||
| 0389f1e984 | |||
| 84ceb11584 | |||
| 0426c8e115 | |||
| a9ca4e7366 | |||
| e4d1118124 | |||
| f2efa53fdf | |||
| 23db38d0e9 | |||
| 194acb078c | |||
| c30ac2d061 | |||
| 621393e67a | |||
| 2386b935aa | |||
| 0071fa515a | |||
| 154059126c | |||
| 4c048e4695 | |||
| 4ce3413cdf | |||
| 33d8ecdc8b | |||
| 193daff617 | |||
| 9f542c30b3 | |||
| a053472558 | |||
| 14fe3e6150 | |||
| df494a3def | |||
| ff7c7932e1 | |||
| 9ea44f0a89 | |||
| ce010f1e56 | |||
| 2a8d43c42c | |||
| ef412c36c6 | |||
| 8c4ab5bf03 | |||
| e0095cd446 | |||
| ea3f5cea1f | |||
| a3c0523fbd | |||
| c819368445 | |||
| 287f08dc7b | |||
| 5ce9d21dff | |||
| d4df21c4be | |||
| 95e76c3ab9 | |||
| 18476ec7f3 | |||
| aeeae7365f | |||
| 9953ce1b39 | |||
| d7adec3d54 | |||
| e0e9892ff0 | |||
| e8f66abf17 | |||
| 560983c6b8 | |||
| 07291022b4 | |||
| 6d94871683 | |||
| 774db8e997 | |||
| 31d49a7bbc | |||
| 542ae6b44a | |||
| c1a92bf1f3 | |||
| a8abc8e379 | |||
| 4f64d044ac | |||
| 1f4d973a79 | |||
| bfd5b3c1c7 | |||
| 8866a34f22 | |||
| 669d88fbb1 | |||
| 7470fb0786 | |||
| b6fca3916e | |||
| 5373c7ee9e | |||
| f708d9cb4d | |||
| f1f055d19d | |||
| e175de5365 | |||
| 31befd424b | |||
| 394b24ddfe | |||
| f184ce5386 | |||
| b7d725254d | |||
| c9f9cf89e8 | |||
| fb6d2b8de2 | |||
| ddca950f65 | |||
| 96756d63d4 | |||
| a5b3c9beb4 | |||
| 1974974071 | |||
| 4225643bc0 | |||
| 4b7bb1533e | |||
| d3fdbf4dae | |||
| 5d0fb18ea1 | |||
| 45708ee287 | |||
| 116a0f1a02 | |||
| 2554280e50 | |||
| 748fcce3f5 | |||
| 5b2733bb3c | |||
| 115a48fc1a | |||
| 06d927f8bb | |||
| de08d3726e | |||
| 4dbfeefe53 | |||
| eb823a5ecd | |||
| 8f7949808a | |||
| 5860c3fe50 | |||
| dbfa4ecf65 | |||
| 1320eac800 | |||
| 81e4ad2ce2 | |||
| 736d91f38e | |||
| a1d99b544a | |||
| ed530f737c | |||
| 0899a2a87b | |||
| 3bdbb3d513 | |||
| e22ec57e16 | |||
| 9b06797efa | |||
| 96ba8e8e29 | |||
| 6e9e270683 | |||
| 53a6192858 | |||
| 2a2efefac3 | |||
| a7cac5c77b | |||
| 1575696a8b | |||
| 555eb44f00 | |||
| 0b555b30d2 | |||
| 06f14774f7 | |||
| 11ddcb0256 | |||
| 0d77ff5270 | |||
| c75fa70d23 | |||
| 6d8ffc27cc | |||
| 531327578f | |||
| 6a47e68b78 | |||
| 5ec10018d9 | |||
| ec0094ef33 | |||
| a2c2cf65a1 | |||
| cfd63392e7 | |||
| 0891df5d6c | |||
| cf95c9151c | |||
| 71de39ea7c | |||
| c487643d76 | |||
| 6dbfb3afb3 | |||
| aa3a362391 | |||
| a2cf95038a | |||
| 96f3ee2f3a | |||
| 1659498c88 | |||
| a992844e93 | |||
| 59b665758d | |||
| 4dc8a27e82 | |||
| e83bdcc820 | |||
| 7ea3d63aa0 | |||
| bd51363532 | |||
| d4ccc0dff9 | |||
| 66fe2331ef | |||
| b7c4f1a1f0 | |||
| c2cf8cd7dd | |||
| efcaf14fbb | |||
| bc884c9dc4 | |||
| c340f496f2 | |||
| b8aefe7df0 | |||
| 8061e22480 | |||
| aa289fd796 | |||
| b911b68aec | |||
| 80af847f99 | |||
| 4d149e8a78 | |||
| c41a4607d1 | |||
| 148f8361be | |||
| d879bf9f89 | |||
| 81399ffd19 | |||
| 21cfd03301 | |||
| 1697e49e65 | |||
| 51211dc527 | |||
| 806975c4b9 | |||
| e3c23072a5 | |||
| d34d3312ba | |||
| 845402ea9c | |||
| e5a59d0bea | |||
| 3bb4243850 | |||
| 094bbe6913 | |||
| 0f6cc7326c | |||
| cc90c92dec | |||
| e81fe60218 | |||
| aa97ff7f16 | |||
| 2168b3b7bd | |||
| cb21f9bc07 | |||
| f930897240 | |||
| 93c47498ef | |||
| 2545769f67 | |||
| 35a4d87a65 | |||
| c7183a58ea | |||
| 5a44a8e325 | |||
| f5e02d1413 | |||
| 1964def3ca | |||
| cc2017518b | |||
| 97393eb173 | |||
| 4c04bc7cb5 | |||
| 005c17084b | |||
| 858e57351e | |||
| 9be392e2f1 | |||
| f4c3ceb693 | |||
| 32c6ea9363 | |||
| ad8131114d | |||
| fe0be0cdae | |||
| 35221ed772 | |||
| 37315e941a | |||
| 11f017e715 | |||
| c63f1cfa88 | |||
| 5fe85e71bb | |||
| f8f3519a68 | |||
| bad6931a84 | |||
| 158f11d58c | |||
| 794154f6f5 | |||
| c06929e1c7 | |||
| 1a587dea7d | |||
| 55df9a42e6 | |||
| be8b325a83 | |||
| 2e192034f8 | |||
| ce8e104b0b | |||
| e48050af4b | |||
| d63961d840 | |||
| edf0804b0e | |||
| aaa3d5c443 | |||
| 2008081d9a | |||
| e546971ada | |||
| 8dc77735a5 | |||
| cc7171060f | |||
| 67a7e713b2 | |||
| 3abe5edd21 | |||
| 34aed93b7b | |||
| 6fd4d8b1ba | |||
| 2d0b262351 | |||
| a4f669e549 | |||
| dc92a8fd27 | |||
| 3bb0901e3e | |||
| 2d68d344bd | |||
| ddf92ab8fb | |||
| 9a6fc58949 | |||
| 765b5c2f5c | |||
| 5616367a03 | |||
| 6cf9875472 | |||
| 5f8fdf233f | |||
| be090a3a81 | |||
| 1da47d9271 | |||
| 4917cd19a2 | |||
| e7ad71b51d | |||
| e87faa167c | |||
| 13ed4d3a6c | |||
| 27b9709b78 | |||
| 2d988ac445 | |||
| 6da7e18f50 | |||
| a3c5c334f1 | |||
| 09b8c4482f | |||
| 680c73f15b | |||
| 89e3650d77 | |||
| bcc3b43dc1 | |||
| 5cfcba5272 | |||
| 2416d91748 | |||
| 7d90c319f0 | |||
| b71034765c | |||
| f82b97e97b | |||
| ba5b27d03c | |||
| 98ab5399f2 | |||
| 2d7614622f | |||
| 1c7b36f233 | |||
| d42890f214 | |||
| c607cbc9d7 | |||
| 83be26e196 | |||
| 2f026218ff | |||
| 7615173924 | |||
| 171c3083da | |||
| fe317f9231 | |||
| 5363c33888 | |||
| 795d2c371e | |||
| 1dfa3f8d79 | |||
| 2626165629 | |||
| 0a17588db8 | |||
| 33001e6c6d | |||
| e224bfd19f | |||
| c1d5c18724 | |||
| 557a120178 | |||
| cea4c0c01d | |||
| 8b1d3a0c5d | |||
| c5fc86d148 | |||
| 8cec717315 | |||
| 7905cd8bd3 | |||
| 0337034d28 | |||
| 88a40e0052 | |||
| c9d2390d9a | |||
| 0055a5dd82 | |||
| 4ff0893a8b | |||
| 791d098800 | |||
| 8e87d0e303 | |||
| eaaa18468a | |||
| 871cd45f53 | |||
| 39fd9ce462 | |||
| 4c68ec6f82 | |||
| be71eb274b | |||
| 008e2c01c3 | |||
| a1e51757b1 | |||
| d5169e85fa | |||
| cfc243949d | |||
| a3ff0a61a6 | |||
| 456ad2af8e | |||
| 7b8e234683 | |||
| 95b7a79c64 | |||
| 4a5e180479 | |||
| d6098a0b6d | |||
| 541a6e18e6 | |||
| 11497f9b37 | |||
| 6b14111b24 | |||
| f27bb6effe | |||
| 6ebec7e0e5 | |||
| ffb2ea2be3 | |||
| 89bb7c76de | |||
| a867e72fd3 | |||
| a03c55d15f | |||
| 1a75d95d47 | |||
| fe5f48c655 | |||
| a4396ee766 | |||
| 977f3c7dcc | |||
| 8ba52a2b1c | |||
| 214c278fc5 | |||
| f521945766 | |||
| 12318fbfc2 | |||
| 56428eb605 | |||
| 88319bfa57 | |||
| cce4e426c3 | |||
| 7782ab5479 | |||
| 7e5bd0ca83 | |||
| 051d5479fb | |||
| deb41d2158 | |||
| ea6df7f617 | |||
| fcf41bf00c | |||
| 7c3fbda01e | |||
| d0712c7186 | |||
| e1a5e73106 | |||
| 1282f5c308 | |||
| b0eaf23d2c | |||
| 0ab70827e5 | |||
| 691815fd41 | |||
| 3c3b56d1ae | |||
| 116192befd | |||
| b6a96828ec | |||
| c4c611d778 | |||
| 290c08d6d5 | |||
| eb7411b310 | |||
| bea837299e | |||
| acbdc0ed4c | |||
| bec1d913c5 | |||
| b957505bfc | |||
| 3c69fffb19 | |||
| 1c7e7c5269 | |||
| 92525db14f | |||
| 01d965fe25 | |||
| d2bc6f9509 | |||
| 88b861737c | |||
| 0f87bd8206 | |||
| 1fe168d331 | |||
| a7925622ed | |||
| 3675429385 | |||
| 44ab38cb24 | |||
| 1a146ada48 | |||
| 2ed2b4fe71 | |||
| 4ce30b3153 | |||
| 4ce2251717 | |||
| c6e8d2a585 | |||
| 90e9c21320 | |||
| 236fbc7864 | |||
| ae75acb0b9 | |||
| 11068fe338 | |||
| 3c333c6b59 | |||
| ccbb253d2c | |||
| 7c542418e6 | |||
| d77f57c68e | |||
| 151244ae79 | |||
| e50a90cc9f | |||
| 5f5ab10b35 | |||
| 0e791c7dfd | |||
| 07be50b365 | |||
| fb422692c8 | |||
| 1a8be005cf | |||
| 9dcb5b103c | |||
| 091824910b | |||
| ea6d5918ba | |||
| a452d29bab | |||
| fb803ac35d | |||
| eec4b37827 | |||
| d1565ec1e6 | |||
| 521b0ab58d | |||
| 04bbfc04ad | |||
| f0167b812a | |||
| 0b8a701423 | |||
| aa7e80fc8c | |||
| f48c7d6af5 | |||
| 977014b86a | |||
| 43e13ef8f3 | |||
| daba504981 | |||
| f8a2cce202 | |||
| b786e129bd | |||
| b82073dd57 | |||
| d4d9fdff1a | |||
| b3b14e87ec | |||
| 6bc31105bf | |||
| 73e3ebd1f0 | |||
| 32f216a594 | |||
| 296ebf5253 | |||
| e072870fd3 | |||
| af4475fe46 | |||
| eb3104367a | |||
| 90f9ebd25e | |||
| f2554bc968 | |||
| 53a78f38e5 | |||
| b476cb825c | |||
| ec21574be5 | |||
| b552665dd9 | |||
| 48b630696f | |||
| 8c94ef6e54 | |||
| bdcadfd100 | |||
| 90e83d77a7 | |||
| 2e80363bac | |||
| b6f80eabb4 | |||
| c93e52089b | |||
| 76ba3947f5 | |||
| 50cb09aac2 | |||
| dac1aaa7f3 | |||
| eed603bbcd | |||
| 1d62f6f736 | |||
| 799cf47316 | |||
| 03935a91da | |||
| 37baa9235a | |||
| b4b2b60935 | |||
| a11c7535d8 | |||
| e8d6dd333c | |||
| 9633d92c17 | |||
| afd5a5b23e | |||
| 15481c0a59 | |||
| 32b37b8823 | |||
| f3ab214752 | |||
| 4a59b30f1b | |||
| 87d6960356 | |||
| aff893e690 | |||
| 486eb259ea | |||
| 8164b496f8 | |||
| fda23f350a | |||
| 711c597065 | |||
| 80a396dc12 | |||
| 2b70f12d44 | |||
| c3bcc2d90c | |||
| 88e5a51f01 | |||
| 8b8af680bc | |||
| 8a57a52949 | |||
| 720eda51c3 | |||
| 01a701007e | |||
| ec5368224f | |||
| 56e9488d3e | |||
| 23ec2fc48e | |||
| fbcb19fdbb | |||
| d9f3c9d012 | |||
| 91e236c42c | |||
| 03ab88b5bb | |||
| 42ef06aae1 | |||
| 691dfb45f0 | |||
| 760816b7f0 | |||
| fc04c6e41e | |||
| 99e67d528f | |||
| c113061742 | |||
| 174ff00a6d | |||
| 84a2d4266d | |||
| 64cba98c3d | |||
| 1aed2a6b3c | |||
| 4e1a956830 | |||
| 747a39b440 | |||
| 1b49515d9f | |||
| dabb85c466 | |||
| b74811b3f4 | |||
| 79eaf895f7 | |||
| 994c85a05a | |||
| 0faacccf6d | |||
| 89d27bdf5f | |||
| 76853db32c | |||
| 002c50e29d | |||
| d2efb2ab78 | |||
| 8bb78959ef | |||
| 48f0dc6a00 | |||
| 27aee31adf | |||
| 24aaefb927 | |||
| 7daec25454 | |||
| 8309b13db0 | |||
| 5e95985d19 | |||
| fefe325ece | |||
| b717abf639 | |||
| 32ae12b45a | |||
| 48b74ebb76 | |||
| 197d0b3258 | |||
| 2a1bc30f19 | |||
| 6dcbd98769 | |||
| 1be025ed86 | |||
| 6fa87a16ab | |||
| a450c24739 | |||
| 56d19039e3 | |||
| 8c19b695ea | |||
| 5831c56380 | |||
| 2b8124071a | |||
| 9fceec35cc | |||
| 3dde1b80b9 | |||
| 08dd8f87ff | |||
| 1c3cf44122 | |||
| 868454c011 | |||
| edcfcd8850 | |||
| 33bbf9fc53 | |||
| 286e90e299 | |||
| ae2933eea5 | |||
| 5aa7758795 | |||
| 6c47baf0de | |||
| 51018177cd | |||
| be9719f5ca | |||
| 9193ff7252 | |||
| 53c3019b73 | |||
| 6bbf3d3917 | |||
| 47a15011f6 | |||
| f26f6dfe5e | |||
| 1745a4fd39 | |||
| fcaac3787b | |||
| 2d3adce59e | |||
| 7e6607adcf | |||
| 46c62d7cdb | |||
| c521892ca2 | |||
| d4a0dd1527 | |||
| 527cdd3304 | |||
| 31e836cfb4 | |||
| 86f123b6b0 | |||
| fea8e3a5c9 | |||
| 3137904055 | |||
| f89dee7668 | |||
| c042d1afe5 | |||
| 42fc72bd4f | |||
| f72c128ba6 | |||
| 3ceeaa739f | |||
| 7a2e58944e | |||
| b9855b289f | |||
| c7a8fd9309 | |||
| daa6b31c5d | |||
| cd411e1f14 | |||
| 7a168069f7 | |||
| 97d2e08286 | |||
| f440a3ca17 | |||
| 81bdb551ab | |||
| 5fdad744d3 | |||
| ab6f408db4 | |||
| cdf8b9c038 | |||
| 918fb41a7e | |||
| 1d8e39e55f | |||
| b0b65e550c | |||
| a1527a7e31 | |||
| 2ca3f86fc9 | |||
| d80bcd1a94 | |||
| 60a06138d2 | |||
| e2001e82dd | |||
| 92f4cb0ffa | |||
| 9f00e8aef0 | |||
| 638d200a27 | |||
| 59717d6e92 | |||
| 009795f7dc | |||
| a0792a4b93 | |||
| 8534b6e154 | |||
| 7910a68028 | |||
| d44ee9874b | |||
| 8cd86581ff | |||
| 0a5b6a41b0 | |||
| 7bac468151 | |||
| b375579dcd | |||
| d3d1ca0169 | |||
| 2b0572af10 | |||
| ed6365b292 | |||
| e3b1e0773f | |||
| 3d38068b90 | |||
| a4a2497f33 | |||
| 1ef2fb89af | |||
| 902d777574 | |||
| 3bd0caade1 | |||
| 301cb4bbdc | |||
| f0338ffdda | |||
| 2100528cbe | |||
| ae4f681f73 | |||
| 53d7624fc4 | |||
| 951b2b76a6 | |||
| e9b4b6560e | |||
| cabffdc549 | |||
| 7ea60742d6 | |||
| 358c834427 | |||
| 5d6655cbc7 | |||
| 4d122d77dc | |||
| d4e863ed3b | |||
| b6ccf505e7 | |||
| ce9842983b | |||
| eb19b26a6e | |||
| 06602d0e39 | |||
| 00279b63de | |||
| 6418353119 | |||
| 3afbbe51d2 | |||
| ddb767674b | |||
| 5575ea8296 | |||
| e8a7a032aa | |||
| f23e419685 | |||
| b87e8136af | |||
| 473d0ae888 | |||
| 94f5137684 | |||
| 4e75945435 | |||
| e31019eb67 | |||
| c505630a20 | |||
| 860fa81f73 | |||
| 847b5d226e | |||
| ad77d09916 | |||
| 9b12cae8b4 | |||
| 84a8c5dfeb | |||
| 12a7b8a79e | |||
| ef04402ed8 | |||
| 45fed747c2 | |||
| 85229fc865 | |||
| 916f854905 | |||
| 1915541d20 | |||
| e37b84a95a | |||
| bcb2a37ce0 | |||
| 018d6847a0 | |||
| 35918265d0 | |||
| 4e48c35425 | |||
| d985fe9240 | |||
| 7c00021aaa | |||
| d2dc8b519f | |||
| 25fe025354 | |||
| 1aae710286 | |||
| 38e8130793 | |||
| 7e94388074 | |||
| ef835e43b8 | |||
| e5d0eb13bf | |||
| 4fcf23319f | |||
| 99154afdb1 | |||
| e83eb68fe1 | |||
| e4a4a33920 | |||
| e8848c9701 | |||
| cfd8044cb3 | |||
| 2d7866570e | |||
| eb0ed7fe6b | |||
| 67733c4449 | |||
| b3940cdc45 | |||
| 94e75aebb0 | |||
| ff203b2a13 | |||
| 10616df494 | |||
| 1dc6d4935e | |||
| 04088af2b6 | |||
| c477173096 | |||
| 2f4093f958 | |||
| 95fc51da76 | |||
| 50e629c02c | |||
| 4a178fca58 | |||
| aff5b238d9 | |||
| 315b6060b0 | |||
| b137197586 | |||
| d1d9db3ed8 | |||
| 7d50491a99 | |||
| 371b4b6079 | |||
| d714eca4ee | |||
| 736b50c6fa | |||
| 64b96633c2 | |||
| 8eae997053 | |||
| fee56fc120 | |||
| d1f87d5467 | |||
| 7539404c27 | |||
| cd3df94a4b | |||
| 0efafada1d | |||
| 71ddc6384a | |||
| 4b402f8ea5 | |||
| c39fa80e09 | |||
| c89ad8c7da | |||
| 6dfe46d9bd | |||
| 1de51ecf77 | |||
| 8a55f18c95 | |||
| 7a1976cfc1 | |||
| 78c6401453 | |||
| e12838d93e | |||
| 7aa5b71a6a | |||
| 2ef3af5e1b | |||
| d399f7c6b7 | |||
| beb7f75368 | |||
| dc6fdc2a98 | |||
| 45be17c25a | |||
| 4f11b19b3d | |||
| f646cf4372 | |||
| 538859f60a | |||
| 75da994faf | |||
| 99827df89a | |||
| 054a900779 | |||
| e800f8b93d | |||
| 7163697362 | |||
| 29600711ae | |||
| 1d0ee61ba7 | |||
| fb909ace1a | |||
| 6ae5dbacf4 | |||
| 52bffa7f9c | |||
| e9a502ccec | |||
| f3d4eed19e | |||
| d260298ab2 | |||
| c7d0cff300 | |||
| 631deeb094 | |||
| 953a49f9ae | |||
| 2e94a7c3f1 | |||
| 68f10e1502 | |||
| 625a57c6de | |||
| 876f059459 | |||
| b5a61753a5 | |||
| 4348b9bff9 | |||
| 28ee1a7781 | |||
| d40119d617 | |||
| 774533911c | |||
| 7597b1b29c | |||
| bd8a2736d4 | |||
| aafb4b6147 | |||
| 2066ebcc11 | |||
| 795ebc0ac1 | |||
| 7612cf92c0 | |||
| db1f4d126a | |||
| b15820dd10 | |||
| f8f7884278 | |||
| 3318de7f6d | |||
| 726bf40d58 | |||
| 3860884db3 | |||
| 5784e3103d | |||
| 2a1d1d6e10 | |||
| 485643863f | |||
| c03777cfe6 | |||
| be442ff1be | |||
| 95e7910f69 | |||
| d5592ed2eb | |||
| a29806ef9e | |||
| 3a9d79dcd3 | |||
| b9c2e312e3 | |||
| f1db51df1a | |||
| 312a58ddce | |||
| 1e71708aba | |||
| ad6e00571e | |||
| c8d05951b0 | |||
| 0f8c3b3c83 | |||
| 25071dfec6 | |||
| cc28f5a92b | |||
| a57a7041ac | |||
| 17b90c3241 | |||
| 90089f91c6 | |||
| 37dd2a5607 | |||
| bc70130123 | |||
| 8d53141d5a | |||
| 0ffc62fbf3 | |||
| 4350e3df82 | |||
| 2ad601ed34 | |||
| f9e8d6d478 | |||
| 65cc5bf4c1 | |||
| 73e5985777 | |||
| de14e92720 | |||
| 09ad47d11b | |||
| debe63c1af | |||
| 6f2eb6c54a | |||
| 56ca431965 | |||
| 74e8b80fb4 | |||
| 7ecfe372ad | |||
| b017ab7596 | |||
| bd3905fe76 | |||
| 0c8048c14d | |||
| 77f72ecdfa | |||
| 723701f1b8 | |||
| bf96c63f7d | |||
| cef8251458 | |||
| 547981f32c | |||
| a3cc0245ca | |||
| 5d934a2baa | |||
| 4409a35441 | |||
| f637ef8016 | |||
| 5e2cefee12 | |||
| 67b01dcd0f | |||
| bc0efd7389 | |||
| 4b4e6fa7e2 | |||
| 17a0face6e | |||
| a3b2860789 | |||
| f8f22257b4 | |||
| 8284ed0cdb | |||
| d995e010cf | |||
| fcfc070825 | |||
| 55552770a6 | |||
| 350df4eee5 | |||
| 04a273c720 | |||
| 61641acf56 | |||
| 10035662dd | |||
| 056df7758b | |||
| 0522adac63 | |||
| 1f781e5cab | |||
| a30fd7cab4 | |||
| 94ca2423e9 | |||
| 2938c1fd0b | |||
| ab8710bd1e | |||
| a791abe23e | |||
| e70122f354 | |||
| 96fdced471 | |||
| 1c95f6cc3f | |||
| 0f03e412c3 | |||
| 6ca3e2a6db | |||
| 079446a4b6 | |||
| e706e568bd | |||
| 526a2a6a80 | |||
| 3e7a2c1b83 | |||
| 9bb04d3ffc | |||
| 7c4d45ab32 | |||
| dafec70097 | |||
| eca1d47d06 | |||
| 9671be1734 | |||
| 644f32d11c | |||
| 1626dd85f4 | |||
| 3c0103b4da | |||
| f9c4b6a98f | |||
| b01e93161e | |||
| ffbd3e50c5 | |||
| 98c4a1d110 | |||
| e2c4bcb217 | |||
| 7e60b6888e | |||
| bbf8ebcb52 | |||
| c1936b871e | |||
| 31d67b5e62 | |||
| 683e168ca1 | |||
| cbc0b4837f | |||
| 02ff6c00ef | |||
| 78f664e892 | |||
| d745265048 | |||
| 917991feab | |||
| 4877ab4426 | |||
| 48c5100d3b | |||
| 72caf7525e | |||
| 242d91bc17 | |||
| afb22ce7d5 | |||
| e45a670ff5 | |||
| 45042aa5e0 | |||
| 07aec0730f | |||
| 7e6da3edb3 | |||
| 4f0eafa92d | |||
| de75eded09 | |||
| f2b4bea4de | |||
| 4f28b2506d | |||
| 281da8ed77 | |||
| bdf4258895 | |||
| 385d9b9df7 | |||
| 0e40df4fa3 | |||
| b0c132f73a | |||
| 4fd802bf3d | |||
| e461040e17 | |||
| 43b574011e | |||
| 9bae914b51 | |||
| 945070bdff | |||
| d2b4afe1be | |||
| 3760f8fafe | |||
| b9e5cefddf | |||
| 9f5f757ccf | |||
| 3a147d7f62 | |||
| 9f1d14e5a7 | |||
| bce71f3797 | |||
| 253dca730d | |||
| e0f33a56ec | |||
| 2287931e9c | |||
| 1f7a498606 |
@@ -22,7 +22,7 @@ jobs:
|
||||
- uses: actions/checkout@v2 # https://github.com/actions/checkout
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
name: ci-blenderbim-daily
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-blenderbim-daily.yml'
|
||||
- 'src/blenderbim/**'
|
||||
- 'src/ifcopenshell-python/ifcopenshell/**'
|
||||
- 'src/bcf/bcf/**'
|
||||
- 'src/ifcclash/ifcclash/**'
|
||||
- 'src/ifccobie/**'
|
||||
- 'src/ifcdiff/**'
|
||||
- 'src/ifccsv/**'
|
||||
- 'src/ifcpatch/ifcpatch/**'
|
||||
- 'src/ifc4d/ifc4d/**'
|
||||
- 'src/ifc5d/ifc5d/**'
|
||||
- 'src/ifccityjson/**'
|
||||
branches:
|
||||
- v0.8.0
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
short_name: win,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux,
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos,
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
- name: Get current version
|
||||
id: version
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/blenderbim && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
filepath=$(ls src/blenderbim/dist/blenderbim_*.zip)
|
||||
echo "filepath=$filepath" >> $GITHUB_OUTPUT
|
||||
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
|
||||
- name: Upload zip file to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.find_zip.outputs.filepath }}
|
||||
asset_name: ${{ steps.find_zip.outputs.filename }}
|
||||
release_name: "blenderbim-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}} (unstable)"
|
||||
tag: "blenderbim-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish-blenderbim-chocolatey package
|
||||
name: ci-bonsai-choco
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -9,11 +9,12 @@ on:
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "30 0 * * *" # 30min past utc midnight
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: blenderbim
|
||||
name: bonsai
|
||||
choco_version: 1.1.0
|
||||
CHOCO_TOKEN: ${{ secrets.CHOCO_TOKEN }}
|
||||
|
||||
@@ -41,5 +42,6 @@ jobs:
|
||||
- name: Check in release tags if we should do a choco release and perform the release if needed
|
||||
id: do_choco_release
|
||||
run: |
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
|
||||
pip install pygithub
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/bonsai/ &&
|
||||
python3 choco_release.py
|
||||
@@ -0,0 +1,158 @@
|
||||
name: ci-bonsai-daily
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-bonsai-daily.yml'
|
||||
- 'src/bonsai/**'
|
||||
- 'src/ifcopenshell-python/ifcopenshell/**'
|
||||
- 'src/bcf/bcf/**'
|
||||
- 'src/ifcclash/ifcclash/**'
|
||||
- 'src/ifccobie/**'
|
||||
- 'src/ifcdiff/**'
|
||||
- 'src/ifccsv/**'
|
||||
- 'src/ifcpatch/ifcpatch/**'
|
||||
- 'src/ifc4d/ifc4d/**'
|
||||
- 'src/ifc5d/ifc5d/**'
|
||||
- 'src/ifccityjson/**'
|
||||
branches:
|
||||
- v0.8.0
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
short_name: win,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux,
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos,
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
- name: Get current version
|
||||
id: version
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Get current date
|
||||
id: date
|
||||
# Include hours and minutes to release tag
|
||||
# to avoid possibility of unstable repo's index.json
|
||||
# pointing to the new file when index.json itself wasn't yet updated.
|
||||
run: echo "date=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/bonsai && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
filepath=$(ls src/bonsai/dist/bonsai_*.zip)
|
||||
echo "filepath=$filepath" >> $GITHUB_OUTPUT
|
||||
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
|
||||
- name: Upload zip file to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.find_zip.outputs.filepath }}
|
||||
asset_name: ${{ steps.find_zip.outputs.filename }}
|
||||
release_name: "bonsai-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}} (unstable)"
|
||||
tag: "bonsai-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
body: "See README in https://github.com/IfcOpenShell/bonsai_unstable_repo/ on how to setup autoupdates for daily Bonsai builds."
|
||||
|
||||
update-extensions-repo-and-run-tests:
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout bonsai_unstable_repo repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: IfcOpenShell/bonsai_unstable_repo
|
||||
token: ${{ secrets.IOS_TO_BLENDER_REPO }}
|
||||
path: bonsai_unstable_repo
|
||||
|
||||
- name: Update index.json on extensions repo
|
||||
run: |
|
||||
set -x -e
|
||||
|
||||
# Download Blender.
|
||||
wget -q -O blender.tar.xz https://ftp.nluug.nl/pub/graphics/blender/release/Blender4.2/blender-4.2.0-linux-x64.tar.xz
|
||||
tar -xf blender.tar.xz
|
||||
|
||||
# Setup Blender.
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
cd bonsai_unstable_repo
|
||||
pip install -r requirements.txt
|
||||
python setup_extensions_repo.py --last-tag
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
git add index.json
|
||||
git add readme.md
|
||||
git commit -m "Update index.json"
|
||||
git push
|
||||
- name: Run bonsai tests
|
||||
run: |
|
||||
set -x -e
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
bonsai_zip="$(pwd)/$(ls bonsai_unstable_repo/bonsai_py311*-linux-x64.zip)"
|
||||
blender --version
|
||||
blender --command extension install-file -r user_default -e $bonsai_zip
|
||||
blender --command extension list
|
||||
|
||||
wget -q -O sverchok.zip https://github.com/nortikin/sverchok/archive/refs/heads/master.zip
|
||||
# ifcsverchok expecting sverchok to be named "sverchok" and not "sverchok-master".
|
||||
unzip -q sverchok.zip
|
||||
mv sverchok-master sverchok
|
||||
zip -q -r sverchok.zip sverchok
|
||||
rm -r sverchok
|
||||
blender --command extension install-file -r user_default sverchok.zip
|
||||
|
||||
git clone https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
|
||||
cd IfcOpenShell/src/ifcsverchok
|
||||
make dist
|
||||
sverchok_zip="$(pwd)/dist/$(ls dist)"
|
||||
blender --command extension install-file -r user_default $sverchok_zip
|
||||
|
||||
# Install Sun Position extension.
|
||||
blender --online-mode --command extension sync
|
||||
blender --online-mode --background --python-expr "import bpy; \
|
||||
bpy.ops.extensions.package_install(repo_index=0, pkg_id='sun_position'); \
|
||||
bpy.ops.preferences.addon_enable(module='bl_ext.blender_org.sun_position'); bpy.ops.wm.save_userpref()"
|
||||
|
||||
cd ../bonsai
|
||||
pip install pytest-blender
|
||||
blender --background --python scripts/setup_pytest.py
|
||||
blender --python-expr "import bonsai; print(bonsai.bbim_semver); import ifcopenshell; print(ifcopenshell.version)" --background
|
||||
make test
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ci-blenderbim
|
||||
name: ci-bonsai
|
||||
|
||||
# Differences from ci-blenderbim-daily.yml:
|
||||
# Differences from ci-bonsai-daily.yml:
|
||||
# - make has IS_STABLE=TRUE
|
||||
# - action is never triggered and executed only manually
|
||||
# - doesn't add a current date to the release and tag
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py310, py311, py312]
|
||||
pyver: [py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
@@ -32,15 +32,15 @@ jobs:
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux
|
||||
short_name: linux,
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos
|
||||
short_name: macos,
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
short_name: macosm1
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -53,12 +53,11 @@ jobs:
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/blenderbim &&
|
||||
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
|
||||
cd src/bonsai && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
filepath=$(ls src/blenderbim/dist/blenderbim_*.zip)
|
||||
filepath=$(ls src/bonsai/dist/bonsai_*.zip)
|
||||
echo "filepath=$filepath" >> $GITHUB_OUTPUT
|
||||
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
|
||||
- name: Upload zip file to release
|
||||
@@ -67,6 +66,6 @@ jobs:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.find_zip.outputs.filepath }}
|
||||
asset_name: ${{ steps.find_zip.outputs.filename }}
|
||||
release_name: "blenderbim-${{steps.version.outputs.version}}"
|
||||
tag: "blenderbim-${{steps.version.outputs.version}}"
|
||||
release_name: "bonsai-${{steps.version.outputs.version}}"
|
||||
tag: "bonsai-${{steps.version.outputs.version}}"
|
||||
overwrite: true
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
lfs: true
|
||||
|
||||
- name: Download
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
@@ -116,6 +116,8 @@ jobs:
|
||||
with:
|
||||
context: artifacts
|
||||
repository: aecgeeks/ifcopenshell
|
||||
tags: aecgeeks/ifcopenshell:latest
|
||||
# Since the dispatch is set to `tag`, `github.ref_name` should evaluate to the pushed tag
|
||||
# On a workflow dispatch, `ref_name` will take on the value from the dispatch payload
|
||||
tags: aecgeeks/ifcopenshell:${{ github.ref_name }}${{ github.ref_name == github.event.repository.default_branch && ',aecgeeks/ifcopenshell:latest' }}
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Run IfcConvert on Sample files
|
||||
run: |
|
||||
(find test/input src/blenderbim/test/files -name '*.ifc' | while read i; do \
|
||||
(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; \
|
||||
@@ -130,5 +130,7 @@ jobs:
|
||||
cd ../bcf && make test
|
||||
pip install requests
|
||||
cd ../bsdd && make test
|
||||
pip install deepdiff
|
||||
cd ../ifcdiff && make test
|
||||
cd ../ifcpatch && make test
|
||||
cd ../ifctester && make test
|
||||
|
||||
@@ -8,12 +8,12 @@ on:
|
||||
jobs:
|
||||
activate:
|
||||
if: github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: echo ok go
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -52,8 +52,8 @@ jobs:
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.10 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.10.so \
|
||||
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
|
||||
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
|
||||
-DGLTF_SUPPORT=On \
|
||||
|
||||
@@ -73,19 +73,16 @@ src/ifcopenshell-python/test/build
|
||||
# mypy cache
|
||||
.mypy_cache
|
||||
|
||||
# blenderbim libs
|
||||
src/blenderbim/blenderbim/libs
|
||||
# bonsai i18n
|
||||
src/bonsai/bonsai/translations.py
|
||||
|
||||
# blenderbim i18n
|
||||
src/blenderbim/blenderbim/translations.py
|
||||
# bonsai test temp files
|
||||
src/bonsai/test/files/temp
|
||||
src/bonsai/test/files/basic.ifc.cache.blend
|
||||
src/bonsai/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
src/blenderbim/test/files/basic.ifc.cache.blend
|
||||
src/blenderbim/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
src/blenderbim/drawings
|
||||
src/blenderbim/layouts
|
||||
src/bonsai/drawings
|
||||
src/bonsai/layouts
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
@@ -100,4 +97,4 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
.cache
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- mode: Dockerfile -*-
|
||||
|
||||
FROM ubuntu:focal
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG CHANNEL
|
||||
ENV CHANNEL=${CHANNEL:-latest}
|
||||
@@ -23,7 +23,7 @@ RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" |
|
||||
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed multiverse" | tee -a /etc/apt/sources.list; \
|
||||
apt-get -qq update; \
|
||||
apt-get -y install tzdata dos2unix rsync; \
|
||||
apt-get -y install python3 libxml2 libpython3.8 \
|
||||
apt-get -y install python3 libxml2 libpython3.10 \
|
||||
libboost-all-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
|
||||
libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
|
||||
@@ -12,7 +12,7 @@ is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application
|
||||
to convert IFC models to other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
to convert IFC models to other formats), Bonsai (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
and many other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
|
||||
For more information, see:
|
||||
@@ -22,10 +22,10 @@ For more information, see:
|
||||
* [IfcOpenShell C++ Installation](https://docs.ifcopenshell.org/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://docs.ifcopenshell.org/ifcopenshell-python/hello_world.html)
|
||||
* [BlenderBIM Add-on Website](https://blenderbim.org)
|
||||
* [BlenderBIM Add-on Documentation](https://docs.blenderbim.org/index.html)
|
||||
* [Add-on Installation](https://docs.blenderbim.org/users/installation.html)
|
||||
* [Exploring an IFC model](https://docs.blenderbim.org/users/exploring_an_ifc_model.html)
|
||||
* [Bonsai Website](https://bonsaibim.org)
|
||||
* [Bonsai Documentation](https://docs.bonsaibim.org/index.html)
|
||||
* [Add-on Installation](https://docs.bonsaibim.org/quickstart/installation.html)
|
||||
* [Exploring an IFC model](https://docs.bonsaibim.org/quickstart/explore_model.html)
|
||||
|
||||
Development is sponsored through your generous donations!
|
||||
|
||||
@@ -36,8 +36,8 @@ 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/) |
|
||||
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://blenderbim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=blenderbim&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| 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/) |
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/COPYING</licenseUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
|
||||
<docsUrl>https://docs.blenderbim.org/</docsUrl>
|
||||
<docsUrl>https://docs.bonsaibim.org/</docsUrl>
|
||||
<!--<mailingListUrl></mailingListUrl>-->
|
||||
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
|
||||
<tags>blender bim blenderbim ifc python opensource foss</tags>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -14,9 +14,11 @@ import os
|
||||
import pathlib
|
||||
import re
|
||||
from urllib import request
|
||||
from github import Github
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def get_repo_tag_names():
|
||||
def get_repo_tag_names() -> list[str]:
|
||||
git_return = os.popen("git tag -l").read()
|
||||
tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name]
|
||||
print(f"{len(tag_names)} tag_names found in repo")
|
||||
@@ -43,7 +45,7 @@ def get_latest_choco_blender_version() -> list:
|
||||
return re.findall(RE_BLENDER_VERSION_MIN_MAJ_PAT, html_txt)
|
||||
|
||||
|
||||
def get_file_sha256_hash(file_path):
|
||||
def get_file_sha256_hash(file_path: str) -> str:
|
||||
BLOCKSIZE = 65536
|
||||
hasher = hashlib.sha256()
|
||||
|
||||
@@ -56,16 +58,29 @@ def get_file_sha256_hash(file_path):
|
||||
return hasher.hexdigest()
|
||||
|
||||
|
||||
def quit_with_error_message(message: str):
|
||||
def quit_with_error_message(message: str) -> NoReturn:
|
||||
print(f"ERROR: {message}")
|
||||
quit(0)
|
||||
|
||||
|
||||
def get_release_zip(tag: str) -> tuple[str, str]:
|
||||
g = Github()
|
||||
repo = g.get_repo("IfcOpenShell/IfcOpenShell")
|
||||
release = repo.get_release(tag)
|
||||
for asset in release.get_assets():
|
||||
asset_name = asset.name
|
||||
if python_version not in asset_name:
|
||||
continue
|
||||
if TARGET_OS not in asset_name:
|
||||
continue
|
||||
return (asset_name, asset.browser_download_url)
|
||||
raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.")
|
||||
|
||||
|
||||
start = datetime.datetime.now()
|
||||
|
||||
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
|
||||
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
URL_IFCOS_RELEASES = "https://github.com/IfcOpenShell/IfcOpenShell/releases/download/"
|
||||
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
|
||||
@@ -79,7 +94,7 @@ os.chdir(BLENDERBIM_DIR)
|
||||
blenderbim_date_yesterday = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime("%y%m%d")
|
||||
should_release = False
|
||||
target_release_tag = ""
|
||||
target_os = "win"
|
||||
TARGET_OS = "windows-x64"
|
||||
|
||||
git_status = os.popen("git status").read()
|
||||
print(git_status)
|
||||
@@ -130,10 +145,7 @@ print(f"{python_version=}")
|
||||
blenderbim_build_version = target_release_tag.replace("blenderbim-", "")
|
||||
|
||||
# url_blenderbim_py3x_win_zip
|
||||
release_zip_file_name = f"{target_release_tag}-{python_version}-{target_os}.zip"
|
||||
|
||||
# download release
|
||||
url_blenderbim_py3x_win_zip = f"{URL_IFCOS_RELEASES}{target_release_tag}/{release_zip_file_name}"
|
||||
release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag)
|
||||
os.popen(f"wget {url_blenderbim_py3x_win_zip} --no-verbose").read()
|
||||
|
||||
# sha256sum_blenderbim_py310_win_zip
|
||||
@@ -27,9 +27,11 @@ 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 OLD)
|
||||
cmake_policy(SET CMP0078 NEW)
|
||||
cmake_policy(SET CMP0086 NEW)
|
||||
if (POLICY CMP0144)
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
@@ -221,7 +223,7 @@ endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
find_path(json_header_path "json.hpp" ${JSON_INCLUDE_DIR} PATH_SUFFIXES "nlohmann")
|
||||
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
|
||||
if(json_header_path)
|
||||
@@ -730,6 +732,10 @@ if(MSVC)
|
||||
# endif()
|
||||
|
||||
add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
|
||||
# See #5158.
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.40)
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
endif()
|
||||
else()
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
@@ -1045,10 +1051,19 @@ if(BUILD_CONVERT)
|
||||
target_include_directories(cityjson_converter PRIVATE ../src)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} cityjson_converter)
|
||||
|
||||
install(TARGETS cityjson_converter
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
)
|
||||
|
||||
add_executable(cityjson_converter_exe ${CITYJSON_CONVERT_FILES})
|
||||
set_target_properties(cityjson_converter_exe PROPERTIES COMPILE_FLAGS "-DCITYJSON_EXECUTABLE")
|
||||
target_include_directories(cityjson_converter_exe PRIVATE ../src)
|
||||
target_link_libraries(cityjson_converter_exe ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
install(TARGETS cityjson_converter_exe
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
# IfcConvert
|
||||
|
||||
@@ -76,14 +76,14 @@ about:
|
||||
For more information, see:
|
||||
|
||||
* [IfcOpenShell Website](http://ifcopenshell.org)
|
||||
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
|
||||
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
|
||||
* [BlenderBIM Add-on Website](https://blenderbim.org)
|
||||
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
|
||||
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
|
||||
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
|
||||
* [IfcOpenShell Documentation](http://bonsaibim.org/docs-python)
|
||||
* [IfcOpenShell C++ Installation](https://bonsaibim.org/docs-python/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://bonsaibim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://bonsaibim.org/docs-python/ifcopenshell-python/hello_world.html)
|
||||
* [Bonsai Website](https://bonsaibim.org)
|
||||
* [Bonsai Documentation](http://bonsaibim.org/docs)
|
||||
* [Add-on Installation](https://bonsaibim.org/docs/users/installation.html)
|
||||
* [Exploring an IFC model](https://bonsaibim.org/docs/users/exploring_an_ifc_model.html)
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
@@ -81,7 +81,7 @@ ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.7.1"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
BOOST_VERSION = "1.80.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
@@ -220,6 +220,7 @@ if "v" in flags:
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
BUILD_STATIC = "shared" not in flags
|
||||
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
|
||||
DISABLE_FLAG = "--disable-shared" if BUILD_STATIC else "--disable-static"
|
||||
@@ -331,7 +332,7 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
if "wasm" in flags:
|
||||
wasm.append("emcmake")
|
||||
|
||||
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
|
||||
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}", f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}"], cwd=cwd)
|
||||
|
||||
|
||||
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
@@ -351,6 +352,8 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
run([git, "pull", clone_url], cwd=target_dir)
|
||||
|
||||
if revision != None:
|
||||
run([git, "reset", "--hard"], cwd=target_dir)
|
||||
run([git, "fetch", "--all"], cwd=target_dir)
|
||||
run([git, "checkout", revision], cwd=target_dir)
|
||||
|
||||
|
||||
@@ -588,6 +591,12 @@ if USE_OCCT and "occ" in targets:
|
||||
if OCCT_VERSION == "7.7.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe.patch")
|
||||
|
||||
if OCCT_VERSION == "7.7.2":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_7_2.patch")
|
||||
|
||||
if OCCT_VERSION == "7.8.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_8_1.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
patches.append("./patches/occt/no_em_js.patch")
|
||||
|
||||
@@ -821,7 +830,6 @@ os.makedirs(IFCOS_DIR, exist_ok=True)
|
||||
executables_dir = os.path.join(IFCOS_DIR, "executables")
|
||||
os.makedirs(executables_dir, exist_ok=True)
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
|
||||
cmake_args = [
|
||||
"-DUSE_MMAP=" "OFF",
|
||||
@@ -963,7 +971,7 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
if python_executable:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 1bacca1a48..11f931ad39 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -820,6 +820,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/bison")
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 86905287dc..9d0bce984c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -828,6 +828,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/bison")
|
||||
@@ -4,7 +4,7 @@ include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|blenderbim
|
||||
|bonsai
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|
||||
@@ -30,8 +30,8 @@ license:
|
||||
# TODO: make this based on xsd file presence
|
||||
.PHONY: models
|
||||
models:
|
||||
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
A simple Python implementation of the BCF standard. Manipulation of BCF-XML is
|
||||
available via `bcfxml.py` and manipulation of BCF-API is available via
|
||||
`bcfapi.py`.
|
||||
|
||||
Python files in 'model' folder are automatically generated from .xsd files (located in 'xsd' folder).
|
||||
To regenerate them you can use `make models`.
|
||||
The only exception is 'v2/model/extensions.py', see it's note for the details.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import bcf.v2.model.extensions
|
||||
import bcf.v3.model.extensions
|
||||
from typing import NamedTuple, Union
|
||||
from dataclasses import fields
|
||||
|
||||
|
||||
class AttributeData(NamedTuple):
|
||||
attr_type: type
|
||||
subattr_name: str
|
||||
subattr_xsd_name: str
|
||||
|
||||
|
||||
Extensions = Union[bcf.v2.model.extensions.Extensions, bcf.v3.model.extensions.Extensions]
|
||||
|
||||
|
||||
def get_extensions_attributes(extensions: Extensions) -> dict[str, AttributeData]:
|
||||
"""Return mapping of xsd attribute name to a tuple that consists of:
|
||||
- Extensions attribute name
|
||||
- Extensions attribute type type
|
||||
- subattribute name"""
|
||||
possible_attributes = {}
|
||||
for field in fields(type(extensions)):
|
||||
field_type = field.type.__args__[0] # type: ignore [reportAttributeAccessIssue]
|
||||
subfield = next(iter(fields(field_type)))
|
||||
xsd_name = subfield.metadata["name"]
|
||||
possible_attributes[field.name] = AttributeData(field_type, subfield.name, xsd_name)
|
||||
|
||||
return possible_attributes
|
||||
@@ -0,0 +1,10 @@
|
||||
import bcf.v2.model
|
||||
import bcf.v3.model
|
||||
from typing import Union
|
||||
|
||||
BimSnippet = Union[bcf.v2.model.BimSnippet, bcf.v3.model.BimSnippet]
|
||||
BitMap = Union[bcf.v2.model.VisualizationInfoBitmap, bcf.v3.model.Bitmap]
|
||||
DocumentReference = Union[bcf.v2.model.TopicDocumentReference, bcf.v3.model.DocumentReference]
|
||||
HeaderFile = Union[bcf.v2.model.HeaderFile, bcf.v3.model.File]
|
||||
Topic = Union[bcf.v2.model.Topic, bcf.v3.model.Topic]
|
||||
ViewPoint = Union[bcf.v2.model.ViewPoint, bcf.v3.model.ViewPoint]
|
||||
@@ -0,0 +1,101 @@
|
||||
import tempfile
|
||||
import bcf.v2.bcfxml
|
||||
import bcf.v2.model
|
||||
import bcf.v2.topic
|
||||
import bcf.v3.bcfxml
|
||||
import bcf.v3.model
|
||||
import bcf.v3.topic
|
||||
import bcf.agnostic.model as mdl
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
from typing_extensions import assert_never
|
||||
|
||||
TopicHandler = Union[bcf.v2.topic.TopicHandler, bcf.v3.topic.TopicHandler]
|
||||
|
||||
|
||||
def extract_file(
|
||||
topic: TopicHandler,
|
||||
entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.DocumentReference, mdl.BitMap],
|
||||
bcfxml: Optional[Union[bcf.v2.bcfxml.BcfXml, bcf.v3.bcfxml.BcfXml]] = None,
|
||||
outfile: Optional[Path] = None,
|
||||
) -> Union[Path, str, None]:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:param bcfxml: The BCF XML file to use for resolving document references files.
|
||||
Required only for BCF v3 document references (in BCF v3 internal documents
|
||||
are stored at BCF root, not in the topic).
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
"""
|
||||
if isinstance(entity, mdl.DocumentReference):
|
||||
if isinstance(entity, bcf.v2.model.TopicDocumentReference):
|
||||
reference = entity.referenced_document
|
||||
else:
|
||||
reference = entity.document_guid
|
||||
else:
|
||||
reference = entity.reference
|
||||
|
||||
if not reference:
|
||||
return None
|
||||
|
||||
# For v3 document references external documents are detected by empty document_guid.
|
||||
# External bitmaps are not supported by bcf.
|
||||
if not isinstance(entity, (bcf.v3.model.DocumentReference, mdl.BitMap)) and entity.is_external:
|
||||
return reference
|
||||
|
||||
if isinstance(entity, bcf.v3.model.DocumentReference):
|
||||
# Extract document reference filename and contents.
|
||||
if not bcfxml:
|
||||
raise TypeError("bcfxml is required for BCF v3 document references.")
|
||||
assert isinstance(bcfxml, bcf.v3.bcfxml.BcfXml)
|
||||
error_msg = f"BCF XML is missing document with guid '{reference}'."
|
||||
if not bcfxml.documents:
|
||||
raise Exception(error_msg)
|
||||
definition_docs = bcfxml.documents.definition.documents
|
||||
if not definition_docs:
|
||||
raise Exception(error_msg)
|
||||
docs = next((doc for doc in definition_docs.document if doc.guid == reference), None)
|
||||
if not docs:
|
||||
raise Exception(error_msg)
|
||||
filename = docs.filename
|
||||
bytes_data = bcfxml.documents.documents[filename]
|
||||
else:
|
||||
if isinstance(entity, mdl.BimSnippet):
|
||||
bytes_data = topic.bim_snippet
|
||||
assert isinstance(bytes_data, bytes)
|
||||
elif isinstance(entity, mdl.HeaderFile):
|
||||
bytes_data = topic.reference_files[reference]
|
||||
elif isinstance(entity, mdl.BitMap):
|
||||
bytes_data = next(
|
||||
byte_data
|
||||
for vp in topic.viewpoints.values()
|
||||
for data_reference, byte_data in vp.bitmaps.items()
|
||||
if data_reference == reference
|
||||
)
|
||||
elif isinstance(entity, bcf.v2.model.TopicDocumentReference):
|
||||
assert isinstance(topic, bcf.v2.topic.TopicHandler)
|
||||
bytes_data = topic.document_references[reference]
|
||||
else:
|
||||
assert_never(entity)
|
||||
|
||||
# We don't really need it if 'outfile' is None, just keeping type checker happy.
|
||||
if isinstance(entity, mdl.HeaderFile) and entity.filename:
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = Path(reference).name
|
||||
|
||||
if not outfile:
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(bytes_data)
|
||||
|
||||
return outfile
|
||||
@@ -0,0 +1,5 @@
|
||||
import bcf.v2.visinfo
|
||||
import bcf.v3.visinfo
|
||||
from typing import Union
|
||||
|
||||
VisualizationInfoHandler = Union[bcf.v2.visinfo.VisualizationInfoHandler, bcf.v3.visinfo.VisualizationInfoHandler]
|
||||
@@ -30,9 +30,10 @@ from bcf.v3.model import Version as Version3
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
def load(
|
||||
filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None
|
||||
) -> Optional[Union[BcfXml2, BcfXml3]]:
|
||||
BcfXml = Union[BcfXml2, BcfXml3]
|
||||
|
||||
|
||||
def load(filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional[BcfXml]:
|
||||
"""
|
||||
Load a BCF file.
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn, Optional, TypeVar
|
||||
|
||||
import bcf.agnostic.extensions
|
||||
import bcf.v2.model as mdl
|
||||
import bcf.v2.model.extensions as mdl_extensions
|
||||
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
||||
from bcf.v2.topic import TopicHandler
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
@@ -24,7 +26,8 @@ class BcfXml:
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._version: Optional[mdl.Version] = None
|
||||
self._project_info: Optional[mdl.ProjectExtension] = None
|
||||
self._topics: dict[str, TopicHandler] = {}
|
||||
self._extensions: Optional[mdl_extensions.Extensions] = None
|
||||
self._topics: Optional[dict[str, TopicHandler]] = None
|
||||
self._extension_schema: Optional[bytes] = None
|
||||
self._zip_file = self._load_zip_file()
|
||||
|
||||
@@ -85,24 +88,63 @@ class BcfXml:
|
||||
def extension_schema(self, value: bytes) -> None:
|
||||
self._extension_schema = value
|
||||
|
||||
@property
|
||||
def extensions(self) -> Optional[mdl_extensions.Extensions]:
|
||||
"""BCF extensions."""
|
||||
|
||||
if not self._extensions and self.extension_schema:
|
||||
import io
|
||||
from xml.etree import ElementTree as etree
|
||||
|
||||
extensions = mdl_extensions.Extensions()
|
||||
|
||||
xs = "{http://www.w3.org/2001/XMLSchema}"
|
||||
root = etree.parse(io.BytesIO((self.extension_schema)))
|
||||
|
||||
attrs = bcf.agnostic.extensions.get_extensions_attributes(extensions)
|
||||
xsd_to_attrs = {v.subattr_xsd_name: k for k, v in attrs.items()}
|
||||
for node in root.findall(f".//{xs}restriction"):
|
||||
attr_type = node.attrib.get("base")
|
||||
if not attr_type:
|
||||
continue
|
||||
attr_name = xsd_to_attrs.get(attr_type)
|
||||
if attr_name is None:
|
||||
continue
|
||||
values = []
|
||||
for enum in node.findall(f".//{xs}enumeration"):
|
||||
values.append(enum.attrib["value"])
|
||||
if not values:
|
||||
continue
|
||||
attr_data = attrs[attr_name]
|
||||
attr = attr_data.attr_type()
|
||||
setattr(attr, attr_data.subattr_name, values)
|
||||
setattr(extensions, attr_name, attr)
|
||||
|
||||
self._extensions = extensions
|
||||
return self._extensions
|
||||
|
||||
@extensions.setter
|
||||
def extensions(self, value: Optional[mdl_extensions.Extensions]) -> None:
|
||||
self._extensions = value
|
||||
|
||||
@property
|
||||
def topics(self) -> dict[str, TopicHandler]:
|
||||
"""BCF topics."""
|
||||
if not self._topics and self._zip_file:
|
||||
self._topics = self._load_topics(self._zip_file, self._xml_handler)
|
||||
if self._topics is None:
|
||||
self._topics = self._load_topics()
|
||||
return self._topics
|
||||
|
||||
def _load_topics(
|
||||
self, zip_file: zipfile.ZipFile, xml_handler: AbstractXmlParserSerializer
|
||||
) -> dict[str, TopicHandler]:
|
||||
def _load_topics(self) -> dict[str, TopicHandler]:
|
||||
topics = {}
|
||||
for topic_dir in zipfile.Path(zip_file).iterdir():
|
||||
if self._zip_file is None:
|
||||
return topics
|
||||
for topic_dir in zipfile.Path(self._zip_file).iterdir():
|
||||
if not topic_dir.is_dir():
|
||||
continue
|
||||
markup_path = topic_dir.joinpath("markup.bcf")
|
||||
if not markup_path.exists():
|
||||
continue
|
||||
topics[topic_dir.name] = TopicHandler(topic_dir, xml_handler)
|
||||
topics[topic_dir.name] = TopicHandler(topic_dir, self._xml_handler)
|
||||
return topics
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# NOTE: This file is not generated from
|
||||
# https://github.com/buildingSMART/BCF-XML/blob/release_2_1/Extension%20Schemas/extensions.xsd
|
||||
# because in bcf 2.1 there is no extensions.xml - I guess, the schema assumes that each .bcf
|
||||
# will use their own schema patched by it's own extensions.xsd.
|
||||
#
|
||||
# To make things simpler we just mimic extensions structures from bcf 3, so they'll have common API,
|
||||
# and parse extensions.xsd inside .bcf as .xml and fill our structures.
|
||||
#
|
||||
# Preferably if we could generate some kind of extensions.xml from extensions.xsd
|
||||
# and leave all the handling to xsdata.
|
||||
#
|
||||
# We also don't add it to __init__.py not to mess with generator.
|
||||
#
|
||||
# Currently extensions support for v2 is only read-only.
|
||||
|
||||
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
priority: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
snippet_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
stage: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_label: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicLabel",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_status: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
user: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "UserIdType",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicTypes",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
topic_statuses: Optional[ExtensionsTopicStatuses] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicStatuses",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
priorities: Optional[ExtensionsPriorities] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Priorities",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
topic_labels: Optional[ExtensionsTopicLabels] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicLabels",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
users: Optional[ExtensionsUsers] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Users",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
snippet_types: Optional[ExtensionsSnippetTypes] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "SnippetTypes",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
stages: Optional[ExtensionsStages] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Stages",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
@@ -5,7 +5,7 @@ import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn, Optional
|
||||
from typing import Any, NoReturn, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
@@ -27,9 +27,9 @@ class TopicHandler:
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self._markup: Optional[mdl.Markup] = None
|
||||
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
|
||||
self._reference_files: dict[str, bytes] = {}
|
||||
self._document_references: dict[str, bytes] = {}
|
||||
self._viewpoints: Optional[dict[str, VisualizationInfoHandler]] = None
|
||||
self._reference_files: Optional[dict[str, bytes]] = None
|
||||
self._document_references: Optional[dict[str, bytes]] = None
|
||||
self._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
@@ -63,11 +63,21 @@ class TopicHandler:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header if self.markup else None
|
||||
|
||||
@header.setter
|
||||
def header(self, header: mdl.Header) -> None:
|
||||
"""Set the header of the topic."""
|
||||
self.markup.header = header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.markup.comment if self.markup else []
|
||||
|
||||
@comments.setter
|
||||
def comments(self, comments: list[mdl.Comment]) -> None:
|
||||
assert self.markup
|
||||
self.markup.comment = comments
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
@@ -80,14 +90,24 @@ class TopicHandler:
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if not self._viewpoints and self._topic_dir:
|
||||
if self._viewpoints is None:
|
||||
self._viewpoints = self._load_viewpoints()
|
||||
return self._viewpoints
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if self._topic_dir and self.markup and (viewpoints := self.markup.viewpoints):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
|
||||
@property
|
||||
def reference_files(self) -> dict[str, bytes]:
|
||||
if self._reference_files or not self.header:
|
||||
if self._reference_files is not None:
|
||||
return self._reference_files
|
||||
|
||||
self._reference_files = {}
|
||||
if not self.header:
|
||||
return self._reference_files
|
||||
|
||||
for ref in self.header.file:
|
||||
if ref.is_external:
|
||||
continue
|
||||
@@ -99,8 +119,13 @@ class TopicHandler:
|
||||
|
||||
@property
|
||||
def document_references(self) -> dict[str, bytes]:
|
||||
if self._document_references or not self.topic:
|
||||
if self._document_references is not None:
|
||||
return self._document_references
|
||||
|
||||
self._document_references = {}
|
||||
if not self.topic:
|
||||
return self._document_references
|
||||
|
||||
for doc in self.topic.document_reference:
|
||||
if doc.is_external or not doc.referenced_document:
|
||||
continue
|
||||
@@ -118,11 +143,6 @@ class TopicHandler:
|
||||
return bim_snippet_path.read_bytes()
|
||||
return None
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if self.markup and (viewpoints := self.markup.viewpoints):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
@@ -220,55 +240,7 @@ class TopicHandler:
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
|
||||
|
||||
def extract_file(self, entity, outfile: Optional[Path] = None) -> Path:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:type entity: bcf.v2.model.HeaderFile,bcf.v2.model.BimSnippet,bcf.v2.model.TopicDocumentReference
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:type outfile: pathlib.Path,optional
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
:rtype: Path
|
||||
"""
|
||||
if hasattr(entity, "reference"):
|
||||
reference = entity.reference
|
||||
else:
|
||||
reference = entity.referenced_document
|
||||
|
||||
if not reference:
|
||||
return
|
||||
|
||||
if getattr(entity, "is_external", False):
|
||||
return entity.reference
|
||||
|
||||
resolved_reference = self._topic_dir
|
||||
|
||||
for part in Path(reference).parts:
|
||||
if part == "..":
|
||||
resolved_reference = resolved_reference.parent
|
||||
else:
|
||||
resolved_reference = resolved_reference.joinpath(part)
|
||||
|
||||
if not outfile:
|
||||
if getattr(entity, "filename", None):
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = resolved_reference.name
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(resolved_reference.read_bytes())
|
||||
|
||||
return outfile
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
|
||||
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
@@ -278,7 +250,9 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
|
||||
def add_viewpoint_from_point_and_guids(
|
||||
self, position: NDArray[np.float64], *guids: str
|
||||
) -> VisualizationInfoHandler:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
|
||||
Args:
|
||||
@@ -291,9 +265,17 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
def add_visinfo_handler(
|
||||
self, new_viewpoint: VisualizationInfoHandler, snapshot_filename: Optional[str] = None
|
||||
) -> mdl.ViewPoint:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid))
|
||||
viewpoint = mdl.ViewPoint(
|
||||
viewpoint=new_viewpoint.guid + ".bcfv",
|
||||
snapshot=snapshot_filename,
|
||||
guid=new_viewpoint.guid,
|
||||
)
|
||||
self.markup.viewpoints.append(viewpoint)
|
||||
return viewpoint
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import lru_cache
|
||||
from typing import Any, Iterable, Optional
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
@@ -134,6 +133,15 @@ class VisualizationInfoHandler:
|
||||
self._save_bitmaps(bcf_zip, topic_dir)
|
||||
|
||||
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
|
||||
if bool(self.snapshot) ^ bool(filename):
|
||||
data = ["data (VisualizationInfoHandler.snapshot)", "filename (ViewPoint.snapshot)"]
|
||||
provided_data, missing_data = data if self.snapshot else data[::-1]
|
||||
print(
|
||||
f"WARNING. Snapshot with viewpoint guid '{self.guid}' won't be saved to bcf. "
|
||||
f"Only snapshot {provided_data} is provided but snapshot {missing_data} is missing."
|
||||
)
|
||||
return
|
||||
|
||||
if self.snapshot and filename:
|
||||
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
|
||||
|
||||
@@ -196,13 +204,110 @@ class VisualizationInfoHandler:
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
def get_selected_guids(self) -> Union[list[str], None]:
|
||||
"""
|
||||
Return viewpoint selected elements IFC guids.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no selection settings, return `None`.
|
||||
Otherwise return a list of selected elements IFC guids.
|
||||
"""
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
selection = components.selection
|
||||
if not selection:
|
||||
return None
|
||||
return [guid for c in selection.component if (guid := c.ifc_guid)]
|
||||
|
||||
def set_selected_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=True)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
selection = components.selection
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
if not selection:
|
||||
selection = mdl.ComponentSelection()
|
||||
components.selection = selection
|
||||
selection.component = components_list
|
||||
|
||||
def set_visible_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="VISIBLE")
|
||||
|
||||
def set_hidden_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="HIDDEN")
|
||||
|
||||
def set_visibility(
|
||||
self, elements: list[ifcopenshell.entity_instance], elements_visibility: Literal["VISIBLE", "HIDDEN"]
|
||||
) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
default_visibility = elements_visibility == "HIDDEN"
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components.visibility = visibility
|
||||
elif visibility.default_visibility != default_visibility:
|
||||
visibility.default_visibility = default_visibility
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
exceptions = mdl.ComponentVisibilityExceptions()
|
||||
visibility.exceptions = exceptions
|
||||
exceptions.component = components_list
|
||||
|
||||
def get_elements_visibility(self) -> Union[tuple[bool, list[str]], None]:
|
||||
"""
|
||||
Return viewpoint elements visibility settings.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no visibility settings, return `None`.
|
||||
Otherwise return a tuple containing the default visibility
|
||||
and a list of IFC element GUIDs listed as exceptions.
|
||||
|
||||
If default visibility is `True`, all elements are visible except the exceptions.
|
||||
|
||||
If default visibility is `False`, all elements are hidden except the exceptions.
|
||||
"""
|
||||
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
return None
|
||||
default_visibility = visibility.default_visibility or False
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
return default_visibility, []
|
||||
guids = [guid for c in exceptions.component if (guid := c.ifc_guid)]
|
||||
return default_visibility, guids
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
@@ -228,7 +333,7 @@ def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guid
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
@@ -249,7 +354,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
*guids: One or more IFC element GUID.
|
||||
*guids: One or more selected IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
|
||||
@@ -26,7 +26,7 @@ class BcfXml:
|
||||
self._version: Optional[mdl.Version] = None
|
||||
self._project_info: Optional[mdl.ProjectInfo] = None
|
||||
self._extensions: Optional[mdl.Extensions] = None
|
||||
self._topics: dict[str, TopicHandler] = {}
|
||||
self._topics: Optional[dict[str, TopicHandler]] = None
|
||||
self._documents: Optional[DocumentsHandler] = None
|
||||
self._zip_file = self._load_zip_file()
|
||||
|
||||
@@ -91,18 +91,22 @@ class BcfXml:
|
||||
@property
|
||||
def topics(self) -> dict[str, TopicHandler]:
|
||||
"""BCF topics."""
|
||||
if not self._topics and self._zip_file:
|
||||
self._load_topics()
|
||||
if self._topics is None:
|
||||
self._topics = self._load_topics()
|
||||
return self._topics
|
||||
|
||||
def _load_topics(self) -> None:
|
||||
def _load_topics(self) -> dict[str, TopicHandler]:
|
||||
topics = {}
|
||||
if self._zip_file is None:
|
||||
return topics
|
||||
for topic_dir in zipfile.Path(self._zip_file).iterdir():
|
||||
if not topic_dir.is_dir():
|
||||
continue
|
||||
markup_path = topic_dir.joinpath("markup.bcf")
|
||||
if not markup_path.exists():
|
||||
continue
|
||||
self._topics[topic_dir.name] = TopicHandler(topic_dir, self._xml_handler)
|
||||
topics[topic_dir.name] = TopicHandler(topic_dir, self._xml_handler)
|
||||
return topics
|
||||
|
||||
@property
|
||||
def documents(self) -> Optional[DocumentsHandler]:
|
||||
|
||||
@@ -26,7 +26,8 @@ class TopicHandler:
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self._markup: Optional[mdl.Markup] = None
|
||||
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
|
||||
self._viewpoints: Optional[dict[str, VisualizationInfoHandler]] = None
|
||||
self._reference_files: Optional[dict[str, bytes]] = None
|
||||
self._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
@@ -49,22 +50,36 @@ class TopicHandler:
|
||||
return self.markup.topic
|
||||
|
||||
@property
|
||||
def guid(self) -> Optional[str]:
|
||||
def guid(self) -> str:
|
||||
"""Return the GUID of the topic."""
|
||||
if self._markup:
|
||||
return self.topic.guid
|
||||
return self._topic_dir.name if self._topic_dir else None
|
||||
return self._topic_dir.name if self._topic_dir else ""
|
||||
|
||||
@property
|
||||
def header(self) -> Optional[mdl.Header]:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header
|
||||
|
||||
@header.setter
|
||||
def header(self, header: mdl.Header) -> None:
|
||||
"""Set the header of the topic."""
|
||||
self.markup.header = header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.topic.comments.comment if self.topic.comments else []
|
||||
|
||||
@comments.setter
|
||||
def comments(self, comments: list[mdl.Comment]) -> None:
|
||||
topic_comments = self.topic.comments
|
||||
if topic_comments is None:
|
||||
if not comments:
|
||||
return
|
||||
self.topic.comments = (topic_comments := mdl.TopicComments())
|
||||
topic_comments.comment = comments
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
@@ -77,15 +92,15 @@ class TopicHandler:
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
if (
|
||||
not self._viewpoints
|
||||
and self._topic_dir
|
||||
and self.topic.viewpoints
|
||||
and (viewpoints := self.topic.viewpoints.view_point)
|
||||
):
|
||||
self._viewpoints = VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
if self._viewpoints is None:
|
||||
self._viewpoints = self._load_viewpoints()
|
||||
return self._viewpoints
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
if self._topic_dir and self.topic.viewpoints and (viewpoints := self.topic.viewpoints.view_point):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
|
||||
def _load_bim_snippet(self) -> Optional[bytes]:
|
||||
bim_snippet_obj = self.topic.bim_snippet
|
||||
if bim_snippet_obj and not bim_snippet_obj.is_external and self._topic_dir:
|
||||
@@ -94,6 +109,27 @@ class TopicHandler:
|
||||
return bim_snippet_path.read_bytes()
|
||||
return None
|
||||
|
||||
@property
|
||||
def reference_files(self) -> dict[str, bytes]:
|
||||
if self._reference_files is not None:
|
||||
return self._reference_files
|
||||
|
||||
self._reference_files = {}
|
||||
if not self.header:
|
||||
return self._reference_files
|
||||
|
||||
if not self.header.files:
|
||||
return self._reference_files
|
||||
|
||||
for ref in self.header.files.file:
|
||||
if ref.is_external:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in ref.reference.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
self._reference_files[ref.reference] = real_path.read_bytes()
|
||||
return self._reference_files
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
@@ -145,6 +181,7 @@ class TopicHandler:
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
self._save_reference_files(destination_zip)
|
||||
|
||||
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
|
||||
@@ -165,7 +202,20 @@ class TopicHandler:
|
||||
if self.bim_snippet:
|
||||
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
def _save_reference_files(self, destination_zip: ZipFileInterface) -> None:
|
||||
if not self.header:
|
||||
return
|
||||
if not self.header.files:
|
||||
return
|
||||
for ref in self.header.files.file:
|
||||
if ref.is_external or not ref.reference:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in ref.reference.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.reference_files[ref.reference])
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
@@ -174,8 +224,11 @@ class TopicHandler:
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
|
||||
def add_viewpoint_from_point_and_guids(
|
||||
self, position: NDArray[np.float64], *guids: str
|
||||
) -> VisualizationInfoHandler:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
@@ -186,14 +239,21 @@ class TopicHandler:
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
def add_visinfo_handler(
|
||||
self, new_viewpoint: VisualizationInfoHandler, snapshot_filename: Optional[str] = None
|
||||
) -> mdl.ViewPoint:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
if self.topic.viewpoints is None:
|
||||
self.topic.viewpoints = mdl.TopicViewpoints()
|
||||
self.topic.viewpoints.view_point.append(
|
||||
mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid)
|
||||
viewpoint = mdl.ViewPoint(
|
||||
viewpoint=new_viewpoint.guid + ".bcfv",
|
||||
snapshot=snapshot_filename,
|
||||
guid=new_viewpoint.guid,
|
||||
)
|
||||
self.topic.viewpoints.view_point.append(viewpoint)
|
||||
return viewpoint
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import lru_cache
|
||||
from typing import Any, Iterable, Optional
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.util import placement
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
@@ -133,6 +133,15 @@ class VisualizationInfoHandler:
|
||||
self._save_bitmaps(bcf_zip, topic_dir)
|
||||
|
||||
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
|
||||
if bool(self.snapshot) ^ bool(filename):
|
||||
data = ["data (VisualizationInfoHandler.snapshot)", "filename (ViewPoint.snapshot)"]
|
||||
provided_data, missing_data = data if self.snapshot else data[::-1]
|
||||
print(
|
||||
f"WARNING. Snapshot with viewpoint guid '{self.guid}' won't be saved to bcf. "
|
||||
f"Only snapshot {provided_data} is provided but snapshot {missing_data} is missing."
|
||||
)
|
||||
return
|
||||
|
||||
if self.snapshot and filename:
|
||||
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
|
||||
|
||||
@@ -195,13 +204,110 @@ class VisualizationInfoHandler:
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
def get_selected_guids(self) -> Union[list[str], None]:
|
||||
"""
|
||||
Return viewpoint selected elements IFC guids.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no selection settings, return `None`.
|
||||
Otherwise return a list of selected elements IFC guids.
|
||||
"""
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
selection = components.selection
|
||||
if not selection:
|
||||
return None
|
||||
return [guid for c in selection.component if (guid := c.ifc_guid)]
|
||||
|
||||
def set_selected_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=True)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
selection = components.selection
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
if not selection:
|
||||
selection = mdl.ComponentSelection()
|
||||
components.selection = selection
|
||||
selection.component = components_list
|
||||
|
||||
def set_visible_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="VISIBLE")
|
||||
|
||||
def set_hidden_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="HIDDEN")
|
||||
|
||||
def set_visibility(
|
||||
self, elements: list[ifcopenshell.entity_instance], elements_visibility: Literal["VISIBLE", "HIDDEN"]
|
||||
) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
default_visibility = elements_visibility == "HIDDEN"
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components.visibility = visibility
|
||||
elif visibility.default_visibility != default_visibility:
|
||||
visibility.default_visibility = default_visibility
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
exceptions = mdl.ComponentVisibilityExceptions()
|
||||
visibility.exceptions = exceptions
|
||||
exceptions.component = components_list
|
||||
|
||||
def get_elements_visibility(self) -> Union[tuple[bool, list[str]], None]:
|
||||
"""
|
||||
Return viewpoint elements visibility settings.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no visibility settings, return `None`.
|
||||
Otherwise return a tuple containing the default visibility
|
||||
and a list of IFC element GUIDs listed as exceptions.
|
||||
|
||||
If default visibility is `True`, all elements are visible except the exceptions.
|
||||
|
||||
If default visibility is `False`, all elements are hidden except the exceptions.
|
||||
"""
|
||||
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
return None
|
||||
default_visibility = visibility.default_visibility or False
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
return default_visibility, []
|
||||
guids = [guid for c in exceptions.component if (guid := c.ifc_guid)]
|
||||
return default_visibility, guids
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
@@ -209,7 +315,12 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
elem_placement = placement.get_local_placement(element.ObjectPlacement)
|
||||
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
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -222,7 +333,7 @@ def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guid
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
@@ -243,7 +354,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
*guids: One or more IFC element GUID.
|
||||
*guids: One or more selected IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
|
||||
@@ -55,9 +55,32 @@ def test_save_maximum_information() -> None:
|
||||
|
||||
def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.version.version_id == "2.1"
|
||||
assert bcf.project
|
||||
assert bcf.project.name == "BCF API Implementation"
|
||||
assert bcf.project_info
|
||||
assert bcf.project_info.extension_schema == "extensions.xsd"
|
||||
|
||||
assert bcf.extensions
|
||||
assert bcf.extensions.topic_types
|
||||
assert bcf.extensions.topic_types.topic_type == ["Architecture", "Hidden Type", "Structural"]
|
||||
assert bcf.extensions.topic_statuses
|
||||
assert bcf.extensions.topic_statuses.topic_status == ["Finished status", "Open", "Closed"]
|
||||
assert bcf.extensions.priorities
|
||||
assert bcf.extensions.priorities.priority == ["Low", "High", "Medium"]
|
||||
assert bcf.extensions.topic_labels
|
||||
assert bcf.extensions.topic_labels.topic_label == [
|
||||
"Architecture",
|
||||
"IT Development",
|
||||
"Management",
|
||||
"Mechanical",
|
||||
"Structural",
|
||||
]
|
||||
assert bcf.extensions.users
|
||||
assert bcf.extensions.users.user == ["dangl@iabi.eu", "linhard@iabi.eu"]
|
||||
assert bcf.extensions.snippet_types
|
||||
assert bcf.extensions.snippet_types.snippet_type == ["IFC2X3", "PDF", "XLSX"]
|
||||
assert bcf.extensions.stages is None
|
||||
|
||||
assert len(bcf.topics) == 2
|
||||
assert_first_topic_handler(bcf.topics["7ddc3ef0-0ab7-43f1-918a-45e38b42369c"])
|
||||
second_th = bcf.topics["d1068c81-af04-4546-b63c-348810f6c716"]
|
||||
|
||||
@@ -39,6 +39,22 @@ def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.project.name == "BCF 3.0 test cases"
|
||||
assert bcf.project.project_id == "de894a86-3a08-4ea0-b2d1-6c222b5602d1"
|
||||
|
||||
assert bcf.extensions
|
||||
assert bcf.extensions.topic_types
|
||||
assert bcf.extensions.topic_types.topic_type == ["ERROR", "WARNING", "INFORMATION", "CLASH", "OTHER"]
|
||||
assert bcf.extensions.topic_statuses
|
||||
assert bcf.extensions.topic_statuses.topic_status == ["OPEN", "IN_PROGRESS", "SOLVED", "CLOSED"]
|
||||
assert bcf.extensions.priorities
|
||||
assert bcf.extensions.priorities.priority == ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
|
||||
assert bcf.extensions.topic_labels
|
||||
assert bcf.extensions.topic_labels.topic_label == []
|
||||
assert bcf.extensions.users
|
||||
assert bcf.extensions.users.user == ["Architect@example.com", "Engineer@example.com", "MEPDesigner@example.com"]
|
||||
assert bcf.extensions.snippet_types
|
||||
assert bcf.extensions.snippet_types.snippet_type == []
|
||||
assert bcf.extensions.stages
|
||||
assert bcf.extensions.stages.stage == []
|
||||
|
||||
assert len(bcf.topics) == 1
|
||||
topic_handler = bcf.topics["8ac9822a-761a-4deb-9f39-f61286acbf6a"]
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# BlenderBIM Add-on
|
||||
|
||||
An add-on to Blender to allow BIM functionality.
|
||||
|
||||
More information on the [BlenderBIM Add-on website](https://blenderbim.org).
|
||||
@@ -1,181 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
blenderbim_lib_path = os.environ.get("BLENDERBIM_LIB_PATH")
|
||||
blenderbim_version = os.environ.get("BLENDERBIM_VERSION")
|
||||
|
||||
if blenderbim_lib_path:
|
||||
sys.path.insert(0, blenderbim_lib_path)
|
||||
|
||||
import argparse
|
||||
from aiohttp import web
|
||||
import socketio
|
||||
import pystache
|
||||
import json
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
sio = socketio.AsyncServer(
|
||||
cors_allowed_origins="*",
|
||||
async_mode="aiohttp",
|
||||
)
|
||||
|
||||
# sio.instrument(
|
||||
# auth={
|
||||
# "username": "admin",
|
||||
# "password": "admin",
|
||||
# }
|
||||
# )
|
||||
|
||||
app = web.Application()
|
||||
sio.attach(app)
|
||||
|
||||
|
||||
# represents a caching for blender messages
|
||||
blender_messages = {}
|
||||
|
||||
|
||||
# Web namespace
|
||||
class WebNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
super().__init__(namespace)
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Web client connected: {sid}")
|
||||
if blender_messages:
|
||||
await self.send_cached_messages(sid)
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
print(f"Web client disconnected: {sid}")
|
||||
|
||||
async def on_web_operator(self, sid, data):
|
||||
await sio.emit(
|
||||
"web_operator",
|
||||
data,
|
||||
namespace="/blender",
|
||||
room=data.get("blenderId", None),
|
||||
)
|
||||
|
||||
async def on_get_svg(self, sid, data):
|
||||
print("hello world!")
|
||||
file_path = data["path"]
|
||||
with open(file_path, "r") as file:
|
||||
svg_data = file.read()
|
||||
await sio.emit("svg_data", svg_data, room=sid, namespace="/web")
|
||||
|
||||
async def send_cached_messages(self, sid):
|
||||
# Send cached messages to the connected web client
|
||||
for blenderId, messages in blender_messages.items():
|
||||
if "csv_data" in messages:
|
||||
await self.emit("csv_data", {"blenderId": blenderId, "data": messages["csv_data"]}, room=sid)
|
||||
if "gantt_data" in messages:
|
||||
await self.emit("gantt_data", {"blenderId": blenderId, "data": messages["gantt_data"]}, room=sid)
|
||||
|
||||
|
||||
# Blender namespace
|
||||
class BlenderNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
super().__init__(namespace)
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Blender client connected: {sid}")
|
||||
# Notify web client about new connection
|
||||
blender_messages[sid] = {}
|
||||
await sio.emit("blender_connect", sid, namespace="/web")
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
print(f"Blender client disconnected: {sid}")
|
||||
# Remove client message and notify web client about disconnection
|
||||
if sid in blender_messages:
|
||||
del blender_messages[sid]
|
||||
await sio.emit("blender_disconnect", sid, namespace="/web")
|
||||
|
||||
async def on_data(self, sid, data):
|
||||
print(f"Data from Blender client {sid}")
|
||||
# blender_messages[sid]["default_data"] = data
|
||||
await sio.emit("default_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_csv_data(self, sid, data):
|
||||
print(f"CSV data from Blender client {sid}")
|
||||
# Store the message and forward it to the web client
|
||||
blender_messages[sid]["csv_data"] = data
|
||||
await sio.emit("csv_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_gantt_data(self, sid, data):
|
||||
print(f"Gant Chart data from Blender client {sid}")
|
||||
blender_messages[sid]["gantt_data"] = data
|
||||
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_drawings_data(self, sid, data):
|
||||
print(f"Drawings directory from Blender client {sid}")
|
||||
print(data)
|
||||
blender_messages[sid]["drwings_data"] = data
|
||||
await sio.emit("drawings_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
sio.register_namespace(BlenderNamespace("/blender"))
|
||||
|
||||
|
||||
# Define a route to render the index.html template
|
||||
async def index(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def gantt(request):
|
||||
with open("templates/gantt.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def drawings(request):
|
||||
with open("templates/drawings.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
pid_file = "running_pid.json"
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
pids = {}
|
||||
|
||||
pids[str(os.getpid())] = sio_port
|
||||
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_get("/drawings", drawings)
|
||||
app.router.add_get("/gantt", gantt)
|
||||
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
|
||||
app.router.add_static("/static/", path="./static", name="static")
|
||||
app.on_startup.append(on_startup)
|
||||
|
||||
|
||||
def main():
|
||||
global sio_port
|
||||
|
||||
parser = argparse.ArgumentParser(description="SocketIO server")
|
||||
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to run the server on")
|
||||
parser.add_argument("--port", type=int, default=8080, help="Port to run the server on")
|
||||
|
||||
args = parser.parse_args()
|
||||
sio_port = args.port
|
||||
web.run_app(app, host=args.host, port=sio_port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# web.run_app(app, host="127.0.0.1", port=sio_port)
|
||||
@@ -1,410 +0,0 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-small: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
--folder-collapse-font-size: 0.75rem;
|
||||
--folder-collapse-font-family: Courier, "Courier New", monospace;
|
||||
--box-shadow: 0 0 0.7rem #5f5f5f66;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--primary-text-color: #e0e0e0;
|
||||
--secondary-text-color: #c7c7c7;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
--hover-bg-color: #3a3a3a;
|
||||
--highlight-color: #009136;
|
||||
--task-complete-bg-color: #777777;
|
||||
--milestone-border-color: #ffffff;
|
||||
--group-item-bg-color: #4b4b4b;
|
||||
--input-bg-color: #3b3b3b;
|
||||
--input-border-color: #000;
|
||||
--details-border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--primary-text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--details-border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--primary-text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 0.0625rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 3.125rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-description {
|
||||
margin-bottom: var(--margin-medium);
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border-color: var(--border-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: var(--box-shadow);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-description tr {
|
||||
display: table-row;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--nav-bg-color);
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.gantt-info {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------ Overwriting JSGantt CSS rules ------------ */
|
||||
:root.dark div.gantt {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gantt table,
|
||||
:root.dark .gantt td {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Headings and cell defaults */
|
||||
:root.dark .gmajorheading,
|
||||
:root.dark .gminorheading,
|
||||
:root.dark .gminorheadingwkend,
|
||||
:root.dark .gtaskcell,
|
||||
:root.dark .gtaskcellcurrent,
|
||||
:root.dark .gtaskcellwkend,
|
||||
:root.dark .gname,
|
||||
:root.dark .ggroupitem,
|
||||
:root.dark .gtaskheading {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskheading,
|
||||
:root.dark .gname,
|
||||
:root.dark .gtaskname,
|
||||
:root.dark .gres,
|
||||
:root.dark .gdur,
|
||||
:root.dark .gcomp,
|
||||
:root.dark .gstartdate,
|
||||
:root.dark .gplanstartdate,
|
||||
:root.dark .gplanenddate,
|
||||
:root.dark .gcost,
|
||||
:root.dark .gchartlbl,
|
||||
:root.dark .gcontainercol,
|
||||
:root.dark .genddate {
|
||||
color: var(--secondary-text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskname>div {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskbarcontainer.gplan,
|
||||
:root.dark .gchartlbl.gcontainercol {
|
||||
background: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskname div,
|
||||
:root.dark .gtaskheading div,
|
||||
:root.dark .gtaskname span {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gtasklist,
|
||||
:root.dark .gadditional {
|
||||
border: var(--border-color) 1px solid;
|
||||
}
|
||||
|
||||
:root.dark .gchartgrid {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
:root.dark .glistgrid,
|
||||
:root.dark .glistlbl {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* .gTaskInfo {
|
||||
background-color: #2e2e2e;
|
||||
color: #c7c7c7;
|
||||
border-color: #c7c7c7;;
|
||||
} */
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:root.dark .frame::-webkit-scrollbar-thumb,
|
||||
:root.dark .frame::-webkit-scrollbar-track {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Highlight row */
|
||||
:root.dark .gitemhighlight td {
|
||||
background-color: var(--highlight-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
/* Differentiate Group, Milestone and Ordinary task items (applied to row) */
|
||||
:root.dark .ggroupitem {
|
||||
background-color: var(--bg-color);
|
||||
font-weight: bold;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gmileitem,
|
||||
:root.dark .glineitem {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Task bar caption text styles */
|
||||
:root.dark .gmilecaption,
|
||||
:root.dark .ggroupcaption,
|
||||
:root.dark .gcaption {
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
/* Task complete %age bar */
|
||||
:root.dark .gtaskcomplete {
|
||||
background-color: var(--task-complete-bg-color);
|
||||
}
|
||||
|
||||
/* Milestones */
|
||||
:root.dark .gmdtop,
|
||||
:root.dark .gmdbottom {
|
||||
border-bottom: 5px solid var(--milestone-border-color);
|
||||
}
|
||||
|
||||
:root.dark .gfoldercollapse {
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: var(--folder-collapse-font-size);
|
||||
font-family: var(--folder-collapse-font-family);
|
||||
}
|
||||
|
||||
/* Highlight for collapsible row */
|
||||
:root.dark .gname.ggroupitem {
|
||||
background-color: var(--group-item-bg-color);
|
||||
}
|
||||
|
||||
/* Form label and selected highlighting */
|
||||
:root.dark .gformlabel {
|
||||
background-color: var(--formlabel-bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
border: var(--formlabel-border-color) 1px solid;
|
||||
}
|
||||
|
||||
:root.dark span.gformlabel:hover {
|
||||
background-color: var(--hover-bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark span.gselected {
|
||||
background-color: var(--formlabel-selected-bg-color);
|
||||
border-color: var(--task-complete-bg-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
:root.dark .gantt-inputtable {
|
||||
background-color: var(--input-bg-color);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--table-border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--table-border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 0.0625rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 3.125rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--nav-bg-color);
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
/* ------------ Overwriting Tabulator CSS rules ------------ */
|
||||
|
||||
:root.light .tabulator-header,
|
||||
:root.light .tabulator .tabulator-header .tabulator-col {
|
||||
background: var(--nav-bg-color) !important;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
:root.light .tabulator {
|
||||
border-top: 1px solid var(--table-border-color);
|
||||
border-bottom: 2px solid var(--table-border-color);
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
// keeps track of blenders connected in form of
|
||||
// shown:bool, workSchedule: {}, ganttTasks: {},
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
// print options
|
||||
const pageSizes = [
|
||||
{ value: "210,297", text: "A4 Portrait" },
|
||||
{ value: "297,210", text: "A4 Landscape" },
|
||||
{ value: "297,420", text: "A3 Portrait" },
|
||||
{ value: "420,297", text: "A3 Landscape" },
|
||||
{ value: "420,594", text: "A2 Portrait" },
|
||||
{ value: "594,420", text: "A2 Landscape" },
|
||||
{ value: "594,841", text: "A1 Portrait" },
|
||||
{ value: "841,594", text: "A1 Landscape" },
|
||||
{ value: "841,1189", text: "A0 Portrait" },
|
||||
{ value: "1189,841", text: "A0 Landscape" },
|
||||
];
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
var theme = localStorage.getItem("theme") || systemTheme;
|
||||
setTheme(theme);
|
||||
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to connect to Socket.IO server
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("gantt_data", handleGanttData);
|
||||
socket.on("default_data", handleDefaultData);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
workSchedule: {},
|
||||
ganttTasks: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeGanttElement(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle 'gantt_data' event
|
||||
function handleGanttData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
const filename = data["data"]["ifc_file"];
|
||||
const ganttTasks = data["data"]["gantt_data"]["tasks"];
|
||||
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
|
||||
// const ganttWorkSched = {};
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
} else {
|
||||
updateGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
connectedClients[blenderId].workSchedule = ganttWorkSched;
|
||||
connectedClients[blenderId].ganttTasks = ganttTasks;
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDefaultData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
const isDirty = data["data"]["is_dirty"];
|
||||
showWarning(blenderId, isDirty);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
// Function to add a new gantt with data and filename
|
||||
function addGanttElement(blenderId, tasks, workSched, filename) {
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
|
||||
const ganttContainer = $("<div></div>")
|
||||
.addClass("gantt-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
|
||||
const ganttTitle = $("<h3></h3>")
|
||||
.attr("id", "title-" + blenderId)
|
||||
.text(filename)
|
||||
.addClass("no-print")
|
||||
.css("margin-bottom", "10px");
|
||||
|
||||
const warning = $("<div></div>")
|
||||
.attr("id", "warning-" + blenderId)
|
||||
.html(
|
||||
"⚠ Warning: This Gantt Chart may contain outdated data due to recent changes in Blender."
|
||||
)
|
||||
.addClass("warning no-print");
|
||||
|
||||
const workSchedDiv = $("<div></div>").attr("id", "workSched" + blenderId);
|
||||
|
||||
const scheduleTable = $("<table></table>")
|
||||
.addClass("no-print table-description")
|
||||
.attr("id", "workSchedTable-" + blenderId)
|
||||
.hide();
|
||||
|
||||
$.each(workSched, (key, value) => {
|
||||
value = value ? value : "null";
|
||||
$("<tr></tr>")
|
||||
.append($("<td></td>").text(key))
|
||||
.append($("<td></td>").text(value))
|
||||
.appendTo(scheduleTable);
|
||||
});
|
||||
|
||||
const toggleButton = $("<button></button>")
|
||||
.text("Show Schedule Info")
|
||||
.addClass("btn no-print")
|
||||
.on("click", function () {
|
||||
scheduleTable.toggle();
|
||||
const buttonText = scheduleTable.is(":visible")
|
||||
? "Hide Schedule Info"
|
||||
: "Show Schedule Info";
|
||||
toggleButton.text(buttonText);
|
||||
});
|
||||
|
||||
var ganttInfoDiv = $("<div></div>")
|
||||
.addClass("gantt-info")
|
||||
.attr("id", "gantt-info-" + blenderId);
|
||||
|
||||
const scheduleName = $("<span></span>").text("Schedule: " + workSched.Name);
|
||||
|
||||
const createdOn = $("<span></span>")
|
||||
.text("Created: " + new Date(workSched.CreationDate).toLocaleDateString())
|
||||
.css("float", "right");
|
||||
|
||||
const ganttDiv = $("<div></div>")
|
||||
.addClass("gantt-chart")
|
||||
.attr("id", "gantt-" + blenderId);
|
||||
|
||||
ganttInfoDiv.append(scheduleName);
|
||||
ganttInfoDiv.append(createdOn);
|
||||
|
||||
workSchedDiv.append(toggleButton);
|
||||
workSchedDiv.append(scheduleTable);
|
||||
|
||||
ganttContainer.append(ganttTitle);
|
||||
ganttContainer.append(warning);
|
||||
ganttContainer.append(workSchedDiv);
|
||||
ganttContainer.append(ganttInfoDiv);
|
||||
ganttContainer.append(ganttDiv);
|
||||
|
||||
$("#container").append(ganttContainer);
|
||||
|
||||
let g = new JSGantt.GanttChart($("#gantt-" + blenderId)[0], "week");
|
||||
g.setOptions({
|
||||
vCaptionType: "Caption", // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: "day dd month yyyy", // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: "mon yyyy - Week ww", // Set format to dates in the "Major" header of the "Day" view
|
||||
vWeekMinorDateDisplayFormat: "dd mon", // Set format to display dates in the "Minor" header of the "Week" view
|
||||
vLang: "en",
|
||||
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ["Day", "Week", "Month", "Quarter"], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vShowRes: true, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {
|
||||
ifcduration: { title: "Duration" },
|
||||
resourceUsage: { title: "Resource Usage" },
|
||||
},
|
||||
vUseToolTip: true, // Disable tooltips.
|
||||
vTooltipTemplate: generateTooltip,
|
||||
vTotalHeight: 900,
|
||||
|
||||
vEventsChange: {
|
||||
taskname: editValue, // if you need to use the this scope, do: editValue.bind(this)
|
||||
res: editValue,
|
||||
dur: editValue,
|
||||
comp: editValue,
|
||||
start: editValue,
|
||||
end: editValue,
|
||||
planstart: editValue,
|
||||
planend: editValue,
|
||||
cost: editValue,
|
||||
additional_category: editValue,
|
||||
},
|
||||
});
|
||||
JSGantt.addJSONTask(g, tasks);
|
||||
g.setEditable(true);
|
||||
g.Draw();
|
||||
|
||||
connectedClients[blenderId]["gantt"] = g;
|
||||
|
||||
let printButton = $("<button>", {
|
||||
id: "print-btn-" + blenderId,
|
||||
html: "Print",
|
||||
class: "btn no-print",
|
||||
});
|
||||
|
||||
let printOptions = $("<select>", {
|
||||
id: "print-options-" + blenderId,
|
||||
class: "no-print",
|
||||
});
|
||||
|
||||
$.each(pageSizes, function (index, size) {
|
||||
printOptions.append(
|
||||
$("<option>", {
|
||||
value: size.value,
|
||||
text: size.text,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
printButton.on("click", function () {
|
||||
// make it only the corresponding gantt chart is printed
|
||||
$(".gantt-chart").removeClass("no-print");
|
||||
$(".gantt-chart")
|
||||
.not("#gantt-" + blenderId)
|
||||
.addClass("your-css-class");
|
||||
|
||||
var values = $("#print-options-" + blenderId)
|
||||
.val()
|
||||
.split(",");
|
||||
|
||||
g.setEditable(false);
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
|
||||
addEventListener("afterprint", (event) => {
|
||||
g.setEditable(true);
|
||||
});
|
||||
|
||||
let css =
|
||||
"@media print {\n" +
|
||||
" @page {\n" +
|
||||
" size: " +
|
||||
values[0] +
|
||||
"mm " +
|
||||
values[1] +
|
||||
"mm;\n" +
|
||||
" }\n" +
|
||||
" /* Make all text black */\n" +
|
||||
" body, p, span, h1, h2, h3, h4, h5, h6, div, a, li, td, th, * {\n" +
|
||||
" color: black !important;\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
g.printChart(values[0], values[1], css);
|
||||
g.setTotalHeight(900);
|
||||
g.Draw();
|
||||
});
|
||||
|
||||
ganttContainer.append(printOptions);
|
||||
ganttContainer.append(printButton);
|
||||
}
|
||||
|
||||
// Function to update gantt and filename
|
||||
function updateGanttElement(blenderId, tasks, workSched, filename) {
|
||||
// update work schedule table
|
||||
const table = $("#workSchedTable-" + blenderId);
|
||||
table.empty();
|
||||
$.each(workSched, (key, value) => {
|
||||
value = value ? value : "null";
|
||||
$("<tr></tr>")
|
||||
.append($("<td></td>").text(key))
|
||||
.append($("<td></td>").text(value))
|
||||
.appendTo(table);
|
||||
});
|
||||
|
||||
// update gantt chart with new data
|
||||
let g = connectedClients[blenderId]["gantt"];
|
||||
g.ClearTasks();
|
||||
g.Draw();
|
||||
JSGantt.addJSONTask(g, tasks);
|
||||
g.Draw();
|
||||
|
||||
$("#title-" + blenderId).text(filename);
|
||||
$("#warning-" + blenderId).css("display", "none");
|
||||
}
|
||||
|
||||
// Function to remove gantt element
|
||||
function removeGanttElement(blenderId) {
|
||||
$("#container-" + blenderId).remove();
|
||||
}
|
||||
|
||||
function showWarning(blenderId, isDirty) {
|
||||
$("#warning-" + blenderId).css("display", "block");
|
||||
}
|
||||
|
||||
// Utility function to create a tooltip for the gantt chars
|
||||
function generateTooltip(task) {
|
||||
var dataObject = task.getDataObject();
|
||||
var numberResources = dataObject.resourceUsage
|
||||
? dataObject.resourceUsage
|
||||
: "NULL";
|
||||
return `
|
||||
<dl>
|
||||
<dt>Name:</dt><dd>{{pName}}</dd>
|
||||
<dt>Start:</dt><dd>{{pStart}}</dd>
|
||||
<dt>End:</dt><dd>{{pEnd}}</dd>
|
||||
<dt>Duration:</dt><dd>${dataObject.ifcduration}</dd>
|
||||
<dt>Number of Resources:</dt><dd>${numberResources}</dd>
|
||||
<dt>Resources:</dt><dd>{{pRes}}</dd>
|
||||
</dl>
|
||||
`;
|
||||
}
|
||||
|
||||
// Event handlers for editing gantt table data
|
||||
function editValue(list, task, event, cell, column) {
|
||||
console.log("editValue function called with the following parameters:");
|
||||
console.log("list:", list);
|
||||
console.log("task:", task);
|
||||
console.log("event:", event);
|
||||
console.log("cell:", cell);
|
||||
console.log("column:", column);
|
||||
|
||||
const ganttId = task.getGantt()["vDiv"].id;
|
||||
const index = ganttId.indexOf("-") + 1;
|
||||
const blenderId = ganttId.substring(index);
|
||||
const workSchedId = connectedClients[blenderId].workSchedule.id;
|
||||
|
||||
// update data object reprsenting the task
|
||||
const dataObj = task.getDataObject();
|
||||
dataObj[column] = event.target.value;
|
||||
task.setDataObject(dataObj);
|
||||
|
||||
const msg = {
|
||||
sourcePage: "gantt",
|
||||
blenderId: blenderId,
|
||||
operator: {
|
||||
type: "editTask",
|
||||
workScheduleId: workSchedId,
|
||||
taskId: task.getOriginalID(),
|
||||
column: column,
|
||||
value: event.target.value,
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
if (theme === "light") {
|
||||
$("html").removeClass("dark").addClass("light");
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else {
|
||||
$("html").removeClass("light").addClass("dark");
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if ($("html").hasClass("dark")) {
|
||||
setTheme("light");
|
||||
} else {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.workSchedule && client.gantt) {
|
||||
const clientScheduleName = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Schedule Name: ${client.workSchedule.Name}`);
|
||||
|
||||
const clientScheduleDate = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(
|
||||
`Schedule Date: ${new Date(
|
||||
client.workSchedule.CreationDate
|
||||
).toLocaleDateString()}`
|
||||
);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Gantt Chart")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#gantt-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientScheduleName);
|
||||
clientDetailsDiv.append(clientScheduleDate);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web Client</title>
|
||||
<link rel="stylesheet" type="text/css" href="/jsgantt/jsgantt.css" />
|
||||
<link rel="stylesheet" href="/static/css/gantt.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script type="text/javascript" src="./jsgantt/jsgantt.js"></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script defer src="./static/js/gantt.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="no-print">
|
||||
<img
|
||||
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
<li><a href="/gantt" class="active">Gantt Chart</a></li>
|
||||
<li><a href="/drawings">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,60 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.spatial.data import SpatialData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def update_relating_object(self, context):
|
||||
def message(self, context):
|
||||
self.layout.label(text="Please select a valid IFC Element")
|
||||
|
||||
if self.relating_object:
|
||||
self.related_object = None
|
||||
if not self.relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
context.window_manager.popup_menu(message, title="Invalid Element Selected", icon="INFO")
|
||||
self.relating_object = None
|
||||
|
||||
|
||||
def update_related_object(self, context):
|
||||
def message(self, context):
|
||||
self.layout.label(text="Please select a valid IFC Element")
|
||||
|
||||
if self.related_object:
|
||||
self.relating_object = None
|
||||
if not self.related_object.BIMObjectProperties.ifc_definition_id:
|
||||
context.window_manager.popup_menu(message, title="Invalid Element Selected", icon="INFO")
|
||||
self.related_object = None
|
||||
|
||||
|
||||
class BIMObjectAggregateProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_object: PointerProperty(name="Relating Whole", type=bpy.types.Object, update=update_relating_object)
|
||||
related_object: PointerProperty(name="Related Part", type=bpy.types.Object, update=update_related_object)
|
||||
@@ -1,308 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
import ifcopenshell.util.unit
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
|
||||
|
||||
class CadTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "EDIT_MESH"
|
||||
|
||||
bl_idname = "bim.cad_tool"
|
||||
bl_label = "CAD Tool"
|
||||
bl_description = "Gives you CAD authoring related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
|
||||
bl_widget = None
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.cad_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
||||
("bim.cad_hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
||||
("bim.cad_hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||
("bim.cad_hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
||||
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.cad_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
||||
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, workspace_tool):
|
||||
obj = context.active_object
|
||||
if not obj or not obj.data:
|
||||
return
|
||||
if hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if element.is_a("IfcProfileDef"):
|
||||
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
|
||||
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
|
||||
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
row.operator("bim.edit_boundary_geometry", text="Save Profile")
|
||||
row.operator("bim.disable_editing_boundary_geometry", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.edit_extrusion_profile", text="Save Profile")
|
||||
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
|
||||
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_R")
|
||||
row.operator("bim.add_rectangle", text="Rectangle")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_C")
|
||||
row.operator("bim.add_ifccircle", text="Circle")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_V")
|
||||
row.operator("bim.set_arc_index", text="3-Point Arc")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_X")
|
||||
row.operator("bim.reset_vertex", text="Reset Vertex")
|
||||
|
||||
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.edit_extrusion_axis", text="Save Axis")
|
||||
row.operator("bim.disable_editing_extrusion_axis", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
|
||||
|
||||
else:
|
||||
if (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["pset_data"]
|
||||
and context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon=f"EVENT_TAB")
|
||||
row.operator("bim.finish_editing_railing_path")
|
||||
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
and context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon=f"EVENT_TAB")
|
||||
row.operator("bim.finish_editing_roof_path")
|
||||
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_R")
|
||||
row.operator("bim.cad_hotkey", text="Set Gable Roof Angle").hotkey = "S_R"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="2-Point Arc", icon="EVENT_C")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_V")
|
||||
row.operator("bim.cad_hotkey", text="3-Point Arc").hotkey = "S_V"
|
||||
|
||||
|
||||
class CadHotkey(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_hotkey"
|
||||
bl_label = "CAD Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMCadProperties
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMCadProperties
|
||||
if self.hotkey == "S_C":
|
||||
if self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "radius")
|
||||
|
||||
elif self.hotkey == "S_F":
|
||||
if not self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "resolution")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "radius")
|
||||
|
||||
elif self.hotkey == "S_O":
|
||||
row = self.layout.row()
|
||||
row.prop(props, "distance")
|
||||
|
||||
elif self.hotkey == "S_R":
|
||||
if self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "x")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "y")
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
self.layout.row().prop(props, "gable_roof_edge_angle")
|
||||
self.layout.row().prop(props, "gable_roof_separate_verts")
|
||||
|
||||
elif self.hotkey == "S_V":
|
||||
if not self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "resolution")
|
||||
|
||||
def hotkey_S_C(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
|
||||
else:
|
||||
bpy.ops.bim.cad_arc_from_2_points()
|
||||
|
||||
def hotkey_S_E(self):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
def hotkey_S_F(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius / si_conversion)
|
||||
else:
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius / si_conversion)
|
||||
|
||||
def hotkey_S_O(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
if element.is_a("IfcProfileDef"):
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
bpy.ops.bim.edit_boundary_geometry()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if self.is_profile():
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.set_gable_roof_edge_angle(
|
||||
angle=self.props.gable_roof_edge_angle, separate_verts=self.props.gable_roof_separate_verts
|
||||
)
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.cad_mitre()
|
||||
|
||||
def hotkey_S_V(self):
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.set_arc_index()
|
||||
else:
|
||||
bpy.ops.bim.cad_arc_from_3_points(resolution=self.props.resolution)
|
||||
|
||||
def hotkey_S_X(self):
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.reset_vertex()
|
||||
|
||||
def is_profile(self):
|
||||
obj = bpy.context.active_object
|
||||
return (
|
||||
obj
|
||||
and obj.data
|
||||
and hasattr(obj.data, "BIMMeshProperties")
|
||||
and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
[
|
||||
{
|
||||
"ceiling": {
|
||||
"Black Ceiling 1": "void plastic black_ceiling\n0\n0\n5 0.0460 0.0456 0.0463 0.0052 0.2000",
|
||||
"Black Ceiling 2": "void plastic black_ceiling\n0\n0\n5 0.1116 0.1085 0.0938 0.0000 0.0000",
|
||||
"Plastic Ceiling Vent E14 526": "void plastic plastic_ceiling_vent_e14_526\n0\n0\n5 0.7384 0.7195 0.6540 0.0110 0.0500",
|
||||
"Specular Reflective Ceiling Panels": "void plastic specular_reflective_ceiling_panels\n0\n0\n5 0.7708 0.7673 0.6819 0.0741 0.0500"
|
||||
},
|
||||
"Door": {
|
||||
"Wooden Door": "void plastic wooden_door\n0\n0\n5 0.6164 0.4257 0.2156 0.0185 0.2000",
|
||||
"Wooden Door 2": "void plastic wooden_door_2\n0\n0\n5 0.0403 0.0388 0.0406 0.0121 0.2000",
|
||||
"White Wooden Door": "void plastic white_wooden_door\n0\n0\n5 0.7893 0.7502 0.6413 0.0080 0.2000",
|
||||
"Off White Door": "void plastic off_white_door\n0\n0\n5 0.8607 0.8611 0.7852 0.0258 0.2000",
|
||||
"Bedroom Door": "void plastic bedroom_door\n0\n0\n5 0.2508 0.0782 0.0111 0.0279 0.2000"
|
||||
},
|
||||
"Floor": {
|
||||
"Light Blue Ceramic": "void plastic light_blue_ceramic\n0\n0\n5 0.7477 0.7776 0.7807 0.0323 0.1000",
|
||||
"Light Grey Ceramic": "void plastic light_grey_ceramic\n0\n0\n5 0.6386 0.5941 0.4982 0.0106 0.2000",
|
||||
"Purple Carpet": "void plastic purple_carpet\n0\n0\n5 0.0425 0.0179 0.0371 0.0001 0.3000"
|
||||
},
|
||||
"Wall": {
|
||||
"Dirty Dark Pink Painted Wall": "void plastic dirty_dark_pink_painted_wall\n0\n0\n5 0.3533 0.1751 0.1690 0.0000 0.2000",
|
||||
"Blue Green Painted Walls": "void plastic blue_green_painted_walls\n0\n0\n5 -0.0013 0.2092 0.2601 0.0140 0.2000",
|
||||
"Red Painted Wall": "void plastic red_painted_wall\n0\n0\n5 0.4621 0.0156 0.0091 0.0396 0.2000"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,293 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gpu
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from math import sin, cos, radians
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from typing import Union
|
||||
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
|
||||
class ProfileDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
"""Note that operators that change mesh in `exit_edit_mode_callback` can freeze blender.
|
||||
The workaround is to move their code to function and use it for callback.
|
||||
|
||||
Example: https://devtalk.blender.org/t/calling-operator-that-saves-bmesh-freezes-blender-forever/28595"""
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(
|
||||
handler, (context, get_custom_bmesh, draw_faces, exit_edit_mode_callback), "WINDOW", "POST_VIEW"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_faces(self, bm, vertices_coords):
|
||||
"""mutates original bm (triangulates it)
|
||||
so the triangulation edges will be shown too
|
||||
"""
|
||||
traingulated_bm = bm
|
||||
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
|
||||
|
||||
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
|
||||
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
|
||||
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
|
||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
error_elements_color = self.addon_prefs.decorator_color_error
|
||||
background_elements_color = self.addon_prefs.decorator_color_background
|
||||
|
||||
obj = context.active_object
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
if exit_edit_mode_callback:
|
||||
ProfileDecorator.uninstall()
|
||||
exit_edit_mode_callback()
|
||||
return
|
||||
|
||||
if get_custom_bmesh:
|
||||
bm = get_custom_bmesh()
|
||||
else:
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
### Actually drawing
|
||||
all_vertices = []
|
||||
error_vertices = []
|
||||
selected_vertices = []
|
||||
unselected_vertices = []
|
||||
# special = associated with arcs/circles
|
||||
special_vertices = []
|
||||
special_vertex_indices = {}
|
||||
selected_edges = []
|
||||
unselected_edges = []
|
||||
arc_edges = []
|
||||
roof_angle_edges = []
|
||||
preview_edges = []
|
||||
|
||||
arc_groups = []
|
||||
circle_groups = []
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
if "IFCARCINDEX" in group.name:
|
||||
arc_groups.append(i)
|
||||
elif "IFCCIRCLE" in group.name:
|
||||
circle_groups.append(i)
|
||||
|
||||
arcs = {}
|
||||
circles = {}
|
||||
|
||||
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
||||
preview_layer = bm.edges.layers.int.get("BBIM_preview")
|
||||
|
||||
for vertex in bm.verts:
|
||||
co = tuple(obj.matrix_world @ vertex.co)
|
||||
all_vertices.append(co)
|
||||
if vertex.hide:
|
||||
continue
|
||||
|
||||
is_arc, is_circle = False, False
|
||||
# deform_layer is None if there are no verts assigned to vertex groups
|
||||
# even if there are vertex groups in the obj.vertex_groups
|
||||
if deform_layer:
|
||||
is_arc, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, arc_groups)
|
||||
if is_arc:
|
||||
arcs.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
is_circle, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, circle_groups)
|
||||
if is_circle:
|
||||
circles.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
if vertex.select:
|
||||
selected_vertices.append(co)
|
||||
else:
|
||||
if len(vertex.link_edges) > 1 and is_circle:
|
||||
error_vertices.append(co)
|
||||
elif is_circle:
|
||||
special_vertices.append(co)
|
||||
elif len(vertex.link_edges) != 2:
|
||||
error_vertices.append(co)
|
||||
elif is_arc:
|
||||
special_vertices.append(co)
|
||||
else:
|
||||
unselected_vertices.append(co)
|
||||
|
||||
for edge in bm.edges:
|
||||
edge_indices = [v.index for v in edge.verts]
|
||||
if edge.hide:
|
||||
continue
|
||||
if edge.select:
|
||||
selected_edges.append(edge_indices)
|
||||
else:
|
||||
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
||||
# making sure that both vertices are in the same group
|
||||
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
||||
arc_edges.append(edge_indices)
|
||||
elif angle_layer and edge[angle_layer] > 0:
|
||||
roof_angle_edges.append(edge_indices)
|
||||
elif preview_layer and edge[preview_layer] == 1:
|
||||
preview_edges.append(edge_indices)
|
||||
else:
|
||||
unselected_edges.append(edge_indices)
|
||||
|
||||
### Actually drawing
|
||||
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind()
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
self.shader.bind()
|
||||
|
||||
# Draw faces
|
||||
if draw_faces:
|
||||
self.draw_faces(bm, all_vertices)
|
||||
|
||||
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
|
||||
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("LINES", all_vertices, background_elements_color, arc_edges)
|
||||
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
|
||||
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
|
||||
|
||||
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
|
||||
self.draw_batch("POINTS", error_vertices, error_elements_color)
|
||||
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
||||
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
||||
|
||||
# Draw arcs
|
||||
arc_centroids = []
|
||||
arc_segments = []
|
||||
for arc in arcs.values():
|
||||
if len(arc) != 3:
|
||||
continue
|
||||
sorted_arc = [None, None, None]
|
||||
for v1 in arc:
|
||||
connections = 0
|
||||
for link_edge in v1.link_edges:
|
||||
v2 = link_edge.other_vert(v1)
|
||||
if v2 in arc:
|
||||
connections += 1
|
||||
if connections == 2: # Midpoint
|
||||
sorted_arc[1] = v1
|
||||
else:
|
||||
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
||||
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
||||
centroid = tool.Cad.get_center_of_arc(points)
|
||||
if centroid:
|
||||
arc_centroids.append(tuple(centroid))
|
||||
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
||||
|
||||
self.draw_batch("POINTS", arc_centroids, background_elements_color)
|
||||
for verts, edges in arc_segments:
|
||||
self.draw_batch("LINES", verts, special_elements_color, edges)
|
||||
|
||||
# Draw circles
|
||||
circle_centroids = []
|
||||
circle_segments = []
|
||||
for circle in circles.values():
|
||||
if len(circle) != 2:
|
||||
continue
|
||||
p1 = obj.matrix_world @ circle[0].co
|
||||
p2 = obj.matrix_world @ circle[1].co
|
||||
radius = (p2 - p1).length / 2
|
||||
centroid = p1.lerp(p2, 0.5)
|
||||
circle_centroids.append(tuple(centroid))
|
||||
segments = self.create_circle_segments(360, 20, radius)
|
||||
matrix = obj.matrix_world.copy()
|
||||
matrix.translation = centroid
|
||||
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
||||
circle_segments.append(segments)
|
||||
|
||||
self.draw_batch("POINTS", circle_centroids, background_elements_color)
|
||||
for verts, edges in circle_segments:
|
||||
self.draw_batch("LINES", verts, special_elements_color, edges)
|
||||
|
||||
def create_matrix(self, p, x, y, z):
|
||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||
|
||||
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
|
||||
# This function is taken from Sverchok, licensed under GPL v2-or-later.
|
||||
# This is a combination of the make_verts and make_edges function.
|
||||
def create_circle_segments(self, Angle, Vertices, Radius):
|
||||
if Angle < 360:
|
||||
theta = Angle / (Vertices - 1)
|
||||
else:
|
||||
theta = Angle / Vertices
|
||||
listVertX = []
|
||||
listVertY = []
|
||||
for i in range(Vertices):
|
||||
listVertX.append(Radius * cos(radians(theta * i)))
|
||||
listVertY.append(Radius * sin(radians(theta * i)))
|
||||
|
||||
if Angle < 360 and self.mode_ == 0:
|
||||
sigma = radians(Angle)
|
||||
listVertX[-1] = Radius * cos(sigma)
|
||||
listVertY[-1] = Radius * sin(sigma)
|
||||
elif Angle < 360 and self.mode_ == 1:
|
||||
listVertX.append(0.0)
|
||||
listVertY.append(0.0)
|
||||
|
||||
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
|
||||
|
||||
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
|
||||
|
||||
if Angle < 360 and self.mode_ == 1:
|
||||
listEdg.append((0, Vertices))
|
||||
listEdg.append((Vertices - 1, Vertices))
|
||||
else:
|
||||
listEdg.append((Vertices - 1, 0))
|
||||
|
||||
return points, listEdg
|
||||
@@ -1,81 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.module.model import root, product, wall, slab, profile, opening, task
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def load_post(*args):
|
||||
ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name)
|
||||
ifcopenshell.api.add_pre_listener("style.edit_presentation_style", "BlenderBIM.Root.SyncStyleName", root.sync_name)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"sequence.edit_task_time", "BlenderBIM.Task.CalculateQuantities", task.calculate_quantities
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"material.edit_profile_usage",
|
||||
"BlenderBIM.Product.RegenerateProfileUsage",
|
||||
product.regenerate_profile_usage,
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbWall.CalculateQuantities", wall.calculate_quantities
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"material.edit_layer", "BlenderBIM.DumbWall.RegenerateFromLayer", wall.DumbWallPlaner().regenerate_from_layer
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbSlab.CalculateQuantities", slab.calculate_quantities
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"material.edit_layer", "BlenderBIM.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"material.edit_profile",
|
||||
"BlenderBIM.DumbProfile.RegenerateFromProfile",
|
||||
profile.DumbProfileRegenerator().regenerate_from_profile,
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"type.assign_type",
|
||||
"BlenderBIM.DumbProfile.RegenerateFromType",
|
||||
profile.DumbProfileRegenerator().regenerate_from_type,
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"type.assign_type",
|
||||
"BlenderBIM.Opening.RegenerateFromType",
|
||||
opening.FilledOpeningGenerator().regenerate_from_type,
|
||||
)
|
||||
@@ -1,90 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.aggregate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class OpenPieClass(bpy.types.Operator):
|
||||
bl_idname = "bim.open_pie_class"
|
||||
bl_label = "Open Pie Class"
|
||||
bl_description = "Assign the IFC Class to the selected objects"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object and not context.selected_objects:
|
||||
cls.poll_message_set("No object selected.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_bim_class")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PieAddOpening(bpy.types.Operator):
|
||||
bl_idname = "bim.pie_add_opening"
|
||||
bl_label = "Add Opening"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
if len(context.selected_objects) == 2:
|
||||
opening_name = None
|
||||
obj_name = None
|
||||
for obj in context.selected_objects:
|
||||
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
opening_name = obj.name
|
||||
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
|
||||
opening_name = obj.children[0].name
|
||||
else:
|
||||
obj_name = obj.name
|
||||
bpy.ops.bim.add_opening(obj=obj_name, opening=opening_name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
bl_label = "Geometry"
|
||||
|
||||
def draw(self, context):
|
||||
pie = self.layout.menu_pie()
|
||||
pie.operator("bim.edit_object_placement")
|
||||
pie.operator("bim.update_representation").ifc_representation_class = ""
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
pie.operator("bim.aggregate_assign_object", text="Assign Aggregation")
|
||||
pie.operator("bim.aggregate_unassign_object", text="Unassign Aggregation")
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
|
||||
bl_label = "Class"
|
||||
|
||||
def draw(self, context):
|
||||
pie = self.layout.menu_pie()
|
||||
pie.operator("bim.assign_class", text="IfcWall").ifc_class = "IfcWall"
|
||||
pie.operator("bim.assign_class", text="IfcSlab").ifc_class = "IfcSlab"
|
||||
pie.operator("bim.assign_class", text="IfcStair").ifc_class = "IfcStair"
|
||||
pie.operator("bim.assign_class", text="IfcDoor").ifc_class = "IfcDoor"
|
||||
pie.operator("bim.assign_class", text="IfcWindow").ifc_class = "IfcWindow"
|
||||
pie.operator("bim.assign_class", text="IfcColumn").ifc_class = "IfcColumn"
|
||||
pie.operator("bim.assign_class", text="IfcBeam").ifc_class = "IfcBeam"
|
||||
@@ -1,870 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.model as core
|
||||
from blenderbim.bim.module.model.wall import DumbWallJoiner
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.system.data import PortData
|
||||
from blenderbim.bim.module.model.prop import get_ifc_class
|
||||
|
||||
|
||||
class BimTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
|
||||
bl_idname = "bim.bim_tool"
|
||||
bl_label = "Create Element"
|
||||
bl_description = "Gives you BIM authoring related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.bim")
|
||||
bl_widget = None
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
|
||||
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
|
||||
# ("bim.sync_modeling", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
|
||||
("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.hotkey", {"type": "B", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_B")]}),
|
||||
("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
||||
("bim.hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
||||
("bim.hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
||||
("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
||||
("bim.hotkey", {"type": "L", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_L")]}),
|
||||
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||
("bim.hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
||||
("bim.hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
||||
("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
("bim.hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
|
||||
("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
|
||||
("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
|
||||
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
|
||||
("bim.hotkey", {"type": "P", "value": "PRESS", "ctrl": True}, {"properties": [("hotkey", "C_P")]}),
|
||||
("bim.hotkey", {"type": "P", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_P")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
# Unlike operators, Blender doesn't treat workspace tools as a class, so we'll create our own.
|
||||
BimToolUI.draw(context, layout, ifc_element_type="all")
|
||||
|
||||
|
||||
class WallTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.wall_tool"
|
||||
bl_label = "Create Wall"
|
||||
bl_description = "Create and edit walls"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.wall")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcWallType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class SlabTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.slab_tool"
|
||||
bl_label = "Create Slab"
|
||||
bl_description = "Create and edit slabs"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.slab")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcSlabType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class DoorTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.door_tool"
|
||||
bl_label = "Create Door"
|
||||
bl_description = "Create and edit doors"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.door")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcDoorType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class WindowTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.window_tool"
|
||||
bl_label = "Create Window"
|
||||
bl_description = "Create and edit windows"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.window")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcWindowType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class ColumnTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.column_tool"
|
||||
bl_label = "Create Column"
|
||||
bl_description = "Create and edit columns"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.column")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcColumnType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class BeamTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.beam_tool"
|
||||
bl_label = "Create Beam"
|
||||
bl_description = "Create and edit beams"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.beam")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcBeamType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class DuctTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.duct_tool"
|
||||
bl_label = "Create Duct"
|
||||
bl_description = "Create and edit ducks" # No, not a typo.
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.duct")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcDuctSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class CableCarrierTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.cable_carrier_tool"
|
||||
bl_label = "Create Cable Carrier"
|
||||
bl_description = "Create and edit cable carriers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cablecarrier")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCableCarrierSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class PipeTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.pipe_tool"
|
||||
bl_label = "Create Pipe"
|
||||
bl_description = "Create and edit pipes"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.pipe")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcPipeSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class CableTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.cable_tool"
|
||||
bl_label = "Create Cable"
|
||||
bl_description = "Create and edit cables"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cable")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCableSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
modifiers = {
|
||||
"A": "EVENT_ALT",
|
||||
"C": "EVENT_CTRL",
|
||||
"S": "EVENT_SHIFT",
|
||||
}
|
||||
modifier, key = hotkey.split("_")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon=modifiers[modifier])
|
||||
row.label(text="", icon=f"EVENT_{key}")
|
||||
|
||||
op = row.operator("bim.hotkey", text=text)
|
||||
op.hotkey = hotkey
|
||||
op.description = description
|
||||
return op
|
||||
|
||||
|
||||
class BimToolUI:
|
||||
@classmethod
|
||||
def draw(cls, context, layout, ifc_element_type=None):
|
||||
cls.layout = layout
|
||||
cls.props = context.scene.BIMModelProperties
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
if not tool.Ifc.get():
|
||||
row.label(text="No IFC Project", icon="ERROR")
|
||||
return
|
||||
|
||||
if not PortData.is_loaded:
|
||||
PortData.load()
|
||||
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load(ifc_element_type)
|
||||
elif ifc_element_type == "all" and AuthoringData.data["ifc_element_type"] is not None:
|
||||
AuthoringData.load("all")
|
||||
elif AuthoringData.data["ifc_element_type"] != ifc_element_type:
|
||||
AuthoringData.load(ifc_element_type)
|
||||
|
||||
if context.region.type == "TOOL_HEADER":
|
||||
cls.draw_header_interface()
|
||||
elif context.region.type in ("UI", "WINDOW"):
|
||||
# same interface for both n-panel sidebar and object properties
|
||||
cls.draw_basic_bim_tool_interface()
|
||||
|
||||
if context.active_object and context.selected_objects:
|
||||
cls.draw_edit_object_interface(context)
|
||||
elif not context.selected_objects:
|
||||
cls.draw_create_object_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_create_object_interface(cls):
|
||||
if not AuthoringData.data["relating_type_id"]:
|
||||
return
|
||||
if cls.props.ifc_class == "IfcWallType":
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl1", text="RL")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="extrusion_depth", text="Height")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="length", text="Length")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="x_angle", text="X Angle")
|
||||
elif cls.props.ifc_class in ("IfcSlabType", "IfcRampType", "IfcRoofType"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="x_angle", text="X Angle")
|
||||
elif cls.props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="cardinal_point", text="Axis")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
label = "Height" if cls.props.ifc_class == "IfcColumn" else "Length"
|
||||
row.prop(data=cls.props, property="extrusion_depth", text=label)
|
||||
elif cls.props.ifc_class in ("IfcDoorType", "IfcDoorStyle"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl1", text="RL")
|
||||
elif cls.props.ifc_class in (
|
||||
"IfcWindowType",
|
||||
"IfcWindowStyle",
|
||||
"IfcDoorType",
|
||||
"IfcDoorStyle",
|
||||
"IfcDuctSegmentType",
|
||||
"IfcPipeSegmentType",
|
||||
"IfcCableCarrierSegmentType",
|
||||
"IfcCableSegmentType",
|
||||
):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl2", text="RL")
|
||||
elif cls.props.ifc_class in ("IfcSpaceType"):
|
||||
add_layout_hotkey_operator(cls.layout, "Generate", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
else:
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl_mode", text="RL")
|
||||
|
||||
@classmethod
|
||||
def draw_edit_object_interface(cls, context):
|
||||
if AuthoringData.data["active_material_usage"] == "LAYER2":
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="extrusion_depth", text="Height")
|
||||
op = row.operator("bim.change_extrusion_depth", icon="FILE_REFRESH", text="")
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="length", text="Length")
|
||||
op = row.operator("bim.change_layer_length", icon="FILE_REFRESH", text="")
|
||||
op.length = cls.props.length
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="x_angle", text="X Angle")
|
||||
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
||||
op.x_angle = cls.props.x_angle
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout,
|
||||
"Mitre",
|
||||
"S_Y",
|
||||
"Join two intersecting walls using a mitre joint.\nOther selected wall is connected to the active",
|
||||
)
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Merge", "S_M", bpy.ops.bim.merge_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Split", "S_K", bpy.ops.bim.split_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_wall.__doc__)
|
||||
row.operator("bim.unjoin_walls", icon="X", text="")
|
||||
|
||||
elif AuthoringData.data["active_material_usage"] == "LAYER3":
|
||||
if len(context.selected_objects) == 1:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
|
||||
elif "LAYER2" in AuthoringData.data["selected_material_usages"]:
|
||||
add_layout_hotkey_operator(cls.layout, "Extend Wall To Slab", "S_E", "")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="x_angle", text="X Angle")
|
||||
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
||||
op.x_angle = cls.props.x_angle
|
||||
|
||||
elif AuthoringData.data["active_material_usage"] == "PROFILE":
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="cardinal_point", text="Axis")
|
||||
op = row.operator("bim.change_cardinal_point", icon="FILE_REFRESH", text="")
|
||||
op.cardinal_point = int(cls.props.cardinal_point)
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
label = (
|
||||
"Height" if AuthoringData.data["active_class"] in ("IfcColumn", "IfcColumnStandardCase") else "Length"
|
||||
)
|
||||
row.prop(data=cls.props, property="extrusion_depth", text=label)
|
||||
op = row.operator("bim.change_profile_depth", icon="FILE_REFRESH", text="")
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
|
||||
if AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegment",
|
||||
"IfcCableSegment",
|
||||
"IfcDuctSegment",
|
||||
"IfcPipeSegment",
|
||||
):
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_Y", "")
|
||||
if context.region.type != "TOOL_HEADER":
|
||||
cls.layout.operator("bim.mep_add_bend")
|
||||
cls.layout.operator("bim.mep_add_transition")
|
||||
cls.layout.operator("bim.mep_add_obstruction")
|
||||
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
|
||||
row.operator("bim.extend_profile", icon="X", text="").join_type = ""
|
||||
|
||||
elif (
|
||||
tool.Model.is_parametric_railing_active() and not context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
# NOTE: should be above "active_representation_type" = "SweptSolid" check
|
||||
# because it could be a SweptSolid too
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_TAB")
|
||||
row.operator("bim.enable_editing_railing_path", text="Edit Railing Path")
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcWindow",
|
||||
"IfcWindowStandardCase",
|
||||
"IfcDoor",
|
||||
"IfcDoorStandardCase",
|
||||
):
|
||||
if AuthoringData.data["active_class"] in ("IfcWindow", "IfcWindowStandardCase"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl2", text="RL")
|
||||
elif AuthoringData.data["active_class"] in ("IfcDoor", "IfcDoorStandardCase"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl1", text="RL")
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_fill.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", "")
|
||||
|
||||
elif AuthoringData.data["active_representation_type"] == "SweptSolid":
|
||||
if not tool.Model.is_parametric_window_active() and not tool.Model.is_parametric_door_active():
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
|
||||
|
||||
elif AuthoringData.data["active_class"] in ("IfcSpace",):
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
elif tool.Model.is_parametric_roof_active() and not context.active_object.BIMRoofProperties.is_editing_path:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_TAB")
|
||||
row.operator("bim.enable_editing_roof_path", text="Edit Roof Path")
|
||||
|
||||
if context.region.type != "TOOL_HEADER" and PortData.data["total_ports"] > 0:
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout, "Regen MEP", "S_G", bpy.ops.bim.regenerate_distribution_element.__doc__
|
||||
)
|
||||
cls.layout.operator("bim.mep_connect_elements")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
|
||||
if len(context.selected_objects) > 1:
|
||||
row.operator("bim.add_opening", text="Apply Void")
|
||||
else:
|
||||
row.operator("bim.add_potential_opening", text="Add Void")
|
||||
|
||||
if AuthoringData.data["is_voidable_element"]:
|
||||
if AuthoringData.data["has_visible_openings"]:
|
||||
row.operator("bim.edit_openings", icon="CHECKMARK", text="")
|
||||
row.operator("bim.hide_openings", icon="CANCEL", text="")
|
||||
else:
|
||||
row.operator("bim.show_openings", icon="HIDE_OFF", text="")
|
||||
|
||||
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
|
||||
row.operator("bim.edit_openings", icon="CHECKMARK", text="")
|
||||
row.operator("bim.hide_openings", icon="CANCEL", text="")
|
||||
if len(context.selected_objects) == 2:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_L")
|
||||
row.operator("bim.clone_opening", text="Clone Opening")
|
||||
|
||||
cls.layout.row(align=True).label(text="Align")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Exterior", "S_X", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Centerline", "S_C", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Interior", "S_V", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mirror", "S_M", bpy.ops.bim.mirror_elements.__doc__)
|
||||
|
||||
cls.layout.row(align=True).label(text="Mode")
|
||||
add_layout_hotkey_operator(cls.layout, "Void", "A_O", "Toggle openings")
|
||||
add_layout_hotkey_operator(cls.layout, "Decomposition", "A_D", "Select decomposition")
|
||||
|
||||
cls.layout.row(align=True).label(text="Aggregation")
|
||||
add_layout_hotkey_operator(cls.layout, "Assign", "C_P", bpy.ops.bim.aggregate_assign_object.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Unassign", "A_P", bpy.ops.bim.aggregate_unassign_object.__doc__)
|
||||
|
||||
cls.layout.separator()
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout, "Perform Quantity Take-off", "S_Q", bpy.ops.bim.perform_quantity_take_off.__doc__
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def draw_header_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls):
|
||||
# shared by both sidebar and header
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text=f"Container: {AuthoringData.data['default_container']}", icon="OUTLINER_COLLECTION")
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
if not AuthoringData.data["ifc_element_type"]:
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
prop_with_search(row, cls.props, "ifc_class", text="")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
if AuthoringData.data["relating_type_id"]:
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
if AuthoringData.data["ifc_element_type"]:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
row = cls.layout.row()
|
||||
row.prop(cls.props, "type_name")
|
||||
row = cls.layout.row(align=True)
|
||||
op = row.operator(
|
||||
"bim.add_default_type", icon="ADD", text=f"Add {AuthoringData.data['ifc_element_type']}"
|
||||
)
|
||||
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
row.label(text="No Element Types Found", icon="ERROR")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
|
||||
|
||||
@classmethod
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
if cls.props.ifc_class:
|
||||
box = cls.layout.box()
|
||||
if AuthoringData.data["type_thumbnail"]:
|
||||
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
|
||||
else:
|
||||
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
|
||||
op.ifc_class = cls.props.ifc_class
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
|
||||
class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.hotkey"
|
||||
bl_label = "Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
description: bpy.props.StringProperty()
|
||||
x: bpy.props.FloatProperty(name="X", default=0.5)
|
||||
y: bpy.props.FloatProperty(name="Y", default=0.5)
|
||||
z: bpy.props.FloatProperty(name="Z", default=0.5)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return operator.description or ""
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.BIMModelProperties
|
||||
self.has_ifc_class = True
|
||||
|
||||
self.active_class = None
|
||||
self.active_material_usage = None
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if element:
|
||||
self.active_class = element.is_a()
|
||||
self.active_material_usage = tool.Model.get_usage_type(element)
|
||||
|
||||
if get_ifc_class(None, None):
|
||||
try:
|
||||
self.has_ifc_class = bool(self.props.ifc_class)
|
||||
except:
|
||||
pass
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
|
||||
self.props = context.scene.BIMModelProperties
|
||||
self.x = self.props.x
|
||||
self.y = self.props.y
|
||||
self.z = self.props.z
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
if self.hotkey == "S_O":
|
||||
row = self.layout.row()
|
||||
row.prop(self, "x")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "y")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "z")
|
||||
|
||||
def hotkey_S_A(self):
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
|
||||
bpy.ops.bim.perform_quantity_take_off()
|
||||
|
||||
def hotkey_C_P(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
bpy.ops.bim.aggregate_assign_object()
|
||||
|
||||
def hotkey_A_P(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
bpy.ops.bim.aggregate_unassign_object()
|
||||
|
||||
def hotkey_S_C(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
if bpy.ops.bim.align_wall.poll():
|
||||
bpy.ops.bim.align_wall(align_type="CENTERLINE")
|
||||
else:
|
||||
bpy.ops.bim.align_product(align_type="CENTERLINE")
|
||||
|
||||
def hotkey_S_E(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
|
||||
# NOTE: placing it before the other operations because railing can also be SweptSolid
|
||||
# and it might conflict with one of the conditions below
|
||||
if (
|
||||
tool.Model.is_parametric_railing_active()
|
||||
and not bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.enable_editing_railing_path()
|
||||
return
|
||||
|
||||
elif tool.Model.is_parametric_roof_active() and not bpy.context.active_object.BIMRoofProperties.is_editing_path:
|
||||
# undo the unselection done above because roof has no usage type
|
||||
bpy.ops.bim.enable_editing_roof_path()
|
||||
return
|
||||
|
||||
elif tool.Model.is_parametric_window_active() or tool.Model.is_parametric_door_active():
|
||||
return
|
||||
|
||||
selected_usages = {}
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
usage = tool.Model.get_usage_type(element)
|
||||
if not usage:
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
representation = tool.Geometry.resolve_mapped_representation(representation)
|
||||
if representation and representation.RepresentationType == "SweptSolid":
|
||||
usage = "SWEPTSOLID"
|
||||
else:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
selected_usages.setdefault(usage, []).append(obj)
|
||||
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
if self.active_material_usage == "LAYER3":
|
||||
# Edit LAYER3 profile
|
||||
if bpy.context.active_object and bpy.context.active_object.mode == "OBJECT":
|
||||
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
elif self.active_material_usage == "LAYER2":
|
||||
# Extend LAYER2 to cursor
|
||||
core.extend_walls(
|
||||
tool.Ifc,
|
||||
tool.Blender,
|
||||
tool.Geometry,
|
||||
DumbWallJoiner(),
|
||||
tool.Model,
|
||||
bpy.context.scene.cursor.location,
|
||||
)
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
# Extend PROFILE to cursor
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
else:
|
||||
# Edit SWEPTSOLID profile (assuming single profile for now)
|
||||
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
|
||||
elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []):
|
||||
# Extend PROFILEs to LAYER2
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER2", []) if o != bpy.context.active_object]
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
elif self.active_material_usage == "LAYER3" and selected_usages.get("LAYER2", []):
|
||||
# Extend LAYER2s to LAYER3
|
||||
[o.select_set(False) for o in selected_usages.get("PROFILE", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", []) if o != bpy.context.active_object]
|
||||
try:
|
||||
core.join_walls_TZ(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model)
|
||||
except core.RequireAtLeastTwoLayeredElements as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
|
||||
elif self.active_material_usage == "LAYER2":
|
||||
# Extend LAYER2s to LAYER2
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("PROFILE", [])]
|
||||
try:
|
||||
core.join_walls_TZ(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model)
|
||||
except core.RequireAtLeastTwoLayeredElements as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
# Extend PROFILEs to PROFILE
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER2", [])]
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
def hotkey_S_F(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.flip_object(flip_local_axes="XZ")
|
||||
|
||||
def hotkey_S_G(self):
|
||||
obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not bpy.context.selected_objects:
|
||||
if self.props.ifc_class == "IfcSpaceType":
|
||||
bpy.ops.bim.generate_space()
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.recalculate_wall()
|
||||
elif tool.System.get_ports(element):
|
||||
bpy.ops.bim.regenerate_distribution_element()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
if self.active_class not in (
|
||||
"IfcCableCarrierSegment",
|
||||
"IfcCableSegment",
|
||||
"IfcDuctSegment",
|
||||
"IfcPipeSegment",
|
||||
):
|
||||
bpy.ops.bim.recalculate_profile()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.recalculate_fill()
|
||||
elif self.active_class in ("IfcSpace"):
|
||||
bpy.ops.bim.generate_space()
|
||||
|
||||
def hotkey_S_M(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.merge_wall()
|
||||
else:
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"At least two objects must be selected: an object to be mirrored, and a mirror axis as the active object.",
|
||||
)
|
||||
else:
|
||||
bpy.ops.bim.mirror_elements()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.rotate_90(axis="Z")
|
||||
elif self.active_class in ("IfcColumn", "IfcColumnStandardCase"):
|
||||
bpy.ops.bim.rotate_90(axis="Z")
|
||||
elif self.active_class in ("IfcBeam", "IfcBeamStandardCase", "IfcMember", "IfcMemberStandardCase"):
|
||||
bpy.ops.bim.rotate_90(axis="Y")
|
||||
|
||||
def hotkey_S_K(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.split_wall()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
try:
|
||||
core.join_walls_LV(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model, join_type="L")
|
||||
except core.RequireTwoWallsError as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.extend_profile(join_type="L")
|
||||
|
||||
def hotkey_S_V(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.align_wall(align_type="INTERIOR")
|
||||
else:
|
||||
bpy.ops.bim.align_product(align_type="POSITIVE")
|
||||
|
||||
def hotkey_S_X(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
if bpy.ops.bim.align_wall.poll():
|
||||
bpy.ops.bim.align_wall(align_type="EXTERIOR")
|
||||
else:
|
||||
bpy.ops.bim.align_product(align_type="NEGATIVE")
|
||||
|
||||
def hotkey_S_Y(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
try:
|
||||
core.join_walls_LV(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model, join_type="V")
|
||||
except core.RequireTwoWallsError as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.extend_profile(join_type="V")
|
||||
|
||||
def hotkey_S_B(self):
|
||||
bpy.ops.bim.add_boundary()
|
||||
|
||||
def hotkey_S_O(self):
|
||||
if len(bpy.context.selected_objects) == 2:
|
||||
bpy.ops.bim.add_opening()
|
||||
else:
|
||||
bpy.ops.bim.add_potential_opening(x=self.x, y=self.y, z=self.z)
|
||||
self.props.x = self.x
|
||||
self.props.y = self.y
|
||||
self.props.z = self.z
|
||||
|
||||
def hotkey_S_L(self):
|
||||
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
|
||||
if len(bpy.context.selected_objects) == 2:
|
||||
bpy.ops.bim.clone_opening()
|
||||
|
||||
def hotkey_A_D(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
bpy.ops.bim.select_decomposition()
|
||||
|
||||
def hotkey_A_E(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
|
||||
def hotkey_A_O(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if AuthoringData.data["has_visible_openings"]:
|
||||
bpy.ops.bim.edit_openings()
|
||||
else:
|
||||
bpy.ops.bim.show_openings()
|
||||
|
||||
|
||||
LIST_OF_TOOLS = [cls.bl_idname for cls in (BimTool.__subclasses__() + [BimTool])]
|
||||
TOOLS_TO_CLASSES_MAP = {cls.bl_idname: cls.ifc_element_type for cls in BimTool.__subclasses__()}
|
||||
@@ -1,78 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def refresh():
|
||||
ProjectData.is_loaded = False
|
||||
LinksData.is_loaded = False
|
||||
|
||||
|
||||
class ProjectData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"export_schema": cls.get_export_schema(),
|
||||
"library_file": cls.library_file(),
|
||||
"template_file": cls.template_file(),
|
||||
"last_saved": cls.last_saved(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def get_export_schema(cls):
|
||||
return [(s, "IFC4X3" if s == "IFC4X3_ADD2" else s, "") for s in IfcStore.schema_identifiers]
|
||||
|
||||
@classmethod
|
||||
def library_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries"))
|
||||
results = [("0", "Custom Library", "")]
|
||||
results.extend([(f, os.path.splitext(f)[0], "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def template_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "projects"))
|
||||
results = [("0", "Blank Project", "")]
|
||||
results.extend([(f, os.path.splitext(f)[0], "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def last_saved(cls):
|
||||
ifc = tool.Ifc.get()
|
||||
if not ifc:
|
||||
return ""
|
||||
try:
|
||||
save_datetime = ifc.wrapped_data.header.file_name.time_stamp
|
||||
save_date, save_time = save_datetime.split("T")
|
||||
return f"{save_date} {':'.join(save_time.split(':')[0:2])}"
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
class LinksData:
|
||||
linked_data = {}
|
||||
enable_culling = False
|
||||
is_loaded = False
|
||||
@@ -1,203 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gpu
|
||||
import bpy
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def toggle_decorations_on_load(*args):
|
||||
if bpy.context.scene.BIMProjectProperties.clipping_planes:
|
||||
ClippingPlaneDecorator.install(bpy.context)
|
||||
else:
|
||||
ClippingPlaneDecorator.uninstall()
|
||||
|
||||
# NOTE: ProjectDecorator cannot be loaded at reopening .blend file
|
||||
# since selected_vertices and other data is stored in queried object's
|
||||
# custom attributes and they get purged after Blender session is closed
|
||||
# as queried object is linked from separate .blend file.
|
||||
|
||||
|
||||
class ProjectDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
|
||||
try:
|
||||
obj = context.scene.BIMProjectProperties.queried_obj
|
||||
selected_vertices = obj["selected_vertices"]
|
||||
selected_edges = obj["selected_edges"]
|
||||
selected_tris = obj["selected_tris"]
|
||||
except:
|
||||
return
|
||||
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
|
||||
|
||||
class ClippingPlaneDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
unselected_vertices = []
|
||||
unselected_edges = []
|
||||
unselected_tris = []
|
||||
|
||||
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||
obj = clipping_plane.obj
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
continue # A profile decorator or something else is used here.
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
obj.data.calc_loop_triangles()
|
||||
|
||||
if obj.select_get():
|
||||
offset = len(selected_vertices)
|
||||
selected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||
selected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||
else:
|
||||
offset = len(unselected_vertices)
|
||||
unselected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||
unselected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||
|
||||
verts = [
|
||||
tuple(obj.matrix_world @ Vector((0, 0, 0))),
|
||||
tuple(obj.matrix_world @ Vector((0, 0, -0.5))),
|
||||
tuple(obj.matrix_world @ Vector((-0.05, 0, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0.05, 0, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0, -0.05, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0, 0.05, -0.45))),
|
||||
]
|
||||
edges = [(0, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
|
||||
color = selected_elements_color if obj in context.selected_objects else special_elements_color
|
||||
self.draw_batch("LINES", verts, color, edges)
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
bm.free()
|
||||
|
||||
if unselected_edges:
|
||||
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
||||
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
@@ -1,308 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.type
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.material
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.style
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.root as core
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import get_enum_items
|
||||
|
||||
|
||||
class EnableReassignClass(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_reassign_class"
|
||||
bl_label = "Enable Reassign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
ifc_class = obj.name.split("/")[0]
|
||||
context.active_object.BIMObjectProperties.is_reassigning_class = True
|
||||
ifc_products = [
|
||||
"IfcElement",
|
||||
"IfcElementType",
|
||||
"IfcSpatialElement",
|
||||
"IfcGroup",
|
||||
"IfcStructural",
|
||||
"IfcPositioningElement",
|
||||
"IfcContext",
|
||||
"IfcAnnotation",
|
||||
"IfcRelSpaceBoundary",
|
||||
]
|
||||
for ifc_product in ifc_products:
|
||||
if ifcopenshell.util.schema.is_a(IfcStore.get_schema().declaration_by_name(ifc_class), ifc_product):
|
||||
context.scene.BIMRootProperties.ifc_product = ifc_product
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
context.scene.BIMRootProperties.ifc_class = element.is_a()
|
||||
context.scene.BIMRootProperties.relating_class_object = None
|
||||
if hasattr(element, "PredefinedType"):
|
||||
if element.PredefinedType:
|
||||
context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
|
||||
userdefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
context.scene.BIMRootProperties.ifc_userdefined_type = userdefined_type or ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableReassignClass(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_reassign_class"
|
||||
bl_label = "Disable Reassign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.active_object.BIMObjectProperties.is_reassigning_class = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ReassignClass(bpy.types.Operator):
|
||||
bl_idname = "bim.reassign_class"
|
||||
bl_label = "Reassign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
if self.obj:
|
||||
objects = [bpy.data.objects.get(self.obj)]
|
||||
else:
|
||||
objects = set(context.selected_objects + [context.active_object])
|
||||
self.file = IfcStore.get_file()
|
||||
predefined_type = context.scene.BIMRootProperties.ifc_predefined_type
|
||||
if predefined_type == "USERDEFINED":
|
||||
predefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
|
||||
|
||||
# NOTE: root.reassign_class
|
||||
# automatically will reassign class for other occurrences of the type
|
||||
# so we need to run it only for the types or non-typed elements
|
||||
elements_to_reassign = set()
|
||||
# need to update blender object name
|
||||
# for all elements that were changed in the process
|
||||
elements_to_update = set()
|
||||
for obj in objects:
|
||||
obj.BIMObjectProperties.is_reassigning_class = False
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element.is_a("IfcTypeObject"):
|
||||
elements_to_reassign.add(element)
|
||||
elements_to_update.update(ifcopenshell.util.element.get_types(element))
|
||||
continue
|
||||
|
||||
# check if element is typed
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
elements_to_reassign.add(element_type)
|
||||
elements_to_update.update(ifcopenshell.util.element.get_types(element_type))
|
||||
continue
|
||||
|
||||
# non-typed element
|
||||
elements_to_reassign.add(element)
|
||||
|
||||
# store elements to objects to update later as elements will get invalid
|
||||
# after class reassignment
|
||||
elements_to_update = elements_to_update | elements_to_reassign
|
||||
objects_to_update = set(o for e in elements_to_update if (o := tool.Ifc.get_object(e)))
|
||||
|
||||
base_class = context.scene.BIMRootProperties.ifc_class
|
||||
if context.scene.BIMRootProperties.ifc_product == "IfcElementType":
|
||||
type_class = base_class
|
||||
occurrence_classes = ifcopenshell.util.type.get_applicable_entities(type_class)
|
||||
occurrence_class = None if len(occurrence_classes) == 0 else occurrence_classes[0]
|
||||
else:
|
||||
occurrence_class = base_class
|
||||
type_classes = ifcopenshell.util.type.get_applicable_types(occurrence_class)
|
||||
type_class = None if len(type_classes) == 0 else type_classes[0]
|
||||
|
||||
reassigned_elements = set()
|
||||
for element in elements_to_reassign:
|
||||
ifc_class = type_class if element.is_a("IfcTypeObject") else occurrence_class
|
||||
if ifc_class is None:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Couldn't find valid class for reassigning element of class {element.is_a()} based on class {base_class}",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
element = ifcopenshell.api.run(
|
||||
"root.reassign_class",
|
||||
self.file,
|
||||
product=element,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
)
|
||||
reassigned_elements.add(element)
|
||||
|
||||
for obj in objects_to_update:
|
||||
obj.name = tool.Loader.get_name(tool.Ifc.get_entity(obj))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_class"
|
||||
bl_label = "Assign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Assign the IFC Class to the selected objects"
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
predefined_type: bpy.props.StringProperty()
|
||||
userdefined_type: bpy.props.StringProperty()
|
||||
context_id: bpy.props.IntProperty()
|
||||
should_add_representation: bpy.props.BoolProperty(default=True)
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMRootProperties
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects or [context.active_object]
|
||||
|
||||
if not objects:
|
||||
self.report({"INFO"}, "No objects selected.")
|
||||
return
|
||||
|
||||
ifc_class = self.ifc_class or props.ifc_class
|
||||
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_enum_items(props, "contexts", context):
|
||||
ifc_context = int(props.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
active_object = context.active_object
|
||||
for obj in objects:
|
||||
if obj.mode != "OBJECT":
|
||||
self.report({"ERROR"}, "Object must be in OBJECT mode to assign class")
|
||||
continue
|
||||
core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=self.should_add_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
)
|
||||
context.view_layer.objects.active = active_object
|
||||
|
||||
|
||||
class UnlinkObject(bpy.types.Operator):
|
||||
bl_idname = "bim.unlink_object"
|
||||
bl_label = "Unlink Object"
|
||||
bl_description = (
|
||||
"Unlink Blender object from it's linked IFC element.\n\n"
|
||||
"You can either remove element the blender object is linked to from IFC or keep it. "
|
||||
"Note that keeping the unlinked element in IFC might lead to unpredictable issues "
|
||||
"and should be used only by advanced users"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty(name="Object Name")
|
||||
should_delete: bpy.props.BoolProperty(name="Delete IFC Element", default=True)
|
||||
skip_invoke: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
if self.obj:
|
||||
objects = [bpy.data.objects.get(self.obj)]
|
||||
else:
|
||||
objects = context.selected_objects
|
||||
|
||||
objects: list[bpy.types.Object]
|
||||
for obj in objects:
|
||||
was_active_object = obj == context.active_object
|
||||
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and self.should_delete:
|
||||
object_name = obj.name
|
||||
|
||||
# Copy object, so it won't be removed by `delete_ifc_object`
|
||||
obj_copy = obj.copy()
|
||||
if obj.data:
|
||||
obj_copy.data = obj.data.copy()
|
||||
|
||||
# prevent unlinking materials that might be used elsewhere
|
||||
replacements: dict[bpy.types.Material, bpy.types.Material] = dict()
|
||||
for material_slot in obj_copy.material_slots:
|
||||
material = material_slot.material
|
||||
if material is None:
|
||||
continue
|
||||
|
||||
if material in replacements:
|
||||
material_replacement = replacements[material]
|
||||
|
||||
# no need to copy non-ifc materials as unlinking won't do anything to them
|
||||
elif tool.Ifc.get_entity(material) is None and tool.Style.get_style(material) is None:
|
||||
replacements[material] = material
|
||||
continue
|
||||
|
||||
else:
|
||||
material_replacement = material.copy()
|
||||
replacements[material] = material_replacement
|
||||
|
||||
material_slot.material = material_replacement
|
||||
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
|
||||
obj = obj_copy
|
||||
obj.name = object_name
|
||||
elif element:
|
||||
tool.Ifc.unlink(element)
|
||||
|
||||
tool.Root.unlink_object(obj)
|
||||
for collection in obj.users_collection:
|
||||
# Reset collection because its original collection may be removed too.
|
||||
collection.objects.unlink(obj)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
|
||||
if was_active_object:
|
||||
tool.Blender.set_active_object(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "should_delete")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.skip_invoke:
|
||||
return self.execute(context)
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class CopyClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.copy_class"
|
||||
bl_label = "Copy Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objects:
|
||||
core.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
@@ -1,138 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.spatial.data import SpatialDecompositionData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_subelement_class(self, context):
|
||||
if not SpatialDecompositionData.is_loaded:
|
||||
SpatialDecompositionData.load()
|
||||
return SpatialDecompositionData.data["subelement_class"]
|
||||
|
||||
|
||||
def update_elevation(self, context):
|
||||
if ifc_definition_id := self.ifc_definition_id:
|
||||
entity = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if not obj:
|
||||
return
|
||||
obj.location.z = self.elevation
|
||||
|
||||
|
||||
def update_name(self, context):
|
||||
if ifc_definition_id := self.ifc_definition_id:
|
||||
tool.Spatial.edit_container_name(tool.Ifc.get().by_id(ifc_definition_id), self.name)
|
||||
|
||||
|
||||
def update_active_container_index(self, context):
|
||||
SpatialDecompositionData.data["subelement_class"] = SpatialDecompositionData.subelement_class()
|
||||
tool.Spatial.load_contained_elements()
|
||||
|
||||
|
||||
def update_should_include_children(self, context):
|
||||
tool.Spatial.load_contained_elements()
|
||||
|
||||
|
||||
def update_container_obj(self, context):
|
||||
if self.container_obj is None or not (obj := context.active_object):
|
||||
return
|
||||
if not (element := tool.Ifc.get_entity(self.container_obj)):
|
||||
self.container_obj = None
|
||||
return
|
||||
if tool.Spatial.can_contain(element, obj):
|
||||
return
|
||||
if (
|
||||
(container := ifcopenshell.util.element.get_container(element))
|
||||
and (container_obj := tool.Ifc.get_object(container))
|
||||
and tool.Spatial.can_contain(container, obj)
|
||||
):
|
||||
self.container_obj = container_obj
|
||||
return
|
||||
self.container_obj = None
|
||||
|
||||
|
||||
def poll_container_obj(self, obj):
|
||||
return obj is None or tool.Ifc.get_entity(obj)
|
||||
|
||||
|
||||
class BIMObjectSpatialProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
container_obj: PointerProperty(
|
||||
type=bpy.types.Object, name="Container", update=update_container_obj, poll=poll_container_obj
|
||||
)
|
||||
|
||||
|
||||
class BIMContainer(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=update_name)
|
||||
ifc_class: StringProperty(name="IFC Class")
|
||||
description: StringProperty(name="Description")
|
||||
long_name: StringProperty(name="Long Name")
|
||||
elevation: FloatProperty(name="Elevation", subtype="DISTANCE", update=update_elevation)
|
||||
level_index: IntProperty(name="Level Index")
|
||||
has_children: BoolProperty(name="Has Children")
|
||||
is_expanded: BoolProperty(name="Is Expanded")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class Element(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_class: BoolProperty(name="Is Class", default=False)
|
||||
is_type: BoolProperty(name="Is Type", default=False)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total: IntProperty(name="Total")
|
||||
|
||||
|
||||
class BIMSpatialDecompositionProperties(PropertyGroup):
|
||||
containers: CollectionProperty(name="Containers", type=BIMContainer)
|
||||
contracted_containers: StringProperty(name="Contracted containers", default="[]")
|
||||
expanded_containers: StringProperty(name="Expanded containers", default="[]")
|
||||
active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index)
|
||||
elements: CollectionProperty(name="Elements", type=Element)
|
||||
active_element_index: IntProperty(name="Active Element Index")
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
|
||||
default_container: IntProperty(name="Default Container", default=0)
|
||||
should_include_children: BoolProperty(
|
||||
name="Should Include Children", default=True, update=update_should_include_children
|
||||
)
|
||||
|
||||
@property
|
||||
def active_container(self):
|
||||
if self.containers and self.active_container_index < len(self.containers):
|
||||
return self.containers[self.active_container_index]
|
||||
|
||||
@property
|
||||
def active_element(self):
|
||||
if self.elements and self.active_element_index < len(self.elements):
|
||||
return self.elements[self.active_element_index]
|
||||
@@ -1,571 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.type as core
|
||||
import blenderbim.core.root
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class AssignType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_type"
|
||||
bl_label = "Assign Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_type: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
type = tool.Ifc.get().by_id(self.relating_type or int(context.active_object.BIMTypeProperties.relating_type))
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)]
|
||||
if self.related_object
|
||||
else context.selected_objects or [context.active_object]
|
||||
)
|
||||
model_props = context.scene.BIMModelProperties
|
||||
for obj in related_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
core.assign_type(tool.Ifc, tool.Type, element=element, type=type)
|
||||
if model_props.occurrence_name_style == "TYPE":
|
||||
obj.name = tool.Model.generate_occurrence_name(type, element.is_a())
|
||||
|
||||
|
||||
class UnassignType(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_type"
|
||||
bl_label = "Unassign Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
def exclude_callback(attribute):
|
||||
return attribute.is_a("IfcProfileDef") and attribute.ProfileName
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
objs = [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or element.is_a("IfcElementType"):
|
||||
continue
|
||||
ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element])
|
||||
|
||||
active_representation = tool.Geometry.get_active_representation(obj)
|
||||
active_context = active_representation.ContextOfItems
|
||||
new_active_representation = None
|
||||
|
||||
if element.Representation:
|
||||
representations = []
|
||||
for representation in element.Representation.Representations:
|
||||
resolved_representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
if representation == resolved_representation:
|
||||
representations.append(representation)
|
||||
else:
|
||||
# We must unmap representations.
|
||||
copied_representation = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
resolved_representation,
|
||||
exclude=["IfcGeometricRepresentationContext"],
|
||||
exclude_callback=exclude_callback,
|
||||
)
|
||||
representations.append(copied_representation)
|
||||
if representation.ContextOfItems == active_context:
|
||||
new_active_representation = copied_representation
|
||||
element.Representation.Representations = representations
|
||||
|
||||
if new_active_representation:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_active_representation,
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingType(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_type"
|
||||
bl_label = "Enable Editing Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.active_object.BIMTypeProperties.is_editing_type = True
|
||||
context.active_object.BIMTypeProperties.relating_type_object = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingType(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_type"
|
||||
bl_label = "Disable Editing Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
obj.BIMTypeProperties.is_editing_type = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectType(bpy.types.Operator):
|
||||
bl_idname = "bim.select_type"
|
||||
bl_label = "Select Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_type: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
if self.relating_type: # if operator button sends a relating_type, the iterator only selects this one type
|
||||
element = tool.Ifc.get().by_id(self.relating_type)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
selected_objs = [obj]
|
||||
else: # else, the iterator selects all the types of all the selected objects
|
||||
selected_objs = context.selected_objects
|
||||
active_obj = context.active_object
|
||||
selected_objs.append(active_obj) # update selected_objs so the active_obj is at the end of the list
|
||||
|
||||
last_relating_type_obj = None
|
||||
for obj in selected_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
relating_type = ifcopenshell.util.element.get_type(element)
|
||||
if relating_type:
|
||||
relating_type_obj = tool.Ifc.get_object(relating_type)
|
||||
if relating_type_obj:
|
||||
if relating_type_obj.hide_get():
|
||||
relating_type_obj.hide_set(False)
|
||||
relating_type_obj.select_set(True)
|
||||
last_relating_type_obj = relating_type_obj
|
||||
if not element.is_a("IfcTypeObject"):
|
||||
obj.select_set(False)
|
||||
|
||||
context.view_layer.objects.active = last_relating_type_obj # makes the active_obj's type the active object
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def find_collection_in_ifcproject(self, context, collection_name):
|
||||
|
||||
ifc_project_collection = None
|
||||
for child in context.view_layer.layer_collection.children:
|
||||
if "IfcProject" in child.name:
|
||||
ifc_project_collection = child
|
||||
break
|
||||
|
||||
if ifc_project_collection:
|
||||
collection_in_view_layer = ifc_project_collection.children.get(collection_name)
|
||||
return collection_in_view_layer
|
||||
|
||||
|
||||
class SelectSimilarType(bpy.types.Operator):
|
||||
bl_idname = "bim.select_similar_type"
|
||||
bl_label = "Select Similar Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
objects = bpy.context.selected_objects
|
||||
|
||||
# store relating types to avoid selecting same elements multiple times
|
||||
relating_types = set()
|
||||
|
||||
for related_object in objects:
|
||||
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(related_object))
|
||||
if not relating_type:
|
||||
related_object.select_set(False)
|
||||
continue
|
||||
relating_types.add(relating_type)
|
||||
|
||||
for relating_type in relating_types:
|
||||
related_objects = ifcopenshell.util.element.get_types(relating_type)
|
||||
for element in related_objects:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj and obj in context.visible_objects:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectTypeObjects(bpy.types.Operator):
|
||||
bl_idname = "bim.select_type_objects"
|
||||
bl_label = "Select Type Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
relating_type = bpy.data.objects.get(self.relating_type) if self.relating_type else context.active_object
|
||||
at_least_one_selectable_typed_object = False
|
||||
for element in ifcopenshell.util.element.get_types(tool.Ifc.get_entity(relating_type)):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj and obj in context.selectable_objects:
|
||||
obj.select_set(True)
|
||||
at_least_one_selectable_typed_object = True
|
||||
if at_least_one_selectable_typed_object:
|
||||
context.active_object.select_set(False)
|
||||
context.view_layer.objects.active = context.selected_objects[0]
|
||||
else:
|
||||
self.report({"INFO"}, "Typed objects can't be selected : They may be hidden or in an excluded collection.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_type"
|
||||
bl_label = "Add Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
ifc_class = props.type_class
|
||||
predefined_type = props.type_predefined_type
|
||||
name = props.type_name
|
||||
template = props.type_template
|
||||
ifc_file = tool.Ifc.get()
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
if not body:
|
||||
props.type_class = props.type_class
|
||||
self.report({"ERROR"}, "No Model/Body/MODEL_VIEW context found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
if template == "MESH":
|
||||
location = context.scene.cursor.location
|
||||
if context.active_object and context.selected_objects and context.active_object.data:
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
mesh = obj.data.copy()
|
||||
mesh.BIMMeshProperties.ifc_definition_id = 0
|
||||
obj = bpy.data.objects.new(element.Name or name, mesh)
|
||||
else:
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
bm = bmesh.new()
|
||||
bmesh.ops.create_cube(bm, size=1)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
obj.matrix_world.translation = location
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
elif template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
materials = ifc_file.by_type("IfcMaterial")
|
||||
if materials:
|
||||
material = materials[0] # Arbitrarily pick a material
|
||||
else:
|
||||
material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown")
|
||||
rel = ifcopenshell.api.run(
|
||||
"material.assign_material", ifc_file, products=[element], type="IfcMaterialLayerSet"
|
||||
)
|
||||
layer_set = rel.RelatingMaterial
|
||||
layer = ifcopenshell.api.run("material.add_layer", ifc_file, layer_set=layer_set, material=material)
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
|
||||
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="EPset_Parametric")
|
||||
if template == "LAYERSET_AXIS2":
|
||||
axis = "AXIS2"
|
||||
elif template == "LAYERSET_AXIS3":
|
||||
axis = "AXIS3"
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis})
|
||||
|
||||
elif template == "PROFILESET" or template.startswith("FLOW_SEGMENT_"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
materials = ifc_file.by_type("IfcMaterial")
|
||||
if materials:
|
||||
material = materials[0] # Arbitrarily pick a material
|
||||
else:
|
||||
material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown")
|
||||
if template == "PROFILESET":
|
||||
named_profiles = [p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName]
|
||||
if named_profiles:
|
||||
profile = named_profiles[0]
|
||||
else:
|
||||
size = 0.5 / unit_scale
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcRectangleProfileDef", ProfileName="New Profile", ProfileType="AREA", XDim=size, YDim=size
|
||||
)
|
||||
else:
|
||||
# NOTE: defaults dims are in meters / mm
|
||||
# for now default names are hardcoded to mm
|
||||
if template == "FLOW_SEGMENT_RECTANGULAR":
|
||||
default_x_dim = 0.4
|
||||
default_y_dim = 0.2
|
||||
profile_name = f"{ifc_class}-{default_x_dim*1000}x{default_y_dim*1000}"
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcRectangleProfileDef",
|
||||
ProfileName=profile_name,
|
||||
ProfileType="AREA",
|
||||
XDim=default_x_dim / unit_scale,
|
||||
YDim=default_y_dim / unit_scale,
|
||||
)
|
||||
elif template == "FLOW_SEGMENT_CIRCULAR":
|
||||
default_diameter = 0.1
|
||||
profile_name = f"{ifc_class}-{default_diameter*1000}"
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcCircleProfileDef",
|
||||
ProfileName=profile_name,
|
||||
ProfileType="AREA",
|
||||
Radius=(default_diameter / 2) / unit_scale,
|
||||
)
|
||||
elif template == "FLOW_SEGMENT_CIRCULAR_HOLLOW":
|
||||
default_diameter = 0.15
|
||||
default_thickness = 0.005
|
||||
profile_name = f"{ifc_class}-{default_diameter*1000}x{default_thickness*1000}"
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcCircleHollowProfileDef",
|
||||
ProfileName=profile_name,
|
||||
ProfileType="AREA",
|
||||
Radius=(default_diameter / 2) / unit_scale,
|
||||
WallThickness=default_thickness,
|
||||
)
|
||||
|
||||
rel = ifcopenshell.api.run(
|
||||
"material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet"
|
||||
)
|
||||
profile_set = rel.RelatingMaterial
|
||||
material_profile = ifcopenshell.api.run(
|
||||
"material.add_profile", ifc_file, profile_set=profile_set, material=material
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_profile", ifc_file, material_profile=material_profile, profile=profile
|
||||
)
|
||||
|
||||
elif template == "EMPTY":
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
elif template == "WINDOW":
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None,
|
||||
ifc_class="IfcWindowType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcWindowStyle",
|
||||
should_add_representation=False,
|
||||
)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
bpy.ops.bim.add_window(obj=obj.name)
|
||||
|
||||
elif template == "DOOR":
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None,
|
||||
ifc_class="IfcDoorType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcDoorStyle",
|
||||
should_add_representation=False,
|
||||
)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
bpy.ops.bim.add_door(obj=obj.name)
|
||||
|
||||
elif template == "STAIR":
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type,
|
||||
ifc_class=ifc_class,
|
||||
should_add_representation=False,
|
||||
)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
bpy.ops.bim.add_stair()
|
||||
|
||||
elif template == "RAILING":
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type,
|
||||
ifc_class="IfcRailingType",
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
bpy.ops.bim.add_railing()
|
||||
|
||||
elif template == "ROOF":
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type,
|
||||
ifc_class="IfcRoofType",
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
bpy.ops.bim.add_roof()
|
||||
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
|
||||
props.type_class = props.type_class
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_type"
|
||||
bl_label = "Remove Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
element: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
|
||||
|
||||
class RenameType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.rename_type"
|
||||
bl_label = "Rename Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
element: bpy.props.IntProperty()
|
||||
name: bpy.props.StringProperty(name="Name")
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
element.Name = self.name
|
||||
if obj:
|
||||
tool.Root.set_object_name(obj, element)
|
||||
|
||||
def invoke(self, context, event):
|
||||
element = tool.Ifc.get().by_id(self.element)
|
||||
self.name = element.Name or "Unnamed"
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.prop(self, "name")
|
||||
|
||||
|
||||
class AutoRenameOccurrences(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.auto_rename_occurrences"
|
||||
bl_label = "Auto Rename Occurrences"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element_type = tool.Ifc.get_entity(obj)
|
||||
if element_type and element_type.is_a("IfcTypeObject"):
|
||||
for occurrence in ifcopenshell.util.element.get_types(element_type):
|
||||
obj = tool.Ifc.get_object(occurrence)
|
||||
occurrence.Name = tool.Model.generate_occurrence_name(element_type, occurrence.is_a())
|
||||
if obj:
|
||||
tool.Root.set_object_name(obj, occurrence)
|
||||
|
||||
|
||||
class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.duplicate_type"
|
||||
bl_label = "Duplicate Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
element: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
return {"FINISHED"}
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
new.Name += " Copy"
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=new.is_a())
|
||||
if obj in context.selectable_objects:
|
||||
tool.Blender.select_and_activate_single_object(context, new_obj)
|
||||
else:
|
||||
self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.")
|
||||
context.scene.BIMModelProperties.ifc_class = new.is_a()
|
||||
context.scene.BIMModelProperties.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PurgeUnusedTypes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.purge_unused_types"
|
||||
bl_label = "Purge Unused Types"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
purged_types = core.purge_unused_types(tool.Ifc, tool.Type, tool.Geometry)
|
||||
self.report({"INFO"}, f"{purged_types} types were purged.")
|
||||
@@ -1,103 +0,0 @@
|
||||
def create_repo(ifcgit, ifc):
|
||||
path_ifc = ifc.get_path()
|
||||
path_dir = ifcgit.get_path_dir(path_ifc)
|
||||
ifcgit.init_repo(path_dir)
|
||||
|
||||
|
||||
def add_file(ifcgit, ifc):
|
||||
path_ifc = ifc.get_path()
|
||||
repo = ifcgit.repo_from_path(path_ifc)
|
||||
ifcgit.add_file_to_repo(repo, path_ifc)
|
||||
|
||||
|
||||
def clone_repo(ifcgit, remote_url, local_folder, operator):
|
||||
repo = ifcgit.clone_repo(remote_url, local_folder)
|
||||
if not repo:
|
||||
operator.report({"ERROR"}, "Clone failed")
|
||||
return
|
||||
operator.report({"INFO"}, "Repository cloned")
|
||||
ifcgit.load_anyifc(repo)
|
||||
|
||||
|
||||
def discard_uncommitted(ifcgit, ifc):
|
||||
path_ifc = ifc.get_path()
|
||||
# NOTE this is calling the git binary in a subprocess
|
||||
ifcgit.git_checkout(path_ifc)
|
||||
ifcgit.load_project(path_ifc)
|
||||
|
||||
|
||||
def commit_changes(ifcgit, ifc, repo):
|
||||
"""Commit and create new branches as required"""
|
||||
path_ifc = ifc.get_path()
|
||||
|
||||
if repo.head.is_detached:
|
||||
ifcgit.git_commit(path_ifc)
|
||||
ifcgit.create_new_branch()
|
||||
else:
|
||||
ifcgit.checkout_new_branch(path_ifc)
|
||||
ifcgit.git_commit(path_ifc)
|
||||
|
||||
|
||||
def add_tag(ifcgit, repo):
|
||||
ifcgit.add_tag(repo)
|
||||
|
||||
|
||||
def delete_tag(ifcgit, repo, tag_name):
|
||||
ifcgit.delete_tag(repo, tag_name)
|
||||
|
||||
|
||||
def add_remote(ifcgit, repo):
|
||||
ifcgit.add_remote(repo)
|
||||
|
||||
|
||||
def delete_remote(ifcgit, repo):
|
||||
ifcgit.delete_remote(repo)
|
||||
|
||||
|
||||
def push(ifcgit, repo, remote_name, operator):
|
||||
error_message = ifcgit.push(repo, remote_name, repo.active_branch.name)
|
||||
if error_message:
|
||||
operator.report({"ERROR"}, error_message)
|
||||
|
||||
|
||||
def refresh_revision_list(ifcgit, repo, ifc):
|
||||
if repo.heads:
|
||||
ifcgit.refresh_revision_list(ifc.get_path())
|
||||
|
||||
|
||||
def colourise_revision(ifcgit):
|
||||
|
||||
step_ids = ifcgit.get_revisions_step_ids()
|
||||
if not step_ids:
|
||||
return
|
||||
modified_shape_object_step_ids = ifcgit.get_modified_shape_object_step_ids(step_ids)
|
||||
final_step_ids = ifcgit.update_step_ids(step_ids, modified_shape_object_step_ids)
|
||||
ifcgit.colourise(final_step_ids)
|
||||
|
||||
|
||||
def colourise_uncommitted(ifcgit, ifc, repo):
|
||||
path_ifc = ifc.get_path()
|
||||
step_ids = ifcgit.ifc_diff_ids(repo, None, "HEAD", path_ifc)
|
||||
ifcgit.colourise(step_ids)
|
||||
|
||||
|
||||
def switch_revision(ifcgit, ifc):
|
||||
# FIXME bad things happen when switching to a revision that predates current project
|
||||
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.switch_to_revision_item()
|
||||
ifcgit.load_project(path_ifc)
|
||||
ifcgit.refresh_revision_list(path_ifc)
|
||||
|
||||
|
||||
def merge_branch(ifcgit, ifc, operator):
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.config_ifcmerge()
|
||||
ifcgit.execute_merge(path_ifc, operator)
|
||||
|
||||
|
||||
def entity_log(ifcgit, ifc, step_id, operator):
|
||||
path_ifc = ifc.get_path()
|
||||
log_text = ifcgit.entity_log(path_ifc, step_id)
|
||||
# ERROR is only way to display a multi-line message
|
||||
operator.report({"ERROR"}, log_text)
|
||||
@@ -1,64 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from blenderbim.tool.aggregate import Aggregate
|
||||
from blenderbim.tool.blender import Blender
|
||||
from blenderbim.tool.boundary import Boundary
|
||||
from blenderbim.tool.brick import Brick
|
||||
from blenderbim.tool.bsdd import Bsdd
|
||||
from blenderbim.tool.cad import Cad
|
||||
from blenderbim.tool.clash import Clash
|
||||
from blenderbim.tool.classification import Classification
|
||||
from blenderbim.tool.collector import Collector
|
||||
from blenderbim.tool.context import Context
|
||||
from blenderbim.tool.debug import Debug
|
||||
from blenderbim.tool.demo import Demo
|
||||
from blenderbim.tool.document import Document
|
||||
from blenderbim.tool.drawing import Drawing
|
||||
from blenderbim.tool.geometry import Geometry
|
||||
from blenderbim.tool.georeference import Georeference
|
||||
from blenderbim.tool.ifc import Ifc
|
||||
from blenderbim.tool.ifcgit import IfcGit
|
||||
from blenderbim.tool.ifcgit import IfcGitRepo
|
||||
from blenderbim.tool.library import Library
|
||||
from blenderbim.tool.loader import Loader
|
||||
from blenderbim.tool.material import Material
|
||||
from blenderbim.tool.misc import Misc
|
||||
from blenderbim.tool.model import Model
|
||||
from blenderbim.tool.nest import Nest
|
||||
from blenderbim.tool.owner import Owner
|
||||
from blenderbim.tool.patch import Patch
|
||||
from blenderbim.tool.project import Project
|
||||
from blenderbim.tool.profile import Profile
|
||||
from blenderbim.tool.pset import Pset
|
||||
from blenderbim.tool.qto import Qto
|
||||
from blenderbim.tool.resource import Resource
|
||||
from blenderbim.tool.root import Root
|
||||
from blenderbim.tool.sequence import Sequence
|
||||
from blenderbim.tool.spatial import Spatial
|
||||
from blenderbim.tool.covering import Covering
|
||||
from blenderbim.tool.structural import Structural
|
||||
from blenderbim.tool.style import Style
|
||||
from blenderbim.tool.surveyor import Surveyor
|
||||
from blenderbim.tool.system import System
|
||||
from blenderbim.tool.tester import Tester
|
||||
from blenderbim.tool.type import Type
|
||||
from blenderbim.tool.unit import Unit
|
||||
from blenderbim.tool.search import Search
|
||||
from blenderbim.tool.cost import Cost
|
||||
from blenderbim.tool.web import Web
|
||||
@@ -1,149 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.element
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Collector(blenderbim.core.tool.Collector):
|
||||
@classmethod
|
||||
def assign(cls, obj: bpy.types.Object, should_clean_users_collection=True) -> None:
|
||||
"""Links an object to an appropriate Blender collection."""
|
||||
if should_clean_users_collection:
|
||||
for users_collection in obj.users_collection:
|
||||
if obj.BIMObjectProperties.collection == users_collection:
|
||||
continue
|
||||
# Users are free to user extra collections for their own
|
||||
# purposes except for the reserved keyword "Ifc" and
|
||||
# "Collection" (which is the default collection that comes with
|
||||
# a Blender session)
|
||||
if "Ifc" in users_collection.name or users_collection.name == "Collection":
|
||||
print('removing', users_collection, 'from', obj)
|
||||
users_collection.objects.unlink(obj)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
if element.is_a("IfcGridAxis"):
|
||||
element = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
|
||||
|
||||
if element.is_a("IfcProject"):
|
||||
if collection := cls._create_own_collection(obj):
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
cls.link_to_collection_safe(collection, bpy.context.scene.collection)
|
||||
elif element.is_a("IfcTypeProduct"):
|
||||
collection = cls._create_project_child_collection("IfcTypeProduct")
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
elif element.is_a("IfcOpeningElement"):
|
||||
collection = cls._create_project_child_collection("IfcOpeningElement")
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
elif element.is_a("IfcStructuralItem"):
|
||||
collection = cls._create_project_child_collection("IfcStructuralItem")
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
elif element.is_a("IfcLinearPositioningElement"):
|
||||
collection = cls._create_project_child_collection("IfcLinearPositioningElement")
|
||||
collection.hide_viewport = False
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
elif element.is_a("IfcReferent"):
|
||||
collection = cls._create_project_child_collection("IfcReferent")
|
||||
collection.hide_viewport = False
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
elif tool.Ifc.get_schema() == "IFC2X3" and element.is_a("IfcSpatialStructureElement"):
|
||||
if collection := cls._create_own_collection(obj):
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_to_collection_safe(collection, project_obj.BIMObjectProperties.collection)
|
||||
elif (
|
||||
tool.Ifc.get_schema() != "IFC2X3"
|
||||
and element.is_a("IfcSpatialElement")
|
||||
and not element.is_a("IfcSpatialZone")
|
||||
):
|
||||
if collection := cls._create_own_collection(obj):
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_to_collection_safe(collection, project_obj.BIMObjectProperties.collection)
|
||||
elif container := ifcopenshell.util.element.get_container(element):
|
||||
container_obj = tool.Ifc.get_object(container)
|
||||
if not (collection := container_obj.BIMObjectProperties.collection):
|
||||
cls.assign(container_obj)
|
||||
collection = container_obj.BIMObjectProperties.collection
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
elif element.is_a("IfcAnnotation"):
|
||||
if element.ObjectType == "DRAWING":
|
||||
if collection := cls._create_own_collection(obj):
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_to_collection_safe(collection, project_obj.BIMObjectProperties.collection)
|
||||
else:
|
||||
for rel in element.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcAnnotation") and related_object.ObjectType == "DRAWING":
|
||||
drawing_obj = tool.Ifc.get_object(related_object)
|
||||
if drawing_obj:
|
||||
cls.link_to_collection_safe(obj, drawing_obj.BIMObjectProperties.collection)
|
||||
else:
|
||||
collection = cls._create_project_child_collection("Unsorted")
|
||||
collection.hide_viewport = False
|
||||
cls.link_to_collection_safe(obj, collection)
|
||||
|
||||
@classmethod
|
||||
def _create_project_child_collection(cls, name: str) -> bpy.types.Collection:
|
||||
"""get or create new collection inside project"""
|
||||
collection = bpy.data.collections.get(name)
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new(name)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
project_obj.BIMObjectProperties.collection.children.link(collection)
|
||||
collection.hide_viewport = True
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
def _create_own_collection(cls, obj: bpy.types.Object) -> bpy.types.Collection:
|
||||
"""get or create own collection for the element"""
|
||||
if obj.BIMObjectProperties.collection:
|
||||
obj.BIMObjectProperties.collection.name = obj.name
|
||||
return
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
obj.BIMObjectProperties.collection = collection
|
||||
collection.BIMCollectionProperties.obj = obj
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
def link_to_collection_safe(
|
||||
cls, obj_or_col: Union[bpy.types.Object, bpy.types.Collection], collection: bpy.types.Collection
|
||||
) -> None:
|
||||
"""Link `obj_or_col` (an object or a collection) to the `collection`
|
||||
if `obj_or_col` is not part of that collection already.
|
||||
|
||||
Method is needed to avoid RuntimeErrors like below that occur if you link object/collection
|
||||
to the collection directly and they are already part of that collection.
|
||||
RuntimeError: Error: Object 'xxx' already in collection 'xxx'.
|
||||
"""
|
||||
# TODO: Maybe just catching RuntimeError is faster?
|
||||
if isinstance(obj_or_col, bpy.types.Object):
|
||||
if collection.objects.find(obj_or_col.name) != -1:
|
||||
return
|
||||
collection.objects.link(obj_or_col)
|
||||
return
|
||||
|
||||
if collection.children.find(obj_or_col.name) != -1:
|
||||
return
|
||||
collection.children.link(obj_or_col)
|
||||
@@ -1,57 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.tool
|
||||
|
||||
|
||||
class Context(blenderbim.core.tool.Context):
|
||||
@classmethod
|
||||
def set_context(cls, context):
|
||||
bpy.context.scene.BIMContextProperties.active_context_id = context.id()
|
||||
|
||||
@classmethod
|
||||
def import_attributes(cls):
|
||||
props = bpy.context.scene.BIMContextProperties
|
||||
props.context_attributes.clear()
|
||||
context = cls.get_context()
|
||||
|
||||
def callback(name, prop, data):
|
||||
if context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if name == "Precision":
|
||||
props.context_attributes.remove(props.context_attributes.find("Precision"))
|
||||
return True
|
||||
elif name == "CoordinateSpaceDimension":
|
||||
props.context_attributes.remove(props.context_attributes.find("CoordinateSpaceDimension"))
|
||||
return True
|
||||
|
||||
blenderbim.bim.helper.import_attributes(context.is_a(), props.context_attributes, context.get_info(), callback)
|
||||
|
||||
@classmethod
|
||||
def clear_context(cls):
|
||||
bpy.context.scene.BIMContextProperties.active_context_id = 0
|
||||
|
||||
@classmethod
|
||||
def get_context(cls):
|
||||
return tool.Ifc.get().by_id(bpy.context.scene.BIMContextProperties.active_context_id)
|
||||
|
||||
@classmethod
|
||||
def export_attributes(cls):
|
||||
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMContextProperties.context_attributes)
|
||||
@@ -1,90 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell.express
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class Debug(blenderbim.core.tool.Debug):
|
||||
@classmethod
|
||||
def add_schema_identifier(cls, schema):
|
||||
IfcStore.schema_identifiers.append(schema.schema_name)
|
||||
|
||||
@classmethod
|
||||
def load_express(cls, filename):
|
||||
schema = ifcopenshell.express.parse(filename)
|
||||
ifcopenshell.register_schema(schema)
|
||||
return schema
|
||||
|
||||
@classmethod
|
||||
def purge_hdf5_cache(cls):
|
||||
cache_dir = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache")
|
||||
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
|
||||
for f in filelist:
|
||||
os.remove(os.path.join(cache_dir, f))
|
||||
|
||||
@classmethod
|
||||
def debug_geometry(cls, verts=[], edges=[], name="Debug"):
|
||||
mesh = bpy.data.meshes.new("Debug")
|
||||
mesh.from_pydata(verts, edges, [])
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def remove_unused_elements(cls, elements):
|
||||
ifc_file = tool.Ifc.get()
|
||||
for element in elements:
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, element)
|
||||
|
||||
@classmethod
|
||||
def print_unused_elements_stats(cls, requested_ifc_class="", ignore_classes=tuple()):
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# get list of ifc classes used in model
|
||||
classes = set()
|
||||
requested_ifc_classes = set()
|
||||
for el in ifc_file:
|
||||
if any(el.is_a(i) for i in ignore_classes):
|
||||
continue
|
||||
classes.add(el.is_a())
|
||||
if requested_ifc_class and el.is_a(requested_ifc_class):
|
||||
requested_ifc_classes.add(el.is_a())
|
||||
|
||||
# count unused elements for each class
|
||||
unused = dict()
|
||||
for c in classes:
|
||||
uses = [i for i in ifc_file.by_type(c) if ifc_file.get_total_inverses(i) == 0]
|
||||
if not uses:
|
||||
continue
|
||||
unused[c] = len(uses)
|
||||
|
||||
# print classes and their unsued elements in ascending order
|
||||
if unused:
|
||||
print("Unused elements by classes:")
|
||||
for ifc_class in sorted(unused.keys(), key=lambda x: unused[x]):
|
||||
class_string = ifc_class
|
||||
if ifc_class in requested_ifc_classes:
|
||||
class_string = "---> " + class_string
|
||||
print(f"{class_string: <50} {unused[ifc_class]: >5}")
|
||||
|
||||
return sum(unused.values())
|
||||
@@ -1,90 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import PIL.ImageDraw
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Profile(blenderbim.core.tool.Profile):
|
||||
@classmethod
|
||||
def draw_image_for_ifc_profile(
|
||||
cls, draw: PIL.ImageDraw.ImageDraw, profile: ifcopenshell.entity_instance, size: float
|
||||
) -> None:
|
||||
"""generates image based on `profile` using `PIL.ImageDraw`"""
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||
shape = ifcopenshell.geom.create_shape(settings, profile)
|
||||
verts = shape.verts
|
||||
edges = shape.edges
|
||||
|
||||
grouped_verts = [[verts[i], verts[i + 1]] for i in range(0, len(verts), 3)]
|
||||
grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
|
||||
|
||||
max_x = max([v[0] for v in grouped_verts])
|
||||
min_x = min([v[0] for v in grouped_verts])
|
||||
max_y = max([v[1] for v in grouped_verts])
|
||||
min_y = min([v[1] for v in grouped_verts])
|
||||
|
||||
dim_x = max_x - min_x
|
||||
dim_y = max_y - min_y
|
||||
max_dim = max([dim_x, dim_y])
|
||||
scale = 100 / max_dim
|
||||
|
||||
for vert in grouped_verts:
|
||||
vert[0] = round(scale * (vert[0] - min_x)) + ((size / 2) - scale * (dim_x / 2))
|
||||
vert[1] = round(scale * (vert[1] - min_y)) + ((size / 2) - scale * (dim_y / 2))
|
||||
|
||||
for e in grouped_edges:
|
||||
draw.line((tuple(grouped_verts[e[0]]), tuple(grouped_verts[e[1]])), fill="white", width=2)
|
||||
|
||||
@classmethod
|
||||
def is_editing_profile(cls) -> bool:
|
||||
return bool(ProfileDecorator.installed)
|
||||
|
||||
@classmethod
|
||||
def get_profile(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
representations = element.Representation
|
||||
for representation in representations.Representations:
|
||||
if not representation.is_a("IfcShapeRepresentation"):
|
||||
continue
|
||||
for representation_item in representation.Items:
|
||||
if representation_item.is_a("IfcExtrudedAreaSolid"):
|
||||
profile = representation_item.SweptArea
|
||||
if profile:
|
||||
return profile
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_model_profiles(cls) -> list[ifcopenshell.entity_instance]:
|
||||
return tool.Ifc.get().by_type("IfcProfileDef")
|
||||
|
||||
@classmethod
|
||||
def duplicate_profile(cls, profile: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
new_profile = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), profile)
|
||||
# In UI unnamed profiles are not available, so we don't handle them.
|
||||
new_profile.ProfileName = profile.ProfileName + "_copy"
|
||||
return new_profile
|
||||
@@ -1,432 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.web.data import WebData
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.api.sequence
|
||||
from typing import Any, Dict, Optional
|
||||
import time
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import asyncio
|
||||
import socketio
|
||||
import threading
|
||||
import queue
|
||||
import json
|
||||
from time import sleep
|
||||
from pathlib import Path
|
||||
|
||||
sio = None
|
||||
ws_process = None
|
||||
ws_thread = None
|
||||
web_operator_queue = queue.Queue()
|
||||
|
||||
RECONNECTION_ATTEMPTS = 3
|
||||
RECONNECTION_DELAY = 2
|
||||
IFC_TASK_ATTRIBUTE_MAP = {
|
||||
"pStart": "ScheduleStart",
|
||||
"pEnd": "ScheduleFinish",
|
||||
"pName": "Name",
|
||||
}
|
||||
|
||||
|
||||
class Web(blenderbim.core.tool.Web):
|
||||
@classmethod
|
||||
def generate_port_number(cls) -> int:
|
||||
"""
|
||||
Generate a free port number.
|
||||
|
||||
This method creates a temporary socket to bind to a free port.
|
||||
It then retrieves the port number, and returns it.
|
||||
|
||||
Returns:
|
||||
int: The port number that was generated.
|
||||
"""
|
||||
print("Generating port number")
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("localhost", 0)) # Bind to a free port
|
||||
port = s.getsockname()[1] # get the bound port
|
||||
print(f"Port number: {port}")
|
||||
return port
|
||||
|
||||
@classmethod
|
||||
def is_port_available(cls, port: int) -> bool:
|
||||
"""
|
||||
Attempts to connect to the specified port on localhost.
|
||||
|
||||
If the connection is refused, the port is available for use; otherwise, it is in use.
|
||||
|
||||
Args:
|
||||
- port (int): The port number to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the port is available, False if it is in use.
|
||||
"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# connect_ex returns errno.SUCCESS (0) if the connection succeeds
|
||||
# otherwise returns errno.ECONNREFUSED (111 or 10061) if no server is listening
|
||||
return s.connect_ex(("localhost", port)) == errno.ECONNREFUSED
|
||||
|
||||
@classmethod
|
||||
def start_websocket_server(cls, port: int) -> None:
|
||||
"""
|
||||
Starts a WebSocket server on the specified port.
|
||||
|
||||
This method sets up the environment, locates paths, and starts
|
||||
the WebSocket server process. It also handles the creation and updating of a PID file to keep track
|
||||
of running server instances.
|
||||
|
||||
Args:
|
||||
- port (int): The port number on which to start the WebSocket server.
|
||||
"""
|
||||
import addon_utils
|
||||
|
||||
global ws_process
|
||||
|
||||
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
|
||||
ws_path = os.path.join(webui_path, "sioserver.py")
|
||||
|
||||
py_version = sys.version_info
|
||||
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
blenderbim_lib_path = (
|
||||
Path(bpy.utils.user_resource("EXTENSIONS"))
|
||||
/ ".local"
|
||||
/ "lib"
|
||||
/ f"python{py_version.major}.{py_version.minor}"
|
||||
/ "site-packages"
|
||||
)
|
||||
else:
|
||||
addon = [a for a in addon_utils.modules() if a.bl_info["name"] == "BlenderBIM"][0]
|
||||
blenderbim_path = os.path.dirname(addon.__file__)
|
||||
blenderbim_lib_path = os.path.join(blenderbim_path, "libs", "site", "packages")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["BLENDERBIM_LIB_PATH"] = str(blenderbim_lib_path)
|
||||
env["BLENDERBIM_VERSION"] = tool.Blender.get_blenderbim_version()
|
||||
|
||||
ws_process = subprocess.Popen(
|
||||
[sys.executable, ws_path, "--p", str(port), "--host", "127.0.0.1"],
|
||||
cwd=webui_path,
|
||||
env=env,
|
||||
)
|
||||
|
||||
cls.set_is_running(True)
|
||||
|
||||
@classmethod
|
||||
def connect_websocket_server(cls, port: int) -> None:
|
||||
"""
|
||||
Connect to a WebSocket server on the specified port.
|
||||
|
||||
This method sets up an asynchronous Socket.IO client with
|
||||
reconnection attempts, starts an asyncio thread, connects to the WebSocket server, and sets the connection status.
|
||||
|
||||
Args:
|
||||
- port (int): The port number to connect to the WebSocket server.
|
||||
"""
|
||||
global ws_thread, sio
|
||||
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
print(f"Already connected to websocket server on port: {port}")
|
||||
return
|
||||
|
||||
sio = socketio.AsyncClient(
|
||||
reconnection=True,
|
||||
reconnection_attempts=RECONNECTION_ATTEMPTS,
|
||||
reconnection_delay=RECONNECTION_DELAY,
|
||||
logger=True,
|
||||
)
|
||||
|
||||
ws_thread = AsyncioThread()
|
||||
ws_thread.daemon = True
|
||||
ws_thread.start()
|
||||
|
||||
ws_url = f"ws://localhost:{port}/blender"
|
||||
ws_thread.run_coro(cls.sio_connect(ws_url))
|
||||
cls.set_is_connected(True)
|
||||
bpy.app.timers.register(cls.check_operator_queue)
|
||||
|
||||
@classmethod
|
||||
def disconnect_websocket_server(cls) -> None:
|
||||
"""
|
||||
Disconnects the WebSocket server and stops the associated thread.
|
||||
|
||||
This method is responsible for disconnecting the WebSocket server, stopping the asyncio thread,
|
||||
and resetting the global variables related to the WebSocket connection.
|
||||
"""
|
||||
global ws_thread, sio
|
||||
ws_thread.run_coro(cls.sio_disconnect())
|
||||
ws_thread.stop()
|
||||
ws_thread = None
|
||||
sio = None
|
||||
cls.set_is_connected(False)
|
||||
|
||||
@classmethod
|
||||
def kill_websocket_server(cls) -> None:
|
||||
"""
|
||||
Terminate the currently running WebSocket server.
|
||||
|
||||
This method checks if there is an active WebSocket server process. If so, it disconnects it (if connected),
|
||||
removes its PID from the PID file, terminates the process, and updates the server's running status.
|
||||
"""
|
||||
global ws_process
|
||||
|
||||
if ws_process is None:
|
||||
print("No Websocket server running")
|
||||
return
|
||||
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
cls.disconnect_websocket_server()
|
||||
|
||||
# sleep(0.5)
|
||||
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
|
||||
pid_file = os.path.join(webui_path, "running_pid.json")
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
|
||||
if str(ws_process.pid) in pids:
|
||||
del pids[str(ws_process.pid)]
|
||||
|
||||
# Write the updated PIDs back to the file
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
ws_process.kill()
|
||||
ws_process = None
|
||||
|
||||
cls.set_is_running(False)
|
||||
print("Websocket server killed successfully")
|
||||
|
||||
@classmethod
|
||||
def has_started(cls, port):
|
||||
max_time = 5
|
||||
start = time.time()
|
||||
while True:
|
||||
if time.time() - start > max_time:
|
||||
return False
|
||||
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
|
||||
pid_file = os.path.join(webui_path, "running_pid.json")
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
data = json.load(f)
|
||||
if port in data.values():
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
|
||||
@classmethod
|
||||
def send_webui_data(
|
||||
cls,
|
||||
data: Optional[Any] = None,
|
||||
data_key: str = "data",
|
||||
event: str = "data",
|
||||
namespace: str = "/blender",
|
||||
use_web_data: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
Sends data to the Web UI via Websocket connection.
|
||||
|
||||
Args:
|
||||
- data (Optional[Any]): The data to send. If None, just sends data from WebData.
|
||||
- data_key (str): The key under which to store the data in the payload. Defaults to "data".
|
||||
- event (str): The WebSocket event to emit. Defaults to "data".
|
||||
- namespace (str): The namespace for the WebSocket event. Defaults to "/blender".
|
||||
- use_web_data (bool): Whether to use data from WebData. Defaults to True.
|
||||
"""
|
||||
|
||||
global ws_thread
|
||||
payload = {}
|
||||
|
||||
if use_web_data:
|
||||
if not WebData.is_loaded:
|
||||
WebData.load()
|
||||
payload = WebData.data
|
||||
|
||||
if data is not None:
|
||||
payload[data_key] = data
|
||||
|
||||
if ws_thread is not None and bpy.context.scene.WebProperties.is_connected:
|
||||
ws_thread.run_coro(cls.sio_send(payload, event, namespace))
|
||||
|
||||
@classmethod
|
||||
def check_operator_queue(cls) -> None | float:
|
||||
if not bpy.context.scene.WebProperties.is_connected:
|
||||
with web_operator_queue.mutex:
|
||||
web_operator_queue.queue.clear()
|
||||
return None # unregister timer if not connected
|
||||
|
||||
while not web_operator_queue.empty():
|
||||
operator = web_operator_queue.get_nowait()
|
||||
if not operator:
|
||||
continue
|
||||
if operator["sourcePage"] == "csv":
|
||||
cls.handle_csv_operator(operator["operator"])
|
||||
elif operator["sourcePage"] == "gantt":
|
||||
cls.handle_gantt_operator(operator["operator"])
|
||||
elif operator["sourcePage"] == "drawings":
|
||||
cls.handle_drawings_operator(operator["operator"])
|
||||
return 1.0
|
||||
|
||||
@classmethod
|
||||
def handle_csv_operator(cls, operator_data: dict) -> None:
|
||||
if operator_data["type"] == "selection":
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
guid = operator_data["globalId"]
|
||||
ele = tool.Ifc.get().by_guid(guid)
|
||||
obj = tool.Ifc.get_object(ele)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def handle_gantt_operator(cls, operator_data: dict) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
if operator_data["type"] == "editTask":
|
||||
task_id = int(operator_data["taskId"])
|
||||
task = ifc_file.by_id(task_id)
|
||||
task_time = task.TaskTime
|
||||
column = operator_data["column"]
|
||||
new_value = operator_data["value"]
|
||||
|
||||
try:
|
||||
ifcopenshell.api.sequence.edit_task(
|
||||
ifc_file, task, attributes={IFC_TASK_ATTRIBUTE_MAP[column]: str(new_value)}
|
||||
)
|
||||
except AttributeError:
|
||||
if task_time is None:
|
||||
ifcopenshell.api.sequence.add_task_time(ifc_file, task)
|
||||
task_time = task.TaskTime
|
||||
ifcopenshell.api.sequence.edit_task_time(
|
||||
ifc_file, task_time=task_time, attributes={IFC_TASK_ATTRIBUTE_MAP[column]: str(new_value)}
|
||||
)
|
||||
|
||||
bpy.ops.bim.load_task_properties()
|
||||
|
||||
# after updating, send new gantt data to handle the case where
|
||||
# changing a task cascades and changes other tasks. as this wouldn't
|
||||
# be reflected in the web ui
|
||||
work_schedule = ifc_file.by_id(operator_data["workScheduleId"])
|
||||
task_json = tool.Sequence.create_tasks_json(work_schedule)
|
||||
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
|
||||
cls.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
|
||||
|
||||
@classmethod
|
||||
def handle_drawings_operator(cls, operator_data: dict) -> None:
|
||||
if operator_data["type"] == "getDrawings":
|
||||
drawings_data = []
|
||||
sheets_data = []
|
||||
ifc_file_dir = os.path.dirname(bpy.context.scene.BIMProperties.ifc_file)
|
||||
|
||||
sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"]
|
||||
for sheet in sorted(sheets, key=lambda s: getattr(s, "Identification", getattr(s, "DocumentId", None))):
|
||||
for reference in tool.Drawing.get_document_references(sheet):
|
||||
reference_description = tool.Drawing.get_reference_description(reference)
|
||||
reference_location = tool.Drawing.get_reference_location(reference)
|
||||
reference_name = os.path.basename(reference_location)
|
||||
reference_path = os.path.join(ifc_file_dir, reference_location)
|
||||
if reference_description == "SHEET":
|
||||
sheets_data.append({"name": reference_name, "path": reference_path})
|
||||
if reference_description == "DRAWING":
|
||||
drawings_data.append({"name": reference_name, "path": reference_path})
|
||||
|
||||
cls.send_webui_data(
|
||||
data={"drawings": drawings_data, "sheets": sheets_data}, data_key="drawings_data", event="drawings_data"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def open_web_browser(cls, port: int) -> None:
|
||||
webbrowser.open(f"http://127.0.0.1:{port}/")
|
||||
|
||||
@classmethod
|
||||
async def sio_connect(cls, url: str) -> None:
|
||||
await sio.connect(url, transports=["websocket"], namespaces="/blender")
|
||||
sio.on("web_operator", cls.sio_listen_web_operator, namespace="/blender")
|
||||
|
||||
@classmethod
|
||||
async def sio_disconnect(cls) -> None:
|
||||
await sio.disconnect()
|
||||
|
||||
@classmethod
|
||||
async def sio_send(cls, data: Any, event: str = "data", namespace: str = "/blender") -> None:
|
||||
await sio.emit(event, data, namespace=namespace)
|
||||
|
||||
@classmethod
|
||||
async def sio_listen_web_operator(cls, data: dict) -> None:
|
||||
try:
|
||||
web_operator_queue.put_nowait(data)
|
||||
except queue.Full:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def set_is_running(cls, is_running: bool) -> None:
|
||||
bpy.context.scene.WebProperties.is_running = is_running
|
||||
|
||||
@classmethod
|
||||
def set_is_connected(cls, is_connected: bool) -> None:
|
||||
bpy.context.scene.WebProperties.is_connected = is_connected
|
||||
|
||||
|
||||
class AsyncioThread(threading.Thread):
|
||||
def __init__(self, *args, loop=None, **kwargs):
|
||||
"""
|
||||
Initialize an instance of AsyncioThread.
|
||||
|
||||
This class represents a thread that runs an asyncio event loop. It is used to handle asynchronous tasks
|
||||
in a separate thread from the main thread.
|
||||
|
||||
Args:
|
||||
- *args: Variable length argument list. These arguments are passed to the superclass constructor.
|
||||
- loop: An existing asyncio event loop. If None, a new event loop is created.
|
||||
- **kwargs: Arbitrary keyword arguments. These arguments are passed to the superclass constructor.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loop = loop or asyncio.new_event_loop()
|
||||
self.running = False
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Start the asyncio event loop and mark the thread as running.
|
||||
"""
|
||||
self.running = True
|
||||
self.loop.run_forever()
|
||||
|
||||
def run_coro(self, coro) -> Any:
|
||||
"""
|
||||
Run a coroutine in the asyncio event loop from a separate thread.
|
||||
|
||||
Args:
|
||||
- coro: The coroutine to be run.
|
||||
|
||||
Returns:
|
||||
The result of the coroutine.
|
||||
"""
|
||||
return asyncio.run_coroutine_threadsafe(coro, loop=self.loop).result()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Stop the asyncio event loop and join the thread.
|
||||
"""
|
||||
self.loop.call_soon_threadsafe(self.loop.stop)
|
||||
self.join()
|
||||
self.running = False
|
||||
@@ -1,20 +0,0 @@
|
||||
Code style
|
||||
============
|
||||
|
||||
|
||||
Black code formatter
|
||||
-------------------------------
|
||||
For Python code formatting, we use `Black code formatter <https://pypi.org/project/black/>`__ with ``--line-length 120``.
|
||||
|
||||
``black`` can be installed using ``pip install black`` and files can be formatted with the following example command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
black --line-length 120 src/blenderbim/blenderbim/bim/module/qto/operator.py
|
||||
|
||||
Using PowerShell, you can run the Black formatter on the last commit in the repository.
|
||||
You can change ``~1`` to ``~n`` to affect ``n`` commits.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
git diff HEAD HEAD~1 --name-only | where {$_ -like "*.py"} | foreach-object { start $_ && black --line-length 120 $_ }
|
||||
@@ -1,29 +0,0 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
BlenderBIM is an open-source project, and its development is driven by the contributions of a dedicated community of developers,
|
||||
architects, engineers, and enthusiasts. If you're interested in contributing to the project, whether by submitting bug reports,
|
||||
suggesting new features, or contributing code, your involvement is highly encouraged and appreciated.
|
||||
|
||||
This part of the documentation covers various aspects of the BlenderBIM development process, including:
|
||||
|
||||
- :doc:`Writing User Documentation </contribute/writing_docs>`
|
||||
- :doc:`Translations and Internationalisation </contribute/translations>`
|
||||
- Contributing Code
|
||||
- :doc:`Installation and Setting up a Development Environment </devs/installation>`
|
||||
- Understanding the Project Structure and Codebase
|
||||
- :doc:`Hello, World! </devs/hello_world>`
|
||||
- :doc:`Undo System </devs/undo_system>`
|
||||
- :doc:`Code Style Guidelines and Best Practices </devs/code_style>`
|
||||
|
||||
..
|
||||
- :doc:`Submitting Pull Requests and Contributing Code </devs/contributing_code>`
|
||||
|
||||
- :doc:`Testing and Quality Assurance </devs/running_tests>`
|
||||
- :doc:`Running Tests </devs/running_tests>`
|
||||
- :doc:`System Support and Multiplatform Compatibility </devs/system_support>`
|
||||
- :doc:`User Experience and User Interface Guidelines </devs/ux_guidelines>`
|
||||
|
||||
The BlenderBIM Developer Documentation is a living resource maintained by the core development team and the open-source community. It serves as a central hub for developers who want to get involved in the project, ensuring a consistent and efficient development process.
|
||||
|
||||
By contributing to BlenderBIM, you'll not only be helping to improve and expand the capabilities of this powerful open-source BIM authoring platform but also be part of a vibrant community driving innovation in the AEC industry.
|
||||
@@ -1,179 +0,0 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
There are different methods of installation, depending on your situation.
|
||||
|
||||
1. **Unstable installation** is recommended for power users helping with testing.
|
||||
2. **Bundling for Blender** is recommended for distributing the add-on.
|
||||
3. **Live development environment** is recommended for developers who are actively coding.
|
||||
4. **Packaged installation** is recommended for those who use a package manager.
|
||||
|
||||
Unstable installation
|
||||
---------------------
|
||||
|
||||
**Unstable installation** is almost the same as **Stable installation**, except
|
||||
that they are typically updated every day. Simply download a daily build from
|
||||
the `GitHub releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases?q=blenderbim&expanded=true>`__,
|
||||
then follow the usual :doc:`installation
|
||||
instructions</users/quickstart/installation>`.
|
||||
|
||||
The BlenderBIM Add-on officially supports all major 64-bit platforms, as well as
|
||||
the Python version shipped by the Blender Foundation for the most recent three
|
||||
major Blender versions:
|
||||
|
||||
- 64-bit Linux (``linux-x64``)
|
||||
- 64-bit MacOS Intel (``macos-x64``)
|
||||
- 64-bit MacOS Silicon (``macos-arm64``)
|
||||
- 64-bit Windows (``windows-x64``)
|
||||
- Blender 4.2 with Python 3.11
|
||||
|
||||
Due to significant changes in the Blender extensions system, Blender versions
|
||||
<4.2 are not supported.
|
||||
|
||||
Developer builds may exist for different versions of Python but there will be
|
||||
no guarantee of the uptime or stability of these builds.
|
||||
|
||||
Other system specifications match the `Blender Requirements
|
||||
<https://www.blender.org/download/requirements/>`_ and the `VFX Platform
|
||||
<https://vfxplatform.com/>`_ standard.
|
||||
|
||||
Sometimes, a build may be delayed, or contain broken code. We try to avoid this,
|
||||
but it happens.
|
||||
|
||||
Bundling for Blender
|
||||
--------------------
|
||||
|
||||
Instead of waiting for an official release on the BlenderBIM Add-on website, it
|
||||
is possible to make your own Blender add-on from the bleeding edge source code
|
||||
of BlenderBIM. BlenderBIM is coded in Python and doesn't require any
|
||||
compilation, so this is a relatively easy process.
|
||||
|
||||
Note that the BlenderBIM Add-on does depend on IfcOpenShell, and IfcOpenShell
|
||||
does require compilation. The following instructions will use a pre-built
|
||||
IfcOpenShell (using an IfcOpenBot build) for convenience. Instructions on how to
|
||||
compile IfcOpenShell is out of scope of this document.
|
||||
|
||||
You can create your own package by using the Makefile as shown below. You can
|
||||
choose between a ``PLATFORM`` of ``linux``, ``macos``, ``macosm1``, and ``win``.
|
||||
You can choose between a ``PYVERSION`` of ``py312``, ``py311``, ``py310``, or
|
||||
``py39``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd src/blenderbim
|
||||
make dist PLATFORM=linux PYVERSION=py311
|
||||
ls dist/
|
||||
|
||||
This will give you a fully packaged Blender add-on zip that you can distribute
|
||||
and install.
|
||||
|
||||
Live development environment
|
||||
----------------------------
|
||||
|
||||
One option for developers who want to actively develop from source is to follow
|
||||
the instructions from :ref:`devs/installation:Bundling for Blender`. However,
|
||||
creating a build, uninstalling the old add-on, and installing a new build is a
|
||||
slow process. Although it works, it is very slow, so we do not recommend it.
|
||||
|
||||
A more rapid approach is to follow the :ref:`devs/installation:Unstable
|
||||
installation` method, as this provides all dependencies for you out of the box.
|
||||
|
||||
Once you've done this, you can replace certain Python files that tend to be
|
||||
updated frequently with those from the Git repository. We're going to use
|
||||
symbolic links, so we can code in our Git repository, and see the changes in
|
||||
our Blender installation (you will need to restart Blender to see changes).
|
||||
|
||||
For Linux or Mac:
|
||||
|
||||
.. literalinclude:: ../../scripts/installation/dev_environment.sh
|
||||
:language: bash
|
||||
:caption: dev_environment.sh
|
||||
|
||||
Or, if you're on Windows, you can use the batch script below. You need to run
|
||||
it as an administrator. Before running it follow the instructions descibed
|
||||
in the `rem` tags.
|
||||
|
||||
.. literalinclude:: ../../scripts/installation/dev_environment.bat
|
||||
:language: bat
|
||||
:caption: dev_environment.bat
|
||||
|
||||
After you modify your code in the Git repository, you will need to restart
|
||||
Blender for the changes to take effect.
|
||||
|
||||
The downside with this approach is that if a new dependency is added, or a
|
||||
compiled dependency version requirement has changed, or the build system
|
||||
changes, you'll need to fix your setup manually. But this is relatively rare.
|
||||
Reviewing the Makefile history, `here <https://github.com/IfcOpenShell/IfcOpenShell/commits/v0.8.0/src/blenderbim/Makefile>`__, is one quick way to see if a dependency has changed.
|
||||
|
||||
.. seealso::
|
||||
|
||||
There is a `useful Blender Addon
|
||||
<https://blenderartists.org/uploads/short-url/yto1sjw7pqDRVNQzpVLmn51PEDN.zip>`__
|
||||
(see `forum thread
|
||||
<https://blenderartists.org/t/reboot-blender-addon/640465/13>`__) that adds
|
||||
a Reboot button in File menu. In this way, it's possible to directly
|
||||
restart Blender and test the modified source code. There is also a VS Code
|
||||
add-on called `Blender Development
|
||||
<https://marketplace.visualstudio.com/items?itemName=JacquesLucke.blender-development>`__
|
||||
that has a similar functionality.
|
||||
|
||||
|
||||
Packaged installation
|
||||
---------------------
|
||||
|
||||
- **Arch Linux**: `Direct from Git <https://aur.archlinux.org/packages/ifcopenshell-git/>`__.
|
||||
- **Chocolatey on Windows**: `Unstable <https://community.chocolatey.org/packages/blenderbim-nightly/>`__.
|
||||
|
||||
Tips for package managers
|
||||
-------------------------
|
||||
|
||||
The BlenderBIM Add-on is fully contained in the ``blenderbim/`` subfolder of the
|
||||
Blender add-ons directory. This is typically distributed as a zipfile as per
|
||||
Blender add-on conventions. Within this folder, you'll find the following file
|
||||
structure:
|
||||
::
|
||||
|
||||
core/ (Blender agnostic core logic)
|
||||
tool/ (Blender specific shared functionality)
|
||||
bim/ (Blender specific UI)
|
||||
libs/ (other assets)
|
||||
wheels/ (dependencies)
|
||||
__init__.py
|
||||
|
||||
This corresponds to the structure found in the source code `here
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/blenderbim/blenderbim>`__.
|
||||
|
||||
The BlenderBIM Add-on is complex, and requires many dependencies, including
|
||||
Python modules, binaries, and static assets. When packaged for users, these
|
||||
dependencies are bundled with the add-on for convenience.
|
||||
|
||||
If you choose to install the BlenderBIM Add-on and use your own system
|
||||
dependencies, the source of truth for how dependencies are bundled are found in
|
||||
the `Makefile
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/src/blenderbim/Makefile>`__
|
||||
in the ``dist`` target.
|
||||
|
||||
Add-on compatibility
|
||||
--------------------
|
||||
|
||||
The BlenderBIM Add-on is a non-trivial add-on. By turning Blender into a
|
||||
graphical front-end to a native IFC authoring platform, some fundamental Blender
|
||||
features (such as hotkeys for basic functionality like object deletion or
|
||||
duplication) have been patched and many dependencies have been introduced.
|
||||
|
||||
Other add-ons may no longer work as intended when the BlenderBIM Add-on is
|
||||
enabled, or vice versa, the BlenderBIM Add-on may no longer work as intended
|
||||
when other add-ons are enabled.
|
||||
|
||||
Known scenarios which will lead to add-on incompatibility include:
|
||||
|
||||
- The add-on also overrides the same hotkeys. For example, if an add-on
|
||||
overrides the "X" key to delete an object, you will need to manually trigger
|
||||
(either via menu or custom hotkey) the BlenderBIM Add-on equivalent operator
|
||||
(e.g. IFC Delete).
|
||||
- The add-on uses object deletion or duplication macros with dictionary
|
||||
override. Note that this is also deprecated in Blender, so the other add-on
|
||||
should be updated to fix this.
|
||||
- The add-on requires a conflicting dependency, or a conflicting version of the
|
||||
same dependency. Neither add-on may work simultaneously.
|
||||
@@ -1,214 +0,0 @@
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
BlenderBIM Reference Manual
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
Welcome to the manual for `BlenderBIM <https://blenderbim.org>`__,
|
||||
the free and open source BIM add-on for Blender.
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/introduction_to_bim` (2-minute read)
|
||||
Brief overview of BIM concepts and BlenderBIM's role.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/installation` (5 to 10-minute process)
|
||||
Installation guide and system requirements.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/explore_model` (15-minute guide)
|
||||
Load and navigate an IFC model.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/create_model` (20-minute guide)
|
||||
Create a simple BIM project.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Quickstart
|
||||
:maxdepth: 1
|
||||
|
||||
users/quickstart/introduction_to_bim
|
||||
users/quickstart/installation
|
||||
users/quickstart/explore_model
|
||||
users/quickstart/create_model
|
||||
users/quickstart/next_steps
|
||||
|
||||
Sections
|
||||
========
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/modeling/interface`
|
||||
Guide to the BlenderBIM interface.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/modeling/ifc_modeling`
|
||||
IFC modeling techniques and concepts.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/git_support`
|
||||
Collaborate on projects using Git
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/drawing/index`
|
||||
2D drawing generation and annotation.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/structural_analysis/index`
|
||||
Structural analysis capabilities in BlenderBIM.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/costing_and_scheduling/index`
|
||||
Cost estimation and project scheduling features.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/advanced/index`
|
||||
Advanced topics and large-scale modeling.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/tutorials/index`
|
||||
Real-world BIM project tutorials.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/user_interface`
|
||||
Detailed UI reference.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/other_addons`
|
||||
Other complementary add-ons
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/troubleshooting`
|
||||
Troubleshooting when things go wrong
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Sections
|
||||
:maxdepth: 2
|
||||
|
||||
users/modeling/interface
|
||||
users/modeling/ifc_modeling
|
||||
users/git_support
|
||||
users/drawing/index
|
||||
users/structural_analysis/index
|
||||
users/costing_and_scheduling/index
|
||||
users/advanced/index
|
||||
users/tutorials/index
|
||||
users/user_interface
|
||||
users/other_addons
|
||||
users/troubleshooting
|
||||
|
||||
Get Involved
|
||||
============
|
||||
|
||||
This manual is maintained largely by volunteers.
|
||||
|
||||
Please consider joining the effort and :doc:`/contribute/writing_docs`.
|
||||
|
||||
You can also can help with :doc:`Translations and Internationalisation </contribute/translations>`
|
||||
|
||||
For users who want to contribute to the BlenderBIM codebase,
|
||||
please refer to the :doc:`Documentation for Developers </devs/getting_started>`.
|
||||
This part of the documentation covers various aspects of the BlenderBIM development process.
|
||||
|
||||
You can get in touch by `joining the chat <https://osarch.org/chat/>`__.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Get Involved
|
||||
:maxdepth: 1
|
||||
|
||||
contribute/writing_docs
|
||||
contribute/translations
|
||||
|
||||
|
||||
Developer Documentation
|
||||
=======================
|
||||
|
||||
BlenderBIM is an open-source project, and its development is driven by the contributions of a dedicated community of developers,
|
||||
architects, engineers, and enthusiasts. If you're interested in contributing to the project, whether by submitting bug reports,
|
||||
suggesting new features, or contributing code, your involvement is highly encouraged and appreciated.
|
||||
|
||||
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/getting_started`
|
||||
Introduction to contributing to BlenderBIM.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/installation`
|
||||
Setting up a development environment.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/hello_world`
|
||||
Understanding the project structure.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/undo_system`
|
||||
Working with BlenderBIM's undo system.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/code_style`
|
||||
Code style guidelines and best practices.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/running_tests`
|
||||
Testing and quality assurance.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/ux_guidelines`
|
||||
User experience and interface guidelines.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Developer Documentation
|
||||
:maxdepth: 1
|
||||
|
||||
devs/getting_started
|
||||
devs/installation
|
||||
devs/hello_world
|
||||
devs/undo_system
|
||||
devs/code_style
|
||||
devs/running_tests
|
||||
devs/ux_guidelines
|
||||
@@ -1,10 +0,0 @@
|
||||
Advanced Use Cases
|
||||
==================
|
||||
|
||||
Advanced topics and large-scale modeling.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
georeferencing
|
||||
dealing_with_large_models
|
||||
@@ -1,51 +0,0 @@
|
||||
============================
|
||||
Advanded Modeling Techniques
|
||||
============================
|
||||
|
||||
Complex modeling techniques for advanced users.
|
||||
|
||||
.. note::
|
||||
This page is a stub. More detailed content will be added in future updates.
|
||||
|
||||
Custom Parametric Wall Types
|
||||
----------------------------
|
||||
|
||||
[Content about creating custom parametric wall types]
|
||||
|
||||
Material assignment
|
||||
-------------------
|
||||
|
||||
:doc:`material_assignment`
|
||||
|
||||
Modeling Furniture and Fixtures
|
||||
-------------------------------
|
||||
|
||||
[Content about modeling furniture and fixtures]
|
||||
|
||||
Structural Elements
|
||||
-------------------
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`/users/structural_analysis/index`
|
||||
|
||||
[Content about modeling structural elements]
|
||||
|
||||
Multi-Story Buildings
|
||||
---------------------
|
||||
|
||||
[Content about modeling multi-story buildings]
|
||||
|
||||
Complex Structures
|
||||
------------------
|
||||
|
||||
[Content about modeling complex structures]
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Sections
|
||||
:maxdepth: 2
|
||||
|
||||
material_assignment
|
||||
parametric_geometry
|
||||
@@ -1,44 +0,0 @@
|
||||
=========================
|
||||
Basic Modeling Techniques
|
||||
=========================
|
||||
|
||||
Fundamental techniques for IFC modeling.
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`creating_walls`
|
||||
Learn how to create and modify walls in your IFC model.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`openings/index`
|
||||
Techniques for adding and customizing doors, windows and other openings.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`modeling_slabs_roofs`
|
||||
Guide to modeling slabs and different types of roofs.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`defining_rooms_spaces`
|
||||
Methods for defining and managing rooms and spaces in your model.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Basic Modeling Techniques
|
||||
:maxdepth: 1
|
||||
|
||||
creating_walls
|
||||
openings/index
|
||||
modeling_slabs_roofs
|
||||
defining_rooms_spaces
|
||||
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
Openings
|
||||
========
|
||||
|
||||
This section covers the creation and management of openings in BlenderBIM.
|
||||
Openings are crucial elements in building design, serving various purposes such as passage, ventilation, and lighting.
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`door`
|
||||
Learn how to add and customize doors in your BIM model.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`window`
|
||||
Discover the process of creating and modifying windows.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`opening`
|
||||
Create openings without fillings for special architectural features.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
In the context of Building Information Modeling (BIM) and the Industry Foundation Classes (IFC)
|
||||
standard, openings are represented through a combination of elements:
|
||||
|
||||
1. Voids: Represented by IfcOpeningElement, these are the actual cut-outs in the wall.
|
||||
2. Fillings: These are the elements that occupy the voids, such as doors (IfcDoor) or windows (IfcWindow).
|
||||
3. Relationships: These are abstract objects that connect fillings to voids and voids to elements in which they're created.
|
||||
|
||||
BlenderBIM provides tools to create and manage these elements:
|
||||
|
||||
- Door Creation Tool: For adding doors to walls.
|
||||
- Window Creation Tool: For adding windows to walls.
|
||||
- Wall Creation Tool > Void Application: For creating openings without fillings
|
||||
(currently achieved by creating a door or window and removing it
|
||||
or by using "Add Void" feature of the Create Wall tool or any element).
|
||||
|
||||
These tools allow you to:
|
||||
|
||||
- Create openings with precise dimensions and positions.
|
||||
- Modify opening properties and geometries.
|
||||
- Manage the relationships between walls, voids, and fillings.
|
||||
|
||||
The following pages provide detailed guides on working with each type of wall opening in BlenderBIM.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Wall Openings
|
||||
:maxdepth: 1
|
||||
|
||||
door
|
||||
window
|
||||
opening
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- :doc:`../creating_walls`
|
||||
@@ -1,4 +0,0 @@
|
||||
Importing and Viewing IFC Models
|
||||
================================
|
||||
|
||||
[Content about importing and viewing IFC models]
|
||||
@@ -1,92 +0,0 @@
|
||||
============================
|
||||
BlenderBIM Interface Guide
|
||||
============================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The BlenderBIM interface extends Blender's powerful 3D environment with specialized tools for Building Information Modeling (BIM). This guide will help you navigate the BlenderBIM interface and understand its key components.
|
||||
|
||||
BlenderBIM Workspace
|
||||
====================
|
||||
|
||||
When you start Blender with BlenderBIM installed, you can switch to the BlenderBIM workspace
|
||||
by clicking on the BIM workspace tab at the top of the Blender window.
|
||||
|
||||
This workspace is preconfigured with the most commonly used panels and tools for BIM workflows.
|
||||
|
||||
|
||||
Key Interface Elements
|
||||
======================
|
||||
|
||||
Properties Extended
|
||||
-------------------
|
||||
|
||||
The Properties Editor is extended to provide IFC-specific properties and settings.
|
||||
|
||||
- Scene
|
||||
- Project Information
|
||||
- Tool
|
||||
- Object
|
||||
|
||||
BIM Toolbar
|
||||
-----------
|
||||
|
||||
The BIM toolbar, typically located at the left of the 3D Viewport, contains shortcuts to frequently used BIM tools:
|
||||
|
||||
- Explore tool
|
||||
- Create various building elements
|
||||
- Create annotations, measure distance and angles, calculate volumes, etc
|
||||
|
||||
|
||||
IFC Tree View
|
||||
-------------
|
||||
|
||||
Found in the Outliner, the IFC Tree View displays the hierarchical structure of your BIM model:
|
||||
|
||||
- Spatial Structure (Site, Building, Storey)
|
||||
- Building Elements
|
||||
- Types
|
||||
|
||||
Customizing the Interface
|
||||
=========================
|
||||
|
||||
BlenderBIM respects Blender's highly customizable interface. You can:
|
||||
|
||||
- Rearrange editors and panels
|
||||
- Save custom workspace layouts
|
||||
- Create custom shortcuts for BIM operations
|
||||
|
||||
Tips for Efficient Use
|
||||
======================
|
||||
|
||||
1. Familiarize yourself with IFC classes and their properties.
|
||||
2. Utilize Blender's search function (F3) to quickly access BlenderBIM tools.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Now that you're familiar with the BlenderBIM interface, you have two main paths to continue your learning:
|
||||
|
||||
1. Proceed with IFC Modeling:
|
||||
If you're ready to start creating and working with BIM models, you can move on to:
|
||||
|
||||
- :doc:`IFC Modeling Basics </users/modeling/ifc_modeling>`
|
||||
|
||||
2. Dive deeper into the User Interface:
|
||||
If you want to explore more details about the BlenderBIM interface, you can refer to:
|
||||
|
||||
- :doc:`User Interface Reference </users/user_interface>`
|
||||
|
||||
Choose the path that best suits your current needs and learning style. You can always come back to explore the other option later.
|
||||
|
||||
Remember, as you become more comfortable with the interface and basic modeling, you can explore more advanced topics such as:
|
||||
|
||||
- :doc:`Generating Documentation </users/drawing/index>`
|
||||
- :doc:`Advanced BIM Techniques </users/advanced/index>`
|
||||
|
||||
The BlenderBIM interface is designed to integrate seamlessly with Blender while providing powerful BIM-specific functionality.
|
||||
As you progress, you'll find your BIM workflow becoming increasingly efficient and productive.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 91 KiB |
@@ -1,140 +0,0 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/videoseries?list=PLMDcOjMJYxUPHHvEHqAsOuBdSPsp6or32" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
|
||||
1. **Download and install Blender**
|
||||
|
||||
Blender is a free and open-source program for 3D authoring. It works on
|
||||
Linux, Mac, and Windows. It is developed by the Blender community.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Download Blender <https://www.blender.org/download/>`__
|
||||
|
||||
.. tip::
|
||||
|
||||
No administrator rights on Windows? Choose the "Portable .zip" option when
|
||||
downloading from the Blender website.
|
||||
|
||||
2. **Download the BlenderBIM Add-on**
|
||||
|
||||
The BlenderBIM Add-on extends Blender with OpenBIM related capabilities.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Download BlenderBIM Add-on <https://blenderbim.org/download.html>`__
|
||||
|
||||
3. **Install the BlenderBIM Add-on**
|
||||
|
||||
Open up Blender, and click on ``Edit > Preferences``.
|
||||
|
||||
.. image:: images/install-blenderbim-1.png
|
||||
|
||||
Select the **Add-ons** tab, and press **Install...** on the top right. Navigate
|
||||
to the .zip you downloaded in Step 2, and press **Install Add-on**.
|
||||
|
||||
.. image:: images/install-blenderbim-2.png
|
||||
|
||||
.. warning::
|
||||
|
||||
You do not need to unzip the add-on file. You should install it as a zipped file.
|
||||
|
||||
You should now see **System: BlenderBIM** available in your add-ons list. Enable the add-on by pressing the checkbox.
|
||||
|
||||
.. image:: images/install-blenderbim-3.png
|
||||
|
||||
All done! Your interface will now look similar to below. If you check the ``File`` menu you should also see an option to ``Open IFC Project``.
|
||||
|
||||
.. image:: images/install-blenderbim-4.png
|
||||
|
||||
You can enable add-ons permanently by using ``Save User Settings`` from the Addons menu.
|
||||
|
||||
.. seealso::
|
||||
|
||||
If you are a poweruser, you may be interested in the **Unstable installation** to help with testing. :doc:`Read more </devs/installation>`
|
||||
|
||||
.. _where is the add-on installed:
|
||||
|
||||
Updating
|
||||
--------
|
||||
|
||||
First follow the `Uninstalling`_ section below, then install the latest version.
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||
you have to **first disable the BlenderBIM Add-on in your Blender preferences**
|
||||
by pressing the checkbox next to the add-on, then **restart Blender**. It is
|
||||
critical to follow this sequence of disabling first, and then restarting.
|
||||
|
||||
After restarting, you can uninstall the BlenderBIM Add-on by pressing the
|
||||
``Remove`` button in the Blender preferences window.
|
||||
|
||||
Alternatively, you may uninstall manually by deleting the ``blenderbim``
|
||||
directory in :ref:`your Blender add-ons directory<where is the add-on
|
||||
installed>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
It is important to follow the sequence of disabling, restarting, then removing.
|
||||
If you do not restart Blender, the add-on will fail to remove correctly, and you
|
||||
will need to uninstall manually.
|
||||
|
||||
Where is the add-on installed?
|
||||
------------------------------
|
||||
|
||||
Upon installation, the BlenderBIM Add-on is stored in Blender configuration
|
||||
folder. However, the location of your Blender configuration folder depends on
|
||||
how you have installed Blender.
|
||||
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, the
|
||||
BlenderBIM Add-on will be installed in the following directory, where ``X.XX``
|
||||
is the Blender version:
|
||||
|
||||
::
|
||||
|
||||
/path/to/blender/X.XX/
|
||||
|
||||
Otherwise, if you installed Blender using an installation package, the Blender
|
||||
configuration folder depends on which operating system you use.
|
||||
|
||||
On Linux, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
~/.config/blender/X.XX/
|
||||
|
||||
On Linux, if you are deploying the add-on system-wide (this may also depend on
|
||||
your Linux distribution):
|
||||
|
||||
::
|
||||
|
||||
/usr/share/blender/X.XX/
|
||||
|
||||
On Mac, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/
|
||||
|
||||
On Mac, if you are deploying the add-on system-wide:
|
||||
|
||||
::
|
||||
|
||||
/Library/Application Support/Blender/X.XX/
|
||||
|
||||
On Windows:
|
||||
|
||||
::
|
||||
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\
|
||||
|
||||
Inside the Blender configuration folder, the BlenderBIM Add-on is stored in two
|
||||
different locations. The extension itself is stored in
|
||||
``extensions/blender_org/blenderbim`` whereas the Python packages are installed
|
||||
into ``extensions/.local/lib/pythonX.XX/site-packages/``.
|
||||
@@ -1,98 +0,0 @@
|
||||
Introduction to BIM
|
||||
===================
|
||||
|
||||
**Building Information Modeling**, or **BIM**, is a way of digitally describing
|
||||
our built environment to computers. Aspects of our built environment that can be
|
||||
described are:
|
||||
|
||||
- **Products**, like walls, doors, and windows
|
||||
- **Processes**, like construction or maintenance tasks, and procedures
|
||||
- **Resources**, like labour, materials, and equipment
|
||||
- **Controls**, like permits, orders, costs, or calendar availability
|
||||
- **Actors**, like occupants, clients, architects, and liable parties
|
||||
- **Groups**, like systems, inventories, or zones
|
||||
|
||||
These objects may have lots of data and relationships. Examples of data might be
|
||||
classification systems, physical materials, associated documents, simulation
|
||||
results, and construction types. The data may be relevant to multiple
|
||||
disciplines, such as architecture, engineering, and construction.
|
||||
|
||||
.. note::
|
||||
|
||||
BIM data is very different from a regular 3D model. In fact, geometry is
|
||||
optional, and most data is non-geometric. This means that it is not simply a
|
||||
3D format that you can import or export from and expect meaningful results.
|
||||
|
||||
**Industry Foundation Classes**, or **IFC**, is an international standard for
|
||||
**BIM**. **IFC** is the most well-established open digital language for our
|
||||
built environment. Most software will be able to describe their **BIM** data
|
||||
using **IFC**. Most commonly, **IFC** models will be shared as a ``.ifc`` file.
|
||||
|
||||
For example, **IFC** will define a wall as an object that can have a name,
|
||||
construction type, and quantities. **IFC** will also describe that a wall that
|
||||
be associated with a location, like a building storey, or have an associated
|
||||
cost item in a schedule.
|
||||
|
||||
When you use the BlenderBIM Add-on, you will be able to view and create **BIM**
|
||||
objects and relationships using the **IFC** standard.
|
||||
|
||||
Things you can do
|
||||
-----------------
|
||||
|
||||
The BlenderBIM Add-on is designed to be a comprehensive and truly native
|
||||
BIM authoring platform. Its capabilities include a wide range of tasks
|
||||
and workflows typically found across various BIM and CAD software, costing
|
||||
programs, scheduling tools, and simulation applications. While not
|
||||
an exhaustive list, some of the key things you can do with BlenderBIM include:
|
||||
|
||||
- View and explore IFC models, including spaces, properties, and relationships
|
||||
- Edit and extract attributes, properties, and metadata directly from IFC data
|
||||
- Move, rotate, and modify the geometry of objects while preserving IFC semantics
|
||||
- Create new objects using predefined library elements or custom parametric types
|
||||
- Manage classification systems, document references, and link to external libraries
|
||||
- Generate 2D drawing views like plans, sections, and elevations with customizable annotations
|
||||
- Investigate and edit structural analysis models with support for various steel profiles
|
||||
- Model and manage complex distribution systems like HVAC, plumbing, and electrical (MEP)
|
||||
- Create construction schedules, perform critical path analysis, and generate sequence animations
|
||||
- Derive quantities from model elements and create cost schedules with formulas
|
||||
- Perform clash detection and coordinate models, managing issues across disciplines
|
||||
- Integrate non-geometric data like costing, scheduling, and asset management
|
||||
|
||||
|
||||
Roadmap and Upcoming Features
|
||||
-----------------------------
|
||||
|
||||
While BlenderBIM already offers a comprehensive set of BIM authoring capabilities,
|
||||
the development team and community are continuously working to expand its functionality and improve existing workflows.
|
||||
Some of the most ambitious features and enhancements on the roadmap include:
|
||||
|
||||
Usability and Workflow Enhancements
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Improve user-friendliness to equip average "Joe" with capabilities to model simple projects like single family homes, making BlenderBIM a viable alternative to SketchUp.
|
||||
- Make BlenderBIM more approachable for users across different skill levels, from advanced BIM experts to architects working on smaller residential projects.
|
||||
|
||||
Improved Drawing and Documentation Workflows
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Continued enhancements to the drawing generation capabilities, including better dimensioning tools and support for associative dimensions linked to model geometry.
|
||||
- Advanced annotation tools with customizable tags, callouts, and the ability to define reusable annotation styles and templates.
|
||||
- Streamlined sheet layout management and improved integration with external tools like Inkscape for creating title blocks and sheet compositions.
|
||||
- Continued improvement of BlenderBIM's ability to generate comprehensive documentation sets, including drawing annotations, door schedules, wall schedules, and material tagging.
|
||||
|
||||
Expanded Parametric Modeling Capabilities
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Native modeling tools for complex building elements like curtain wall systems and facade panels.
|
||||
- Expanded libraries with support for country-specific and manufacturer-provided object types.
|
||||
- Enhanced support for multi-story modeling, enabling efficient duplication and coordination of building elements across various levels.
|
||||
- Expanded parametric relationships and automating more common BIM tasks to reduce manual effort and enhance productivity.
|
||||
|
||||
Enhanced Coordination and Collaboration Features
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Robust model merge and coordination workflows, with detailed clash detection capabilities across different disciplines and data sources.
|
||||
- Built-in support for version control and model sharing using Git repositories, enabling better team collaboration and change tracking.
|
||||
|
||||
|
||||
The development roadmap is continuously updated based on user feedback, industry requirements,
|
||||
and contributions from the open-source community. By embracing an open and collaborative approach,
|
||||
BlenderBIM aims to push the boundaries of what's possible in BIM authoring,
|
||||
ensuring that it remains at the forefront of innovation in the AEC industry.
|
||||
@@ -1,86 +0,0 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
The BlenderBIM Add-on is alpha software. There are many bugs! When something
|
||||
goes wrong, you may see some computer code flash up on your screen. You may
|
||||
also see an error message:
|
||||
|
||||
.. image:: images/error-message.png
|
||||
|
||||
**Don't panic!** Click on the button that says **Copy Error Message To
|
||||
Clipboard**. You will need to paste this text in a bug report.
|
||||
|
||||
If you do not have a GitHub account, you will need to sign up to report a bug.
|
||||
In addition to pasting the error message text, please also describe what you
|
||||
were doing, and attach your IFC file or screenshots if relevant.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Report a bug <https://github.com/IfcOpenShell/IfcOpenShell/issues/new>`__
|
||||
|
||||
If your issue is particularly complex, you can also chat live with developers
|
||||
or other powerusers.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Chat live with a developer <https://osarch.org/chat>`_
|
||||
|
||||
Installation issues
|
||||
-------------------
|
||||
|
||||
If you are unable to install the BlenderBIM Add-on, make sure you are using
|
||||
**Blender 4.2** installed from https://blender.org/ and have
|
||||
:ref:`updated to the latest version <users/quickstart/installation:Updating>`.
|
||||
|
||||
Other common solutions are listed below. If none of these fix the problem, you
|
||||
can `report a bug <https://github.com/ifcopenshell/ifcopenshell/issues>`_ or
|
||||
`live chat with a developer <https://osarch.org/chat/>`_.
|
||||
|
||||
1. **Some other error prevents me from installing or doing basic functions with
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Try installing and using the BlenderBIM Add-on on a "clean environment". A
|
||||
clean environment is a fresh Blender installation with no other add-ons
|
||||
enabled with factory settings.
|
||||
|
||||
To quickly test in a clean environment, first :ref:`find your Blender
|
||||
configuration folder<users/quickstart/installation:Where is the add-on
|
||||
installed?>`. Rename the folder from ``X.XX`` to something else like
|
||||
``X.XX_backup``, then restart Blender and try follow the :doc:`installation
|
||||
instructions</users/quickstart/installation>` again.
|
||||
|
||||
If this fixes your issue, consider disabling other add-ons one by one until
|
||||
you find a conflict as a next step to isolating the issue.
|
||||
|
||||
2. **I am on Ubuntu and get an error similar to "ImportError:
|
||||
/lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
|
||||
|
||||
Our latest package which uses IfcOpenShell v0.8.0 is built using Ubuntu 20 LTS.
|
||||
If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
|
||||
or you'll need to compile IfcOpenShell yourself.
|
||||
|
||||
3. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, such as from your
|
||||
distro's package repositories, then you may be missing some modules like
|
||||
``numpy``. Try installing it manually like ``apt install python-numpy``.
|
||||
|
||||
Saving and loading blend files
|
||||
------------------------------
|
||||
|
||||
The BlenderBIM Add-on transform Blender into a native IFC authoring platform.
|
||||
This means that you can open and save IFC files directly without using
|
||||
Blender's ``.blend`` format.
|
||||
|
||||
All data about your model is saved in your IFC. No data is stored in the
|
||||
``.blend`` format. This means that if you save or open a ``.blend`` file, you
|
||||
are **not** saving and loading your model. At best, you are saving and loading
|
||||
Blender geometry that represents what the model might've looked at at some
|
||||
point. At worst, you might be looking at a completely wrong model.
|
||||
|
||||
If you continue to open and save ``.blend`` files, you will run the risk of
|
||||
editing something that doesn't actually exist in your IFC model. This will
|
||||
create unpredictable, and sometimes unrecoverable errors.
|
||||
|
||||
To avoid this issue, only open and save IFCs.
|
||||
@@ -1,98 +0,0 @@
|
||||
=======================
|
||||
Tutorials
|
||||
=======================
|
||||
|
||||
Project Series
|
||||
==============
|
||||
|
||||
Step-by-step guides for real-world BIM projects.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:hidden:
|
||||
|
||||
project0
|
||||
project1
|
||||
project2
|
||||
project3
|
||||
project4
|
||||
project5
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 0: Designing a Room <project0>`
|
||||
* Basic walls and openings
|
||||
* Slabs
|
||||
* MEP
|
||||
* Basic furniture
|
||||
* Basic floor plan drawings
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 1: Designing a Flat <project1>`
|
||||
* Advanced walls and openings
|
||||
* Profiles
|
||||
* Rooms and spaces
|
||||
* Furniture/fixture libraries
|
||||
* Elevation/section drawings
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 2: Building a Bungalow <project2>`
|
||||
* Roof
|
||||
* Advanced foundation
|
||||
* Structural elements
|
||||
* Roof openings
|
||||
* Underground services
|
||||
* Running services in subfloor space
|
||||
* Drawing details
|
||||
* Basic costing and scheduling
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 3: Multi-Storey Single-Family Home <project3>`
|
||||
* Templating
|
||||
* Types
|
||||
* Complex roofs
|
||||
* Complex fixtures (e.g. solar panels)
|
||||
* Complex HVAC, plumbing, electrical
|
||||
* Basic site landscaping
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 4: Terraced/Town Houses/Duplexes <project4>`
|
||||
* Shared walls
|
||||
* Shared roofs
|
||||
* Complex landscaping
|
||||
* Duplicating units
|
||||
* Basic parametric floor plans
|
||||
* Generating drawings parametrically
|
||||
* Clash detection
|
||||
* Coordination with other stakeholders
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 5: Apartment Block <project5>`
|
||||
* Multiple levels
|
||||
* Typical floor
|
||||
* Shared amenities and common areas
|
||||
* Elevators
|
||||
* Parametric services
|
||||
* Complex structural elements
|
||||
* Advanced exporting
|
||||
* Merging
|
||||
* Coordination
|
||||
* Analysis
|
||||
* Advanced costing and scheduling
|
||||
* External tools
|
||||
|
||||
Quickstart Video Tutorial
|
||||
=========================
|
||||
|
||||
Learn how to model a small building and create simple architectural plans, sections, 2D details, and sheet layouts in this short tutorial series.
|
||||
|
||||
`View all tutorial videos <https://www.youtube.com/playlist?list=PLMDcOjMJYxUPHHvEHqAsOuBdSPsp6or32>`__
|
||||
@@ -1,23 +0,0 @@
|
||||
User Interface Reference
|
||||
========================
|
||||
|
||||
This section covers the general user interface of BlenderBIM. User interface documentation does not cover any specific use case but provides a general reference of BlenderBIM UI.
|
||||
|
||||
The documentation doesn't cover Blender UI, but makes references to `Blender documentation <https://docs.blender.org/manual/en/latest/interface/index.html>`__ where appropriate.
|
||||
|
||||
.. * Navigating the Project Browser and Outliner
|
||||
.. * Using the Property Panels
|
||||
.. * Toolbars and Menus
|
||||
.. * Customizing the Interface
|
||||
.. * Keyboard Shortcuts and Preferences
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
user_interface/topbar
|
||||
user_interface/workspace
|
||||
user_interface/toolbar/index
|
||||
user_interface/property-editor
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 513 KiB |
@@ -1,60 +0,0 @@
|
||||
Property Editor Scene Properties
|
||||
================================
|
||||
|
||||
.. container:: location-scene
|
||||
|
||||
|location| Scene Properties
|
||||
|
||||
.. |location| image:: /images/location-scene.svg
|
||||
|
||||
BlenderBIM adds new functionality to the `Property Editor` -> `Scene` tab.
|
||||
|
||||
.. figure:: images/interface_property-editor_project-overview_start-up.png
|
||||
:alt: Property editor on Blender start-up
|
||||
|
||||
The property editor on Blender startup.
|
||||
|
||||
Most of these sub-tabs become available with a created or loaded IFC file.
|
||||
Don't worry, the default Blender scene properties are still reachable under their own dedicated sub-tab.
|
||||
|
||||
.. figure:: images/interface_property-editor_icons.png
|
||||
:alt: Overview over the added property sub-tabs by BlenderBIM
|
||||
|
||||
Overview over the added property sub-tabs by BlenderBIM.
|
||||
|
||||
1. Project Overview
|
||||
2. Object Information
|
||||
3. Geometry and Materials
|
||||
4. Drawings and Documents
|
||||
5. Services and Systems
|
||||
6. Structural Analyses
|
||||
7. Costing and Scheduling
|
||||
8. Facility Management
|
||||
9. Quality and Coordination
|
||||
10. Blender Properties
|
||||
11. Switch Tab
|
||||
|
||||
You can also select the needed panel via the drop-down menue.
|
||||
|
||||
.. figure:: images/interface_property-editor_panel-dropdown.png
|
||||
:alt: BlenderBIM property editor sub-tabs drop-down menue
|
||||
|
||||
Switching between BlenderBIM property editor sub-tabs via the drop-down menue.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
property_editor/scene_editor/project_overview/index
|
||||
property_editor/scene_editor/object_information/index
|
||||
property_editor/scene_editor/geometry_and_materials/index
|
||||
property_editor/scene_editor/drawings_and_documents/index
|
||||
property_editor/scene_editor/services_and_systems/index
|
||||
property_editor/scene_editor/structural_analysis/index
|
||||
property_editor/scene_editor/costing_and_scheduling/index
|
||||
property_editor/scene_editor/facility_management/index
|
||||
property_editor/scene_editor/quality_and_coordination/index
|
||||
property_editor/scene_editor/blender
|
||||
property_editor/scene_editor/switch_tab
|
||||