mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
772 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fca38625f | |||
| cf2ad43ed7 | |||
| fb3023bb79 | |||
| 52f18fea85 | |||
| d5ecb9b8ea | |||
| 67d33001d5 | |||
| 4f571fa2fc | |||
| c5bd902dc6 | |||
| 68282c7a1e | |||
| d40dcae212 | |||
| 12151c40ff | |||
| 9c8b15fa1a | |||
| 3d49cc9d13 | |||
| 955c0d19a6 | |||
| 3cf7a9e925 | |||
| 8cbf719772 | |||
| 2f614330c9 | |||
| 5a40519114 | |||
| 96271e8bd8 | |||
| 9b75a415d4 | |||
| 5f71abb5fa | |||
| affb066b7b | |||
| 17add48254 | |||
| f5b6fdcb5b | |||
| 68bdbe4c4f | |||
| 363ddc2a9b | |||
| 20d44378d9 | |||
| 4fe321275a | |||
| 60b1192703 | |||
| b25233a748 | |||
| d9b54c923c | |||
| 131ee8270b | |||
| e71bace6a8 | |||
| 2c4a0669ed | |||
| ee451b6b29 | |||
| 7b858fffb2 | |||
| 6ce0deeb63 | |||
| 96b2eaeae4 | |||
| 8fe8175a87 | |||
| ee2bedfb64 | |||
| aa8da74ed8 | |||
| f2c03a508f | |||
| aa172488b0 | |||
| ae09adf854 | |||
| 5b5dd73684 | |||
| 3849a87adc | |||
| 708dc6aca2 | |||
| 2fe2c349fd | |||
| d396d32a8f | |||
| 1fd02755f8 | |||
| 79d92b511b | |||
| ff7d93e66c | |||
| 68a8c7a235 | |||
| 5e744ff7d1 | |||
| 921a185ffa | |||
| 855751f026 | |||
| 255c2efe98 | |||
| 892b282054 | |||
| 26efc6b4e7 | |||
| a0618c5827 | |||
| d4bbb6e2c7 | |||
| 66b7692227 | |||
| 2429f507ad | |||
| 03a66d2806 | |||
| 9d8551cb48 | |||
| 2ca0b52c6a | |||
| 9cc754488b | |||
| f81ceb53cf | |||
| d1d0b5724d | |||
| 40560b71c2 | |||
| 378a25bbaa | |||
| 1dfabe2591 | |||
| 0e1689c2d9 | |||
| bd071cc472 | |||
| dde847cb5a | |||
| 3c17718f70 | |||
| 0445119e77 | |||
| e82cb5c149 | |||
| 984548f315 | |||
| a0be4f58e6 | |||
| 147602e107 | |||
| ac81169290 | |||
| 1d78e05370 | |||
| 4b15b02ac8 | |||
| 57d18a0644 | |||
| 2f73af7e6a | |||
| e712ef344d | |||
| f91e9a3119 | |||
| 9cf36f1d91 | |||
| ab9a4856ab | |||
| d52618277e | |||
| 743bd2c48f | |||
| 11894de455 | |||
| 0c8122afc4 | |||
| f893503d0c | |||
| 504782f156 | |||
| 5d1f32bfc0 | |||
| 5bc315be92 | |||
| e8f21c79b1 | |||
| ba24f9b491 | |||
| 937fa21cce | |||
| 16cbab2de7 | |||
| a85f81d8ca | |||
| 1617ede010 | |||
| d9f1d501aa | |||
| 8ee60db0df | |||
| d2e02c8ab3 | |||
| 2d4e4d2445 | |||
| 9dc69f3aa7 | |||
| 476ab506d0 | |||
| a6fa6201f7 | |||
| 26f7db0504 | |||
| 77a0da2a17 | |||
| 785d2e601c | |||
| 3687e4ed91 | |||
| 31cf1127a6 | |||
| 97385efbca | |||
| 6a87b68c3b | |||
| be06bd6369 | |||
| a5b4ac863b | |||
| e535f911d1 | |||
| 18670bcbd6 | |||
| 27511d362e | |||
| c634bffa10 | |||
| dffa90bd5d | |||
| 6a47d33974 | |||
| e87524ba11 | |||
| b1154ae377 | |||
| cbc24c31a2 | |||
| 0a8a6e5825 | |||
| e088a10aa8 | |||
| 007d984baf | |||
| 7d979e5a04 | |||
| 4134c709a8 | |||
| ff5e4991ab | |||
| 0b4b45b7c8 | |||
| 145ac1a0f4 | |||
| 74de7436b7 | |||
| 60fa05a913 | |||
| 303de61417 | |||
| a9a4c6049f | |||
| 82b201a14e | |||
| c9bd7adf8a | |||
| 0655d77210 | |||
| 515ec28fb3 | |||
| c28c2dd077 | |||
| b04383bb64 | |||
| f4b93ef243 | |||
| 05cf00bef6 | |||
| e81346c339 | |||
| ac911cefd6 | |||
| c4cea20849 | |||
| e30992b905 | |||
| 5beea8640e | |||
| d3b176e5eb | |||
| 0d0691a012 | |||
| d4eb56e360 | |||
| 5cfc122b79 | |||
| fa0f484922 | |||
| ceaa930aad | |||
| bbd2b67041 | |||
| 6c82e6f889 | |||
| 531e07767f | |||
| da39c903f3 | |||
| f458aa9932 | |||
| 9e383959c8 | |||
| 3654e49f87 | |||
| 13f5767921 | |||
| 6d1e9f73a6 | |||
| d9ddf67afb | |||
| 47e6352350 | |||
| ae564d1b61 | |||
| d9cee04769 | |||
| 50d71d5ea1 | |||
| 25c650f4cd | |||
| f0502c123e | |||
| 693960a4ab | |||
| 5df0cf39a0 | |||
| 578220a1ab | |||
| ae5ea3857e | |||
| 93d0ffd459 | |||
| 1b85928810 | |||
| f118c50d1d | |||
| ac5ec72acf | |||
| 7fe2634d32 | |||
| 1279b9848f | |||
| ea91f4302c | |||
| 88bddb9531 | |||
| 4e308a6e31 | |||
| 4bd7fc5304 | |||
| 0122fe4382 | |||
| 1730f25a5a | |||
| 3f8314bcf3 | |||
| 5879d14803 | |||
| 4c1a821922 | |||
| e1456533f2 | |||
| 7522a3a2ab | |||
| 144d01a0ef | |||
| f6cb6c8b21 | |||
| 0f5264af99 | |||
| e57ff19e16 | |||
| b2bdbdd8c3 | |||
| a8400ec963 | |||
| d028b52d77 | |||
| 65990255e8 | |||
| 3a71b03a9a | |||
| 99274c2d7a | |||
| e40bbf2525 | |||
| 38c6378315 | |||
| c050ca1037 | |||
| 9813dda778 | |||
| 6b7aeb90c7 | |||
| 3cb29e1344 | |||
| 4d4961a490 | |||
| ccd7db06f8 | |||
| 77234706b0 | |||
| 9f399b34fb | |||
| dc77a798b5 | |||
| 207093fe99 | |||
| 42c4a68108 | |||
| a7cc58fb50 | |||
| 6bab10d2ac | |||
| c4b77c2a07 | |||
| 66d0627035 | |||
| 3f4417c9f2 | |||
| 5526bed4c3 | |||
| 078bf8646a | |||
| 6adbcdd8e1 | |||
| b4d633a34d | |||
| e1e4380fc7 | |||
| 1ed7b21ab0 | |||
| 158a4c5e75 | |||
| f72d48aab5 | |||
| be26a77ecb | |||
| 7c275a99a2 | |||
| f77d2f449d | |||
| 18ba805a15 | |||
| a5adf0021f | |||
| 7d944255d0 | |||
| b984a77862 | |||
| d69aa20802 | |||
| 7e16cb797f | |||
| b3c7971ea9 | |||
| a778b98fdb | |||
| 4efb80efe4 | |||
| cfaacf8650 | |||
| 60da4cb87b | |||
| db6207db6c | |||
| 9b4568c881 | |||
| cd132d4fe9 | |||
| f01900bdb1 | |||
| 0fddd243c2 | |||
| 1066e5e0a7 | |||
| 7e0b1b9cbd | |||
| 95e924a5bc | |||
| f9cfd17303 | |||
| 9f37f73e52 | |||
| 9acd2043d7 | |||
| ab6159f173 | |||
| f9bff64f0d | |||
| 978f362073 | |||
| 7a185e7f7c | |||
| 044cd6e97b | |||
| b939370f2b | |||
| 4af0c1c98c | |||
| 181531ada8 | |||
| e92d908063 | |||
| 686f4a023d | |||
| c0c6c23fca | |||
| 396fd5520f | |||
| efc3a692cb | |||
| 2902d96690 | |||
| 70f35095ff | |||
| 87c67abac9 | |||
| 7df9640807 | |||
| 25edc91976 | |||
| 2174fda88d | |||
| 2fc5c6c110 | |||
| 6a2307308c | |||
| 1a05fae826 | |||
| cce27d73fd | |||
| 87515f3839 | |||
| 24f3918769 | |||
| f7af38c863 | |||
| 6c2a4711da | |||
| 41fdd77bfa | |||
| 9185a51234 | |||
| c5ae86d10b | |||
| 74477d0051 | |||
| cee0ff2074 | |||
| 85bba9d5f8 | |||
| 8e696cbf02 | |||
| c7d1f5efc0 | |||
| 68bbb4d7a1 | |||
| b1dd20fef7 | |||
| a701624d7b | |||
| f912be7875 | |||
| 04bdb12e79 | |||
| d69f6c02b6 | |||
| 95c3a57a4d | |||
| 0b6d321d83 | |||
| e0fd3fd90e | |||
| 7686e35858 | |||
| 8eeb8804d4 | |||
| cb9a7cf5be | |||
| 54cec30a60 | |||
| 9f0ad9be3c | |||
| a6271d0936 | |||
| cf6acd5dd6 | |||
| f8468e66e6 | |||
| d0cf5f9d0c | |||
| 2c123fdbfb | |||
| 49b5bbd585 | |||
| bf3063ec00 | |||
| 1e2ac1fe24 | |||
| 9d7145746f | |||
| 6e7fee5bc8 | |||
| 9638ae3e77 | |||
| 69cee56b7c | |||
| 2257942676 | |||
| a5ca7c540f | |||
| c9b0a7c3de | |||
| 358d19c883 | |||
| e624d56516 | |||
| 78a6532615 | |||
| 44bb4ac74d | |||
| f782d784af | |||
| caf9034839 | |||
| 893fd50b85 | |||
| 7a3e53a9f8 | |||
| 07ee62eb91 | |||
| b41878e960 | |||
| d9f1cb5525 | |||
| 8110951987 | |||
| 6b63b5aae7 | |||
| e814736e68 | |||
| 802e3a0ea1 | |||
| 4e8a962f60 | |||
| e20d4c93b7 | |||
| 1be76c8aaa | |||
| a4566797fe | |||
| 4ee3458ff9 | |||
| f86de533f8 | |||
| 693831dca3 | |||
| 7500919b5d | |||
| e37b6fa8a2 | |||
| 9de9645229 | |||
| 373d664251 | |||
| 6ab912a049 | |||
| 9b9c900340 | |||
| a1ac820050 | |||
| e812a886dc | |||
| b759e22875 | |||
| 93e0babe5a | |||
| 7d021fb7a4 | |||
| b2cb8ffd8f | |||
| 25a5651e50 | |||
| b301eb6520 | |||
| 9f90d37401 | |||
| ba7741fef5 | |||
| d1a40d129f | |||
| 791076df2d | |||
| 608952a4dd | |||
| e64047c511 | |||
| fc3c6599c7 | |||
| 2336f299f7 | |||
| 4a71ab9631 | |||
| ac3c4cac89 | |||
| 2be1baab32 | |||
| 3e42fdb883 | |||
| ed37d11002 | |||
| 4cf896f564 | |||
| 7e151799b0 | |||
| 597b2bb0a8 | |||
| ddc0f7e652 | |||
| 4845d191f4 | |||
| da84cc5925 | |||
| c8ba96cde8 | |||
| 66e067d1ce | |||
| 9d39760f6f | |||
| 8aa4934d92 | |||
| f848340124 | |||
| b699f55d9c | |||
| 745709c56a | |||
| 0c4c8ad9bc | |||
| 3ce010de77 | |||
| dd600dd20f | |||
| 360b9956c0 | |||
| 1888048d4d | |||
| 4ab6d648c9 | |||
| c29048ecfb | |||
| 0294c4e02c | |||
| 3cfcd847cf | |||
| 694b21a0e1 | |||
| 73d43649a5 | |||
| 55036653df | |||
| 66ecb92cc2 | |||
| 23c7bc3f56 | |||
| 74fee24004 | |||
| 618ecccb7b | |||
| f818fc659a | |||
| 4afada48a6 | |||
| 59451cb90c | |||
| 53bf746227 | |||
| cd6f995471 | |||
| 839a00c2a8 | |||
| 9208781b8f | |||
| 25af42f4cb | |||
| d386a884aa | |||
| cb92be2b08 | |||
| 70b7f1424f | |||
| 56858540b8 | |||
| 0331a21280 | |||
| 9f53d724d1 | |||
| c114f09f9a | |||
| 1a2183ef71 | |||
| 0ab0213dea | |||
| 421f861e46 | |||
| 6e5f2f3f8d | |||
| 05633cd657 | |||
| 41ae350f33 | |||
| 8a26f30506 | |||
| 4ae7643874 | |||
| b09068e1f4 | |||
| 87dbaa39d8 | |||
| d193e8a3eb | |||
| f9e766138c | |||
| 36899d362e | |||
| fd789178c1 | |||
| a0757851c8 | |||
| 039727cb19 | |||
| b5133f1c41 | |||
| ab218b842a | |||
| 37aa17fcf3 | |||
| 6542fc7db3 | |||
| c8c690afc2 | |||
| 5d569dea30 | |||
| 38031a02bc | |||
| 25460661ab | |||
| 3684be1936 | |||
| 0ada921ad8 | |||
| 8fb285596a | |||
| ba4130d395 | |||
| f818446e7e | |||
| 82ac895bbb | |||
| fd8774718e | |||
| 90d4d5afda | |||
| 92623b98e2 | |||
| b86818ead3 | |||
| 9e45435024 | |||
| 981b6cd65f | |||
| 0c77fb81ce | |||
| e53a9cd66c | |||
| 2805109cef | |||
| 6594445fa3 | |||
| bac6c62073 | |||
| 6180d73f75 | |||
| 599c391407 | |||
| 1f18c5ab82 | |||
| 2bb4e645b2 | |||
| 5326e21fef | |||
| 891b49ff6f | |||
| 0f9c84c1ce | |||
| 2ccf25f7eb | |||
| 082a326e67 | |||
| d33a7244a3 | |||
| bdb352ab22 | |||
| 04fb7cdf9c | |||
| 16de2f2e7e | |||
| 0b6c8fe83e | |||
| d11959d303 | |||
| 2bd5f3bbb3 | |||
| cabc91dd5e | |||
| 88c9f15320 | |||
| b206e90097 | |||
| 73d3e9589c | |||
| 84a07c3fdf | |||
| ea1c306733 | |||
| 30dc13f1a7 | |||
| 9b3b06adf3 | |||
| 436482532d | |||
| d2fee728f2 | |||
| 0ccea3cef8 | |||
| 241406f676 | |||
| a89c061b58 | |||
| 1211c153d9 | |||
| a5bdcf488b | |||
| a5a1ef2e8e | |||
| 05cd11ae12 | |||
| 307f9a2322 | |||
| 3d05dc234a | |||
| 1f87165601 | |||
| a513e1b56a | |||
| ec0c8f509e | |||
| a23bac6ecb | |||
| 569c19abca | |||
| dfeac601e4 | |||
| 53cecb3546 | |||
| cf87852d9c | |||
| 7da3d9ee70 | |||
| 9650bbb5ee | |||
| 45953c6b49 | |||
| b34caa1b5f | |||
| ea8433b187 | |||
| 390f23a30c | |||
| c8ba2b53c9 | |||
| dbbbb52e9e | |||
| 052d6e2254 | |||
| b05c711fb9 | |||
| eb0322759c | |||
| 25a7a95284 | |||
| 8ec1b7bb3e | |||
| 5f094f6a95 | |||
| f0b1935861 | |||
| 2540ba3467 | |||
| 88093728a7 | |||
| af6734d7b0 | |||
| 8a108f37c3 | |||
| d88510b919 | |||
| afe9f7602f | |||
| ff2b79a41f | |||
| 631722303f | |||
| 4b20a407e9 | |||
| b23f445946 | |||
| 09c8268494 | |||
| 0bc3543914 | |||
| 0412eb48a0 | |||
| e7d5add13d | |||
| 25948a608a | |||
| 11f772b5a3 | |||
| 412cf7c6cb | |||
| b2d5580c37 | |||
| 8386aa0e0a | |||
| ae0f2a9de6 | |||
| 4e0a19e013 | |||
| 9a840174d2 | |||
| 83528ba80a | |||
| eb23372fd0 | |||
| 286e9dfda9 | |||
| f0b9c9ad7b | |||
| afd79ee16d | |||
| caa4445eb0 | |||
| f850bd5741 | |||
| 8c5883ffaa | |||
| 8f8940de2a | |||
| bf59871db5 | |||
| 22eb0bf2e4 | |||
| 68838a37e5 | |||
| 22b4bfe07d | |||
| ff2714d991 | |||
| 308ee7f7a9 | |||
| c87924c4ba | |||
| 8f33c2ca9d | |||
| 9351038009 | |||
| 66281ec1f1 | |||
| ae843e6faf | |||
| 5ca0a254ff | |||
| c18458b3db | |||
| 3736a8b4b4 | |||
| 6558f68cd4 | |||
| 4fd5806f70 | |||
| b1c730c04d | |||
| bdec443410 | |||
| d9afb0f8a2 | |||
| 9dfd7e03f7 | |||
| 31b633b8b6 | |||
| 20f4737ffd | |||
| 4d4ab63404 | |||
| 056f7610c1 | |||
| bfb2f06e44 | |||
| a7febaa9b0 | |||
| ff984f62af | |||
| d005524c4c | |||
| a2362668a3 | |||
| 277317e6fd | |||
| b3f0b9b37b | |||
| 6de1e73e4f | |||
| c6cdf4ab77 | |||
| 21f8aca00f | |||
| 1d6c3b3fc2 | |||
| 4362163ea9 | |||
| 67808b8384 | |||
| 2cbc7a10ab | |||
| d97594df96 | |||
| c7eca135de | |||
| e63ec83325 | |||
| 1ddae8a6f5 | |||
| 03781bdf9b | |||
| 62e5c3684b | |||
| ff90728a22 | |||
| dadf23681b | |||
| 516bd37518 | |||
| 521ec1b124 | |||
| 4881e4c686 | |||
| a57cac5f9c | |||
| ca98e77b40 | |||
| cbd9c898c3 | |||
| f57731a10a | |||
| 0142bface5 | |||
| 5acbf4658f | |||
| 32e00f6d49 | |||
| 4e3ce192cc | |||
| 841bb59d18 | |||
| 8dd8a23af9 | |||
| adc59de2bf | |||
| 20198bccdb | |||
| c0909dace0 | |||
| bd054d4baf | |||
| 35a70a4ae3 | |||
| a1d30d157b | |||
| 621d89c80e | |||
| 2fa3793622 | |||
| 872c057c4f | |||
| f6a530e5e3 | |||
| 2043a629c7 | |||
| a7f9d548cf | |||
| 1391dd38ec | |||
| 38f6b7aca0 | |||
| f0e4397ef9 | |||
| 6b7ffc8ca6 | |||
| 5458f4f9dd | |||
| 0c3269d9e1 | |||
| 84dde4c5c0 | |||
| 7eefb3f62f | |||
| 90369bf7f7 | |||
| eff3318399 | |||
| ee59d366dc | |||
| c0b91848a3 | |||
| d05d430642 | |||
| e8a73e008e | |||
| 259bb4f36a | |||
| b87191a358 | |||
| a359230181 | |||
| a95e12c0b2 | |||
| 2ac27c6fdd | |||
| 757578a74b | |||
| 3466a38a44 | |||
| f07cf1d1ef | |||
| 7fb4cdaa16 | |||
| 427411e758 | |||
| cc056d5d53 | |||
| 31600033bf | |||
| 0338825175 | |||
| d9f8aa4e7b | |||
| 6803788676 | |||
| d421cc0bbb | |||
| bf57c8b1bf | |||
| 9a25db6584 | |||
| 17ccd0adf7 | |||
| 937f39c898 | |||
| 0b5ccd88a5 | |||
| ebf3cd8909 | |||
| d9b1c26bf0 | |||
| 490da99792 | |||
| 528df00027 | |||
| bc10f934d6 | |||
| 819db11db4 | |||
| 37a0854d40 | |||
| b984268b5d | |||
| e4d2ae185d | |||
| 345098b4af | |||
| 98b517954f | |||
| dba8d2df9d | |||
| 6756ba7022 | |||
| 08afec3461 | |||
| 4408a23873 | |||
| c33b5aa4b7 | |||
| 0ebbc62a06 | |||
| 0494373155 | |||
| c897cb3ea0 | |||
| f38e8f26f5 | |||
| 7164103a80 | |||
| 5fb7f33fb0 | |||
| e602f16ed3 | |||
| 17a872a8d8 | |||
| 4533b74d65 | |||
| 62229b032e | |||
| f2c2fa8dce | |||
| ae170bbfac | |||
| 706b925f4c | |||
| 65288eac04 | |||
| bf5a9bdac6 | |||
| e22ebfbf94 | |||
| fb2989c17f | |||
| ffbf6483b5 | |||
| 5c5488fa49 | |||
| 7ff1b8a34d | |||
| fb786bb90c | |||
| fad80eacaf | |||
| 84020c9832 | |||
| 59bce590cc | |||
| a7ae78ec2a | |||
| 523a97329e | |||
| 248dab936d | |||
| a908fa62ba | |||
| 2f7643424d | |||
| 226c94381b | |||
| 548a2972bc | |||
| 6263110733 | |||
| 89e8bf1ed6 | |||
| 3809b5a34b | |||
| 3cda25773d | |||
| 77f2ad8f68 | |||
| 733b89be12 | |||
| 6346c5ba47 | |||
| dbb342d494 | |||
| cb0ad67bcc | |||
| f0266a16a3 | |||
| cdde536651 | |||
| c1aca6ba8c | |||
| f9391f80c4 | |||
| f012286eda | |||
| 45087e8822 | |||
| 8fa944d2a8 | |||
| 67955b4e35 | |||
| 9a0f29170a | |||
| d09286eeca | |||
| 89f64eacef | |||
| 3334ea5c10 | |||
| 16b05d9981 | |||
| 7f278fbde4 | |||
| 2cad62554e | |||
| 83bd0a973c | |||
| b2c5153c1c | |||
| ec955ca717 | |||
| 9c28a95f41 | |||
| 80020970e1 | |||
| f55b47dcb3 | |||
| 7197db796c | |||
| a5aa970977 | |||
| 159e2ca779 | |||
| ec1fbabd00 | |||
| b70bf5ed33 | |||
| d6be887bde | |||
| 6098d94223 | |||
| f045e86a38 | |||
| 9ef65c9cc5 | |||
| f8a8250429 | |||
| a75341b36e | |||
| 0a0271a30e | |||
| 871f6ebdf1 | |||
| 90f1a179bc | |||
| 81d80ee415 | |||
| 1eeb820d26 | |||
| 4dc860c019 | |||
| cb28a0b298 | |||
| 76e6923a3e | |||
| 56a09e51bd | |||
| b90ab70658 | |||
| 28aab490df | |||
| 0810fa7564 | |||
| a34c0cb35c | |||
| 10bde73c1a | |||
| 670399f327 | |||
| 241c5b1f48 | |||
| 9ef3b4389b | |||
| 2d37d3c914 | |||
| ede9c5fca5 | |||
| 4cccfd08d3 | |||
| 153c5a06e7 | |||
| 9a4d3e706a | |||
| 0b20d3ae2c | |||
| 92fb46558f | |||
| 8be1a913ec | |||
| c354de7b83 | |||
| 19b718cdf5 | |||
| cdffeba4cc | |||
| e6e8db8a00 | |||
| 8dab1810c7 | |||
| c5c9a192b0 | |||
| 4279576d3c | |||
| dff35c173d |
@@ -29,7 +29,7 @@ jobs:
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
libhdf5-dev libcgal-dev nlohmann-json3-dev
|
||||
|
||||
-
|
||||
name: ccache
|
||||
@@ -61,6 +61,8 @@ jobs:
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
../cmake
|
||||
make -j $(nproc)
|
||||
make install
|
||||
|
||||
@@ -19,20 +19,19 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: '3.10'
|
||||
- name: Build package
|
||||
run: |
|
||||
cd src/bcf
|
||||
pip install build
|
||||
python -m build
|
||||
- name: Test
|
||||
run: |
|
||||
cd src/bcf
|
||||
make test
|
||||
- name: Publish package
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
packages_dir: src/bcf/dist
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "56 23 * * *" # 4min before utc midnight
|
||||
- cron: "30 0 * * *" # 30min past utc midnight
|
||||
|
||||
env:
|
||||
major: 0
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
python-version: '3.10.9' # 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 }}
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Fill chocolatey scripts on win with latest blender python
|
||||
if: ${{steps.do_choco.outputs.choco_release}} == 'do_choco_release'
|
||||
run: |
|
||||
today_non_iso="$(date +'%y%m%d' | tr -d '\n')" &&
|
||||
yesterday_non_iso="$(date --date='yesterday' +'%y%m%d' | tr -d '\n')" &&
|
||||
target_os=${{ matrix.config.short_name }} &&
|
||||
latest_blender_python_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_python_version_maj_min?) &&
|
||||
echo "latest_blender_python_version_maj_min?: $latest_blender_python_version_maj_min" &&
|
||||
@@ -63,10 +63,10 @@ jobs:
|
||||
export latest_blender_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min?) &&
|
||||
export latest_blender_version_maj_min_pat=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
|
||||
echo latest_blender_version_maj_min_pat?: $latest_blender_version_maj_min_pat &&
|
||||
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$today_non_iso" &&
|
||||
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$today_non_iso/blenderbim-$today_non_iso-$pyver-$target_os.zip" &&
|
||||
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$yesterday_non_iso" &&
|
||||
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$yesterday_non_iso/blenderbim-$yesterday_non_iso-$pyver-$target_os.zip" &&
|
||||
wget $url_blenderbim_py310_win_zip --no-verbose &&
|
||||
export sha256sum_blenderbim_py310_win_zip=$( sha256sum blenderbim-$today_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
|
||||
export sha256sum_blenderbim_py310_win_zip=$( sha256sum blenderbim-$yesterday_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
|
||||
echo sha256sum_blenderbim_py310_win_zip: $sha256sum_blenderbim_py310_win_zip &&
|
||||
python3 choco/blenderbim/fill_dynamic_parameters.py &&
|
||||
echo __build choco with mono &&
|
||||
|
||||
@@ -61,7 +61,6 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.7.7' # 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,10 +1,17 @@
|
||||
name: ci-ifcopenshell-conda-daily
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["ci"]
|
||||
types:
|
||||
- completed
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/ci-ifcopenshell-conda-daily.yml
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "49 23 * * *" # 11min before utc midnight every day
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
@@ -26,19 +33,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [
|
||||
{ name: py39, distver: '3.9' },
|
||||
{ name: py310, distver: '3.10'}
|
||||
{ name: py310, distver: '3.10'},
|
||||
{ name: py311, distver: '3.11'}
|
||||
]
|
||||
platform: [
|
||||
{ name: Windows, distver: windows-2022, upload: 'false' },
|
||||
{ name: Windows, distver: windows-2019, upload: 'true' },
|
||||
{ name: Linux, distver: ubuntu-20.04, upload: 'false' },
|
||||
{ name: Linux, distver: ubuntu-18.04, upload: 'true' },
|
||||
{ name: macOS, distver: macos-11, upload: 'true' },
|
||||
{ name: macOS, distver: macos-10.15, upload: 'false' }
|
||||
{ name: Windows, distver: windows-2022, upload: 'true' },
|
||||
{ name: Linux, distver: ubuntu-22.04, upload: 'true' },
|
||||
{ name: macOS, distver: macos-12, upload: 'true' }
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Download MacOSX SDK
|
||||
@@ -57,8 +61,10 @@ jobs:
|
||||
- name: build, test and upload ifcopenshell
|
||||
if: ${{ matrix.platform.upload == 'true' }}
|
||||
run: |
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell --no-remove-work-dir
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell
|
||||
working-directory: ./conda
|
||||
- name: build & test ifcopenshell
|
||||
if: ${{ matrix.platform.upload == 'false' }}
|
||||
run: |
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --no-remove-work-dir
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge
|
||||
working-directory: ./conda
|
||||
|
||||
@@ -8,7 +8,11 @@ on:
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "51 23 1 * *" # 9min before utc midnight
|
||||
- cron: "0 0 18 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-ifcopenshell-pypi.yml'
|
||||
|
||||
|
||||
env:
|
||||
major: 0
|
||||
@@ -31,7 +35,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py36, py37, py38, py39, py310]
|
||||
pyver: [py36, py37, py38, py39, py310, py311]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
@@ -53,7 +57,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
python-version: '3.10' # 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
|
||||
|
||||
@@ -85,6 +85,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -86,6 +86,20 @@ jobs:
|
||||
sudo make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
- name: Run IfcConvert on Sample files
|
||||
run: |
|
||||
(find test/input src/blenderbim/test/files -name '*.ifc' | while read i; do \
|
||||
echo $i | tee -a log; \
|
||||
timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
|
||||
echo $i $? >> statuses; \
|
||||
done) || true
|
||||
echo Failed
|
||||
grep -v 0$ statuses
|
||||
grep -v 0$ statuses | wc -l
|
||||
echo Succeeded
|
||||
grep 0$ statuses
|
||||
grep 0$ statuses | wc -l
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
sudo /usr/bin/python -m pip install -U pip
|
||||
@@ -95,6 +109,8 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
+17
@@ -73,3 +73,20 @@ _build/
|
||||
|
||||
# IDS Docs
|
||||
src/ifcopenshell-python/test/build
|
||||
|
||||
# tox cache
|
||||
.tox/
|
||||
*.egg-info/
|
||||
|
||||
# mypy cache
|
||||
.mypy_cache
|
||||
|
||||
# blenderbim libs
|
||||
src/blenderbim/blenderbim/libs
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
language: cpp
|
||||
compiler: gcc
|
||||
cache: ccache
|
||||
os: linux
|
||||
dist: focal
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- libboost-date-time-dev
|
||||
- libboost-filesystem-dev
|
||||
- libboost-iostreams-dev
|
||||
- libboost-program-options-dev
|
||||
- libboost-regex-dev
|
||||
- libboost-system-dev
|
||||
- libboost-thread-dev
|
||||
- libocct-data-exchange-dev
|
||||
- libocct-foundation-dev
|
||||
- libocct-modeling-algorithms-dev
|
||||
- libocct-modeling-data-dev
|
||||
- libxml2-dev
|
||||
- nlohmann-json3-dev
|
||||
- opencollada-dev
|
||||
- python3-all-dev
|
||||
- python3-pip
|
||||
- swig
|
||||
- libhdf5-dev
|
||||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
|
||||
|
||||
install:
|
||||
# for IDS
|
||||
- python3 -m pip install xmlschema
|
||||
|
||||
script:
|
||||
- pwd
|
||||
- mkdir build && cd build
|
||||
- |
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-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 \
|
||||
"-DSCHEMA_VERSIONS=2x3;4" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
../cmake
|
||||
|
||||
# TODO: Drop sudo
|
||||
- sudo make -j $(nproc) install
|
||||
|
||||
- cd ../test
|
||||
- /usr/bin/python tests.py
|
||||
- /usr/bin/python ../src/ifcopenshell-python/ifcopenshell/test_ids.py
|
||||
|
||||
- cd input
|
||||
|
||||
- /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb
|
||||
|
||||
- /usr/bin/python -c "from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
|
||||
|
||||
- |
|
||||
(for i in *.ifc; do \
|
||||
echo $i | tee -a log; \
|
||||
timeout 1m /usr/local/bin/IfcConvert -yv "$i" "$i.dae" --validate >> log 2>&1; \
|
||||
echo $i $? >> statuses; \
|
||||
done) || true
|
||||
- echo Failed
|
||||
- grep -v 0$ statuses
|
||||
- grep -v 0$ statuses | wc -l
|
||||
- echo Succeeded
|
||||
- grep 0$ statuses
|
||||
- grep 0$ statuses | wc -l
|
||||
|
||||
after_script:
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -s; fi
|
||||
@@ -1,325 +1,74 @@
|
||||
|
||||
IfcOpenShell
|
||||
============
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with the Industry Foundation Classes ([IFC])
|
||||
file format. Extensive geometric support is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1].
|
||||
Support for parsing is provided for IFC4x1, IFC4x2, and the IFC4x3 release candidates. Extending with support for
|
||||
arbitrary IFC schemas is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
For more information, see
|
||||
* [http://ifcopenshell.org](http://ifcopenshell.org)
|
||||
* [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org)
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
|
||||
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
|
||||
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), and many
|
||||
other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
|
||||
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)
|
||||
|
||||
| Service | Status |
|
||||
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Anaconda Daily Build | [](https://anaconda.org/ifcopenshell/ifcopenshell) |
|
||||
| Anaconda v0.7.0 Stable | [](https://anaconda.org/conda-forge/ifcopenshell) |
|
||||
| PyPi Daily Build | [](https://pypi.org/project/ifcopenshell-python/) |
|
||||
| ArchLinux AUR Package | [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| PyPi Daily Build | [](https://pypi.org/project/ifcopenshell/) |
|
||||
| ArchLinux AUR Package Stable | [](https://aur.archlinux.org/packages/ifcopenshell) |
|
||||
| ArchLinux AUR Package git | [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| BlenderBIM Add-on Chocolatey (under moderation) | [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| Sponsor development on OpenCollective | [](https://opencollective.com/opensourcebim/) |
|
||||
| Docker hub | [](https://hub.docker.com/r/aecgeeks/ifcopenshell) |
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
* Git
|
||||
* CMake (3.1.3 or newer)
|
||||
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
|
||||
* *nix: GCC 4.7 or newer, or Clang (any version)
|
||||
|
||||
Dependencies
|
||||
-------------
|
||||
* [Boost](http://www.boost.org/)
|
||||
* [Open Cascade](https://dev.opencascade.org/) - *optional*, but required for building IfcGeom
|
||||
([official](https://dev.opencascade.org/release), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
|
||||
For converting IFC representation items into BRep solids and tessellated meshes
|
||||
* [OpenCOLLADA](https://github.com/khronosGroup/OpenCOLLADA/) - *optional*
|
||||
For IfcConvert to be able to write tessellated Collada (.dae) files
|
||||
* [SWIG](http://www.swig.org/) and [Python](https://www.python.org/) - *optional*
|
||||
For building the IfcOpenShell Python interface and the Blender add-on
|
||||
* [3ds Max SDK](http://www.autodesk.com/products/3ds-max/free-trial) - *optional*
|
||||
For building the 3ds Max plug-in.
|
||||
All recent versions of 3ds Max (2014 and newer) are 64-bit only, so a 64-bit installation is assumed.
|
||||
|
||||
Building IfcOpenShell
|
||||
---------------------
|
||||
|
||||
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
|
||||
|
||||
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
|
||||
|
||||
Note 3: Be careful with special characters is the path when using the nix or win build scripts, because the OpenCASCADE build will fail on paths containing ++ and likely other situations.
|
||||
|
||||
### Compiling on Windows
|
||||
The preferred way to fetch and build this project's dependencies is to use the build scripts
|
||||
in win/ folder. **See [win/readme.md] for more information**.
|
||||
|
||||
#### Using Visual Studio
|
||||
Instructions in a nutshell (**assuming Visual Studio 2015 x64 environment variables set**):
|
||||
|
||||
> cd IfcOpenShell\win
|
||||
> build-deps.cmd
|
||||
> run-cmake.bat
|
||||
|
||||
NB: `build-deps.cmd` need to be ran from the directory containing it, i.e. the `./win` folder.
|
||||
|
||||
You can now open and build the solution file in Visual Studio:
|
||||
|
||||
> ..\build-vs2015-x64\IfcOpenShell.sln
|
||||
|
||||
As the scripts default to using the `RelWithDebInfo` configuration, and a freshly created solution by CMake defaults
|
||||
to `Debug`, make sure to switch the used build configuration. Build the `INSTALL` project (right-click -> Project
|
||||
Only) to deploy the headers and binaries into a single location if wanted/needed.
|
||||
|
||||
Alternatively, one can use the utility batch file(s) to build and install the project easily from the command-line
|
||||
(installing a project will build it also, if required):
|
||||
|
||||
> install-ifcopenshell.bat
|
||||
|
||||
#### Using MSYS2 + MinGW
|
||||
|
||||
Start the MSYS2 Shell and then:
|
||||
|
||||
$ cd IfcOpenShell/win
|
||||
$ ./build-deps.sh
|
||||
$ ./run-cmake.sh
|
||||
$ ./install-ifcopenshell.sh
|
||||
|
||||
#### Using Bash on Ubuntu on Windows
|
||||
|
||||
Start Bash on Ubuntu on Windows and follow the instructions below. Compiling on Ubuntu 14.04.4 LTS using GCC 4.8.4
|
||||
or Clang 3.5 has been confirmed to work.
|
||||
|
||||
### Compiling on *nix
|
||||
|
||||
The following instructions are for Ubuntu, modify as required for other operating systems. [nix/build-all.py] script
|
||||
can be experimented with and studied for pointers for other operating systems, but note that this script is not currently
|
||||
meant to be used for a typical IfcOpenShell workspace setup.
|
||||
|
||||
Note 1: It is recommended to use OCCT for IfcOpenShell. You could use OCE as well, but sometimes it may lag behind OCCT and
|
||||
therefore not compile with the latest IfcOpenShell.
|
||||
|
||||
Note 2: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
|
||||
|
||||
**1)** Install most of the prerequisites and dependencies:
|
||||
|
||||
$ sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
|
||||
|
||||
**2a)** Either use an OCCT package from your operating system's software repository
|
||||
|
||||
$ sudo apt-get install libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev
|
||||
|
||||
**2b)** or, if OCCT is not available or the latest code is wanted, obtain and compile OCCT from https://dev.opencascade.org/release
|
||||
|
||||
**2c)** or, if you'd like to try using OCE, use the package from your operating system's software repository
|
||||
|
||||
$ sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
|
||||
|
||||
**2d)** or if OCE is not available, or the latest code is wanted, compile OCE yourself (note that the build takes a long time)
|
||||
|
||||
$ sudo apt-get install libftgl-dev libtbb2 libtbb-dev libgl1-mesa-dev libfreetype6-dev
|
||||
$ git clone https://github.com/tpaviot/oce.git
|
||||
$ cd oce
|
||||
$ mkdir build && cd build
|
||||
$ cmake ..
|
||||
$ make -j
|
||||
$ sudo make install
|
||||
|
||||
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
|
||||
|
||||
$ sudo apt-get install libpcre3-dev libxml2-dev
|
||||
$ git clone https://github.com/KhronosGroup/OpenCOLLADA.git
|
||||
$ cd OpenCOLLADA
|
||||
Using a known good revision, but HEAD should work too:
|
||||
$ git checkout 064a60b65c2c31b94f013820856bc84fb1937cc6
|
||||
$ mkdir build && cd build
|
||||
$ cmake ..
|
||||
$ make -j
|
||||
$ sudo make install
|
||||
|
||||
**4)** For building the IfcPython wrapper (on by default), SWIG and Python development are needed, if not already available:
|
||||
|
||||
$ sudo apt-get install python-all-dev swig
|
||||
|
||||
**5)** To build IfcOpenShell please take the following steps. Alternatively use environment variables for setting the
|
||||
dependencies' paths. `OCC_INCLUDE_DIR` might be needed to set also. `OPENCOLLADA_INCLUDE_DIR` and `OPENCOLLADA_LIBRARY_DIR`
|
||||
(and potentially `PCRE_LIBRARY_DIR`) are needed if building with COLLADA support. (`-DCOLLADA_SUPPORT=0` disables it).
|
||||
|
||||
$ cd /path/to/IfcOpenShell
|
||||
$ mkdir build && cd build
|
||||
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
|
||||
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
|
||||
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
|
||||
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_SUPPORT=Off
|
||||
$ make -j
|
||||
|
||||
If all worked out correctly you can now use IfcOpenShell. See the examples below.
|
||||
|
||||
**6)** Install the project if wanted:
|
||||
|
||||
$ sudo make install
|
||||
|
||||
### Installing on MacOS Using Homebrew
|
||||
|
||||
**1)** Install all dependencies using [Homebrew](https://brew.sh/)
|
||||
|
||||
```{shell}
|
||||
$ brew install boost cmake python3 cgal ftgl gmp libaec opencascade swig hdf5 zlib
|
||||
# homebrew automatically links most libraries, except some keg-only ones
|
||||
$ brew link zlib --force
|
||||
```
|
||||
|
||||
**2)** Clone the git repo and its submodules
|
||||
```{shell}
|
||||
$ git clone --recurse-submodules https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
```
|
||||
**3)** Build IfcOpenShell with flags for Homebrew dependencies: (``/usr/local/``) for Intel machines with x84_64 architecture,
|
||||
(``/opt/homebrew/``) for Apple Silicon processors with arm64 architecture.
|
||||
```{shell}
|
||||
$ cd /path/to/IfcOpenShell
|
||||
$ mkdir build && cd build
|
||||
# set library flags
|
||||
$ export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
|
||||
$ cmake ../cmake
|
||||
-DPYTHON_EXECUTABLE=/opt/homebrew/bin/python3.10 \
|
||||
-DPYTHON_LIBRARY=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib \
|
||||
-DPYTHON_INCLUDE_DIR=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/include/python3.10/ \
|
||||
-DOCC_LIBRARY_DIR=/opt/homebrew/lib/ \
|
||||
-DOCC_INCLUDE_DIR=/opt/homebrew/include/opencascade/ \
|
||||
-DCGAL_INCLUDE_DIR=/opt/homebrew/include/ \
|
||||
-DGMP_LIBRARY_DIR=/opt/homebrew/lib/ \
|
||||
-DMPFR_LIBRARY_DIR=/opt/homebrew/lib/ \
|
||||
-DHDF5_LIBRARY_DIR=/opt/homebrew/lib/ \
|
||||
-DHDF5_INCLUDE_DIR=/opt/homebrew/include/ \
|
||||
-DCOLLADA_SUPPORT=0
|
||||
# `sysctl -n hw.ncpu` returns the number of cpu cores on macOS
|
||||
$ make -j$(sysctl -n hw.ncpu)
|
||||
```
|
||||
|
||||
Note: Make sure to compile using the XCode provided Apple Clang compiler, installed with `xcode-select --install
|
||||
`, rather than a ```brew``` installed C/C++ compiler.
|
||||
|
||||
Installing IfcOpenShell with Conda
|
||||
----------------------------------
|
||||
Another option for building and installing IfcOpenShell is to use the popular
|
||||
[Anaconda Python Distribution](https://www.anaconda.com/download).
|
||||
The requirements are spread across a number of channels.
|
||||
You can add these channels to your configuration, or specify them all on the command line:
|
||||
|
||||
$ conda install -c conda-forge -c oce -c dlr-sc -c ifcopenshell ifcopenshell
|
||||
|
||||
Usage examples
|
||||
--------------
|
||||
|
||||
**Invoking IfcConvert from the command line**
|
||||
|
||||
$ wget -O duplex.zip https://portal.nibs.org/files/wl/\?id=4DsTgHFQAcOXzFetxbpRCECPbbfUqpgo
|
||||
$ unzip duplex.zip
|
||||
$ ./IfcConvert Duplex_A_20110907_optimized.ifc
|
||||
$ less Duplex_A_20110907_optimized.obj
|
||||
|
||||
**Using the IfcOpenShell Python interface**
|
||||
|
||||
$ wget -O duplex.zip https://portal.nibs.org/files/wl/\?id=4DsTgHFQAcOXzFetxbpRCECPbbfUqpgo
|
||||
$ unzip duplex.zip
|
||||
$ python
|
||||
>>> import ifcopenshell
|
||||
>>> f = ifcopenshell.open("Duplex_A_20110907_optimized.ifc")
|
||||
>>>
|
||||
>>> # Accessing entity instances by type:
|
||||
>>> f.by_type("ifcwall")[:2]
|
||||
[#91=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FL9r',#1,'Basic Wall:Interior - Partition (92mm Stud):144586',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5198,#18806,'144586'), #92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')]
|
||||
>>> wall = _[0]
|
||||
>>> len(wall) # number of EXPRESS attributes
|
||||
8
|
||||
>>>
|
||||
>>> # Accessing EXPRESS attributes by name:
|
||||
>>> wall.GlobalId
|
||||
'2O2Fr$t4X7Zf8NOew3FL9r'
|
||||
>>> wall.Name = "My wall"
|
||||
>>> wall.NonExistingAttr
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File ".\ifcopenshell.py", line 14, in __getattr__
|
||||
except: raise AttributeError("entity instance of type '%s' has no attribute'%s'"%(self.wrapped_data.is_a(), name)) from None
|
||||
AttributeError: entity instance of type 'IfcWallStandardCase' has no attribute 'NonExistingAttr'
|
||||
>>> wall.GlobalId = 3
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File ".\ifcopenshell.py", line 26, in __setattr__
|
||||
self[self.wrapped_data.get_argument_index(key)] = value
|
||||
File ".\ifcopenshell.py", line 30, in __setitem__
|
||||
self.wrapped_data.set_argument(idx, entity_instance.map_value(value))
|
||||
File ".\ifc_wrapper.py", line 118, in <lambda>
|
||||
set_argument = lambda self,x,y: self._set_argument(x) if y is None else self
|
||||
._set_argument(x,y)
|
||||
File ".\ifc_wrapper.py", line 114, in _set_argument
|
||||
def _set_argument(self, *args): return _ifc_wrapper.entity_instance__set_argument(self, *args)
|
||||
RuntimeError: INT is not a valid type for 'GlobalId'
|
||||
>>> # Creating new entity instances
|
||||
>>> f.createIfcCartesianPoint(Coordinates=(1.0,1.5,2.0))
|
||||
#27530=IfcCartesianPoint((1.,1.5,2.))
|
||||
>>>
|
||||
>>> # Working with GlobalId attributes:
|
||||
>>> import uuid
|
||||
>>> ifcopenshell.guid.compress(uuid.uuid1().hex)
|
||||
'3x4C8Q_6qHuv$P$FYkANRX'
|
||||
>>> new_guid = _
|
||||
>>> owner_hist = f.by_type("IfcOwnerHistory")[0]
|
||||
>>> new_wall = f.createIfcWallStandardCase(new_guid, owner_hist, None, None, Tag='my_tag')
|
||||
>>> new_wall.ObjectType = ''
|
||||
>>> new_wall.ObjectPlacement = new_wall.Representation = None
|
||||
>>>
|
||||
>>> # Accessing entity instances by instance id or GlobalId:
|
||||
>>> f[92]
|
||||
#92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')
|
||||
>>> f['2O2Fr$t4X7Zf8NOew3FLIE']
|
||||
#92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')
|
||||
>>>
|
||||
>>> # Writing IFC-SPF files to disk:
|
||||
>>> f.write("out.ifc")
|
||||
|
||||
Extra tools
|
||||
-----------
|
||||
|
||||
Also available are a series of utilities that are based on or related to IfcOpenShell.
|
||||
Contents
|
||||
--------
|
||||
|
||||
Those marked with an asterisk are part of IfcOpenShell.
|
||||
|
||||
| Name | License |
|
||||
| ------------------------- | ------------------- |
|
||||
| bcf | LGPL-3.0-or-later |
|
||||
| blenderbim | GPL-3.0-or-later |
|
||||
| bsdd | LGPL-3.0-or-later |
|
||||
| ifc2ca | LGPL-3.0-or-later |
|
||||
| ifc4d | LGPL-3.0-or-later |
|
||||
| ifc5d | LGPL-3.0-or-later |
|
||||
| ifcbimtester | LGPL-3.0-or-later |
|
||||
| ifcblender | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | LGPL-3.0-or-later |
|
||||
| ifcclash | LGPL-3.0-or-later |
|
||||
| ifccobie | LGPL-3.0-or-later |
|
||||
| ifcconvert | LGPL-3.0-or-later\* |
|
||||
| ifccsv | LGPL-3.0-or-later |
|
||||
| ifcdiff | LGPL-3.0-or-later |
|
||||
| ifcfm | LGPL-3.0-or-later |
|
||||
| ifcgeom | LGPL-3.0-or-later\* |
|
||||
| ifcgeom\_schema\_agnostic | LGPL-3.0-or-later\* |
|
||||
| ifcgeomserver | LGPL-3.0-or-later\* |
|
||||
| ifcjni | LGPL-3.0-or-later\* |
|
||||
| ifcmax | LGPL-3.0-or-later\* |
|
||||
| ifcopenshell-python | LGPL-3.0-or-later\* |
|
||||
| ifcparse | LGPL-3.0-or-later\* |
|
||||
| ifcpatch | LGPL-3.0-or-later |
|
||||
| ifcsverchok | GPL-3.0-or-later |
|
||||
| ifcwrap | LGPL-3.0-or-later\* |
|
||||
| qtviewer | LGPL-3.0-or-later\* |
|
||||
| serializers | LGPL-3.0-or-later\* |
|
||||
| Name | Description | License |
|
||||
| ------------------------- | ----------------------------------------------------------------------| ------------------- |
|
||||
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later |
|
||||
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later |
|
||||
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later |
|
||||
| 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 |
|
||||
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later |
|
||||
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
|
||||
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later |
|
||||
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later |
|
||||
| ifccobie | Extract IFC data for COBie handover requirements | LGPL-3.0-or-later |
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* |
|
||||
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later |
|
||||
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later |
|
||||
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later |
|
||||
| ifcgeom | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcgeom\_schema\_agnostic | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcgeomserver | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcjni | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* |
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* |
|
||||
| ifcparse | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later |
|
||||
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later |
|
||||
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later |
|
||||
| ifcwrap | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| qtviewer | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| serializers | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
|
||||
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
|
||||
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
|
||||
|
||||
@@ -15,10 +15,11 @@ def request_repo_info(url: str):
|
||||
|
||||
if sys.argv[1] == "--do_choco_release?":
|
||||
now = datetime.datetime.now()
|
||||
blenderbim_date = now.strftime("%y%m%d")
|
||||
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases/latest"
|
||||
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
|
||||
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
|
||||
resp = request_repo_info(url)
|
||||
if blenderbim_date in resp.url:
|
||||
text = str(resp.read())
|
||||
if blenderbim_date in text:
|
||||
print("do_choco_release", end="")
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ $appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
|
||||
$unzipTargetDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons"
|
||||
|
||||
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
|
||||
$addonEnable = "$chocoBaseDir\lib\blenderbim\tools\enable_blenderbim_addon.py"
|
||||
$addonEnable = "$chocoBaseDir\lib\blenderbim-nightly\tools\enable_blenderbim_addon.py"
|
||||
|
||||
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
|
||||
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
$unzippedDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons\blenderbim"
|
||||
|
||||
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
|
||||
$addonDisable = "$chocoBaseDir\lib\blenderbim\tools\disable_blenderbim_addon.py"
|
||||
$addonDisable = "$chocoBaseDir\lib\blenderbim-nightly\tools\disable_blenderbim_addon.py"
|
||||
|
||||
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
|
||||
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
|
||||
|
||||
+64
-14
@@ -54,6 +54,7 @@ OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only."
|
||||
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
|
||||
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
|
||||
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
|
||||
OPTION(WASM_BUILD OFF)
|
||||
if (${HAS_MAX})
|
||||
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||
endif()
|
||||
@@ -149,6 +150,14 @@ UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
|
||||
endif()
|
||||
|
||||
if(WASM_BUILD)
|
||||
# when using the nix/build-all.py build script we should not
|
||||
# look into the sysroot for most of the dependencies but rather
|
||||
# in the designated build/ folder created by the script.
|
||||
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
|
||||
set(CMAKE_FIND_ROOT_PATH "")
|
||||
endif()
|
||||
|
||||
if (NOT MINIMAL_BUILD AND GLTF_SUPPORT AND BUILD_CONVERT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
FIND_FILE(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
|
||||
@@ -199,7 +208,14 @@ ELSE()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
set(BOOST_COMPONENTS system program_options regex thread date_time)
|
||||
if (WASM_BUILD)
|
||||
set(BOOST_COMPONENTS)
|
||||
else()
|
||||
# @todo review this, shouldn't this be all possible header-only now?
|
||||
# ... or rewritten using C++17 features?
|
||||
set(BOOST_COMPONENTS system program_options regex thread date_time)
|
||||
endif()
|
||||
|
||||
if(USE_MMAP)
|
||||
if(MSVC)
|
||||
# filesystem is necessary for the utf-16 wpath
|
||||
@@ -210,6 +226,10 @@ if(USE_MMAP)
|
||||
add_definitions(-DUSE_MMAP)
|
||||
endif()
|
||||
|
||||
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
|
||||
MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
|
||||
|
||||
if (NOT MINIMAL_BUILD)
|
||||
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
@@ -220,11 +240,6 @@ if (NOT MINIMAL_BUILD AND IFCXML_SUPPORT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
|
||||
endif()
|
||||
|
||||
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
|
||||
MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
|
||||
|
||||
|
||||
# Usage:
|
||||
# set(SOME_LIRARIES foo bar)
|
||||
# add_debug_variants(SOME_LIRARIES "${SOME_LIRARIES}" d)
|
||||
@@ -343,10 +358,21 @@ if("${libTKernelExt}" STREQUAL ".a")
|
||||
set(OCCT_STATIC ON)
|
||||
endif()
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
|
||||
endif()
|
||||
|
||||
if(OCCT_STATIC)
|
||||
find_package(Threads)
|
||||
# OPENCASCADE_LIBRARIES repeated three times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
|
||||
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
if (NOT APPLE AND NOT WIN32)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
|
||||
endif()
|
||||
@@ -620,7 +646,12 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
endfunction()
|
||||
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3")
|
||||
if(WASM_BUILD)
|
||||
# super arbitrarily try to keep size down at least a little bit
|
||||
set(SCHEMA_VERSIONS "2x3" "4")
|
||||
else()
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3" "4x3_tc1" "4x3_add1")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
@@ -697,7 +728,11 @@ if (BUILD_IFCGEOM)
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom_ifc${s})
|
||||
endforeach()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
if (WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
@@ -741,7 +776,11 @@ set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
|
||||
add_library(IfcParse ${IFCPARSE_FILES})
|
||||
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "0.6.0" SOVERSION "0.6")
|
||||
|
||||
TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
|
||||
if (WASM_BUILD)
|
||||
TARGET_LINK_LIBRARIES(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
|
||||
else()
|
||||
TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (BUILD_IFCGEOM)
|
||||
|
||||
@@ -753,7 +792,9 @@ set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
add_library(IfcGeom_ifc${s} STATIC ${IFCGEOM_FILES})
|
||||
set_target_properties(IfcGeom_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s}")
|
||||
TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES})
|
||||
if (NOT WASM_BUILD)
|
||||
TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# IfcGeom (schema agnostic)
|
||||
@@ -768,7 +809,11 @@ if (UNIX)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
if (WASM_BUILD)
|
||||
TARGET_LINK_LIBRARIES(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
@@ -785,7 +830,12 @@ set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES})
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${s} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s} ${CONVERT_PRECISION}")
|
||||
TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
|
||||
|
||||
if (WASM_BUILD)
|
||||
TARGET_LINK_LIBRARIES(Serializers_ifc${s} ${HDF5_LIBRARIES})
|
||||
else()
|
||||
TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
|
||||
+1
-1
@@ -41,4 +41,4 @@ cmake -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install
|
||||
ninja install -j 2
|
||||
|
||||
+3
-2
@@ -13,6 +13,7 @@ source:
|
||||
|
||||
build:
|
||||
binary_relocation: false [osx]
|
||||
number: 1
|
||||
|
||||
requirements:
|
||||
build:
|
||||
@@ -24,7 +25,7 @@ requirements:
|
||||
|
||||
host:
|
||||
- python
|
||||
- boost-cpp <=1.74.0
|
||||
- boost-cpp
|
||||
- occt
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
@@ -36,7 +37,7 @@ requirements:
|
||||
|
||||
run:
|
||||
- python
|
||||
- boost-cpp <=1.74.0
|
||||
- boost-cpp
|
||||
- occt
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
|
||||
+176
-72
@@ -26,7 +26,7 @@
|
||||
# * cmake * git * bzip2 * tar * c(++) compilers * autoconf #
|
||||
# #
|
||||
# if building with USE_OCCT additionally: #
|
||||
# * freetype * glx.h #
|
||||
# * glx.h #
|
||||
# #
|
||||
# if building with OCCT 7.4.0 additionally: #
|
||||
# * libfontconfig1-dev #
|
||||
@@ -39,14 +39,14 @@
|
||||
# #
|
||||
# on debian 7.8 these can be obtained with: #
|
||||
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
|
||||
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# #
|
||||
# on ubuntu 14.04: #
|
||||
# $ apt-get install git gcc g++ autoconf bison make cmake #
|
||||
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# #
|
||||
# on OS X El Capitan with homebrew: #
|
||||
# $ brew install git bison autoconf automake freetype libffi cmake #
|
||||
# $ brew install git bison autoconf automake libffi cmake #
|
||||
# #
|
||||
###############################################################################
|
||||
import logging
|
||||
@@ -72,11 +72,11 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3"]
|
||||
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.0"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.5.3"
|
||||
BOOST_VERSION = "1.71.0"
|
||||
BOOST_VERSION = "1.80.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
SWIG_VERSION = "4.0.2"
|
||||
@@ -103,6 +103,9 @@ curl = "curl"
|
||||
wget = "wget"
|
||||
strip = "strip"
|
||||
|
||||
explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
|
||||
flags = set(s.lstrip('-') for s in sys.argv[1:] if s.startswith("-"))
|
||||
|
||||
# Helper function for coloured printing
|
||||
|
||||
NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
|
||||
@@ -135,9 +138,11 @@ if platform.system() == "Darwin":
|
||||
|
||||
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
|
||||
|
||||
CMAKE_DIR = os.path.realpath(os.path.join("..", "cmake"))
|
||||
CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmake"))
|
||||
|
||||
path = ["..", "build", platform.system(), platform.machine()]
|
||||
build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
|
||||
|
||||
path = [build_dir, platform.system(), "wasm" if "wasm" in flags else platform.machine()]
|
||||
if TOOLSET:
|
||||
path.append(TOOLSET)
|
||||
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
|
||||
@@ -186,11 +191,12 @@ dependency_tree = {
|
||||
'boost': (),
|
||||
'libxml2': (),
|
||||
'python': (),
|
||||
'occ': (),
|
||||
'occ': ('freetype',),
|
||||
'pcre': (),
|
||||
'json': (),
|
||||
'hdf5': (),
|
||||
'cgal': ()
|
||||
'cgal': (),
|
||||
'freetype': (),
|
||||
}
|
||||
|
||||
def v(dep):
|
||||
@@ -199,10 +205,12 @@ def v(dep):
|
||||
for x in v(d):
|
||||
yield x
|
||||
|
||||
tgts = [s for s in sys.argv[1:] if not s.startswith("-")]
|
||||
flags = set(s for s in sys.argv[1:] if s.startswith("-"))
|
||||
if "v" in flags:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
BUILD_STATIC = not "-shared" in flags
|
||||
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"
|
||||
LINK_TYPE = "static" if BUILD_STATIC else "shared"
|
||||
@@ -210,10 +218,15 @@ LINK_TYPE_UCFIRST = LINK_TYPE[0].upper() + LINK_TYPE[1:]
|
||||
LIBRARY_EXT = "a" if BUILD_STATIC else "so"
|
||||
PIC = "-fPIC" if BUILD_STATIC else ""
|
||||
|
||||
if len(tgts):
|
||||
targets = set(sum((list(v(target)) for target in tgts), []))
|
||||
if any(f.startswith("py-") for f in flags):
|
||||
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "py-%s" % "".join(pyv.split('.')[0:2]) in flags]
|
||||
|
||||
if len(explicit_targets):
|
||||
targets = set(sum((list(v(target)) for target in explicit_targets), []))
|
||||
else:
|
||||
targets = set(dependency_tree.keys())
|
||||
|
||||
targets = set(t for t in targets if 'without-%s' % t.lower() not in flags)
|
||||
|
||||
print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
|
||||
|
||||
@@ -237,7 +250,7 @@ if not os.path.exists(LOG_FILE):
|
||||
open(LOG_FILE, "w").close()
|
||||
logger.info(f"using command log file '{LOG_FILE}'")
|
||||
|
||||
def run(cmds, cwd=None):
|
||||
def run(cmds, cwd=None, can_fail=False):
|
||||
|
||||
"""
|
||||
Wraps `subprocess.Popen.communicate()` and logs the command being executed,
|
||||
@@ -254,7 +267,7 @@ def run(cmds, cwd=None):
|
||||
log_file_handle.close()
|
||||
logger.debug(f"command returned {proc.returncode}")
|
||||
|
||||
if proc.returncode != 0:
|
||||
if proc.returncode != 0 and not can_fail:
|
||||
print("-" * 70)
|
||||
print(stderr)
|
||||
print("-" * 70)
|
||||
@@ -266,6 +279,9 @@ if platform.system() == "Darwin":
|
||||
if run(["sw_vers", "-productVersion"]) >= "11.":
|
||||
# Apparently not supported
|
||||
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
|
||||
if run(["sw_vers", "-productVersion"]) < "10.16":
|
||||
# Apparently not supported
|
||||
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
|
||||
|
||||
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
|
||||
|
||||
@@ -281,7 +297,12 @@ def run_autoconf(arg1, configure_args, cwd):
|
||||
run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
|
||||
# Using `sh` over `bash` fixes issues with building swig
|
||||
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
|
||||
run(["/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
|
||||
|
||||
wasm = []
|
||||
if "wasm" in flags:
|
||||
wasm.append("emconfigure")
|
||||
|
||||
run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
|
||||
|
||||
|
||||
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
@@ -289,7 +310,12 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
P = ".."
|
||||
else:
|
||||
P = cmake_dir
|
||||
run(["cmake", P] + cmake_args + [f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
|
||||
|
||||
wasm = []
|
||||
if "wasm" in flags:
|
||||
wasm.append("emcmake")
|
||||
|
||||
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
|
||||
|
||||
|
||||
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
@@ -371,12 +397,15 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
urlretrieve(url, os.path.join(extract_dir, path))
|
||||
|
||||
if patch is not None:
|
||||
patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), patch))
|
||||
if os.path.exists(patch_abs):
|
||||
try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
|
||||
except Exception as e:
|
||||
# Assert that the patch has already been applied
|
||||
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
|
||||
if isinstance(patch, str):
|
||||
patch = [patch]
|
||||
for p in patch:
|
||||
patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
|
||||
if os.path.exists(patch_abs):
|
||||
try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
|
||||
except Exception as e:
|
||||
# Assert that the patch has already been applied
|
||||
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
|
||||
|
||||
if mode == "ctest":
|
||||
run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
|
||||
@@ -407,7 +436,7 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
logger.info(f"\rConfiguring {name}...")
|
||||
run([bash, "./bootstrap.sh"], cwd=extract_dir)
|
||||
logger.info(f"\rBuilding {name}... ")
|
||||
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir)
|
||||
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
|
||||
logger.info(f"\rInstalling {name}... ")
|
||||
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
|
||||
logger.info(f"\rInstalled {name} \n")
|
||||
@@ -418,11 +447,13 @@ cecho("Collecting dependencies:", GREEN)
|
||||
# TODO: This is untested
|
||||
|
||||
ADDITIONAL_ARGS = []
|
||||
BOOST_ADDRESS_MODEL = []
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
|
||||
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))
|
||||
|
||||
# If the linker supports GC sections, set it up to reduce binary file size
|
||||
# -fPIC is required for the shared libraries to work
|
||||
|
||||
@@ -431,7 +462,7 @@ CFLAGS = os.environ.get("CFLAGS", "")
|
||||
LDFLAGS = os.environ.get("LDFLAGS", "")
|
||||
|
||||
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
|
||||
if sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
|
||||
if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
|
||||
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
if BUILD_STATIC:
|
||||
@@ -501,8 +532,27 @@ if "swig" in targets:
|
||||
download_tool=download_tool_git,
|
||||
revision=f"rel-{SWIG_VERSION}"
|
||||
)
|
||||
|
||||
if "freetype" in targets:
|
||||
build_dependency(
|
||||
name=f"freetype",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/freetype"
|
||||
],
|
||||
download_url = "https://github.com/freetype/freetype",
|
||||
download_name = "freetype2",
|
||||
download_tool=download_tool_git,
|
||||
)
|
||||
|
||||
if USE_OCCT and "occ" in targets:
|
||||
patches = []
|
||||
if OCCT_VERSION < "7.4":
|
||||
patches.append("./patches/occt/enable-exception-handling.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
patches.append("./patches/occt/no_em_js.patch")
|
||||
|
||||
build_dependency(
|
||||
name=f"occt-{OCCT_VERSION}",
|
||||
mode="cmake",
|
||||
@@ -510,12 +560,13 @@ if USE_OCCT and "occ" in targets:
|
||||
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
|
||||
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
|
||||
"-DBUILD_MODULE_Draw=0",
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
|
||||
f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
|
||||
],
|
||||
download_url = "https://github.com/Open-Cascade-SAS/OCCT",
|
||||
download_name = "occt",
|
||||
download_tool=download_tool_git,
|
||||
patch=None if OCCT_VERSION >= "7.4" else "./patches/occt/enable-exception-handling.patch",
|
||||
patch=patches,
|
||||
revision="V" + OCCT_VERSION.replace('.', '_')
|
||||
)
|
||||
elif "occ" in targets:
|
||||
@@ -552,6 +603,15 @@ if "libxml2" in targets:
|
||||
)
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
|
||||
|
||||
if "wasm" in flags:
|
||||
# This is necessary for the WASM build, because recent versions of
|
||||
# clang don't have the tr1:: namespace anymore. However, it breaks
|
||||
# some versions of gcc (9.4.0 at least) due to specializing std::hash
|
||||
# outside of the std:: namespace.
|
||||
patches.append("./patches/opencollada/remove_tr1.patch")
|
||||
|
||||
build_dependency(
|
||||
"OpenCOLLADA",
|
||||
"cmake",
|
||||
@@ -566,11 +626,11 @@ if "OpenCOLLADA" in targets:
|
||||
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
|
||||
download_name="OpenCOLLADA",
|
||||
download_tool=download_tool_git,
|
||||
patch="./patches/opencollada/pr622_and_disable_subdirs.patch",
|
||||
patch=patches,
|
||||
revision=OPENCOLLADA_VERSION
|
||||
)
|
||||
|
||||
if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
|
||||
if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flags:
|
||||
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
|
||||
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
|
||||
OLD_C_FLAGS = os.environ["CFLAGS"]
|
||||
@@ -609,6 +669,9 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
|
||||
|
||||
if "boost" in targets:
|
||||
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
|
||||
toolset = []
|
||||
if "wasm" in flags:
|
||||
toolset.append("toolset=emscripten")
|
||||
build_dependency(
|
||||
f"boost-{BOOST_VERSION}",
|
||||
mode="bjam",
|
||||
@@ -620,21 +683,30 @@ if "boost" in targets:
|
||||
"--with-thread",
|
||||
"--with-date_time",
|
||||
"--with-iostreams",
|
||||
f"link={LINK_TYPE}"
|
||||
] + \
|
||||
BOOST_ADDRESS_MODEL + \
|
||||
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
|
||||
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
|
||||
["stage", "-s", "NO_BZIP2=1"],
|
||||
f"link={LINK_TYPE}",
|
||||
*toolset,
|
||||
*map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
|
||||
*map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
|
||||
"stage", "-s", "NO_BZIP2=1"],
|
||||
download_url=BOOST_LOCATION,
|
||||
patch="./patches/boost/boostorg_regex_62.patch",
|
||||
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
|
||||
)
|
||||
if "wasm" in flags:
|
||||
# only supported on nix for now
|
||||
run(("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"), cwd=f"{DEPS_DIR}/install/boost-{BOOST_VERSION}/lib")
|
||||
|
||||
if "cgal" in targets:
|
||||
gmp_args = []
|
||||
mpfr_args = []
|
||||
if "wasm" in flags:
|
||||
gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
|
||||
mpfr_args.extend(("--host", "none"))
|
||||
|
||||
build_dependency(
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-shared", "--with-pic"],
|
||||
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
|
||||
download_url="https://ftp.gnu.org/gnu/gmp/",
|
||||
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
|
||||
)
|
||||
@@ -642,7 +714,7 @@ if "cgal" in targets:
|
||||
build_dependency(
|
||||
name=f"mpfr-{MPFR_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-shared", f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
|
||||
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, *mpfr_args, f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
|
||||
download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
|
||||
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
|
||||
)
|
||||
@@ -651,9 +723,9 @@ if "cgal" in targets:
|
||||
name=f"cgal-{CGAL_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.a",
|
||||
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.{LIBRARY_EXT}",
|
||||
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.a" ,
|
||||
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.{LIBRARY_EXT}" ,
|
||||
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
|
||||
@@ -676,18 +748,8 @@ os.makedirs(IFCOS_DIR, exist_ok=True)
|
||||
executables_dir = os.path.join(IFCOS_DIR, "executables")
|
||||
os.makedirs(executables_dir, exist_ok=True)
|
||||
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
|
||||
exec_args = [
|
||||
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
|
||||
"-DBUILD_IFCPYTHON=" "OFF",
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
cmake_args = [
|
||||
"-DUSE_MMAP=" "OFF",
|
||||
"-DBUILD_EXAMPLES=" "OFF",
|
||||
@@ -699,6 +761,11 @@ cmake_args = [
|
||||
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
|
||||
]
|
||||
|
||||
if "wasm" in flags:
|
||||
# Boost is built by the build script so should not be found
|
||||
# inside of the sysroot set by the emscriptem toolchain
|
||||
cmake_args.append("-DWASM_BUILD=On")
|
||||
|
||||
if "cgal" in targets:
|
||||
cmake_args.extend([
|
||||
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
|
||||
@@ -750,20 +817,36 @@ if "hdf5" in targets:
|
||||
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
|
||||
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
|
||||
])
|
||||
else:
|
||||
cmake_args.append("-DHDF5_SUPPORT=Off")
|
||||
|
||||
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets):
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
logger.info("\rBuilding executables... ")
|
||||
exec_args = [
|
||||
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
|
||||
"-DBUILD_IFCPYTHON=" "OFF",
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
|
||||
logger.info("\rBuilding executables... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
|
||||
|
||||
if "IfcOpenShell-Python" in targets:
|
||||
# On OSX the actual Python library is not linked against.
|
||||
ADDITIONAL_ARGS = ""
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
|
||||
|
||||
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
|
||||
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
|
||||
@@ -780,36 +863,57 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
|
||||
|
||||
swig_when_built = []
|
||||
if "swig" in targets:
|
||||
swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")
|
||||
|
||||
run_cmake("",
|
||||
cmake_args + [
|
||||
"-DPYTHON_LIBRARY=" +python_library,
|
||||
"-DPYTHON_EXECUTABLE=" +python_executable,
|
||||
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
|
||||
# *([f"-DPYTHON_MODULE_INSTALL_DIR={os.environ['PYTHONPATH']}/ifcopenshell"] if "wasm" in flags else []),
|
||||
*(["-DPYTHON_MODULE_INSTALL_DIR="+os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "package"))] if "wasm" in flags else []),
|
||||
"-DPYTHON_INCLUDE_DIR=" +python_include,
|
||||
"-DSWIG_EXECUTABLE=" f"{DEPS_DIR}/install/swig/bin/swig",
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}]
|
||||
], cmake_dir=CMAKE_DIR, cwd=python_dir)
|
||||
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
|
||||
*swig_when_built],
|
||||
cmake_dir=CMAKE_DIR, cwd=python_dir)
|
||||
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
|
||||
if python_executable:
|
||||
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
|
||||
|
||||
if platform.system() != "Darwin":
|
||||
# TODO: This symbol name depends on the Python version?
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
|
||||
return module_dir
|
||||
if platform.system() != "Darwin":
|
||||
# TODO: This symbol name depends on the Python version?
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
|
||||
|
||||
return module_dir
|
||||
|
||||
if USE_CURRENT_PYTHON_VERSION:
|
||||
python_info = sysconfig.get_paths()
|
||||
python_lib = os.path.join(
|
||||
sysconfig.get_config_var('LIBDIR'),
|
||||
sysconfig.get_config_var('multiarchsubdir').replace("/", ""),
|
||||
sysconfig.get_config_var("INSTSONAME")
|
||||
if "wasm" in flags:
|
||||
compile_python_wrapper(
|
||||
f"{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.{os.environ['PYMICRO']}",
|
||||
f"{os.environ['TARGETINSTALLDIR']}/lib/libpython{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.a",
|
||||
os.environ['PYTHONINCLUDE'],
|
||||
None
|
||||
)
|
||||
|
||||
elif USE_CURRENT_PYTHON_VERSION:
|
||||
python_info = sysconfig.get_paths()
|
||||
|
||||
py_path_components = [
|
||||
sysconfig.get_config_var('LIBDIR'),
|
||||
sysconfig.get_config_var("INSTSONAME")
|
||||
]
|
||||
|
||||
if sysconfig.get_config_var('multiarchsubdir'):
|
||||
py_path_components.insert(1, sysconfig.get_config_var('multiarchsubdir').replace("/", ""))
|
||||
|
||||
python_lib = os.path.join(*py_path_components)
|
||||
|
||||
compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
|
||||
else:
|
||||
for python_version in PYTHON_VERSIONS:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
--- a/tools/build/src/tools/emscripten.jam
|
||||
+++ b/tools/build/src/tools/emscripten.jam
|
||||
@@ -6,6 +6,7 @@
|
||||
import feature ;
|
||||
import os ;
|
||||
import toolset ;
|
||||
+import generators ;
|
||||
import common ;
|
||||
import gcc ;
|
||||
import type ;
|
||||
@@ -52,6 +53,8 @@
|
||||
<debug-symbols>off <debug-symbols>on
|
||||
<rtti>off <rtti>on
|
||||
;
|
||||
+generators.override builtin.lib-generator : emscripten.prebuilt ;
|
||||
+generators.override emscripten.searched-lib-generator : searched-lib-generator ;
|
||||
|
||||
type.set-generated-target-suffix EXE : <toolset>emscripten : "js" ;
|
||||
type.set-generated-target-suffix OBJ : <toolset>emscripten : "bc" ;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
diff --git a/src/Message/Message_PrinterSystemLog.cxx b/src/Message/Message_PrinterSystemLog.cxx
|
||||
index 0c82c2167..a2e9e6d68 100644
|
||||
--- a/src/Message/Message_PrinterSystemLog.cxx
|
||||
+++ b/src/Message/Message_PrinterSystemLog.cxx
|
||||
@@ -55,27 +55,6 @@
|
||||
return ANDROID_LOG_DEBUG;
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
- #include <emscripten/emscripten.h>
|
||||
-
|
||||
- //! Print message to console.debug().
|
||||
- EM_JS(void, occJSConsoleDebug, (const char* theStr), {
|
||||
- console.debug(UTF8ToString(theStr));
|
||||
- });
|
||||
-
|
||||
- //! Print message to console.info().
|
||||
- EM_JS(void, occJSConsoleInfo, (const char* theStr), {
|
||||
- console.info(UTF8ToString(theStr));
|
||||
- });
|
||||
-
|
||||
- //! Print message to console.warn().
|
||||
- EM_JS(void, occJSConsoleWarn, (const char* theStr), {
|
||||
- console.warn(UTF8ToString(theStr));
|
||||
- });
|
||||
-
|
||||
- //! Print message to console.error().
|
||||
- EM_JS(void, occJSConsoleError, (const char* theStr), {
|
||||
- console.error(UTF8ToString(theStr));
|
||||
- });
|
||||
#else
|
||||
#include <syslog.h>
|
||||
|
||||
@@ -169,16 +148,6 @@ void Message_PrinterSystemLog::send (const TCollection_AsciiString& theString,
|
||||
#elif defined(__ANDROID__)
|
||||
__android_log_write (getAndroidLogPriority (theGravity), myEventSourceName.ToCString(), theString.ToCString());
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
- // don't use bogus emscripten_log() corrupting UNICODE strings
|
||||
- switch (theGravity)
|
||||
- {
|
||||
- case Message_Trace: occJSConsoleDebug(theString.ToCString()); return;
|
||||
- case Message_Info: occJSConsoleInfo (theString.ToCString()); return;
|
||||
- case Message_Warning: occJSConsoleWarn (theString.ToCString()); return;
|
||||
- case Message_Alarm: occJSConsoleError(theString.ToCString()); return;
|
||||
- case Message_Fail: occJSConsoleError(theString.ToCString()); return;
|
||||
- }
|
||||
- occJSConsoleWarn (theString.ToCString());
|
||||
#else
|
||||
syslog (getSysLogPriority (theGravity), "%s", theString.ToCString());
|
||||
#endif
|
||||
diff --git a/src/OSD/OSD_MemInfo.cxx b/src/OSD/OSD_MemInfo.cxx
|
||||
index 08a939beb..7c1fc79b3 100644
|
||||
--- a/src/OSD/OSD_MemInfo.cxx
|
||||
+++ b/src/OSD/OSD_MemInfo.cxx
|
||||
@@ -37,15 +37,6 @@
|
||||
|
||||
#include <OSD_MemInfo.hxx>
|
||||
|
||||
-#if defined(__EMSCRIPTEN__)
|
||||
- #include <emscripten.h>
|
||||
-
|
||||
- //! Return WebAssembly heap size in bytes.
|
||||
- EM_JS(size_t, OSD_MemInfo_getModuleHeapLength, (), {
|
||||
- return Module.HEAP8.length;
|
||||
- });
|
||||
-#endif
|
||||
-
|
||||
// =======================================================================
|
||||
// function : OSD_MemInfo
|
||||
// purpose :
|
||||
@@ -156,29 +147,6 @@ void OSD_MemInfo::Update()
|
||||
}
|
||||
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
- if (IsActive (MemHeapUsage)
|
||||
- || IsActive (MemWorkingSet)
|
||||
- || IsActive (MemWorkingSetPeak))
|
||||
- {
|
||||
- // /proc/%d/status is not emulated - get more info from mallinfo()
|
||||
- const struct mallinfo aMI = mallinfo();
|
||||
- if (IsActive (MemHeapUsage))
|
||||
- {
|
||||
- myCounters[MemHeapUsage] = aMI.uordblks;
|
||||
- }
|
||||
- if (IsActive (MemWorkingSet))
|
||||
- {
|
||||
- myCounters[MemWorkingSet] = aMI.uordblks;
|
||||
- }
|
||||
- if (IsActive (MemWorkingSetPeak))
|
||||
- {
|
||||
- myCounters[MemWorkingSetPeak] = aMI.usmblks;
|
||||
- }
|
||||
- }
|
||||
- if (IsActive (MemVirtual))
|
||||
- {
|
||||
- myCounters[MemVirtual] = OSD_MemInfo_getModuleHeapLength();
|
||||
- }
|
||||
#elif (defined(__linux__) || defined(__linux))
|
||||
if (IsActive (MemHeapUsage))
|
||||
{
|
||||
@@ -0,0 +1,81 @@
|
||||
diff --git a/COLLADABaseUtils/include/COLLADABUhash_map.h b/COLLADABaseUtils/include/COLLADABUhash_map.h
|
||||
index 8ab0fb9b..12503bfb 100644
|
||||
--- a/COLLADABaseUtils/include/COLLADABUhash_map.h
|
||||
+++ b/COLLADABaseUtils/include/COLLADABUhash_map.h
|
||||
@@ -32,9 +32,9 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
|
||||
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
|
||||
- #define COLLADABU_HASH_SET std::tr1::unordered_set
|
||||
+ #define COLLADABU_HASH_MAP std::unordered_map
|
||||
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
|
||||
+ #define COLLADABU_HASH_SET std::unordered_set
|
||||
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
|
||||
#define COLLADABU_HASH_NAMESPACE_CLOSE }
|
||||
#define COLLADABU_HASH_FUN hash
|
||||
@@ -50,12 +50,12 @@
|
||||
#define COLLADABU_HASH_FUN hash
|
||||
#endif
|
||||
#elif defined(__MINGW32__) || defined(__MINGW64__)
|
||||
- #include <tr1/unordered_map>
|
||||
- #include <tr1/unordered_set>
|
||||
+ #include <unordered_map>
|
||||
+ #include <unordered_set>
|
||||
|
||||
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
|
||||
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
|
||||
- #define COLLADABU_HASH_SET std::tr1::unordered_set
|
||||
+ #define COLLADABU_HASH_MAP std::unordered_map
|
||||
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
|
||||
+ #define COLLADABU_HASH_SET std::unordered_set
|
||||
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
|
||||
#define COLLADABU_HASH_NAMESPACE_CLOSE }
|
||||
#define COLLADABU_HASH_FUN hash
|
||||
@@ -107,12 +107,12 @@
|
||||
#define COLLADABU_HASH_NAMESPACE_CLOSE
|
||||
#define COLLADABU_HASH_FUN hash
|
||||
#else
|
||||
- #include <tr1/unordered_map>
|
||||
- #include <tr1/unordered_set>
|
||||
+ #include <unordered_map>
|
||||
+ #include <unordered_set>
|
||||
|
||||
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
|
||||
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
|
||||
- #define COLLADABU_HASH_SET std::tr1::unordered_set
|
||||
+ #define COLLADABU_HASH_MAP std::unordered_map
|
||||
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
|
||||
+ #define COLLADABU_HASH_SET std::unordered_set
|
||||
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
|
||||
#define COLLADABU_HASH_NAMESPACE_CLOSE }
|
||||
#define COLLADABU_HASH_FUN hash
|
||||
diff --git a/common/libBuffer/include/CommonFWriteBufferFlusher.h b/common/libBuffer/include/CommonFWriteBufferFlusher.h
|
||||
index c7af45b2..fac4f133 100644
|
||||
--- a/common/libBuffer/include/CommonFWriteBufferFlusher.h
|
||||
+++ b/common/libBuffer/include/CommonFWriteBufferFlusher.h
|
||||
@@ -15,12 +15,12 @@
|
||||
|
||||
#if (defined(WIN64) || defined(_WIN64) || defined(__WIN64__)) || (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__APPLE__))
|
||||
#if defined(__GNUC__) && !defined(_LIBCPP_VERSION)
|
||||
-# include <tr1/unordered_map>
|
||||
+# include <unordered_map>
|
||||
#else
|
||||
# include <unordered_map>
|
||||
#endif
|
||||
#else
|
||||
-# include <tr1/unordered_map>
|
||||
+# include <unordered_map>
|
||||
#endif
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
@@ -58,7 +58,7 @@ namespace Common
|
||||
#else
|
||||
typedef __int64 FilePosType;
|
||||
#endif
|
||||
- typedef std::tr1::unordered_map<MarkId, FilePosType > MarkIdToFilePos;
|
||||
+ typedef std::unordered_map<MarkId, FilePosType > MarkIdToFilePos;
|
||||
|
||||
public:
|
||||
static const size_t DEFAUL_BUFFER_SIZE = 64*1024;
|
||||
@@ -0,0 +1,18 @@
|
||||
package:
|
||||
name: ifcopenshell
|
||||
version: 0.7.0
|
||||
|
||||
source:
|
||||
path: IfcOpenShell
|
||||
|
||||
build:
|
||||
script: |
|
||||
python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py310 IfcOpenShell-Python
|
||||
cp pyodide/setup.py .
|
||||
|
||||
about:
|
||||
home: http://ifcopenshell.org
|
||||
license: LGPL-3.0-or-later
|
||||
summary: |
|
||||
IfcOpenShell is an open source (LGPL) software library for
|
||||
working with the Industry Foundation Classes (IFC) file format.
|
||||
@@ -0,0 +1,11 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='IfcOpenShell',
|
||||
version='0.7.0',
|
||||
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
|
||||
author='Thomas Krijnen',
|
||||
author_email='thomas@aecgeeks.com',
|
||||
url='http://ifcopenshell.org',
|
||||
packages=find_packages(),
|
||||
package_data={'': ['*.so']},
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
.tox
|
||||
@@ -0,0 +1,17 @@
|
||||
.PHONY: ci
|
||||
ci:
|
||||
tox
|
||||
|
||||
.PHONY: license
|
||||
license:
|
||||
copyright-header --license LGPL3 --copyright-holder "Andrea Ghensi <andrea.ghensi@gmail.com>" --copyright-year "2022" --copyright-software "IfcOpenShell" --copyright-software-description "BCF XML file handling" -a ./ -o ./
|
||||
|
||||
# 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
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
+31
-48
@@ -1,64 +1,54 @@
|
||||
# bcf
|
||||
|
||||
A simple Python implementation of BCF. The data model is described in `data.py`.
|
||||
A simple Python implementation of BCF.
|
||||
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
|
||||
is available via `bcfapi.py`.
|
||||
|
||||
- BCF-XML version 2.1: Fully supported
|
||||
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
|
||||
- BCF-XML version 3.0: Almost fully supported, except for the documents module
|
||||
- BCF-API version 3.0: Almost fully supported, except for two requests.
|
||||
It tries to support BCF-XML version 2.1 and 3.0, and BCF-API 3.0.
|
||||
|
||||
## bcfxml
|
||||
|
||||
The `bcfxml` module lets you interact with the BCF-XML standard.
|
||||
The `bcfxml.load` function lets you read a BCF-XML file.
|
||||
It takes care of using the right version based on the "bcf.version" file contained in the BCF package.
|
||||
|
||||
The BCF files are extracted and parsed on-demand, and edits are stored in memory until you call the `save` method.
|
||||
|
||||
```python
|
||||
from bcf import bcfxml
|
||||
|
||||
from bcf.bcfxml import load
|
||||
|
||||
# Load a project
|
||||
bcfxml = bcfxml.load("/path/to/file.bcf")
|
||||
with load("/path/to/file.bcf") as bcfxml:
|
||||
project = bcfxml.project
|
||||
print(project.name)
|
||||
|
||||
# To edit a project, just modify the object directly
|
||||
bcfxml.project.name = "New name"
|
||||
|
||||
# The project is also stored in the module
|
||||
# project == bcfxml.project
|
||||
project=bcfxml.get_project()
|
||||
print(project.name)
|
||||
# Get a dictionary of topics
|
||||
topics = bcfxml.topics
|
||||
|
||||
# To edit a project, just modify the object directly
|
||||
bcfxml.project.name = "New name"
|
||||
bcfxml.edit_project()
|
||||
for topic_guid, topic_handler in bcfxml.topics.items():
|
||||
topic = topic_handler.topic
|
||||
print("Topic guid is", topic.guid)
|
||||
print("Topic title is", topic.title)
|
||||
|
||||
# The BCF file is extracted to this temporary directory
|
||||
print(bcfxml.filepath)
|
||||
# Fetch extra data about a topic
|
||||
header = topic_handler.header
|
||||
comments = topic_handler.comments
|
||||
viewpoints = topic_handler.viewpoints
|
||||
|
||||
# Get a dictionary of topics
|
||||
topics = bcfxml.get_topics()
|
||||
for comment in comments:
|
||||
print(comment.guid)
|
||||
print(comment.comment)
|
||||
print(comment.author)
|
||||
|
||||
# Note: topics == bcfxml.topics
|
||||
for guid, topic in bcfxml.topics.items():
|
||||
print("Topic guid is", guid)
|
||||
print("Topic guid is", topic.guid)
|
||||
print("Topic title is", topic.title)
|
||||
# Get a particular topic
|
||||
topic = bcfxml.get_topic(guid)
|
||||
|
||||
# Fetch extra data about a topic
|
||||
header = bcfxml.get_header(guid)
|
||||
comments = bcfxml.get_comments(guid)
|
||||
viewpoints = bcfxml.get_viewpoints(guid)
|
||||
# Modify a topic
|
||||
topic.title = "New title"
|
||||
|
||||
# Note: comments == topic.comments, and so on
|
||||
for comment_guid, comment in comments.items():
|
||||
print(comment_guid)
|
||||
print(comment.comment)
|
||||
print(comment.author)
|
||||
|
||||
# Get a particular topic
|
||||
topic = bcfxml.get_topic(guid)
|
||||
|
||||
# Modify a topic
|
||||
topic.title = "New title"
|
||||
bcfxml.edit_topic(topic)
|
||||
bcfxml.save()
|
||||
```
|
||||
|
||||
## bcfapi
|
||||
@@ -92,10 +82,3 @@ print(data)
|
||||
data = bcf_client.get_extensions(project_id)
|
||||
print(data)
|
||||
```
|
||||
|
||||
## Todo List
|
||||
|
||||
The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
|
||||
|
||||
- For `bcfxml.py` two xsds support is remaining namely 'documents.xsd`and`extensions.xsd`.
|
||||
- For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
|
||||
|
||||
+131
-2
@@ -1,12 +1,141 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"setuptools>=61",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "bcf-client"
|
||||
# author = "IfcOpenShell"
|
||||
description = "BCF-XML file handler."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
keywords = ["IFC", "BCF", "BIM", "eingineering"]
|
||||
dependencies = [
|
||||
"xsdata",
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
version = "0.0.1"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Scientific/Engineering",
|
||||
"Topic :: Utilities",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Source = "https://github.com/IfcOpenShell/IfcOpenShell"
|
||||
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
extend-exclude = "model"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
profile = "black"
|
||||
extend_skip_glob = ["src/bcf/*/model/*"]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = ["src"]
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
source = ["bcf"]
|
||||
omit = ["*/model/*"]
|
||||
|
||||
[tool.coverage.report]
|
||||
show_missing = true
|
||||
fail_under = 65
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"if TYPE_CHECKING",
|
||||
"if __name__ == .__main__.:",
|
||||
"Protocol",
|
||||
]
|
||||
|
||||
[tool.tox]
|
||||
legacy_tox_ini = """
|
||||
[tox]
|
||||
env_list = lint, type, py3{10,11}
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
pytest
|
||||
pytest-cov
|
||||
coverage
|
||||
commands = pytest --cov --cov-report=term tests
|
||||
|
||||
[testenv:lint]
|
||||
description = run linters
|
||||
skip_install = true
|
||||
deps =
|
||||
black
|
||||
isort
|
||||
pylint
|
||||
commands =
|
||||
black {posargs:.}
|
||||
isort {posargs:.}
|
||||
pylint {posargs:.} --output-format=colorized
|
||||
|
||||
[testenv:type]
|
||||
description = run type checks
|
||||
deps =
|
||||
mypy>=0.991
|
||||
commands =
|
||||
- mypy {posargs:src}
|
||||
"""
|
||||
|
||||
[tool.mypy]
|
||||
check_untyped_defs = true
|
||||
disallow_any_generics = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
#no_implicit_reexport = true
|
||||
show_column_numbers = true
|
||||
show_error_codes = true
|
||||
show_error_context = true
|
||||
strict_equality = true
|
||||
strict_optional = true
|
||||
warn_redundant_casts = true
|
||||
#warn_return_any = true
|
||||
warn_unreachable = true
|
||||
warn_unused_configs = true
|
||||
warn_unused_ignores = true
|
||||
exclude= "src/bcf/v(2|3)/model"
|
||||
plugins = "numpy.typing.mypy_plugin"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests"
|
||||
disallow_untyped_decorators = false
|
||||
disallow_untyped_defs = false
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"pytest",
|
||||
"pytest_mock",
|
||||
"ifcopenshell",
|
||||
"ifcopenshell.*",
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pylint.main]
|
||||
ignore = ["model"]
|
||||
ignored-modules = ["bcf.v2.model", "bcf.v3.model", "xsdata"]
|
||||
jobs = 0
|
||||
disable="all"
|
||||
enable="E" # B,B9,BLK,C,D,E,F,I,N,S,W
|
||||
|
||||
[tool.pylint.design]
|
||||
max-args = 10
|
||||
max-attributes = 10
|
||||
|
||||
[tool.pylint.format]
|
||||
expected-line-ending-format = "LF"
|
||||
max-line-length = 120
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
black
|
||||
mypy
|
||||
pylint
|
||||
isort
|
||||
xsdata
|
||||
tox==3.27.1
|
||||
@@ -1 +0,0 @@
|
||||
xmlschema
|
||||
@@ -1,37 +0,0 @@
|
||||
[metadata]
|
||||
name=bcf-client
|
||||
version=0.0.1
|
||||
author = Ifcopenshell
|
||||
description = A simple Python implementation of BCF
|
||||
url = https://github.com/IfcOpenShell/IfcOpenShell
|
||||
project_urls =
|
||||
Code=https://github.com/IfcOpenShell/IfcOpenShell
|
||||
Issues=https://github.com/IfcOpenShell/IfcOpenShell/issues
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
classifiers =
|
||||
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python :: 3
|
||||
Topic :: Scientific/Engineering
|
||||
Topic :: Utilities
|
||||
keywords =
|
||||
Python
|
||||
file formats
|
||||
engineering
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= src
|
||||
packages = find:
|
||||
python_requires = >=3
|
||||
install_requires=
|
||||
xmlschema
|
||||
include_package_data = True
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
ignore = E24, E121, E123, E126, E203, E226, E704, E741, W503, W504
|
||||
@@ -0,0 +1,3 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
+62
-50
@@ -1,61 +1,73 @@
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
#
|
||||
# This file is part of BCF.
|
||||
#
|
||||
# BCF is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BCF is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
BCF - BCF Python library
|
||||
Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
Copyright (C) 2022 Andrea Ghensi <andrea.ghensi@gmail.com>
|
||||
|
||||
This file is part of BCF.
|
||||
|
||||
import os.path
|
||||
BCF is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
BCF is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import zipfile
|
||||
import tempfile
|
||||
from xml.dom import minidom
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
from bcf.v2.bcfxml import BcfXml as BcfXml2
|
||||
from bcf.v2.model import Version as Version2
|
||||
from bcf.v3.bcfxml import BcfXml as BcfXml3
|
||||
from bcf.v3.model import Version as Version3
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
def load(filepath):
|
||||
filepath = extract_project(filepath)
|
||||
if os.path.isfile(os.path.join(filepath, "bcf.version")):
|
||||
version_path = os.path.join(filepath, "bcf.version")
|
||||
version_id = get_version(version_path)
|
||||
# TODO: we actually coded it for 2.1, let's check the difference between 2.0 and 2.1
|
||||
if version_id == "2.1" or version_id == "2.0":
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
def load(
|
||||
filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None
|
||||
) -> Optional[Union[BcfXml2, BcfXml3]]:
|
||||
"""
|
||||
Load a BCF file.
|
||||
|
||||
bcfxml = BcfXml()
|
||||
bcfxml.filepath = filepath
|
||||
return bcfxml
|
||||
elif version_id == "3.0":
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
Args:
|
||||
filepath: The path to the BCF file.
|
||||
|
||||
bcfxml = BcfXml()
|
||||
bcfxml.filepath = filepath
|
||||
return bcfxml
|
||||
else:
|
||||
raise Exception(f"Version {version_id} not supported.")
|
||||
Returns:
|
||||
The loaded BCF file.
|
||||
|
||||
Raises:
|
||||
ValueError: If the BCF version is not supported.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
version_id = _get_version(filepath, xml_handler)
|
||||
if version_id in {"2.1", "2.0"}:
|
||||
return BcfXml2.load(filepath, xml_handler)
|
||||
if version_id == "3.0":
|
||||
return BcfXml3.load(filepath, xml_handler)
|
||||
raise ValueError(f"Version {version_id} not supported.")
|
||||
|
||||
|
||||
def get_version(version_path):
|
||||
xmlparse = minidom.parse(version_path)
|
||||
version_el = xmlparse.getElementsByTagName("Version")[0]
|
||||
version = version_el.getAttribute("VersionId")
|
||||
return version
|
||||
def _get_version(filepath: Union[str, Path], xml_handler: Optional[AbstractXmlParserSerializer] = None) -> str:
|
||||
"""
|
||||
Returns the version of the BCF file.
|
||||
|
||||
Args:
|
||||
filepath: The path to the BCF file.
|
||||
xml_handler: The XML handler. If none is given, XmlParserSerializer is used.
|
||||
|
||||
def extract_project(filepath):
|
||||
if not filepath:
|
||||
return
|
||||
zip_file = zipfile.ZipFile(filepath)
|
||||
filepath = tempfile.mkdtemp()
|
||||
zip_file.extractall(filepath)
|
||||
return filepath
|
||||
Returns:
|
||||
The version of the BCF file.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
with zipfile.ZipFile(filepath) as bcf_zip:
|
||||
try:
|
||||
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version3)
|
||||
except:
|
||||
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version2)
|
||||
return version.version_id
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
|
||||
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float_],
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to an element.
|
||||
|
||||
Args:
|
||||
elem_placement: Placement matrix of an element.
|
||||
|
||||
Returns:
|
||||
Camera position, direction and up vectors
|
||||
"""
|
||||
target_position = elem_placement[:3, 3]
|
||||
return camera_vectors_from_target_position(target_position)
|
||||
|
||||
|
||||
def camera_vectors_from_target_position(
|
||||
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
Args:
|
||||
target_position: point the camera is pointing to.
|
||||
camera_offset: offset of the camera from the target point.
|
||||
|
||||
Returns:
|
||||
Camera position, direction and up vectors
|
||||
"""
|
||||
camera_offset = np.array((5, 5, 5)) if offset is None else offset
|
||||
camera_position = target_position + camera_offset
|
||||
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||
return camera_position, camera_direction, camera_up
|
||||
# rotation_transform = np.eye(4)
|
||||
# rotation_transform[0, :3] = camera_right
|
||||
# rotation_transform[1, :3] = camera_up
|
||||
# rotation_transform[2, :3] = camera_direction
|
||||
# translation_transform = np.eye(4)
|
||||
# translation_transform[:3, -1] = -camera_position
|
||||
# look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
# mat = np.linalg.inv(look_at_transform)
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
|
||||
"""
|
||||
Return the unit vector of a vector.
|
||||
|
||||
Args:
|
||||
v: vector
|
||||
|
||||
Returns:
|
||||
unit vector.
|
||||
"""
|
||||
norm = np.linalg.norm(v)
|
||||
return v if norm == 0 else v / norm
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
In Memory Zip File management, taken from ruamel.std.zipfile
|
||||
|
||||
Copyright (c) 2017-2020 Anthon van der Neut, Ruamel bvba
|
||||
|
||||
original idea from https://stackoverflow.com/a/19722365/1307905
|
||||
"""
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from os import PathLike
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Protocol
|
||||
|
||||
|
||||
class ZipFileInterface(Protocol):
|
||||
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
|
||||
...
|
||||
|
||||
|
||||
class InMemoryZipFile:
|
||||
def __init__(
|
||||
self, file_name: Optional[str | Path] = None, compression: int = zipfile.ZIP_DEFLATED, debug: int = 0
|
||||
) -> None:
|
||||
# Create the in-memory file-like object
|
||||
self._file_name: Optional[str | Path] = str(file_name) if hasattr(file_name, "_from_parts") else file_name
|
||||
self.in_memory_data = BytesIO()
|
||||
# Create the in-memory zipfile
|
||||
self.in_memory_zip = zipfile.ZipFile(self.in_memory_data, "w", compression, False)
|
||||
self.in_memory_zip.debug = debug
|
||||
|
||||
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
|
||||
"""Appends a file with name filename_in_zip and contents of
|
||||
file_contents to the in-memory zip."""
|
||||
self.in_memory_zip.writestr(filename_in_zip, file_contents)
|
||||
|
||||
def write_to_file(self, filename: str | bytes | PathLike[str] | PathLike[bytes] | int) -> None:
|
||||
"""Writes the in-memory zip to a file."""
|
||||
# Mark the files as having been created on Windows so that
|
||||
# Unix permissions are not inferred as 0000
|
||||
for zfile in self.in_memory_zip.filelist:
|
||||
zfile.create_system = 0
|
||||
self.in_memory_zip.close()
|
||||
with open(filename, "wb") as f:
|
||||
f.write(self.data)
|
||||
|
||||
@property
|
||||
def data(self) -> bytes:
|
||||
return self.in_memory_data.getvalue()
|
||||
|
||||
def __enter__(self) -> "InMemoryZipFile":
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
||||
if self._file_name:
|
||||
self.write_to_file(self._file_name)
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
@@ -16,4 +15,3 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
+253
-768
File diff suppressed because it is too large
Load Diff
@@ -1,198 +0,0 @@
|
||||
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BCF.
|
||||
#
|
||||
# BCF is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BCF is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class Project:
|
||||
def __init__(self):
|
||||
self.project_id = ""
|
||||
self.name = ""
|
||||
self.extension_schema = ""
|
||||
|
||||
|
||||
class BimSnippet:
|
||||
def __init__(self):
|
||||
self.snippet_type = None
|
||||
self.is_external = False
|
||||
self.reference = None
|
||||
self.reference_schema = None
|
||||
|
||||
|
||||
class DocumentReference:
|
||||
def __init__(self):
|
||||
self.referenced_document = None
|
||||
self.description = None
|
||||
self.guid = None
|
||||
self.is_external = False
|
||||
|
||||
|
||||
class RelatedTopic:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
|
||||
|
||||
class HeaderFile:
|
||||
def __init__(self):
|
||||
self.filename = None
|
||||
self.date = None
|
||||
self.reference = None
|
||||
self.ifc_project = None
|
||||
self.ifc_spatial_structure_element = None
|
||||
self.is_external = True
|
||||
|
||||
|
||||
class Header:
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
|
||||
|
||||
class Topic:
|
||||
def __init__(self):
|
||||
self.reference_links = []
|
||||
self.title = ""
|
||||
self.priority = None
|
||||
self.index = None # Deprecated, stored, but ignored
|
||||
self.labels = []
|
||||
self.creation_date = None
|
||||
self.creation_author = None
|
||||
self.modified_date = None
|
||||
self.modified_author = None
|
||||
self.due_date = None
|
||||
self.assigned_to = None
|
||||
self.stage = None
|
||||
self.description = None
|
||||
self.bim_snippet = None
|
||||
self.document_references = []
|
||||
self.related_topics = []
|
||||
self.topic_status = None
|
||||
self.topic_type = None
|
||||
self.guid = None
|
||||
|
||||
self.header = None
|
||||
self.comments = {}
|
||||
self.viewpoints = {}
|
||||
|
||||
|
||||
class Comment:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
self.date = None
|
||||
self.author = None
|
||||
self.comment = None
|
||||
self.viewpoint = None
|
||||
self.modified_date = None
|
||||
self.modified_author = None
|
||||
self.topic_guid = None # Part of BCF-API
|
||||
|
||||
|
||||
class ViewSetupHints:
|
||||
def __init__(self):
|
||||
self.spaces_visible = False
|
||||
self.space_boundaries_visible = False
|
||||
self.openings_visible = False
|
||||
|
||||
|
||||
class Component:
|
||||
def __init__(self):
|
||||
self.originating_system = None
|
||||
self.authoring_tool_id = None
|
||||
self.ifc_guid = None
|
||||
|
||||
|
||||
class ComponentVisibility:
|
||||
def __init__(self):
|
||||
self.exceptions = []
|
||||
self.default_visibility = False
|
||||
|
||||
|
||||
class Color:
|
||||
def __init__(self):
|
||||
self.color = None
|
||||
self.components = []
|
||||
|
||||
|
||||
class Components:
|
||||
def __init__(self):
|
||||
self.view_setup_hints = None
|
||||
self.selection = []
|
||||
self.visibility = None
|
||||
self.coloring = []
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.z = 0
|
||||
|
||||
|
||||
class Direction(Point):
|
||||
pass
|
||||
|
||||
|
||||
class OrthogonalCamera:
|
||||
def __init__(self):
|
||||
self.camera_view_point = Point()
|
||||
self.camera_direction = Direction()
|
||||
self.camera_up_vector = Direction()
|
||||
self.view_to_world_scale = 1.0
|
||||
|
||||
|
||||
class PerspectiveCamera:
|
||||
def __init__(self):
|
||||
self.camera_view_point = Point()
|
||||
self.camera_direction = Direction()
|
||||
self.camera_up_vector = Direction()
|
||||
self.field_of_view = 60.0
|
||||
|
||||
|
||||
class Line:
|
||||
def __init__(self):
|
||||
self.start_point = Point()
|
||||
self.end_point = Point()
|
||||
|
||||
|
||||
class ClippingPlane:
|
||||
def __init__(self):
|
||||
self.location = Point()
|
||||
self.direction = Direction()
|
||||
|
||||
|
||||
class Bitmap:
|
||||
def __init__(self):
|
||||
self.reference = "" # Only in BCF-XML
|
||||
self.bitmap_data = None # Only in BCF-API
|
||||
self.bitmap_format = "PNG" # Enum of png or jpg
|
||||
self.location = Point()
|
||||
self.normal = Direction()
|
||||
self.up = Direction()
|
||||
self.height = 1.0
|
||||
|
||||
|
||||
class Viewpoint:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
self.viewpoint = None
|
||||
self.snapshot = None
|
||||
self.index = None
|
||||
|
||||
self.components = None # It's not a list, despite the plural name
|
||||
self.orthogonal_camera = None
|
||||
self.perspective_camera = None
|
||||
self.lines = []
|
||||
self.clipping_planes = []
|
||||
self.bitmaps = []
|
||||
@@ -0,0 +1,70 @@
|
||||
from bcf.v2.model.markup import (
|
||||
BimSnippet,
|
||||
Comment,
|
||||
CommentViewpoint,
|
||||
Header,
|
||||
HeaderFile,
|
||||
Markup,
|
||||
Topic,
|
||||
TopicDocumentReference,
|
||||
TopicRelatedTopic,
|
||||
ViewPoint,
|
||||
)
|
||||
from bcf.v2.model.project import Project, ProjectExtension
|
||||
from bcf.v2.model.version import Version
|
||||
from bcf.v2.model.visinfo import (
|
||||
BitmapFormat,
|
||||
ClippingPlane,
|
||||
Component,
|
||||
ComponentColoring,
|
||||
ComponentColoringColor,
|
||||
Components,
|
||||
ComponentSelection,
|
||||
ComponentVisibility,
|
||||
ComponentVisibilityExceptions,
|
||||
Direction,
|
||||
Line,
|
||||
OrthogonalCamera,
|
||||
PerspectiveCamera,
|
||||
Point,
|
||||
ViewSetupHints,
|
||||
VisualizationInfo,
|
||||
VisualizationInfoBitmap,
|
||||
VisualizationInfoClippingPlanes,
|
||||
VisualizationInfoLines,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BimSnippet",
|
||||
"Comment",
|
||||
"CommentViewpoint",
|
||||
"Header",
|
||||
"HeaderFile",
|
||||
"Markup",
|
||||
"Topic",
|
||||
"TopicDocumentReference",
|
||||
"TopicRelatedTopic",
|
||||
"ViewPoint",
|
||||
"Project",
|
||||
"ProjectExtension",
|
||||
"Version",
|
||||
"BitmapFormat",
|
||||
"ClippingPlane",
|
||||
"Component",
|
||||
"ComponentColoring",
|
||||
"ComponentColoringColor",
|
||||
"ComponentSelection",
|
||||
"ComponentVisibility",
|
||||
"ComponentVisibilityExceptions",
|
||||
"Components",
|
||||
"Direction",
|
||||
"Line",
|
||||
"OrthogonalCamera",
|
||||
"PerspectiveCamera",
|
||||
"Point",
|
||||
"ViewSetupHints",
|
||||
"VisualizationInfo",
|
||||
"VisualizationInfoBitmap",
|
||||
"VisualizationInfoClippingPlanes",
|
||||
"VisualizationInfoLines",
|
||||
]
|
||||
@@ -0,0 +1,461 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
reference_schema: str = field(
|
||||
metadata={
|
||||
"name": "ReferenceSchema",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
snippet_type: str = field(
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
is_external: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "isExternal",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class HeaderFile:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
filename: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Filename",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Date",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
ifc_project: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IfcProject",
|
||||
"type": "Attribute",
|
||||
"length": 22,
|
||||
"pattern": r"[0-9,A-Z,a-z,_$]*",
|
||||
}
|
||||
)
|
||||
ifc_spatial_structure_element: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IfcSpatialStructureElement",
|
||||
"type": "Attribute",
|
||||
"length": 22,
|
||||
"pattern": r"[0-9,A-Z,a-z,_$]*",
|
||||
}
|
||||
)
|
||||
is_external: bool = field(
|
||||
default=True,
|
||||
metadata={
|
||||
"name": "isExternal",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicDocumentReference:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
referenced_document: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ReferencedDocument",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
description: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Description",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
guid: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
is_external: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "isExternal",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Viewpoint",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
snapshot: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Snapshot",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
index: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Index",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
"name": "Date",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
author: str = field(
|
||||
metadata={
|
||||
"name": "Author",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
comment: str = field(
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
viewpoint: Optional[CommentViewpoint] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Viewpoint",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
modified_date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
modified_author: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedAuthor",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Header:
|
||||
file: List[HeaderFile] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "File",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Topic:
|
||||
reference_link: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ReferenceLink",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
title: str = field(
|
||||
metadata={
|
||||
"name": "Title",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
priority: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
index: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Index",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
labels: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Labels",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
creation_date: XmlDateTime = field(
|
||||
metadata={
|
||||
"name": "CreationDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
creation_author: str = field(
|
||||
metadata={
|
||||
"name": "CreationAuthor",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
modified_date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
modified_author: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedAuthor",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
due_date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DueDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
assigned_to: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "AssignedTo",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
stage: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
description: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Description",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
bim_snippet: Optional[BimSnippet] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "BimSnippet",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
document_reference: List[TopicDocumentReference] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DocumentReference",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
related_topic: List[TopicRelatedTopic] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "RelatedTopic",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
topic_type: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
topic_status: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Header",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
topic: Topic = field(
|
||||
metadata={
|
||||
"name": "Topic",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
comment: List[Comment] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
viewpoints: List[ViewPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Viewpoints",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Name",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
project_id: str = field(
|
||||
metadata={
|
||||
"name": "ProjectId",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectExtension:
|
||||
project: Optional[Project] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Project",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
extension_schema: str = field(
|
||||
metadata={
|
||||
"name": "ExtensionSchema",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Version:
|
||||
detailed_version: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DetailedVersion",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
version_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "VersionId",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,476 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
PNG = "PNG"
|
||||
JPG = "JPG"
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OriginatingSystem",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
authoring_tool_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "AuthoringToolId",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
ifc_guid: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IfcGuid",
|
||||
"type": "Attribute",
|
||||
"length": 22,
|
||||
"pattern": r"[0-9,A-Z,a-z,_$]*",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
"name": "X",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
y: float = field(
|
||||
metadata={
|
||||
"name": "Y",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
z: float = field(
|
||||
metadata={
|
||||
"name": "Z",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
"name": "X",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
y: float = field(
|
||||
metadata={
|
||||
"name": "Y",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
z: float = field(
|
||||
metadata={
|
||||
"name": "Z",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: Optional[bool] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "SpacesVisible",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
space_boundaries_visible: Optional[bool] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "SpaceBoundariesVisible",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
openings_visible: Optional[bool] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OpeningsVisible",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
"name": "Location",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
direction: Direction = field(
|
||||
metadata={
|
||||
"name": "Direction",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
color: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[0-9,a-f,A-F]{6}([0-9,a-f,A-F]{2})?",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentSelection:
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
"name": "StartPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
end_point: Point = field(
|
||||
metadata={
|
||||
"name": "EndPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
camera_view_point:
|
||||
camera_direction:
|
||||
camera_up_vector:
|
||||
view_to_world_scale: view's visible size in meters
|
||||
"""
|
||||
camera_view_point: Point = field(
|
||||
metadata={
|
||||
"name": "CameraViewPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_direction: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraDirection",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_up_vector: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraUpVector",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
view_to_world_scale: float = field(
|
||||
metadata={
|
||||
"name": "ViewToWorldScale",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
camera_view_point:
|
||||
camera_direction:
|
||||
camera_up_vector:
|
||||
field_of_view: It is currently limited to a value between 45 and
|
||||
60 degrees. This limitation will be dropped in the next
|
||||
release and viewers should be expect values outside this
|
||||
range in current implementations.
|
||||
"""
|
||||
camera_view_point: Point = field(
|
||||
metadata={
|
||||
"name": "CameraViewPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_direction: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraDirection",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_up_vector: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraUpVector",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
field_of_view: float = field(
|
||||
metadata={
|
||||
"name": "FieldOfView",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
"min_inclusive": 1.0,
|
||||
"max_inclusive": 170.0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoBitmap:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
bitmap: BitmapFormat = field(
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
reference: str = field(
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
location: Point = field(
|
||||
metadata={
|
||||
"name": "Location",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
normal: Direction = field(
|
||||
metadata={
|
||||
"name": "Normal",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
up: Direction = field(
|
||||
metadata={
|
||||
"name": "Up",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
height: float = field(
|
||||
metadata={
|
||||
"name": "Height",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoring:
|
||||
color: List[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibility:
|
||||
exceptions: Optional[ComponentVisibilityExceptions] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Exceptions",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
default_visibility: Optional[bool] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DefaultVisibility",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
clipping_plane: List[ClippingPlane] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ClippingPlane",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
line: List[Line] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Line",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Components:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ViewSetupHints",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
selection: Optional[ComponentSelection] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Selection",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
visibility: ComponentVisibility = field(
|
||||
metadata={
|
||||
"name": "Visibility",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
coloring: Optional[ComponentColoring] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Coloring",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
"""
|
||||
components: Optional[Components] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Components",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
orthogonal_camera: Optional[OrthogonalCamera] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OrthogonalCamera",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
perspective_camera: Optional[PerspectiveCamera] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "PerspectiveCamera",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
lines: Optional[VisualizationInfoLines] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Lines",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ClippingPlanes",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
bitmap: List[VisualizationInfoBitmap] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,304 @@
|
||||
"""BCF XML V2 Topic handler."""
|
||||
import datetime
|
||||
import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from numpy.typing import NDArray
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.v2.visinfo import VisualizationInfoHandler
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
class TopicHandler:
|
||||
"""BCF Topic and related objects handler."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
topic_dir: Optional[zipfile.Path] = None,
|
||||
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._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
|
||||
@property
|
||||
def markup(self) -> Optional[mdl.Markup]:
|
||||
if not self._markup:
|
||||
markup_path = self._topic_dir.joinpath("markup.bcf")
|
||||
if markup_path.exists():
|
||||
self._markup = self._xml_handler.parse(markup_path.read_bytes(), mdl.Markup)
|
||||
return self._markup
|
||||
|
||||
@markup.setter
|
||||
def markup(self, value: mdl.Markup) -> None:
|
||||
self._markup = value
|
||||
|
||||
@property
|
||||
def topic(self) -> mdl.Topic:
|
||||
"""Return the Topic object."""
|
||||
return self.markup.topic
|
||||
|
||||
@property
|
||||
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 ""
|
||||
|
||||
@property
|
||||
def header(self) -> Optional[mdl.Header]:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header if self.markup else None
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.markup.comment if self.markup else []
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
self._bim_snippet = self._load_bim_snippet()
|
||||
return self._bim_snippet
|
||||
|
||||
@bim_snippet.setter
|
||||
def bim_snippet(self, value: bytes) -> None:
|
||||
self._bim_snippet = value
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if not self._viewpoints and self._topic_dir:
|
||||
self._viewpoints = self._load_viewpoints()
|
||||
return self._viewpoints
|
||||
|
||||
@property
|
||||
def reference_files(self) -> dict[str, bytes]:
|
||||
if self._reference_files or not self.header:
|
||||
return self._reference_files
|
||||
for ref in self.header.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
|
||||
|
||||
@property
|
||||
def document_references(self) -> dict[str, bytes]:
|
||||
if self._document_references or not self.topic:
|
||||
return self._document_references
|
||||
for doc in self.topic.document_reference:
|
||||
if doc.is_external or not doc.referenced_document:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in doc.referenced_document.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
self._document_references[doc.referenced_document] = real_path.read_bytes()
|
||||
return self._document_references
|
||||
|
||||
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:
|
||||
bim_snippet_path = self._topic_dir.joinpath(bim_snippet_obj.reference)
|
||||
if bim_snippet_path.exists():
|
||||
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,
|
||||
title: str,
|
||||
description: str,
|
||||
author: str,
|
||||
topic_type: str = "",
|
||||
topic_status: str = "",
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "TopicHandler":
|
||||
"""
|
||||
Create a new BCF topic.
|
||||
|
||||
Args:
|
||||
title: The title of the topic.
|
||||
description: The description of the topic.
|
||||
author: The author of the topic.
|
||||
topic_type: The type of the topic.
|
||||
topic_status: The status of the topic.
|
||||
xml_handler: The XML parser/serializer to use.
|
||||
|
||||
Returns:
|
||||
The BCF topic definition.
|
||||
"""
|
||||
creation_date = XmlDateTime.from_datetime(datetime.datetime.now())
|
||||
guid = str(uuid.uuid4())
|
||||
topic = mdl.Topic(
|
||||
title=title,
|
||||
description=description,
|
||||
creation_author=author,
|
||||
creation_date=creation_date,
|
||||
guid=guid,
|
||||
topic_type=topic_type,
|
||||
topic_status=topic_status,
|
||||
)
|
||||
markup = mdl.Markup(topic=topic)
|
||||
obj = cls(topic_dir=Path(guid), xml_handler=xml_handler or XmlParserSerializer())
|
||||
obj.markup = markup
|
||||
return obj
|
||||
|
||||
def save(self, destination_zip: ZipFileInterface) -> None:
|
||||
"""
|
||||
Save the topic to a BCF zip file.
|
||||
|
||||
Args:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
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)
|
||||
self._save_document_references(destination_zip)
|
||||
|
||||
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.markup or not (viewpoints := self.markup.viewpoints):
|
||||
return
|
||||
for vpt in viewpoints:
|
||||
if vpt.viewpoint:
|
||||
self.viewpoints[vpt.viewpoint].save(destination_zip, topic_dir, vpt)
|
||||
|
||||
def _save_xml(self, destination_zip: ZipFileInterface, item: Any, target: str) -> None:
|
||||
if self._topic_dir is None:
|
||||
return
|
||||
to_write = self._xml_handler.serialize(item) if item else self._topic_dir.joinpath(target).read_bytes()
|
||||
destination_zip.writestr(f"{self._topic_dir.name}/{target}", to_write)
|
||||
|
||||
def _save_bim_snippet(self, destination_zip: ZipFileInterface) -> None:
|
||||
snippet = self.topic.bim_snippet
|
||||
if not snippet or snippet.is_external:
|
||||
return
|
||||
ref_filename = Path(snippet.reference).name
|
||||
if self.bim_snippet:
|
||||
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
|
||||
|
||||
def _save_reference_files(self, destination_zip: ZipFileInterface) -> None:
|
||||
if not self.header:
|
||||
return
|
||||
for ref in self.header.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 _save_document_references(self, destination_zip: ZipFileInterface) -> None:
|
||||
if not self.topic:
|
||||
return
|
||||
for doc in self.topic.document_reference:
|
||||
if doc.is_external or not doc.referenced_document:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in doc.referenced_document.split("/"):
|
||||
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:
|
||||
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
|
||||
Args:
|
||||
position: the XYZ point in space
|
||||
guids: one or more element GlobalIds.
|
||||
"""
|
||||
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid))
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
(
|
||||
self.markup == other.markup
|
||||
and self.viewpoints == other.viewpoints
|
||||
and self.bim_snippet == other.bim_snippet
|
||||
)
|
||||
if isinstance(other, TopicHandler)
|
||||
else NotImplemented
|
||||
)
|
||||
@@ -0,0 +1,299 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import lru_cache
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.geometry import (
|
||||
camera_vectors_from_element_placement,
|
||||
camera_vectors_from_target_position,
|
||||
)
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
class VisualizationInfoHandler:
|
||||
"""Handle the VisualizationInfo and related objects."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
visualization_info: mdl.VisualizationInfo,
|
||||
snapshot: Optional[bytes] = None,
|
||||
bitmaps: Optional[dict[str, bytes]] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self.visualization_info = visualization_info
|
||||
self.snapshot = snapshot
|
||||
self.bitmaps = bitmaps or {}
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
|
||||
@property
|
||||
def guid(self) -> str:
|
||||
"""Return the GUID of the visualization info."""
|
||||
return self.visualization_info.guid
|
||||
|
||||
@classmethod
|
||||
def from_topic_viewpoints(
|
||||
cls,
|
||||
topic_dir: zipfile.Path,
|
||||
vps: Iterable[mdl.ViewPoint],
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> dict[str, "VisualizationInfoHandler"]:
|
||||
"""Create VisualizationInfoHandler objects of a Topic's ViewPoints."""
|
||||
viewpoints = {}
|
||||
for vpt in vps:
|
||||
visinfo = cls.load(topic_dir, vpt, xml_handler)
|
||||
if visinfo and vpt.viewpoint:
|
||||
viewpoints[vpt.viewpoint] = visinfo
|
||||
return viewpoints
|
||||
|
||||
@classmethod
|
||||
def load(
|
||||
cls,
|
||||
topic_dir: zipfile.Path,
|
||||
vpt: mdl.ViewPoint,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> Optional["VisualizationInfoHandler"]:
|
||||
"""
|
||||
Load the VisualizationInfo and related objects from a BCF zip file.
|
||||
|
||||
Args:
|
||||
topic_dir: The directory in the BCF zip file to load from.
|
||||
vpt: The ViewPoint to load.
|
||||
xml_handler: The XML handler to use to parse the VisualizationInfo.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
visinfo = cls._load_visinfo(topic_dir, vpt.viewpoint, xml_handler)
|
||||
if not visinfo:
|
||||
return None
|
||||
snapshot = cls._load_snapshot(topic_dir, vpt.snapshot)
|
||||
bitmaps = cls._load_bitmaps(topic_dir, visinfo)
|
||||
return cls(visinfo, snapshot, bitmaps, xml_handler)
|
||||
|
||||
@staticmethod
|
||||
def _load_visinfo(
|
||||
topic_dir: zipfile.Path,
|
||||
vp_name: Optional[str],
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> Optional[mdl.VisualizationInfo]:
|
||||
if not vp_name:
|
||||
return None
|
||||
vp_path = topic_dir.joinpath(vp_name)
|
||||
if vp_path.exists():
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return xml_handler.parse(vp_path.read_bytes(), mdl.VisualizationInfo)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _load_snapshot(topic_dir: zipfile.Path, vp_snapshot: Optional[str]) -> Optional[bytes]:
|
||||
if vp_snapshot:
|
||||
snapshot_path = topic_dir.joinpath(vp_snapshot)
|
||||
if snapshot_path.exists():
|
||||
return snapshot_path.read_bytes()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _load_bitmaps(topic_dir: zipfile.Path, visinfo: Optional[mdl.VisualizationInfo]) -> dict[str, bytes]:
|
||||
if not visinfo or not (bitmaps := visinfo.bitmap):
|
||||
return {}
|
||||
bitmaps_dict = {}
|
||||
for bitmap in bitmaps:
|
||||
if not bitmap.reference:
|
||||
continue
|
||||
bitmap_path = topic_dir.joinpath(bitmap.reference)
|
||||
if bitmap_path.exists():
|
||||
bitmaps_dict[bitmap.reference] = bitmap_path.read_bytes()
|
||||
return bitmaps_dict
|
||||
|
||||
def save(
|
||||
self,
|
||||
bcf_zip: ZipFileInterface,
|
||||
topic_dir: str,
|
||||
vpt: mdl.ViewPoint,
|
||||
) -> None:
|
||||
"""
|
||||
Save the VisualizationInfo and related objects to a BCF zip file.
|
||||
|
||||
Args:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
topic_dir: The directory in the BCF zip file to save to.
|
||||
vpt: The ViewPoint to save.
|
||||
"""
|
||||
if not (vp_name := vpt.viewpoint):
|
||||
return
|
||||
self._save_visinfo(bcf_zip, topic_dir, vp_name)
|
||||
self._save_snapshot(bcf_zip, topic_dir, vpt.snapshot)
|
||||
self._save_bitmaps(bcf_zip, topic_dir)
|
||||
|
||||
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
|
||||
if self.snapshot and filename:
|
||||
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
|
||||
|
||||
def _save_visinfo(self, bcf_zip: ZipFileInterface, topic_dir: str, vp_name: str) -> None:
|
||||
bcf_zip.writestr(
|
||||
f"{topic_dir}/{vp_name}",
|
||||
self._xml_handler.serialize(self.visualization_info),
|
||||
)
|
||||
|
||||
def _save_bitmaps(self, bcf_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.bitmaps:
|
||||
return
|
||||
if not (bitmaps_defs := self.visualization_info.bitmap):
|
||||
return
|
||||
for bitmap_def in bitmaps_defs:
|
||||
if not (bitmap_name := bitmap_def.reference):
|
||||
continue
|
||||
if bitmap_name in self.bitmaps:
|
||||
bcf_zip.writestr(f"{topic_dir}/{bitmap_name}", self.bitmaps[bitmap_name])
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
element: entity_instance,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
"""
|
||||
Create a new VisualizationInfoHandler object from an IFC element.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
xml_handler: The XML handler to use.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
||||
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
"""
|
||||
Create a new VisualizationInfoHandler object from an IFC element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
xml_handler: The XML handler to use.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
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()),
|
||||
components=build_components(element.GlobalId),
|
||||
perspective_camera=build_camera(elem_placement),
|
||||
)
|
||||
|
||||
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> 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.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(*guids),
|
||||
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
|
||||
)
|
||||
|
||||
|
||||
def build_components(*guids: str) -> mdl.Components:
|
||||
"""
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
"""
|
||||
components = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
return mdl.Components(
|
||||
selection=mdl.ComponentSelection(component=components),
|
||||
visibility=mdl.ComponentVisibility(default_visibility=True),
|
||||
)
|
||||
|
||||
|
||||
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
Args:
|
||||
elem_placement: The IFC element placement as a rototranslation matrix.
|
||||
|
||||
Returns:
|
||||
The BCF camera definition.
|
||||
"""
|
||||
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
Args:
|
||||
camera_position: camera position array
|
||||
camera_dir: camera direction versor
|
||||
camera_up_vector: camera up versor
|
||||
|
||||
Returns:
|
||||
The BCF camera definition.
|
||||
"""
|
||||
camera_viewpoint = mdl.Point(x=camera_position[0], y=camera_position[1], z=camera_position[2])
|
||||
camera_direction = mdl.Direction(x=camera_dir[0], y=camera_dir[1], z=camera_dir[2])
|
||||
camera_up_vector = mdl.Direction(x=camera_up[0], y=camera_up[1], z=camera_up[2])
|
||||
return mdl.PerspectiveCamera(
|
||||
camera_view_point=camera_viewpoint,
|
||||
camera_direction=camera_direction,
|
||||
camera_up_vector=camera_up_vector,
|
||||
field_of_view=60.0,
|
||||
)
|
||||
@@ -1,19 +1 @@
|
||||
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
#
|
||||
# This file is part of BCF.
|
||||
#
|
||||
# BCF is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BCF is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""BCF XML v3 handler."""
|
||||
|
||||
+172
-184
@@ -1,4 +1,3 @@
|
||||
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
#
|
||||
@@ -17,25 +16,27 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import uuid
|
||||
import time
|
||||
import json
|
||||
import urllib
|
||||
import requests
|
||||
import webbrowser
|
||||
import http.server
|
||||
import base64
|
||||
import tempfile
|
||||
import http.server
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import urllib
|
||||
import uuid
|
||||
import webbrowser
|
||||
from re import A
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
client_id, client_secret = "", ""
|
||||
|
||||
|
||||
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
def do_GET(self) -> None:
|
||||
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
||||
self.server.auth_code = query.get("code", [""])[0]
|
||||
self.server.auth_state = query.get("state", [""])[0]
|
||||
self.server.auth_code = query.get("code", [""])[0] # type:ignore
|
||||
self.server.auth_state = query.get("state", [""])[0] # type:ignore
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
@@ -43,20 +44,21 @@ class OAuthReceiver(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
|
||||
class FoundationClient:
|
||||
def __init__(self, client_id, client_secret, base_url=None, redirect_subdir=None):
|
||||
def __init__(
|
||||
self, client_id: str, client_secret: str, base_url: Optional[str] = None, redirect_subdir: Optional[str] = None
|
||||
) -> None:
|
||||
self.baseurl = base_url
|
||||
self.access_token = ""
|
||||
self.refresh_token = ""
|
||||
self.access_token_expires_on = time.time()
|
||||
self.refresh_token_expires_on = float("inf")
|
||||
self.auth_endpoint = None
|
||||
self.token_endpoint = None
|
||||
self.token_endpoint = ""
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.auth_method = None
|
||||
self.auth_method: Optional[str] = None
|
||||
self.redirect_subdir = redirect_subdir
|
||||
|
||||
def get_access_token(self):
|
||||
def get_access_token(self) -> str:
|
||||
if self.access_token and self.access_token_expires_on > time.time():
|
||||
return self.access_token
|
||||
elif self.refresh_token and self.refresh_token_expires_on > time.time():
|
||||
@@ -65,18 +67,18 @@ class FoundationClient:
|
||||
self.login()
|
||||
return self.access_token
|
||||
|
||||
def get_auth_methods(self):
|
||||
def get_auth_methods(self) -> list[Any]:
|
||||
resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
|
||||
return resp.json()["supported_oauth2_flows"]
|
||||
|
||||
def get_versions(self):
|
||||
def get_versions(self) -> list[Any]:
|
||||
resp = requests.get(f"{self.baseurl}foundation/versions")
|
||||
return resp.json()["versions"]
|
||||
|
||||
def login(self):
|
||||
def login(self) -> None:
|
||||
resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
|
||||
values = resp.json()
|
||||
self.auth_endpoint = values["oauth2_auth_url"]
|
||||
auth_endpoint = values["oauth2_auth_url"]
|
||||
self.token_endpoint = values["oauth2_token_url"]
|
||||
|
||||
with http.server.HTTPServer(("", 8080), OAuthReceiver) as server:
|
||||
@@ -89,25 +91,22 @@ class FoundationClient:
|
||||
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
|
||||
}
|
||||
)
|
||||
if "?" in self.auth_endpoint:
|
||||
webbrowser.open(f"{self.auth_endpoint}&{query}")
|
||||
if "?" in auth_endpoint:
|
||||
webbrowser.open(f"{auth_endpoint}&{query}")
|
||||
else:
|
||||
webbrowser.open(f"{self.auth_endpoint}?{query}")
|
||||
webbrowser.open(f"{auth_endpoint}?{query}")
|
||||
server.timeout = 100
|
||||
server.state = state
|
||||
server.handle_request()
|
||||
if server.auth_code and server.auth_state == state:
|
||||
if server.auth_code and server.auth_state == state: # pylint: disable=E1101
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": server.auth_code,
|
||||
"code": server.auth_code, # pylint: disable=E1101 type:ignore
|
||||
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
|
||||
}
|
||||
auth_string = f"{self.client_id}:{self.client_secret}"
|
||||
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
|
||||
headers = {"Authorization": f"Basic {header_string}"}
|
||||
headers = self._get_access_token_headers()
|
||||
self.set_tokens_from_response(requests.post(self.token_endpoint, data=data, headers=headers))
|
||||
|
||||
def get_refresh_token(self):
|
||||
def get_refresh_token(self) -> None:
|
||||
self.set_tokens_from_response(
|
||||
requests.post(
|
||||
self.token_endpoint,
|
||||
@@ -118,10 +117,8 @@ class FoundationClient:
|
||||
).json()
|
||||
)
|
||||
|
||||
def get_new_access_token(self):
|
||||
auth_string = f"{self.client_id}:{self.client_secret}"
|
||||
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
|
||||
headers = {"Authorization": f"Basic {header_string}"}
|
||||
def get_new_access_token(self) -> None:
|
||||
headers = self._get_access_token_headers()
|
||||
self.set_tokens_from_response(
|
||||
requests.post(
|
||||
self.token_endpoint,
|
||||
@@ -133,35 +130,41 @@ class FoundationClient:
|
||||
).json()
|
||||
)
|
||||
|
||||
def set_auth_method(self, method="authorization_code_grant"):
|
||||
def _get_access_token_headers(self) -> dict[str, str]:
|
||||
auth_string = f"{self.client_id}:{self.client_secret}"
|
||||
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
|
||||
return {"Authorization": f"Basic {header_string}"}
|
||||
|
||||
def set_auth_method(self, method: str = "authorization_code_grant") -> None:
|
||||
if method != "authorization_code_grant":
|
||||
raise NotImplementedError(f"{method} not supported")
|
||||
else:
|
||||
self.auth_method = method
|
||||
|
||||
def set_tokens_from_response(self, response):
|
||||
response = response.json()
|
||||
self.access_token = response["access_token"]
|
||||
self.refresh_token = response["refresh_token"]
|
||||
self.access_token_expires_on = time.time() + response["expires_in"]
|
||||
if "refresh_token_expires_in" in response:
|
||||
self.refresh_token_expires_on = time.time() + response["refresh_token_expires_in"]
|
||||
def set_tokens_from_response(self, response: requests.Response) -> None:
|
||||
response_dict = response.json()
|
||||
self.access_token = response_dict["access_token"]
|
||||
self.refresh_token = response_dict["refresh_token"]
|
||||
self.access_token_expires_on = time.time() + response_dict["expires_in"]
|
||||
if "refresh_token_expires_in" in response_dict:
|
||||
self.refresh_token_expires_on = time.time() + response_dict["refresh_token_expires_in"]
|
||||
|
||||
|
||||
class BcfClient:
|
||||
def __init__(self, foundation_client):
|
||||
def __init__(self, foundation_client: FoundationClient) -> None:
|
||||
self.foundation_client = foundation_client
|
||||
self.version_id = None
|
||||
self.baseurl = None
|
||||
self.version_id: Optional[str] = None
|
||||
self.baseurl: Optional[str] = None
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
|
||||
def set_version(self, version):
|
||||
def set_version(self, version: dict[str, str]) -> None:
|
||||
self.version_id = version["version_id"]
|
||||
self.baseurl = version["api_base_url"]
|
||||
|
||||
def get(self, endpoint, params=None, is_auth_required=False):
|
||||
def get(self, endpoint: str, params: Any = None, is_auth_required: bool = False) -> Any:
|
||||
# TODO: handle error http status codes and raise exception. Follow error.json standard.
|
||||
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
|
||||
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
|
||||
|
||||
response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
|
||||
try:
|
||||
response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
|
||||
@@ -171,68 +174,62 @@ class BcfClient:
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"message: {response.reason}' '{response.status_code}' '{ e }")
|
||||
|
||||
def post(self, endpoint, data=None, params=None):
|
||||
def post(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.baseurl}{endpoint}",
|
||||
headers=headers,
|
||||
params=params or None,
|
||||
data=data or None,
|
||||
f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None
|
||||
)
|
||||
if response.status_code == 201:
|
||||
return response.status_code, response.text
|
||||
response.raise_for_status()
|
||||
|
||||
if response.status_code != 201:
|
||||
response.raise_for_status()
|
||||
return response.status_code, response.text
|
||||
except requests.exceptions.HTTPError as errh:
|
||||
print(f"message: {response.reason}' '{response.status_code}, {errh}")
|
||||
return response.status_code, response.reason
|
||||
|
||||
def put(self, endpoint, data=None, params=None):
|
||||
def put(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.put(
|
||||
f"{self.baseurl}{endpoint}",
|
||||
headers=headers,
|
||||
params=params or None,
|
||||
data=data or None,
|
||||
f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.status_code, response.text
|
||||
response.raise_for_status()
|
||||
|
||||
if response.status_code != 200:
|
||||
response.raise_for_status()
|
||||
return response.status_code, response.text
|
||||
except requests.exceptions.HTTPError as errh:
|
||||
print(f"message: {response.reason}' '{response.status_code}, {errh}")
|
||||
return response.status_code, response.reason
|
||||
|
||||
def delete(self, endpoint, params=None):
|
||||
def delete(self, endpoint: str, params: Any = None) -> Tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.delete(
|
||||
f"{self.baseurl}{endpoint}",
|
||||
headers=headers,
|
||||
params=params or None,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.status_code, response.text
|
||||
response.raise_for_status()
|
||||
response = requests.delete(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
|
||||
|
||||
if response.status_code != 200:
|
||||
response.raise_for_status()
|
||||
return response.status_code, response.text
|
||||
except requests.exceptions.HTTPError as errh:
|
||||
print(f"message: {response.reason}' '{response.status_code}, {errh}")
|
||||
return response.status_code, response.reason
|
||||
|
||||
def get_projects(self) -> list:
|
||||
return self.get(
|
||||
f"/projects",
|
||||
)
|
||||
def get_projects(self) -> list[Any]:
|
||||
return self.get("/projects")
|
||||
|
||||
def get_project(
|
||||
self,
|
||||
project_id="",
|
||||
) -> dict:
|
||||
def get_project(self, project_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}",
|
||||
{
|
||||
@@ -240,16 +237,13 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def update_project(self, project_id="", data=None) -> dict:
|
||||
def update_project(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
url = f"{self.baseurl}/projects/{project_id}"
|
||||
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
|
||||
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
|
||||
resp = requests.put(url, headers=headers, data=data)
|
||||
return resp.status_code, resp.text
|
||||
|
||||
def get_extensions(
|
||||
self,
|
||||
project_id="",
|
||||
) -> dict:
|
||||
def get_extensions(self, project_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/extensions",
|
||||
{
|
||||
@@ -259,10 +253,10 @@ class BcfClient:
|
||||
|
||||
def get_topics(
|
||||
self,
|
||||
project_id="",
|
||||
topics="",
|
||||
query_string=None,
|
||||
) -> list:
|
||||
project_id: str = "",
|
||||
topics: str = "",
|
||||
query_string: Optional[str] = None,
|
||||
) -> list[Any]:
|
||||
# return self.get(
|
||||
# f"/projects/{project_id}/topics",
|
||||
# {
|
||||
@@ -273,7 +267,7 @@ class BcfClient:
|
||||
# )
|
||||
pass
|
||||
|
||||
def get_topic(self, project_id="", topic_id="") -> dict:
|
||||
def get_topic(self, project_id: str = "", topic_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}",
|
||||
{
|
||||
@@ -282,42 +276,40 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def create_topic(self, project_id="", data=None):
|
||||
def create_topic(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
return self.post(f"/projects/{project_id}/topics", data=data)
|
||||
|
||||
def update_topic(self, project_id="", topic_id="", data=None) -> dict:
|
||||
def update_topic(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
|
||||
|
||||
def delete_topic(self, project_id="", topic_id=""):
|
||||
def delete_topic(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
|
||||
return self.delete(f"/projects/{project_id}/topics/{topic_id}")
|
||||
|
||||
def get_snippet(self, project_id="", topic_id="") -> str:
|
||||
def get_snippet(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/octet-stream",
|
||||
}
|
||||
response = requests.get(
|
||||
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
|
||||
headers=headers,
|
||||
)
|
||||
# TODO: write to tmpdir
|
||||
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "wb") as f:
|
||||
f.write(response.content.decode("utf-8"))
|
||||
return response.status_code, response.content
|
||||
|
||||
def update_snippet(self, project_id="", topic_id="", files=None, data=None):
|
||||
response = requests.get(f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", headers=headers)
|
||||
content = response.content.decode("utf-8")
|
||||
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "w") as f:
|
||||
f.write(content)
|
||||
return response.status_code, content
|
||||
|
||||
def update_snippet(self, project_id: str = "", topic_id: str = "", files: Any = None, data: Any = None) -> int:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/octet-stream",
|
||||
}
|
||||
|
||||
response = requests.put(
|
||||
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
|
||||
headers=headers,
|
||||
files=files,
|
||||
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", headers=headers, files=files
|
||||
)
|
||||
|
||||
return response.status_code
|
||||
|
||||
def get_files_information(self, project_id="") -> list:
|
||||
def get_files_information(self, project_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/files_information",
|
||||
{
|
||||
@@ -325,7 +317,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_files(self, project_id="", topic_id="") -> list:
|
||||
def get_files(self, project_id: str = "", topic_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/files",
|
||||
{
|
||||
@@ -336,32 +328,32 @@ class BcfClient:
|
||||
|
||||
def update_files(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
params=None,
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
params: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/files",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_comments(self, project_id="", topic_id="") -> list:
|
||||
def get_comments(self, project_id: str = "", topic_id: str = "") -> None:
|
||||
pass
|
||||
|
||||
def create_comments(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
params=None,
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
params: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_comment(self, project_id="", topic_id="", comment_id="") -> dict:
|
||||
def get_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
|
||||
{
|
||||
@@ -371,22 +363,22 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def delete_comment(self, project_id="", topic_id="", comment_id=""):
|
||||
def delete_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> Tuple[int, str]:
|
||||
return self.delete(f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}")
|
||||
|
||||
def update_comment(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
comment_id="",
|
||||
data=None,
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
comment_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_viewpoints(self, project_id="", topic_id="") -> list:
|
||||
def get_viewpoints(self, project_id: str = "", topic_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
|
||||
{
|
||||
@@ -395,13 +387,13 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def create_viewpoints(self, project_id="", topic_id="", data=None):
|
||||
def create_viewpoints(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_viewpoint(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
def get_viewpoint(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
|
||||
{
|
||||
@@ -413,15 +405,15 @@ class BcfClient:
|
||||
|
||||
def delete_viewpoint(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
viewpoint_id="",
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
viewpoint_id: str = "",
|
||||
) -> Tuple[int, str]:
|
||||
return self.delete(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
|
||||
)
|
||||
|
||||
def get_snapshot(self, project_id="", topic_id="", viewpoint_id="") -> str:
|
||||
def get_snapshot(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> str:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
|
||||
{
|
||||
@@ -431,7 +423,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_bitmap(self, project_id="", topic_id="", viewpoint_id="", bitmap_id="") -> str:
|
||||
def get_bitmap(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "", bitmap_id: str = "") -> str:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
|
||||
{
|
||||
@@ -442,7 +434,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_selection(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
def get_selection(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
|
||||
{
|
||||
@@ -452,7 +444,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_coloring(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
def get_coloring(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
|
||||
{
|
||||
@@ -462,7 +454,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_visibility(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
def get_visibility(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
|
||||
{
|
||||
@@ -472,7 +464,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_related_topics(self, project_id="", topic_id="") -> list:
|
||||
def get_related_topics(self, project_id: str = "", topic_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/related_topics",
|
||||
{
|
||||
@@ -483,16 +475,16 @@ class BcfClient:
|
||||
|
||||
def update_related_topics(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/related_topics",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_document_references(self, project_id="", topic_id="") -> list:
|
||||
def get_document_references(self, project_id: str = "", topic_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
{
|
||||
@@ -503,10 +495,10 @@ class BcfClient:
|
||||
|
||||
def create_document_reference(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
data=data,
|
||||
@@ -514,17 +506,17 @@ class BcfClient:
|
||||
|
||||
def update_document_references(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
document_reference_id="",
|
||||
data=None,
|
||||
):
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
document_reference_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_documents(self, project_id="", topic_id="") -> list:
|
||||
def get_documents(self, project_id: str = "", topic_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/documents",
|
||||
{
|
||||
@@ -534,40 +526,36 @@ class BcfClient:
|
||||
)
|
||||
|
||||
def create_document(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
guid=None,
|
||||
files=None,
|
||||
data=None,
|
||||
):
|
||||
self, project_id: str = "", topic_id: str = "", guid: Optional[str] = None, files: Any = None, data: Any = None
|
||||
) -> int:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/octet-stream",
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/documents",
|
||||
data=data,
|
||||
params={guid},
|
||||
params={"guid": guid},
|
||||
files=files,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
return response.status_code
|
||||
|
||||
def get_document(self, project_id="", topic_id="", document_id="") -> str:
|
||||
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> Tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/octet-stream",
|
||||
}
|
||||
response = requests.get(
|
||||
f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}",
|
||||
headers=headers,
|
||||
)
|
||||
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "wb") as f:
|
||||
f.write(response.content.decode("utf-8"))
|
||||
return response.status_code, response.content
|
||||
|
||||
def get_topics_events(self, project_id="") -> list:
|
||||
response = requests.get(f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}", headers=headers)
|
||||
content = response.content.decode("utf-8")
|
||||
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "w") as f:
|
||||
f.write(content)
|
||||
return response.status_code, content
|
||||
|
||||
def get_topics_events(self, project_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/events",
|
||||
{
|
||||
@@ -575,7 +563,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_topic_events(self, project_id="", topic_id="") -> list:
|
||||
def get_topic_events(self, project_id: str = "", topic_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/events",
|
||||
{
|
||||
@@ -584,7 +572,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_comments_events(self, project_id="") -> list:
|
||||
def get_comments_events(self, project_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/comments/events",
|
||||
{
|
||||
@@ -592,7 +580,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def get_comment_events(self, project_id="", topic_id="", comment_id="") -> list:
|
||||
def get_comment_events(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> list[Any]:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
|
||||
{
|
||||
|
||||
+269
-825
File diff suppressed because it is too large
Load Diff
@@ -1,200 +0,0 @@
|
||||
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
#
|
||||
# This file is part of BCF.
|
||||
#
|
||||
# BCF is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BCF is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class Project:
|
||||
def __init__(self):
|
||||
self.project_id = ""
|
||||
self.name = ""
|
||||
|
||||
|
||||
class BimSnippet:
|
||||
def __init__(self):
|
||||
self.snippet_type = None
|
||||
self.is_external = False
|
||||
self.reference = None
|
||||
self.reference_schema = None
|
||||
|
||||
|
||||
class DocumentReference:
|
||||
def __init__(self):
|
||||
self.description = None
|
||||
self.document_guid = None
|
||||
self.url = None
|
||||
self.guid = None
|
||||
|
||||
|
||||
class RelatedTopic:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
|
||||
|
||||
class HeaderFile:
|
||||
def __init__(self):
|
||||
self.filename = ""
|
||||
self.date = None
|
||||
self.reference = ""
|
||||
self.ifc_project = None
|
||||
self.ifc_spatial_structure_element = None
|
||||
self.is_external = True
|
||||
|
||||
|
||||
class Header:
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
|
||||
|
||||
class Topic:
|
||||
def __init__(self):
|
||||
self.reference_links = []
|
||||
self.title = ""
|
||||
self.priority = None
|
||||
self.index = None # Deprecated, stored, but ignored
|
||||
self.labels = []
|
||||
self.creation_date = None
|
||||
self.creation_author = None
|
||||
self.modified_date = None
|
||||
self.modified_author = None
|
||||
self.due_date = None
|
||||
self.assigned_to = None
|
||||
self.stage = None
|
||||
self.description = None
|
||||
self.bim_snippet = None
|
||||
self.document_references = []
|
||||
self.related_topics = []
|
||||
self.topic_status = None
|
||||
self.topic_type = None
|
||||
self.guid = None
|
||||
|
||||
self.header = None
|
||||
self.comments = {}
|
||||
self.viewpoints = {}
|
||||
self.server_assigned_id = ""
|
||||
|
||||
|
||||
class Comment:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
self.date = None
|
||||
self.author = ""
|
||||
self.comment = ""
|
||||
self.viewpoint = None
|
||||
self.modified_date = None
|
||||
self.modified_author = ""
|
||||
|
||||
|
||||
class ViewSetupHints:
|
||||
def __init__(self):
|
||||
self.spaces_visible = False
|
||||
self.space_boundaries_visible = False
|
||||
self.openings_visible = False
|
||||
|
||||
|
||||
class Component:
|
||||
def __init__(self):
|
||||
self.originating_system = None
|
||||
self.authoring_tool_id = None
|
||||
self.ifc_guid = None
|
||||
|
||||
|
||||
class ComponentVisibility:
|
||||
def __init__(self):
|
||||
self.exceptions = []
|
||||
self.default_visibility = False
|
||||
self.view_setup_hints = None
|
||||
|
||||
|
||||
class Color:
|
||||
def __init__(self):
|
||||
self.color = None
|
||||
self.components = []
|
||||
|
||||
|
||||
class Components:
|
||||
def __init__(self):
|
||||
|
||||
self.selection = []
|
||||
self.visibility = None
|
||||
self.coloring = []
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.z = 0
|
||||
|
||||
|
||||
class Direction(Point):
|
||||
pass
|
||||
|
||||
|
||||
class OrthogonalCamera:
|
||||
def __init__(self):
|
||||
self.camera_view_point = Point()
|
||||
self.camera_direction = Direction()
|
||||
self.camera_up_vector = Direction()
|
||||
self.view_to_world_scale = 1.0
|
||||
self.aspect_ratio = 1.0
|
||||
|
||||
|
||||
class PerspectiveCamera:
|
||||
def __init__(self):
|
||||
self.camera_view_point = Point()
|
||||
self.camera_direction = Direction()
|
||||
self.camera_up_vector = Direction()
|
||||
self.field_of_view = 60.0
|
||||
self.aspect_ratio = 1.0
|
||||
|
||||
|
||||
class Line:
|
||||
def __init__(self):
|
||||
self.start_point = Point()
|
||||
self.end_point = Point()
|
||||
|
||||
|
||||
class ClippingPlane:
|
||||
def __init__(self):
|
||||
self.location = Point()
|
||||
self.direction = Direction()
|
||||
|
||||
|
||||
class Bitmap:
|
||||
def __init__(self):
|
||||
self.reference = "" # Only in BCF-XML
|
||||
self.bitmap_data = None # Only in BCF-API
|
||||
self.bitmap_format = "PNG" # Enum of png or jpg
|
||||
self.location = Point()
|
||||
self.normal = Direction()
|
||||
self.up = Direction()
|
||||
self.height = 1.0
|
||||
|
||||
|
||||
class Viewpoint:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
self.viewpoint = None
|
||||
self.snapshot = None
|
||||
self.index = None
|
||||
|
||||
self.components = None # It's not a list, despite the plural name
|
||||
self.orthogonal_camera = None
|
||||
self.perspective_camera = None
|
||||
self.lines = []
|
||||
self.clipping_planes = []
|
||||
self.bitmaps = []
|
||||
@@ -0,0 +1,61 @@
|
||||
"""BCF XML V3 Documents handler."""
|
||||
import zipfile
|
||||
from typing import Any, Optional
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
class DocumentsHandler:
|
||||
"""BCF documents handler."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
definition: mdl.DocumentInfo,
|
||||
documents: Optional[dict[str, bytes]] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self.definition = definition
|
||||
self.documents = documents or {}
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
|
||||
@classmethod
|
||||
def load(
|
||||
cls,
|
||||
zip_file: zipfile.ZipFile,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> Optional["DocumentsHandler"]:
|
||||
"""
|
||||
Loads the documents from the given zip file directory.
|
||||
|
||||
Args:
|
||||
zip_path: The directory path inside the zip file.
|
||||
xml_handler: The xml parser/serializer to use.
|
||||
|
||||
Returns:
|
||||
The documents handler.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
file_to_open = zipfile.Path(zip_file, "documents.xml")
|
||||
if not file_to_open.exists():
|
||||
return None
|
||||
definition = xml_handler.parse(file_to_open.read_bytes(), mdl.DocumentInfo)
|
||||
documents = {}
|
||||
if def_docs := definition.documents:
|
||||
for document in def_docs.document:
|
||||
document_path = zipfile.Path(zip_file, f"documents/{document.guid}")
|
||||
if document_path.exists():
|
||||
documents[document.filename] = document_path.read_bytes()
|
||||
return cls(definition, documents=documents)
|
||||
|
||||
def save(self, bcf_zip: ZipFileInterface) -> None:
|
||||
"""Save the documents to the zip file."""
|
||||
bcf_zip.writestr("documents.xml", self._xml_handler.serialize(self.definition))
|
||||
if documents := self.definition.documents:
|
||||
for doc in documents.document:
|
||||
if doc.filename in self.documents:
|
||||
bcf_zip.writestr(
|
||||
f"documents/{doc.guid}",
|
||||
self.documents[doc.filename],
|
||||
)
|
||||
@@ -0,0 +1,110 @@
|
||||
from bcf.v3.model.documents import Document, DocumentInfo, DocumentInfoDocuments
|
||||
from bcf.v3.model.extensions import (
|
||||
Extensions,
|
||||
ExtensionsPriorities,
|
||||
ExtensionsSnippetTypes,
|
||||
ExtensionsStages,
|
||||
ExtensionsTopicLabels,
|
||||
ExtensionsTopicStatuses,
|
||||
ExtensionsTopicTypes,
|
||||
ExtensionsUsers,
|
||||
)
|
||||
from bcf.v3.model.markup import (
|
||||
BimSnippet,
|
||||
Comment,
|
||||
CommentViewpoint,
|
||||
DocumentReference,
|
||||
File,
|
||||
Header,
|
||||
HeaderFiles,
|
||||
Markup,
|
||||
Topic,
|
||||
TopicComments,
|
||||
TopicDocumentReferences,
|
||||
TopicLabels,
|
||||
TopicReferenceLinks,
|
||||
TopicRelatedTopics,
|
||||
TopicRelatedTopicsRelatedTopic,
|
||||
TopicViewpoints,
|
||||
ViewPoint,
|
||||
)
|
||||
from bcf.v3.model.project import Project, ProjectInfo
|
||||
from bcf.v3.model.version import Version
|
||||
from bcf.v3.model.visinfo import (
|
||||
Bitmap,
|
||||
BitmapFormat,
|
||||
ClippingPlane,
|
||||
Component,
|
||||
ComponentColoring,
|
||||
ComponentColoringColor,
|
||||
ComponentColoringColorComponents,
|
||||
Components,
|
||||
ComponentSelection,
|
||||
ComponentVisibility,
|
||||
ComponentVisibilityExceptions,
|
||||
Direction,
|
||||
Line,
|
||||
OrthogonalCamera,
|
||||
PerspectiveCamera,
|
||||
Point,
|
||||
ViewSetupHints,
|
||||
VisualizationInfo,
|
||||
VisualizationInfoBitmaps,
|
||||
VisualizationInfoClippingPlanes,
|
||||
VisualizationInfoLines,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Document",
|
||||
"DocumentInfo",
|
||||
"DocumentInfoDocuments",
|
||||
"Extensions",
|
||||
"ExtensionsPriorities",
|
||||
"ExtensionsSnippetTypes",
|
||||
"ExtensionsStages",
|
||||
"ExtensionsTopicLabels",
|
||||
"ExtensionsTopicStatuses",
|
||||
"ExtensionsTopicTypes",
|
||||
"ExtensionsUsers",
|
||||
"BimSnippet",
|
||||
"Comment",
|
||||
"CommentViewpoint",
|
||||
"DocumentReference",
|
||||
"File",
|
||||
"Header",
|
||||
"HeaderFiles",
|
||||
"Markup",
|
||||
"Topic",
|
||||
"TopicComments",
|
||||
"TopicDocumentReferences",
|
||||
"TopicLabels",
|
||||
"TopicReferenceLinks",
|
||||
"TopicRelatedTopics",
|
||||
"TopicRelatedTopicsRelatedTopic",
|
||||
"TopicViewpoints",
|
||||
"ViewPoint",
|
||||
"Project",
|
||||
"ProjectInfo",
|
||||
"Version",
|
||||
"Bitmap",
|
||||
"BitmapFormat",
|
||||
"ClippingPlane",
|
||||
"Component",
|
||||
"ComponentColoring",
|
||||
"ComponentColoringColor",
|
||||
"ComponentColoringColorComponents",
|
||||
"ComponentSelection",
|
||||
"ComponentVisibility",
|
||||
"ComponentVisibilityExceptions",
|
||||
"Components",
|
||||
"Direction",
|
||||
"Line",
|
||||
"OrthogonalCamera",
|
||||
"PerspectiveCamera",
|
||||
"Point",
|
||||
"ViewSetupHints",
|
||||
"VisualizationInfo",
|
||||
"VisualizationInfoBitmaps",
|
||||
"VisualizationInfoClippingPlanes",
|
||||
"VisualizationInfoLines",
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Document:
|
||||
filename: str = field(
|
||||
metadata={
|
||||
"name": "Filename",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
description: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Description",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentInfoDocuments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
document: List[Document] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Document",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentInfo:
|
||||
documents: Optional[DocumentInfoDocuments] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Documents",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,181 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, 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": "User",
|
||||
"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": "",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,616 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
reference_schema: str = field(
|
||||
metadata={
|
||||
"name": "ReferenceSchema",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
snippet_type: str = field(
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
is_external: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "IsExternal",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentReference:
|
||||
document_guid: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DocumentGuid",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
url: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Url",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
description: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Description",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class File:
|
||||
filename: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Filename",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Date",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
ifc_project: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IfcProject",
|
||||
"type": "Attribute",
|
||||
"length": 22,
|
||||
"pattern": r"[0-9A-Za-z_$]*",
|
||||
}
|
||||
)
|
||||
ifc_spatial_structure_element: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IfcSpatialStructureElement",
|
||||
"type": "Attribute",
|
||||
"length": 22,
|
||||
"pattern": r"[0-9A-Za-z_$]*",
|
||||
}
|
||||
)
|
||||
is_external: bool = field(
|
||||
default=True,
|
||||
metadata={
|
||||
"name": "IsExternal",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
label: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Label",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicReferenceLinks:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
reference_link: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ReferenceLink",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopicsRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Viewpoint",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
snapshot: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Snapshot",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
index: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Index",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
"name": "Date",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
author: str = field(
|
||||
metadata={
|
||||
"name": "Author",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
comment: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
viewpoint: Optional[CommentViewpoint] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Viewpoint",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
modified_date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
modified_author: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedAuthor",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class HeaderFiles:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
file: List[File] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "File",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicDocumentReferences:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
document_reference: List[DocumentReference] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DocumentReference",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopics:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
related_topic: List[TopicRelatedTopicsRelatedTopic] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "RelatedTopic",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicViewpoints:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
view_point: List[ViewPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ViewPoint",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Header:
|
||||
files: Optional[HeaderFiles] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Files",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicComments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
comment: List[Comment] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Topic:
|
||||
reference_links: Optional[TopicReferenceLinks] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ReferenceLinks",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
title: str = field(
|
||||
metadata={
|
||||
"name": "Title",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
priority: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
index: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Index",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
labels: Optional[TopicLabels] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Labels",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
creation_date: XmlDateTime = field(
|
||||
metadata={
|
||||
"name": "CreationDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
creation_author: str = field(
|
||||
metadata={
|
||||
"name": "CreationAuthor",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
modified_date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
modified_author: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ModifiedAuthor",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
due_date: Optional[XmlDateTime] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DueDate",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
assigned_to: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "AssignedTo",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
stage: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
description: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Description",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
bim_snippet: Optional[BimSnippet] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "BimSnippet",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
document_references: Optional[TopicDocumentReferences] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DocumentReferences",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
related_topics: Optional[TopicRelatedTopics] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "RelatedTopics",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
comments: Optional[TopicComments] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Comments",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
viewpoints: Optional[TopicViewpoints] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Viewpoints",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
server_assigned_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ServerAssignedId",
|
||||
"type": "Attribute",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
topic_type: str = field(
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
topic_status: str = field(
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Header",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
}
|
||||
)
|
||||
topic: Topic = field(
|
||||
metadata={
|
||||
"name": "Topic",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Name",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
project_id: str = field(
|
||||
metadata={
|
||||
"name": "ProjectId",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectInfo:
|
||||
project: Project = field(
|
||||
metadata={
|
||||
"name": "Project",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Version:
|
||||
version_id: str = field(
|
||||
metadata={
|
||||
"name": "VersionId",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,524 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
PNG = "png"
|
||||
JPG = "jpg"
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OriginatingSystem",
|
||||
"type": "Element",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
authoring_tool_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "AuthoringToolId",
|
||||
"type": "Element",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
ifc_guid: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IfcGuid",
|
||||
"type": "Attribute",
|
||||
"length": 22,
|
||||
"pattern": r"[0-9A-Za-z_$]*",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
"name": "X",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
y: float = field(
|
||||
metadata={
|
||||
"name": "Y",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
z: float = field(
|
||||
metadata={
|
||||
"name": "Z",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
"name": "X",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
y: float = field(
|
||||
metadata={
|
||||
"name": "Y",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
z: float = field(
|
||||
metadata={
|
||||
"name": "Z",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "SpacesVisible",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
space_boundaries_visible: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "SpaceBoundariesVisible",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
openings_visible: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "OpeningsVisible",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Bitmap:
|
||||
format: BitmapFormat = field(
|
||||
metadata={
|
||||
"name": "Format",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
reference: str = field(
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
}
|
||||
)
|
||||
location: Point = field(
|
||||
metadata={
|
||||
"name": "Location",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
normal: Direction = field(
|
||||
metadata={
|
||||
"name": "Normal",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
up: Direction = field(
|
||||
metadata={
|
||||
"name": "Up",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
height: float = field(
|
||||
metadata={
|
||||
"name": "Height",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
"name": "Location",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
direction: Direction = field(
|
||||
metadata={
|
||||
"name": "Direction",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColorComponents:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentSelection:
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
"name": "StartPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
end_point: Point = field(
|
||||
metadata={
|
||||
"name": "EndPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
camera_view_point:
|
||||
camera_direction:
|
||||
camera_up_vector:
|
||||
view_to_world_scale: view's visible vertical size in meters
|
||||
aspect_ratio: Proportional relationship between the width and
|
||||
the height of the view (w/h).
|
||||
"""
|
||||
camera_view_point: Point = field(
|
||||
metadata={
|
||||
"name": "CameraViewPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_direction: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraDirection",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_up_vector: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraUpVector",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
view_to_world_scale: float = field(
|
||||
metadata={
|
||||
"name": "ViewToWorldScale",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
aspect_ratio: float = field(
|
||||
metadata={
|
||||
"name": "AspectRatio",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
"min_exclusive": 0.0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
camera_view_point:
|
||||
camera_direction:
|
||||
camera_up_vector:
|
||||
field_of_view: Vertical field of view, in degrees. It is
|
||||
currently limited to a value between 45 and 60 degrees. This
|
||||
limitation will be dropped in the next release and viewers
|
||||
should be expect values outside this range in current
|
||||
implementations.
|
||||
aspect_ratio: Proportional relationship between the width and
|
||||
the height of the view (w/h).
|
||||
"""
|
||||
camera_view_point: Point = field(
|
||||
metadata={
|
||||
"name": "CameraViewPoint",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_direction: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraDirection",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
camera_up_vector: Direction = field(
|
||||
metadata={
|
||||
"name": "CameraUpVector",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
field_of_view: float = field(
|
||||
metadata={
|
||||
"name": "FieldOfView",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
"min_exclusive": 0.0,
|
||||
"max_exclusive": 180.0,
|
||||
}
|
||||
)
|
||||
aspect_ratio: float = field(
|
||||
metadata={
|
||||
"name": "AspectRatio",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
"min_exclusive": 0.0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
components: ComponentColoringColorComponents = field(
|
||||
metadata={
|
||||
"name": "Components",
|
||||
"type": "Element",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
color: str = field(
|
||||
metadata={
|
||||
"name": "Color",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibility:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ViewSetupHints",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
exceptions: Optional[ComponentVisibilityExceptions] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Exceptions",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
default_visibility: bool = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"name": "DefaultVisibility",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoBitmaps:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
bitmap: List[Bitmap] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
clipping_plane: List[ClippingPlane] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ClippingPlane",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
line: List[Line] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Line",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoring:
|
||||
color: List[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Components:
|
||||
selection: Optional[ComponentSelection] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Selection",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
visibility: Optional[ComponentVisibility] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Visibility",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
coloring: Optional[ComponentColoring] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Coloring",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
"""
|
||||
components: Optional[Components] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Components",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
orthogonal_camera: Optional[OrthogonalCamera] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OrthogonalCamera",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
perspective_camera: Optional[PerspectiveCamera] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "PerspectiveCamera",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
lines: Optional[VisualizationInfoLines] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Lines",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ClippingPlanes",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
bitmaps: Optional[VisualizationInfoBitmaps] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Bitmaps",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
guid: str = field(
|
||||
metadata={
|
||||
"name": "Guid",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,206 @@
|
||||
"""BCF XML V3 Topic handler."""
|
||||
import datetime
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from numpy.typing import NDArray
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.v3.visinfo import VisualizationInfoHandler
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
class TopicHandler:
|
||||
"""BCF Topic and related objects handler."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
topic_dir: Optional[zipfile.Path] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self._markup: Optional[mdl.Markup] = None
|
||||
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
|
||||
self._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
|
||||
@property
|
||||
def markup(self) -> Optional[mdl.Markup]:
|
||||
if not self._markup and self._topic_dir:
|
||||
markup_path = self._topic_dir.joinpath("markup.bcf")
|
||||
if markup_path.exists():
|
||||
self._markup = self._xml_handler.parse(markup_path.read_bytes(), mdl.Markup)
|
||||
return self._markup
|
||||
|
||||
@markup.setter
|
||||
def markup(self, value: mdl.Markup) -> None:
|
||||
self._markup = value
|
||||
|
||||
@property
|
||||
def topic(self) -> mdl.Topic:
|
||||
"""Return the Topic object."""
|
||||
return self.markup.topic
|
||||
|
||||
@property
|
||||
def guid(self) -> Optional[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
|
||||
|
||||
@property
|
||||
def header(self) -> Optional[mdl.Header]:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.topic.comments.comment if self.topic.comments else []
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
self._bim_snippet = self._load_bim_snippet()
|
||||
return self._bim_snippet
|
||||
|
||||
@bim_snippet.setter
|
||||
def bim_snippet(self, value: bytes) -> None:
|
||||
self._bim_snippet = value
|
||||
|
||||
@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)
|
||||
return self._viewpoints
|
||||
|
||||
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:
|
||||
bim_snippet_path = self._topic_dir.joinpath(bim_snippet_obj.reference)
|
||||
if bim_snippet_path.exists():
|
||||
return bim_snippet_path.read_bytes()
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
title: str,
|
||||
description: str,
|
||||
author: str,
|
||||
topic_type: str = "",
|
||||
topic_status: str = "",
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "TopicHandler":
|
||||
"""
|
||||
Create a new BCF topic.
|
||||
|
||||
Args:
|
||||
title: The title of the topic.
|
||||
description: The description of the topic.
|
||||
author: The author of the topic.
|
||||
topic_type: The type of the topic.
|
||||
topic_status: The status of the topic.
|
||||
xml_handler: The XML parser/serializer to use.
|
||||
|
||||
Returns:
|
||||
The BCF topic definition.
|
||||
"""
|
||||
creation_date = XmlDateTime.from_datetime(datetime.datetime.now())
|
||||
guid = str(uuid.uuid4())
|
||||
topic = mdl.Topic(
|
||||
title=title,
|
||||
description=description,
|
||||
creation_author=author,
|
||||
creation_date=creation_date,
|
||||
guid=guid,
|
||||
topic_type=topic_type,
|
||||
topic_status=topic_status,
|
||||
)
|
||||
markup = mdl.Markup(topic=topic)
|
||||
obj = cls(topic_dir=Path(guid), xml_handler=xml_handler or XmlParserSerializer())
|
||||
obj.markup = markup
|
||||
return obj
|
||||
|
||||
def save(self, destination_zip: ZipFileInterface) -> None:
|
||||
"""
|
||||
Save the topic to a BCF zip file.
|
||||
|
||||
Args:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(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):
|
||||
return
|
||||
for vpt in viewpoints:
|
||||
if vpt.viewpoint:
|
||||
self.viewpoints[vpt.viewpoint].save(destination_zip, topic_dir, vpt)
|
||||
|
||||
def _save_xml(self, destination_zip: ZipFileInterface, item: Any, target: str) -> None:
|
||||
to_write = self._xml_handler.serialize(item) if item else self._topic_dir.joinpath(target).read_bytes()
|
||||
destination_zip.writestr(f"{self._topic_dir.name}/{target}", to_write)
|
||||
|
||||
def _save_bim_snippet(self, destination_zip: ZipFileInterface) -> None:
|
||||
snippet = self.topic.bim_snippet
|
||||
if not snippet or snippet.is_external:
|
||||
return
|
||||
ref_filename = Path(snippet.reference).name
|
||||
if self.bim_snippet:
|
||||
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
"""
|
||||
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
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)
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
(
|
||||
self.markup == other.markup
|
||||
and self.viewpoints == other.viewpoints
|
||||
and self.bim_snippet == other.bim_snippet
|
||||
)
|
||||
if isinstance(other, TopicHandler)
|
||||
else NotImplemented
|
||||
)
|
||||
@@ -0,0 +1,294 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import lru_cache
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.util import placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.geometry import (
|
||||
camera_vectors_from_element_placement,
|
||||
camera_vectors_from_target_position,
|
||||
)
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
class VisualizationInfoHandler:
|
||||
"""Handle the VisualizationInfo and related objects."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
visualization_info: mdl.VisualizationInfo,
|
||||
snapshot: Optional[bytes] = None,
|
||||
bitmaps: Optional[dict[str, bytes]] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self.visualization_info = visualization_info
|
||||
self.snapshot = snapshot
|
||||
self.bitmaps = bitmaps or {}
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
|
||||
@property
|
||||
def guid(self) -> str:
|
||||
"""Return the GUID of the visualization info."""
|
||||
return self.visualization_info.guid
|
||||
|
||||
@classmethod
|
||||
def from_topic_viewpoints(
|
||||
cls,
|
||||
topic_dir: zipfile.Path,
|
||||
vps: Iterable[mdl.ViewPoint],
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> dict[str, "VisualizationInfoHandler"]:
|
||||
"""Create VisualizationInfoHandler objects of a Topic's ViewPoints."""
|
||||
viewpoints = {}
|
||||
for vpt in vps:
|
||||
visinfo = cls.load(topic_dir, vpt, xml_handler)
|
||||
if visinfo and vpt.viewpoint:
|
||||
viewpoints[vpt.viewpoint] = visinfo
|
||||
return viewpoints
|
||||
|
||||
@classmethod
|
||||
def load(
|
||||
cls,
|
||||
topic_dir: zipfile.Path,
|
||||
vpt: mdl.ViewPoint,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> Optional["VisualizationInfoHandler"]:
|
||||
"""
|
||||
Load the VisualizationInfo and related objects from a BCF zip file.
|
||||
|
||||
Args:
|
||||
topic_dir: The directory in the BCF zip file to load from.
|
||||
vpt: The ViewPoint to load.
|
||||
xml_handler: The XML handler to use to parse the VisualizationInfo.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
visinfo = cls._load_visinfo(topic_dir, vpt.viewpoint, xml_handler)
|
||||
if not visinfo:
|
||||
return None
|
||||
snapshot = cls._load_snapshot(topic_dir, vpt.snapshot)
|
||||
bitmaps = cls._load_bitmaps(topic_dir, visinfo)
|
||||
return cls(visinfo, snapshot, bitmaps, xml_handler)
|
||||
|
||||
@staticmethod
|
||||
def _load_visinfo(
|
||||
topic_dir: zipfile.Path,
|
||||
vp_name: Optional[str],
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> Optional[mdl.VisualizationInfo]:
|
||||
if not vp_name:
|
||||
return None
|
||||
vp_path = topic_dir.joinpath(vp_name)
|
||||
if vp_path.exists():
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return xml_handler.parse(vp_path.read_bytes(), mdl.VisualizationInfo)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _load_snapshot(topic_dir: zipfile.Path, vp_snapshot: Optional[str]) -> Optional[bytes]:
|
||||
if vp_snapshot:
|
||||
snapshot_path = topic_dir.joinpath(vp_snapshot)
|
||||
if snapshot_path.exists():
|
||||
return snapshot_path.read_bytes()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _load_bitmaps(topic_dir: zipfile.Path, visinfo: Optional[mdl.VisualizationInfo]) -> dict[str, bytes]:
|
||||
if not visinfo or not (bitmaps := visinfo.bitmaps):
|
||||
return {}
|
||||
bitmaps_dict = {}
|
||||
for bitmap in bitmaps.bitmap:
|
||||
if not bitmap.reference:
|
||||
continue
|
||||
bitmap_path = topic_dir.joinpath(bitmap.reference)
|
||||
if bitmap_path.exists():
|
||||
bitmaps_dict[bitmap.reference] = bitmap_path.read_bytes()
|
||||
return bitmaps_dict
|
||||
|
||||
def save(
|
||||
self,
|
||||
bcf_zip: ZipFileInterface,
|
||||
topic_dir: str,
|
||||
vpt: mdl.ViewPoint,
|
||||
) -> None:
|
||||
"""
|
||||
Save the VisualizationInfo and related objects to a BCF zip file.
|
||||
|
||||
Args:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
topic_dir: The directory in the BCF zip file to save to.
|
||||
vpt: The ViewPoint to save.
|
||||
"""
|
||||
if not (vp_name := vpt.viewpoint):
|
||||
return
|
||||
self._save_visinfo(bcf_zip, topic_dir, vp_name)
|
||||
self._save_snapshot(bcf_zip, topic_dir, vpt.snapshot)
|
||||
self._save_bitmaps(bcf_zip, topic_dir)
|
||||
|
||||
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
|
||||
if self.snapshot and filename:
|
||||
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
|
||||
|
||||
def _save_visinfo(self, bcf_zip: ZipFileInterface, topic_dir: str, vp_name: str) -> None:
|
||||
bcf_zip.writestr(
|
||||
f"{topic_dir}/{vp_name}",
|
||||
self._xml_handler.serialize(self.visualization_info),
|
||||
)
|
||||
|
||||
def _save_bitmaps(self, bcf_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.bitmaps:
|
||||
return
|
||||
if not (bitmaps_defs := self.visualization_info.bitmaps):
|
||||
return
|
||||
for bitmap_def in bitmaps_defs.bitmap:
|
||||
if not (bitmap_name := bitmap_def.reference):
|
||||
continue
|
||||
if bitmap_name in self.bitmaps:
|
||||
bcf_zip.writestr(f"{topic_dir}/{bitmap_name}", self.bitmaps[bitmap_name])
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
element: entity_instance,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
"""
|
||||
Create a new VisualizationInfoHandler object from an IFC element.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
xml_handler: The XML handler to use.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
||||
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
"""
|
||||
Create a new VisualizationInfoHandler object from an IFC element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
xml_handler: The XML handler to use.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
elem_placement = placement.get_local_placement(element.ObjectPlacement)
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(element.GlobalId),
|
||||
perspective_camera=build_camera(elem_placement),
|
||||
)
|
||||
|
||||
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> 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.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(*guids),
|
||||
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
|
||||
)
|
||||
|
||||
|
||||
def build_components(*guids: str) -> mdl.Components:
|
||||
"""
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
"""
|
||||
components = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
return mdl.Components(
|
||||
selection=mdl.ComponentSelection(component=components),
|
||||
visibility=mdl.ComponentVisibility(default_visibility=True),
|
||||
)
|
||||
|
||||
|
||||
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
Args:
|
||||
elem_placement: The IFC element placement as a rototranslation matrix.
|
||||
|
||||
Returns:
|
||||
The BCF camera definition.
|
||||
"""
|
||||
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
Args:
|
||||
camera_position: camera position array
|
||||
camera_dir: camera direction versor
|
||||
camera_up_vector: camera up versor
|
||||
|
||||
Returns:
|
||||
The BCF camera definition.
|
||||
"""
|
||||
camera_viewpoint = mdl.Point(x=camera_position[0], y=camera_position[1], z=camera_position[2])
|
||||
camera_direction = mdl.Direction(x=camera_dir[0], y=camera_dir[1], z=camera_dir[2])
|
||||
camera_up_vector = mdl.Direction(x=camera_up[0], y=camera_up[1], z=camera_up[2])
|
||||
return mdl.PerspectiveCamera(
|
||||
camera_view_point=camera_viewpoint,
|
||||
camera_direction=camera_direction,
|
||||
camera_up_vector=camera_up_vector,
|
||||
aspect_ratio=1.0,
|
||||
field_of_view=60.0,
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
"""XML Parser and Serializer factories."""
|
||||
from typing import Optional, Protocol, Type, TypeVar
|
||||
|
||||
from xsdata.formats.dataclass.context import XmlContext
|
||||
from xsdata.formats.dataclass.parsers import XmlParser
|
||||
from xsdata.formats.dataclass.serializers import XmlSerializer
|
||||
from xsdata.formats.dataclass.serializers.config import SerializerConfig
|
||||
|
||||
|
||||
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
|
||||
"""Return a parser for an XML file."""
|
||||
parser = XmlParser(context=context or XmlContext())
|
||||
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
return parser
|
||||
|
||||
|
||||
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
|
||||
"""Return a serializer for an XML file."""
|
||||
return XmlSerializer(
|
||||
config=SerializerConfig(pretty_print=True),
|
||||
context=context or XmlContext(),
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class AbstractXmlParserSerializer(Protocol):
|
||||
"""XML Parser and serializer wrapper."""
|
||||
|
||||
def parse(self, xml: bytes, clazz: Type[T]) -> T:
|
||||
"""
|
||||
Parse an XML file to an object.
|
||||
|
||||
Args:
|
||||
xml: The XML file as bytes.
|
||||
clazz: The class to parse to.
|
||||
"""
|
||||
|
||||
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
"""
|
||||
Serialize an object to XML.
|
||||
|
||||
Args:
|
||||
obj: The object to serialize.
|
||||
ns_map: The namespace map to use.
|
||||
|
||||
Returns:
|
||||
The XML as string.
|
||||
"""
|
||||
|
||||
|
||||
class XmlParserSerializer:
|
||||
"""XML Parser and serializer wrapper."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.context = XmlContext()
|
||||
self.parser = build_xml_parser(self.context)
|
||||
self.serializer = build_serializer(self.context)
|
||||
|
||||
def parse(self, xml: bytes, clazz: Type[T]) -> T:
|
||||
"""
|
||||
Parse an XML file to an object.
|
||||
|
||||
Args:
|
||||
xml: The XML file as bytes.
|
||||
clazz: The class to parse to.
|
||||
"""
|
||||
return self.parser.from_bytes(xml, clazz)
|
||||
|
||||
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
"""
|
||||
Serialize an object to XML.
|
||||
|
||||
Args:
|
||||
obj: The object to serialize.
|
||||
ns_map: The namespace map to use.
|
||||
|
||||
Returns:
|
||||
The XML as string.
|
||||
"""
|
||||
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
|
||||
return self.serializer.render(obj, ns_map)
|
||||
@@ -0,0 +1 @@
|
||||
"""BCF tests."""
|
||||
@@ -0,0 +1,8 @@
|
||||
import pytest
|
||||
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def xml_handler() -> XmlParserSerializer:
|
||||
return XmlParserSerializer()
|
||||
Binary file not shown.
@@ -0,0 +1,126 @@
|
||||
"""BCF XML tests."""
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
from bcf.v2.topic import TopicHandler
|
||||
from bcf.v2.visinfo import (
|
||||
VisualizationInfoHandler,
|
||||
build_camera_from_vectors,
|
||||
build_components,
|
||||
)
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
|
||||
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
|
||||
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
|
||||
return bcf, orig_th
|
||||
|
||||
|
||||
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
assert parsed == bcf
|
||||
parsed_th = parsed.topics[orig_th.guid]
|
||||
assert parsed_th == orig_th
|
||||
|
||||
|
||||
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
th.topic.title = "New Topic Title"
|
||||
modified_path = Path(tmp_dir) / "edited.bcf"
|
||||
parsed.save(modified_path)
|
||||
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
|
||||
|
||||
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
|
||||
assert modified_parsed == bcf
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
|
||||
|
||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
th.topic.title = "New Topic Title"
|
||||
parsed.save()
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
assert len(modified_parsed.topics) == 1
|
||||
|
||||
|
||||
def test_save_no_filename(build_sample) -> None:
|
||||
bcf, _ = build_sample
|
||||
with pytest.raises(ValueError):
|
||||
bcf.save()
|
||||
|
||||
|
||||
def test_load_no_filename() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
BcfXml.load("")
|
||||
|
||||
|
||||
def test_save_keep_open(build_sample) -> None:
|
||||
bcf, _ = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path, keep_open=True)
|
||||
assert bcf._zip_file is not None
|
||||
bcf._zip_file.close()
|
||||
|
||||
|
||||
def test_massive_bcf(xml_handler) -> None:
|
||||
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
|
||||
for i in range(100):
|
||||
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
|
||||
vi = mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(str(uuid.uuid4())),
|
||||
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
|
||||
)
|
||||
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
|
||||
th.add_visinfo_handler(vh)
|
||||
assert len(bcf.topics) == 100
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
|
||||
|
||||
def test_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[0] != "Wrong object"
|
||||
|
||||
|
||||
def test_topic_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[1] != "Wrong object"
|
||||
|
||||
|
||||
def test_bcf_get_set_version(build_sample) -> None:
|
||||
bcf = build_sample[0]
|
||||
assert bcf.version.version_id == "2.1"
|
||||
bcf.version.version_id = "2.0"
|
||||
assert bcf.version.version_id == "2.0"
|
||||
@@ -0,0 +1,365 @@
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
from bcf.v2.topic import TopicHandler
|
||||
|
||||
|
||||
def test_maximum_information() -> None:
|
||||
"""
|
||||
All the info in a BCF is parsed correctly.
|
||||
|
||||
Uses sample file from https://github.com/buildingSMART/BCF-XML/
|
||||
"""
|
||||
bcf_path = Path(__file__).parent / "MaximumInformation.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
assert_everything_in_place(bcf)
|
||||
|
||||
|
||||
def test_save_maximum_information() -> None:
|
||||
base_dir = Path(__file__).parent
|
||||
bcf_path = base_dir / "MaximumInformation.bcf"
|
||||
target_path = base_dir / "MaximumInformationSaved.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
bcf.save(target_path)
|
||||
assert target_path.exists()
|
||||
with BcfXml.load(target_path) as bcf2:
|
||||
assert_everything_in_place(bcf2)
|
||||
|
||||
expected_files = [
|
||||
"bcf.version",
|
||||
"project.bcfp",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/markup.bcf",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/bitmap.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/tux.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/JsonElement.json",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
|
||||
"d1068c81-af04-4546-b63c-348810f6c716/markup.bcf",
|
||||
"extensions.xsd",
|
||||
"IfcPile_01.ifc",
|
||||
"markup.xsd",
|
||||
]
|
||||
assert_files_present(target_path, expected_files)
|
||||
os.unlink(target_path)
|
||||
|
||||
|
||||
def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.version.version_id == "2.1"
|
||||
assert bcf.project.name == "BCF API Implementation"
|
||||
assert bcf.project_info.extension_schema == "extensions.xsd"
|
||||
|
||||
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"]
|
||||
assert second_th.topic == mdl.Topic(
|
||||
title="Referenced topic",
|
||||
creation_date=XmlDateTime(2017, 5, 22, 7, 51, 0, 42987900),
|
||||
creation_author="dangl@iabi.eu",
|
||||
description="This is just an empty topic that acts as a referenced topic.",
|
||||
guid="d1068c81-af04-4546-b63c-348810f6c716",
|
||||
)
|
||||
|
||||
|
||||
def assert_first_topic_handler(topic_handler: TopicHandler):
|
||||
assert topic_handler.guid == "7ddc3ef0-0ab7-43f1-918a-45e38b42369c"
|
||||
|
||||
expected_bs1 = mdl.BimSnippet(
|
||||
reference="JsonElement.json", reference_schema="http://json-schema.org", snippet_type="JSON"
|
||||
)
|
||||
expected_t1 = mdl.Topic(
|
||||
reference_link=["https://bim--it.net"],
|
||||
title="Maximum Content",
|
||||
priority="High",
|
||||
index=0,
|
||||
labels=["Structural", "IT Development"],
|
||||
creation_date=XmlDateTime(2015, 6, 21, 12, 0, 0),
|
||||
creation_author="dangl@iabi.eu",
|
||||
modified_date=XmlDateTime(2015, 6, 21, 14, 22, 47),
|
||||
modified_author="dangl@iabi.eu",
|
||||
due_date=XmlDateTime(2016, 10, 2, 14, 22, 47),
|
||||
assigned_to="linhard@iabi.eu",
|
||||
stage="Construction Start",
|
||||
description="This is a topic with all informations present.",
|
||||
bim_snippet=expected_bs1,
|
||||
document_reference=[
|
||||
mdl.TopicDocumentReference(
|
||||
referenced_document="https://github.com/BuildingSMART/BCF-XML",
|
||||
description="GitHub BCF Specification",
|
||||
is_external=True,
|
||||
),
|
||||
mdl.TopicDocumentReference(
|
||||
referenced_document="../markup.xsd",
|
||||
description="Markup.xsd Schema",
|
||||
is_external=False,
|
||||
),
|
||||
],
|
||||
related_topic=[mdl.TopicRelatedTopic(guid="d1068c81-af04-4546-b63c-348810f6c716")],
|
||||
guid="7ddc3ef0-0ab7-43f1-918a-45e38b42369c",
|
||||
topic_type="Structural",
|
||||
topic_status="Open",
|
||||
)
|
||||
assert topic_handler.topic == expected_t1
|
||||
expected_h1 = mdl.Header(
|
||||
file=[
|
||||
mdl.HeaderFile(
|
||||
filename="IfcPile_01.ifc",
|
||||
date=XmlDateTime(2014, 10, 27, 16, 27, 27),
|
||||
reference="../IfcPile_01.ifc",
|
||||
ifc_project="0M6o7Znnv7hxsbWgeu7oQq",
|
||||
ifc_spatial_structure_element="23B$bNeGHFQuMYJzvUX0FD",
|
||||
is_external=False,
|
||||
)
|
||||
]
|
||||
)
|
||||
assert topic_handler.header == expected_h1
|
||||
|
||||
expected_th1_comments = [
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 12, 40, 17),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This is an unmodified topic at the uppermost hierarchical level.\nAll times in the XML are marked as UTC times.",
|
||||
guid="07ccdba0-1736-47e1-807d-67dc6f3addaa",
|
||||
),
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 14, 0, 1),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.",
|
||||
guid="a12766c2-61bc-40b4-ab19-e9f45fd0b0bf",
|
||||
),
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 13, 7, 11),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This comment again is in the highest hierarchy level.\nIt references a viewpoint.",
|
||||
viewpoint=mdl.CommentViewpoint(guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a"),
|
||||
guid="c2bb5bb0-773d-45dd-bdaa-19a216439ed3",
|
||||
),
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 15, 42, 58),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This comment contained some spllng errs.\nHopefully, the modifier did catch them all.",
|
||||
modified_date=XmlDateTime(2015, 8, 31, 16, 7, 11),
|
||||
modified_author="dangl@iabi.eu",
|
||||
guid="0b843a5c-c3bf-41ef-be98-52a9f7bd9790",
|
||||
),
|
||||
]
|
||||
assert topic_handler.comments == expected_th1_comments
|
||||
|
||||
expected_th1_viewpoints = [
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
|
||||
snapshot="Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
|
||||
index=2,
|
||||
guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a",
|
||||
),
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
|
||||
snapshot="Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
|
||||
index=0,
|
||||
guid="fc4019d7-365e-47f3-b6d0-b39fc48f15fc",
|
||||
),
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
|
||||
snapshot="Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
|
||||
index=1,
|
||||
guid="9a4a1878-ecbd-4916-83a8-dad82e560231",
|
||||
),
|
||||
]
|
||||
|
||||
expected_m1 = mdl.Markup(
|
||||
header=expected_h1,
|
||||
topic=expected_t1,
|
||||
comment=expected_th1_comments,
|
||||
viewpoints=expected_th1_viewpoints,
|
||||
)
|
||||
assert topic_handler.markup == expected_m1
|
||||
|
||||
assert json.loads(topic_handler.bim_snippet) == {"Material": "Concrete", "Temperatures": ["Cold", "Hot", "Hotter"]}
|
||||
|
||||
assert topic_handler.reference_files["../IfcPile_01.ifc"] is not None
|
||||
|
||||
assert_viewpoints(topic_handler.viewpoints)
|
||||
|
||||
|
||||
def assert_viewpoints(viewpoints):
|
||||
assert len(viewpoints) == 3
|
||||
|
||||
expected_selection = mdl.ComponentSelection(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
|
||||
mdl.Component(ifc_guid="1jQQiGIAnFzxOUzrdmJYDS"),
|
||||
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
|
||||
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
|
||||
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
|
||||
]
|
||||
)
|
||||
|
||||
expected_exception = mdl.ComponentVisibilityExceptions(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="0Gl71cVurFn8bxAOox6M4X"),
|
||||
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
|
||||
mdl.Component(ifc_guid="3DvyPxGIn8qR0KDwbL_9r1"),
|
||||
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
|
||||
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
|
||||
]
|
||||
)
|
||||
|
||||
expected_coloring = mdl.ComponentColoring(
|
||||
color=[
|
||||
mdl.ComponentColoringColor(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
|
||||
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
|
||||
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
|
||||
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
|
||||
],
|
||||
color="3498DB",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
assert_first_viewpoint(
|
||||
viewpoints["Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv"],
|
||||
expected_selection,
|
||||
expected_exception,
|
||||
expected_coloring,
|
||||
)
|
||||
assert_second_viewpoint(
|
||||
viewpoints["Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv"],
|
||||
expected_selection,
|
||||
expected_exception,
|
||||
expected_coloring,
|
||||
)
|
||||
assert_third_viewpoint(
|
||||
viewpoints["Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv"],
|
||||
expected_selection,
|
||||
expected_exception,
|
||||
expected_coloring,
|
||||
)
|
||||
|
||||
|
||||
def assert_first_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=True,
|
||||
space_boundaries_visible=True,
|
||||
openings_visible=True,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=0.43079984188079834, y=69.52057647705078, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.09159398823976517, y=-0.9375035166740417, z=-0.3357048034667969),
|
||||
camera_up_vector=mdl.Direction(x=0.01938679628074169, y=-0.3353792130947113, z=0.9418837428092957),
|
||||
field_of_view=60,
|
||||
),
|
||||
lines=mdl.VisualizationInfoLines(
|
||||
line=[
|
||||
mdl.Line(start_point=mdl.Point(x=0, y=0, z=0), end_point=mdl.Point(x=0, y=0, z=1)),
|
||||
mdl.Line(start_point=mdl.Point(x=0, y=0, z=1), end_point=mdl.Point(x=0, y=1, z=1)),
|
||||
mdl.Line(start_point=mdl.Point(x=0, y=1, z=1), end_point=mdl.Point(x=1, y=1, z=1)),
|
||||
]
|
||||
),
|
||||
clipping_planes=mdl.VisualizationInfoClippingPlanes(
|
||||
clipping_plane=[
|
||||
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=0, z=1)),
|
||||
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=1, z=0)),
|
||||
]
|
||||
),
|
||||
bitmap=[
|
||||
mdl.VisualizationInfoBitmap(
|
||||
bitmap=mdl.BitmapFormat.PNG,
|
||||
reference="bitmap.png",
|
||||
location=mdl.Point(x=10, y=-10, z=7),
|
||||
normal=mdl.Direction(x=0, y=1, z=0),
|
||||
up=mdl.Direction(x=0, y=0, z=1),
|
||||
height=5.0,
|
||||
),
|
||||
mdl.VisualizationInfoBitmap(
|
||||
bitmap=mdl.BitmapFormat.PNG,
|
||||
reference="tux.png",
|
||||
location=mdl.Point(x=20, y=-10, z=7),
|
||||
normal=mdl.Direction(x=0, y=1, z=0),
|
||||
up=mdl.Direction(x=0, y=0, z=1),
|
||||
height=5.0,
|
||||
),
|
||||
],
|
||||
guid="8dc86298-9737-40b4-a448-98a9e953293a",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=False,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=False,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
|
||||
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=True,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
|
||||
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_files_present(saved_bcf_path, expected_files):
|
||||
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
|
||||
for file_path in expected_files:
|
||||
assert zipfile.Path(bcf_zip, file_path).exists()
|
||||
@@ -0,0 +1,36 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
|
||||
|
||||
def test_create_clash_set_bcf() -> None:
|
||||
bcfxml = BcfXml.create_new("Clash Test")
|
||||
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
|
||||
topic.add_viewpoint_from_point_and_guids(
|
||||
np.array([10, 10, 10]),
|
||||
"firstId",
|
||||
"secondId",
|
||||
)
|
||||
assert len(topic.viewpoints) == 1
|
||||
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||
v_info = vi_handler.visualization_info
|
||||
assert f"{v_info.guid}.bcfv" == guid
|
||||
components = v_info.components.selection.component
|
||||
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||
camera = v_info.perspective_camera
|
||||
viewpoint = camera.camera_view_point
|
||||
assert viewpoint.x == 15
|
||||
assert viewpoint.y == 15
|
||||
assert viewpoint.z == 15
|
||||
# default direction is the unit vector of -1, -1, -1
|
||||
direction = camera.camera_direction
|
||||
assert direction.x == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.y == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.z == pytest.approx(-1 / 3**0.5)
|
||||
# default
|
||||
up_vector = camera.camera_up_vector
|
||||
assert up_vector.x == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.y == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
|
||||
assert camera.field_of_view == 60
|
||||
Binary file not shown.
@@ -0,0 +1,121 @@
|
||||
"""BCF XML tests."""
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
from bcf.v3.topic import TopicHandler
|
||||
from bcf.v3.visinfo import (
|
||||
VisualizationInfoHandler,
|
||||
build_camera_from_vectors,
|
||||
build_components,
|
||||
)
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
|
||||
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
|
||||
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
|
||||
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
|
||||
return bcf, orig_th
|
||||
|
||||
|
||||
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
assert parsed == bcf
|
||||
parsed_th = parsed.topics[orig_th.guid]
|
||||
assert parsed_th == orig_th
|
||||
|
||||
|
||||
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
th.topic.title = "New Topic Title"
|
||||
modified_path = Path(tmp_dir) / "edited.bcf"
|
||||
parsed.save(modified_path)
|
||||
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
|
||||
|
||||
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
|
||||
assert modified_parsed == bcf
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
|
||||
|
||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
th.topic.title = "New Topic Title"
|
||||
parsed.save()
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
assert len(modified_parsed.topics) == 1
|
||||
|
||||
|
||||
def test_save_no_filename(build_sample) -> None:
|
||||
bcf, _ = build_sample
|
||||
with pytest.raises(ValueError):
|
||||
bcf.save()
|
||||
|
||||
|
||||
def test_load_no_filename() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
BcfXml.load("")
|
||||
|
||||
|
||||
def test_save_keep_open(build_sample) -> None:
|
||||
bcf, _ = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path, keep_open=True)
|
||||
assert bcf._zip_file is not None
|
||||
bcf._zip_file.close()
|
||||
|
||||
|
||||
def test_massive_bcf(xml_handler) -> None:
|
||||
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
|
||||
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
|
||||
for i in range(100):
|
||||
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
|
||||
vi = mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(str(uuid.uuid4())),
|
||||
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
|
||||
)
|
||||
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
|
||||
th.add_visinfo_handler(vh)
|
||||
assert len(bcf.topics) == 100
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
|
||||
|
||||
def test_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[0] != "Wrong object"
|
||||
|
||||
|
||||
def test_topic_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[1] != "Wrong object"
|
||||
@@ -0,0 +1,230 @@
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
from bcf.v3.topic import TopicHandler
|
||||
|
||||
|
||||
def test_doc_ref_internal() -> None:
|
||||
"""
|
||||
All the info in a BCF is parsed correctly.
|
||||
|
||||
Uses sample file from https://github.com/buildingSMART/BCF-XML/
|
||||
"""
|
||||
bcf_path = Path(__file__).parent / "Document reference internal.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
assert_everything_in_place(bcf)
|
||||
|
||||
|
||||
def test_doc_ref_internal_save() -> None:
|
||||
base_dir = Path(__file__).parent
|
||||
bcf_path = base_dir / "Document reference internal.bcf"
|
||||
target_path = base_dir / "Document reference internal Saved.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
bcf.save(target_path)
|
||||
assert target_path.exists()
|
||||
with BcfXml.load(target_path) as bcf2:
|
||||
assert_everything_in_place(bcf2)
|
||||
assert_files_present(target_path)
|
||||
os.unlink(target_path)
|
||||
|
||||
|
||||
def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.version.version_id == "3.0"
|
||||
assert bcf.project.name == "BCF 3.0 test cases"
|
||||
assert bcf.project.project_id == "de894a86-3a08-4ea0-b2d1-6c222b5602d1"
|
||||
|
||||
assert len(bcf.topics) == 1
|
||||
topic_handler = bcf.topics["8ac9822a-761a-4deb-9f39-f61286acbf6a"]
|
||||
|
||||
expected_h1 = mdl.Header(
|
||||
files=mdl.HeaderFiles(
|
||||
file=[
|
||||
mdl.File(
|
||||
filename="BCF-ARK",
|
||||
date=XmlDateTime(2021, 1, 4, 9, 37, 45),
|
||||
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/007afab57f264d2296aae0a452486ae1",
|
||||
is_external=True,
|
||||
),
|
||||
mdl.File(
|
||||
filename="BCF-MEP",
|
||||
date=XmlDateTime(2017, 8, 7, 9, 51, 34),
|
||||
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/a21ed391f9e046a2bb2bc879c48f1d48",
|
||||
is_external=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
assert topic_handler.header == expected_h1
|
||||
|
||||
expected_comments = [
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
|
||||
author="Architect@example.com",
|
||||
comment="A comment",
|
||||
viewpoint=mdl.CommentViewpoint(guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182"),
|
||||
guid="c045ef04-324d-4c36-9ac8-831f2a15c6d6",
|
||||
),
|
||||
]
|
||||
assert topic_handler.comments == expected_comments
|
||||
|
||||
expected_viewpoints = [
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
|
||||
snapshot="Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
|
||||
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
|
||||
),
|
||||
]
|
||||
|
||||
expected_topic = mdl.Topic(
|
||||
reference_links=mdl.TopicReferenceLinks(),
|
||||
title="Document Reference Internal",
|
||||
labels=mdl.TopicLabels(),
|
||||
creation_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
|
||||
creation_author="Architect@example.com",
|
||||
modified_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
|
||||
assigned_to="OtherUser@doe.com",
|
||||
document_references=mdl.TopicDocumentReferences(
|
||||
document_reference=[
|
||||
mdl.DocumentReference(
|
||||
document_guid="b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
|
||||
description="ThisIsADocument.txt",
|
||||
guid="048e898f-555f-47c3-a273-6c664fb2ef69",
|
||||
),
|
||||
],
|
||||
),
|
||||
related_topics=mdl.TopicRelatedTopics(),
|
||||
comments=mdl.TopicComments(comment=expected_comments),
|
||||
viewpoints=mdl.TopicViewpoints(view_point=expected_viewpoints),
|
||||
guid="8ac9822a-761a-4deb-9f39-f61286acbf6a",
|
||||
server_assigned_id="6",
|
||||
topic_type="ERROR",
|
||||
topic_status="OPEN",
|
||||
)
|
||||
assert topic_handler.topic == expected_topic
|
||||
|
||||
expected_m1 = mdl.Markup(
|
||||
header=expected_h1,
|
||||
topic=expected_topic,
|
||||
)
|
||||
assert topic_handler.markup == expected_m1
|
||||
|
||||
assert topic_handler.bim_snippet is None
|
||||
|
||||
assert_viewpoints(topic_handler.viewpoints)
|
||||
|
||||
|
||||
def assert_viewpoints(viewpoints):
|
||||
assert len(viewpoints) == 1
|
||||
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
selection=mdl.ComponentSelection(),
|
||||
visibility=mdl.ComponentVisibility(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=False,
|
||||
),
|
||||
exceptions=mdl.ComponentVisibilityExceptions(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="1m5wAJelDFdhn6qBdOGjos", originating_system="https://bimsync.com"),
|
||||
mdl.Component(ifc_guid="1bbI761TbBCOoIa5Kt6PXt", originating_system="https://bimsync.com"),
|
||||
]
|
||||
),
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=mdl.ComponentColoring(),
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=23.112083480037754, y=-25.45574897560043, z=18.52519828443092),
|
||||
camera_direction=mdl.Direction(x=-0.6289237666876837, y=0.5933487270610425, z=-0.5023864884632315),
|
||||
camera_up_vector=mdl.Direction(x=-0.36542566067664123, y=0.3447553774917179, z=0.8646431727652648),
|
||||
field_of_view=60,
|
||||
aspect_ratio=1,
|
||||
),
|
||||
lines=mdl.VisualizationInfoLines(),
|
||||
clipping_planes=mdl.VisualizationInfoClippingPlanes(),
|
||||
bitmaps=mdl.VisualizationInfoBitmaps(),
|
||||
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
|
||||
)
|
||||
|
||||
viewpoint = viewpoints["Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv"]
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=False,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=False,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
|
||||
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=True,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
|
||||
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_files_present(saved_bcf_path):
|
||||
expected_files = [
|
||||
"bcf.version",
|
||||
"project.bcfp",
|
||||
"documents.xml",
|
||||
"extensions.xml",
|
||||
"documents/b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
|
||||
"8ac9822a-761a-4deb-9f39-f61286acbf6a/markup.bcf",
|
||||
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
|
||||
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
|
||||
for file_path in expected_files:
|
||||
assert zipfile.Path(bcf_zip, file_path).exists()
|
||||
@@ -0,0 +1,36 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
|
||||
|
||||
def test_create_clash_set_bcf() -> None:
|
||||
bcfxml = BcfXml.create_new("Clash Test")
|
||||
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
|
||||
topic.add_viewpoint_from_point_and_guids(
|
||||
np.array([10, 10, 10]),
|
||||
"firstId",
|
||||
"secondId",
|
||||
)
|
||||
assert len(topic.viewpoints) == 1
|
||||
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||
v_info = vi_handler.visualization_info
|
||||
assert f"{v_info.guid}.bcfv" == guid
|
||||
components = v_info.components.selection.component
|
||||
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||
camera = v_info.perspective_camera
|
||||
viewpoint = camera.camera_view_point
|
||||
assert viewpoint.x == 15
|
||||
assert viewpoint.y == 15
|
||||
assert viewpoint.z == 15
|
||||
# default direction is the unit vector of -1, -1, -1
|
||||
direction = camera.camera_direction
|
||||
assert direction.x == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.y == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.z == pytest.approx(-1 / 3**0.5)
|
||||
# default
|
||||
up_vector = camera.camera_up_vector
|
||||
assert up_vector.x == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.y == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
|
||||
assert camera.field_of_view == 60
|
||||
+50
-17
@@ -47,7 +47,7 @@ PYNUMBER:=39
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYLIBDIR:=python3.10
|
||||
PYNUMBER:=31
|
||||
PYNUMBER:=310
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
@@ -56,7 +56,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a2/73/07f507b539527579c026ed5f411c35e287685ce794c4dfb7edf6675cdc24/Shapely-1.8.4-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -64,7 +64,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/08/12/5af915b2bc0470f4a652ef9978557a0fa8b705b122ccaec6626029e1674c/Shapely-1.8.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -72,7 +72,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/60/20/c6321e3da92a4ad9495727c9aa8c423e47295572ccd35ece82d1ac013e28/Shapely-1.8.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
@@ -86,7 +86,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -94,7 +94,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/f9/bf6cc34b02b3c7d3777c5e47fa57d2f69387613bbd8decdf3577a9d83a83/Shapely-1.8.4-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -102,7 +102,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/a8/02347c450b5507b9a7a2c15ad6a029613149468fc55ac2100ab215666159/Shapely-1.8.4-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
@@ -117,7 +117,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -125,7 +125,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/8e/60/8186995a96c5a27c9b79c015bbdd89c6d9a6df9988f86f3f1027ecd4a56a/Shapely-1.8.4-cp39-cp39-macosx_11_0_arm64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -133,7 +133,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ba/d0/75cd36c81a1aa26dd6d37cf7ae1c79b0bc2d379e0f32c359bcb00b14e5c3/Shapely-1.8.4-cp310-cp310-macosx_11_0_arm64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
@@ -147,7 +147,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ca/4d/85b912d7947261e6b3218b01946bcd7c2efb2bbafaf517c6fe1dc3bb8399/Shapely-1.8.4-cp37-cp37m-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e2/87/b8b8d8d57b429b01aa56b7d723075c09f33c988b8091bb6f790c83436909/shapely-2.0.1-cp37-cp37m-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -155,7 +155,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/72/c3/0d52812db25ca71203f6203891277e6ee4f0244b21a36c062123ece15f36/Shapely-1.8.4-cp39-cp39-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a7/ae/eab645c4075093584b7a65ab71cb8ff4563a015bd935c9b55dba14b2c1eb/shapely-2.0.1-cp39-cp39-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -163,7 +163,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/24/b5/46d13799804b5fe9ede2eca8b19023410e29b78baca0bff727671a1df8c1/Shapely-1.8.4-cp310-cp310-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
@@ -171,6 +171,13 @@ OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octo
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
endif
|
||||
|
||||
.PHONY: bump
|
||||
bump:
|
||||
cd . && $(SED) "s/$(OLD)/$(NEW)/" Makefile
|
||||
cd ../ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" Makefile
|
||||
cd ../ifcopenshell-python/docs/ifcconvert/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
|
||||
cd ../ifcopenshell-python/docs/ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
ifndef PLATFORM
|
||||
@@ -182,7 +189,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fa6bbf2-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-476ab50-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -200,6 +207,7 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCClash functionality
|
||||
@@ -229,6 +237,7 @@ endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
# TODO: remove this dependency, it seems overkill and we seem to get by with str replaces
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/3f/e7/8750ba6c6101d6aa5ceeb20c013adf2c6f3554a12c71d75654b468404bfa/pystache-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf pystache*
|
||||
@@ -270,6 +279,13 @@ endif
|
||||
cp -r dist/working/decorator-5.0.9/src/decorator.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides audio playback for costing
|
||||
mkdir -p dist/working
|
||||
git clone https://github.com/Andrej730/aud.git --branch master-reduced-size --depth 1 dist/working/aud
|
||||
cd dist/working/aud && git checkout 529af208fbcb707c387bfb681d2605c0b6112de7
|
||||
cp -r dist/working/aud/aud dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
|
||||
@@ -312,6 +328,13 @@ endif
|
||||
cp -r dist/working/xmlschema-2.0.2/xmlschema dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
|
||||
cd dist/working && tar -xzvf xsdata*
|
||||
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
|
||||
@@ -346,10 +369,10 @@ endif
|
||||
cd dist/working && cp *.whl shapely.zip && unzip shapely.zip
|
||||
cp -r dist/working/shapely dist/blenderbim/libs/site/packages/
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/shapely.libs dist/blenderbim/libs/site/packages/
|
||||
endif
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/shapely.libs dist/blenderbim/libs/site/packages/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
@@ -371,11 +394,13 @@ endif
|
||||
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCityJSON
|
||||
mkdir dist/working
|
||||
cd dist/working && $(PYTHON) -m venv env
|
||||
cd dist/working && mkdir site-packages
|
||||
# Required by IFCCityJSON
|
||||
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
cd dist/working && . env/bin/activate && pip install pytest --target=./site-packages
|
||||
cd dist/working/site-packages/ && rm -r *dist-info*
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
@@ -521,6 +546,7 @@ endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for XLSX support
|
||||
# TODO: see if we can replace this with openpyxl which does both read/write
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz
|
||||
cd dist/working && tar -xzvf XlsxWriter*
|
||||
@@ -624,6 +650,13 @@ endif
|
||||
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required for hipped roof generation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip
|
||||
cd dist/working && unzip master.zip
|
||||
cp -r dist/working/bpypolyskel-master/bpypolyskel dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
@@ -91,9 +91,12 @@ classes = [
|
||||
operator.RemoveIfcFile,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.ReloadSelectedIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectURIAttribute,
|
||||
operator.EditBlenderCollection,
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
operator.BIM_OT_show_description,
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
@@ -114,6 +117,7 @@ classes = [
|
||||
ui.BIM_PT_project_info,
|
||||
ui.BIM_PT_project_setup,
|
||||
ui.BIM_PT_collaboration,
|
||||
ui.BIM_PT_selection,
|
||||
ui.BIM_PT_geometry,
|
||||
ui.BIM_PT_services,
|
||||
ui.BIM_PT_structural,
|
||||
@@ -158,6 +162,8 @@ def register():
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
|
||||
|
||||
for mod in modules.values():
|
||||
mod.register()
|
||||
@@ -178,6 +184,8 @@ def unregister():
|
||||
del bpy.types.Camera.BIMMeshProperties
|
||||
del bpy.types.PointLight.BIMMeshProperties
|
||||
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
|
||||
|
||||
for mod in reversed(list(modules.values())):
|
||||
mod.unregister()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,13 +1,13 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
|
||||
FILE_NAME('/dev/null','2021-11-08T10:06:41+11:00',(),(),'IfcOpenShell v0.7.0-ab506098','IfcOpenShell v0.7.0-ab506098','Nobody');
|
||||
FILE_NAME('/dev/null','2023-02-04T13:08:05+11:00',(),(),'IfcOpenShell v0.7.0-07ee62eb9','IfcOpenShell v0.7.0-07ee62eb9','Nobody');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROJECT('1oJZZVd$b1PA6ggg_hh4cE',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('3jWhYPJYL2eOMZ9UZerKqx',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('1Wjv8iy5XBshHpEwChjCFa',$,$,$,#1,(#2));
|
||||
#1=IFCPROJECT('3ubSuEuavEPfXTf4v0gmbA',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('1Qq43hq0v3B9Ko7EGW0hJ5',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('0hU2lTJfX70AKkhyURrbam',$,$,$,#1,(#2));
|
||||
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
@@ -17,89 +17,89 @@ DATA;
|
||||
#10=IFCDIRECTION((1.,0.,0.));
|
||||
#11=IFCAXIS2PLACEMENT3D(#8,#9,#10);
|
||||
#12=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#11,$);
|
||||
#13=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#14=IFCDIRECTION((1.,0.,0.));
|
||||
#13=IFCCARTESIANPOINT((0.,0.));
|
||||
#14=IFCDIRECTION((1.,0.));
|
||||
#15=IFCAXIS2PLACEMENT2D(#13,#14);
|
||||
#16=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#15,$);
|
||||
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
|
||||
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCMATERIAL('Unknown',$,$);
|
||||
#20=IFCWALLTYPE('24Js5pn1PCeA_DXGWh67_f',$,'DEMO50',$,$,$,$,$,$,$);
|
||||
#20=IFCWALLTYPE('3yGmcLQT96CPWJXcV3vNPJ',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#21=IFCMATERIALLAYERSET((#23),$,$);
|
||||
#22=IFCRELASSOCIATESMATERIAL('3gPuTuaVf1AAgeln9$alDb',$,$,$,(#20),#21);
|
||||
#22=IFCRELASSOCIATESMATERIAL('0JMzROKjj7i8EKtMLZ2nXy',$,$,$,(#20),#21);
|
||||
#23=IFCMATERIALLAYER(#19,0.05,$,$,$,$,$);
|
||||
#24=IFCRELDECLARES('3AJpFBgAf4OQzrklK7UbEn',$,$,$,#2,(#37,#41,#76,#47,#58,#91,#25,#225,#71,#159,#29,#62,#95,#20,#53,#86,#33,#66,#81));
|
||||
#25=IFCWALLTYPE('1HCqnc9S51NhU$e0Xceg6o',$,'DEMO100',$,$,$,$,$,$,$);
|
||||
#24=IFCRELDECLARES('1TTgyh7ynBQ8XpgpGSU6ao',$,$,$,#2,(#41,#76,#47,#58,#91,#25,#159,#225,#71,#29,#62,#95,#20,#53,#86,#33,#66,#81,#37));
|
||||
#25=IFCWALLTYPE('20jY5HfLH4LBwMrDEoTdML',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#26=IFCMATERIALLAYERSET((#28),$,$);
|
||||
#27=IFCRELASSOCIATESMATERIAL('2Ikoi$G2PCXvk61V36wh3N',$,$,$,(#25),#26);
|
||||
#27=IFCRELASSOCIATESMATERIAL('2sICOUhur5WgrNUOi1pvhV',$,$,$,(#25),#26);
|
||||
#28=IFCMATERIALLAYER(#19,0.1,$,$,$,$,$);
|
||||
#29=IFCWALLTYPE('3UPlZcHuTCi8CevFkzQjZ_',$,'DEMO200',$,$,$,$,$,$,$);
|
||||
#29=IFCWALLTYPE('0xhGqQenH5Ov9L_CiLywqN',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#30=IFCMATERIALLAYERSET((#32),$,$);
|
||||
#31=IFCRELASSOCIATESMATERIAL('3uAo69i4j4nwMM7VRW3TQn',$,$,$,(#29),#30);
|
||||
#31=IFCRELASSOCIATESMATERIAL('2pOhWSsWT4bQwtduxS8HNJ',$,$,$,(#29),#30);
|
||||
#32=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
|
||||
#33=IFCWALLTYPE('1y03E8PfTAJPit3967Y5Uf',$,'DEMO300',$,$,$,$,$,$,$);
|
||||
#33=IFCWALLTYPE('1HrJFcNVH4XQJIRrEmUD6Z',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#34=IFCMATERIALLAYERSET((#36),$,$);
|
||||
#35=IFCRELASSOCIATESMATERIAL('2pvy9lWJL62BoA9QC5KCOG',$,$,$,(#33),#34);
|
||||
#35=IFCRELASSOCIATESMATERIAL('0cAK6yBnXAnO$5LYcU3QQl',$,$,$,(#33),#34);
|
||||
#36=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
|
||||
#37=IFCCOVERINGTYPE('3ppA8IYrP2nffVvxie6ghZ',$,'DEMO10',$,$,$,$,$,$,$);
|
||||
#37=IFCCOVERINGTYPE('11MVutxgn20fTEibOhUiS8',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#38=IFCMATERIALLAYERSET((#40),$,$);
|
||||
#39=IFCRELASSOCIATESMATERIAL('2Lg3S9QRH4kPgHpYrtokt6',$,$,$,(#37),#38);
|
||||
#39=IFCRELASSOCIATESMATERIAL('1$dvrpTAn6oPSVqcMtsvmV',$,$,$,(#37),#38);
|
||||
#40=IFCMATERIALLAYER(#19,0.01,$,$,$,$,$);
|
||||
#41=IFCCOVERINGTYPE('3KuCqMbY16DRUygYOrLyYE',$,'DEMO20',$,$,(#45),$,$,$,$);
|
||||
#41=IFCCOVERINGTYPE('1DWvr7poP3ggxFE7vxOot2',$,'COV20',$,$,(#45),$,$,$,.NOTDEFINED.);
|
||||
#42=IFCMATERIALLAYERSET((#44),$,$);
|
||||
#43=IFCRELASSOCIATESMATERIAL('27BA_9uQT6OfxSGgEbsKY4',$,$,$,(#41),#42);
|
||||
#43=IFCRELASSOCIATESMATERIAL('2uf2C_54501xFgLlk_jqMG',$,$,$,(#41),#42);
|
||||
#44=IFCMATERIALLAYER(#19,0.02,$,$,$,$,$);
|
||||
#45=IFCPROPERTYSET('11$WHz$79EMw60BdaUh77w',$,'EPset_Parametric',$,(#46));
|
||||
#45=IFCPROPERTYSET('0fSGHPSBH9U8H_ph5xpH65',$,'EPset_Parametric',$,(#46));
|
||||
#46=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
|
||||
#47=IFCCOVERINGTYPE('2cYyQpe9f1vBW8C4D58QZS',$,'DEMO30',$,$,(#51),$,$,$,$);
|
||||
#47=IFCCOVERINGTYPE('1ayqDfj8XDxB$G7ZFyRagM',$,'COV30',$,$,(#51),$,$,$,.NOTDEFINED.);
|
||||
#48=IFCMATERIALLAYERSET((#50),$,$);
|
||||
#49=IFCRELASSOCIATESMATERIAL('1ir_2IiXX4Ax4VH5lcXGEu',$,$,$,(#47),#48);
|
||||
#49=IFCRELASSOCIATESMATERIAL('01vVE$oHTA3BLmh2imRGuu',$,$,$,(#47),#48);
|
||||
#50=IFCMATERIALLAYER(#19,0.03,$,$,$,$,$);
|
||||
#51=IFCPROPERTYSET('2IalIx1bj02AXv21tZC0Sz',$,'EPset_Parametric',$,(#52));
|
||||
#51=IFCPROPERTYSET('3v7cOUGgT0qxrxlQkzwSIR',$,'EPset_Parametric',$,(#52));
|
||||
#52=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
|
||||
#53=IFCRAMPTYPE('23wMhiydD6yw5XpaFs6XCA',$,'DEMO200',$,$,$,$,$,$,$);
|
||||
#53=IFCRAMPTYPE('0VEv7VZVnDzxHXC$QE2Crm',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#54=IFCMATERIALLAYERSET((#56),$,$);
|
||||
#55=IFCRELASSOCIATESMATERIAL('2SLXH7fU1D6OIOdV8Gq6T_',$,$,$,(#53),#54);
|
||||
#55=IFCRELASSOCIATESMATERIAL('0ToQjR2aLAdgGPuqu6UH1o',$,$,$,(#53),#54);
|
||||
#56=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
|
||||
#57=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
|
||||
#58=IFCPILETYPE('3J5r90lrL3kv3b8uqOGsY1',$,'DEMO1',$,$,$,$,$,$,$);
|
||||
#58=IFCPILETYPE('26kmCVC0H4lOhCGn6cCLSQ',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#59=IFCMATERIALPROFILESET($,$,(#61),$);
|
||||
#60=IFCRELASSOCIATESMATERIAL('2vYxThgzHDSOHXKehbpw4S',$,$,$,(#58),#59);
|
||||
#60=IFCRELASSOCIATESMATERIAL('2Ev3ucMBPDtwntRLG_4cBU',$,$,$,(#58),#59);
|
||||
#61=IFCMATERIALPROFILE($,$,#19,#57,$,$);
|
||||
#62=IFCSLABTYPE('0OWxf_nd97uvH$Sr4QbAVW',$,'DEMO150',$,$,$,$,$,$,$);
|
||||
#62=IFCSLABTYPE('3cLRr85lHDAPjHswaO7574',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#63=IFCMATERIALLAYERSET((#65),$,$);
|
||||
#64=IFCRELASSOCIATESMATERIAL('3WSlLxuwH3Sfsul8DwR0LU',$,$,$,(#62),#63);
|
||||
#64=IFCRELASSOCIATESMATERIAL('07afdGZLHCFej$qXPfCLn9',$,$,$,(#62),#63);
|
||||
#65=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
|
||||
#66=IFCSLABTYPE('0wVKQLFFb2oAHccogmWWWy',$,'DEMO250',$,$,$,$,$,$,$);
|
||||
#66=IFCSLABTYPE('2WiuelwGf3Ah3gnVgDGEAn',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#67=IFCMATERIALLAYERSET((#69),$,$);
|
||||
#68=IFCRELASSOCIATESMATERIAL('01R$pwjx14qOlDDfB34EBC',$,$,$,(#66),#67);
|
||||
#68=IFCRELASSOCIATESMATERIAL('03XoY4bYD4ZwZgvg3JqIIK',$,$,$,(#66),#67);
|
||||
#69=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
|
||||
#70=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,0.5,0.6);
|
||||
#71=IFCCOLUMNTYPE('2OGUvw6Tj9deH3dTY4Hk3T',$,'DEMO1',$,$,$,$,$,$,$);
|
||||
#70=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
|
||||
#71=IFCCOLUMNTYPE('187MdL9O13_OzlwsOaT0zY',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#72=IFCMATERIALPROFILESET($,$,(#74),$);
|
||||
#73=IFCRELASSOCIATESMATERIAL('1f717ijc53DgmnoMRFBZ62',$,$,$,(#71),#72);
|
||||
#73=IFCRELASSOCIATESMATERIAL('1CMVTn08P2Tu8afsZHQiUk',$,$,$,(#71),#72);
|
||||
#74=IFCMATERIALPROFILE($,$,#19,#70,$,$);
|
||||
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,$,$,0.25,0.005);
|
||||
#76=IFCCOLUMNTYPE('3_jg43QeDAWvPCl1D0Lw_q',$,'DEMO2',$,$,$,$,$,$,$);
|
||||
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
|
||||
#76=IFCCOLUMNTYPE('3e6tIcskjDOuj1_UAWcT87',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#77=IFCMATERIALPROFILESET($,$,(#79),$);
|
||||
#78=IFCRELASSOCIATESMATERIAL('3mX_vYbF92vxfOrjZFAuYk',$,$,$,(#76),#77);
|
||||
#78=IFCRELASSOCIATESMATERIAL('0ahT30pYb7zQ968gWY9q1A',$,$,$,(#76),#77);
|
||||
#79=IFCMATERIALPROFILE($,$,#19,#75,$,$);
|
||||
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,$,$,0.075,0.15,0.005,0.005,0.005);
|
||||
#81=IFCCOLUMNTYPE('2ZJTQa2Db0MAv2N5bjUuXK',$,'DEMO3',$,$,$,$,$,$,$);
|
||||
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
|
||||
#81=IFCCOLUMNTYPE('0ZxXYg0BTFg8sbqXr50qPW',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#82=IFCMATERIALPROFILESET($,$,(#84),$);
|
||||
#83=IFCRELASSOCIATESMATERIAL('0Yslq2hlz9Qe3PO16oqESz',$,$,$,(#81),#82);
|
||||
#83=IFCRELASSOCIATESMATERIAL('0ul_xSpIH2MxsZcv$ZFXG7',$,$,$,(#81),#82);
|
||||
#84=IFCMATERIALPROFILE($,$,#19,#80,$,$);
|
||||
#85=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
|
||||
#86=IFCBEAMTYPE('2bUnl1uHnB$QSMF_fVajUw',$,'DEMO1',$,$,$,$,$,$,$);
|
||||
#86=IFCBEAMTYPE('06Td_2IhD5OgyKW34Ikv$Z',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#87=IFCMATERIALPROFILESET($,$,(#89),$);
|
||||
#88=IFCRELASSOCIATESMATERIAL('08d9DxbyHAyRgRPc6W4Qsn',$,$,$,(#86),#87);
|
||||
#88=IFCRELASSOCIATESMATERIAL('2cT5kDtU11jv2sFtIKVfJa',$,$,$,(#86),#87);
|
||||
#89=IFCMATERIALPROFILE($,$,#19,#85,$,$);
|
||||
#90=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
|
||||
#91=IFCBEAMTYPE('0f1mNiwDTAiuSgrvotHh5$',$,'DEMO2',$,$,$,$,$,$,$);
|
||||
#91=IFCBEAMTYPE('1JyktsoBP8DgMbJiECPZ6d',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#92=IFCMATERIALPROFILESET($,$,(#94),$);
|
||||
#93=IFCRELASSOCIATESMATERIAL('2cO0XhlFf8MxIg_eNDWX_X',$,$,$,(#91),#92);
|
||||
#93=IFCRELASSOCIATESMATERIAL('2$_YKuP914sBZmflM_8l1s',$,$,$,(#91),#92);
|
||||
#94=IFCMATERIALPROFILE($,$,#19,#90,$,$);
|
||||
#95=IFCWINDOWTYPE('0ihTx2Iw1E2gRAq1N4pWVg',$,'DEMO1',$,$,$,(#136,#158),$,$,$,$,$,$);
|
||||
#95=IFCWINDOWTYPE('0g10V5zVP8c96j3mPV0Xvn',$,'WT01',$,$,$,(#136,#158),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#96=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
|
||||
#97=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
|
||||
#98=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
|
||||
@@ -126,14 +126,14 @@ DATA;
|
||||
#119=IFCPOLYGONALFACESET(#118,$,(#96,#97,#98,#99,#100,#102,#103,#104,#105,#106,#107,#108,#109,#110,#111,#112),$);
|
||||
#120=IFCPOLYGONALFACESET(#118,$,(#101,#113,#114,#115,#116,#117),$);
|
||||
#121=IFCSHAPEREPRESENTATION(#17,'Body','Tessellation',(#119,#120));
|
||||
#122=IFCSURFACESTYLE('Frame',.BOTH.,(#125));
|
||||
#123=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
|
||||
#122=IFCSURFACESTYLE('Frame',.BOTH.,(#123));
|
||||
#123=IFCSURFACESTYLERENDERING(#124,0.,#125,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.4),.NOTDEFINED.);
|
||||
#124=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
|
||||
#125=IFCSURFACESTYLERENDERING(#123,0.,#124,$,$,$,$,$,$);
|
||||
#126=IFCSURFACESTYLE('Glass',.BOTH.,(#129));
|
||||
#127=IFCCOLOURRGB($,0.800000011920929,1.,1.);
|
||||
#125=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
|
||||
#126=IFCSURFACESTYLE('Glass',.BOTH.,(#127));
|
||||
#127=IFCSURFACESTYLERENDERING(#128,0.,#129,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.5),.NOTDEFINED.);
|
||||
#128=IFCCOLOURRGB($,0.800000011920929,1.,1.);
|
||||
#129=IFCSURFACESTYLERENDERING(#127,0.799999997019768,#128,$,$,$,$,$,$);
|
||||
#129=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
|
||||
#130=IFCSTYLEDITEM(#119,(#122),'Frame');
|
||||
#131=IFCSTYLEDITEM(#120,(#126),'Glass');
|
||||
#132=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
@@ -157,13 +157,13 @@ DATA;
|
||||
#150=IFCINDEXEDPOLYCURVE(#137,(IFCLINEINDEX((5,11))),$);
|
||||
#151=IFCINDEXEDPOLYCURVE(#137,(IFCLINEINDEX((6,12))),$);
|
||||
#152=IFCGEOMETRICCURVESET((#138,#139,#140,#141,#142,#143,#144,#145,#146,#147,#148,#149,#150,#151));
|
||||
#153=IFCSHAPEREPRESENTATION(#18,'Annotation','Annotation2D',(#152));
|
||||
#153=IFCSHAPEREPRESENTATION(#18,'Body','Annotation2D',(#152));
|
||||
#154=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#155=IFCDIRECTION((1.,0.,0.));
|
||||
#156=IFCDIRECTION((0.,0.,1.));
|
||||
#157=IFCAXIS2PLACEMENT3D(#154,#156,#155);
|
||||
#158=IFCREPRESENTATIONMAP(#157,#153);
|
||||
#159=IFCDOORTYPE('0HLh8Cp3f58hay4OhB7SHy',$,'DEMO1',$,$,$,(#204,#224),$,$,$,$,$,$);
|
||||
#159=IFCDOORTYPE('0nu4v3oPPFmQIz2RA1Vp_t',$,'DT01',$,$,$,(#204,#224),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#160=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
|
||||
#161=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
|
||||
#162=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
|
||||
@@ -194,14 +194,14 @@ DATA;
|
||||
#187=IFCPOLYGONALFACESET(#186,$,(#161,#162,#163,#164,#165,#166,#169,#170,#171,#172,#173,#174,#175,#176,#179,#180,#182,#183,#184,#185),$);
|
||||
#188=IFCPOLYGONALFACESET(#186,$,(#160,#167,#168,#177,#178,#181),$);
|
||||
#189=IFCSHAPEREPRESENTATION(#17,'Body','Tessellation',(#187,#188));
|
||||
#190=IFCSURFACESTYLE('Frame',.BOTH.,(#193));
|
||||
#191=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
|
||||
#190=IFCSURFACESTYLE('Frame',.BOTH.,(#191));
|
||||
#191=IFCSURFACESTYLERENDERING(#192,0.,#193,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.4),.NOTDEFINED.);
|
||||
#192=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
|
||||
#193=IFCSURFACESTYLERENDERING(#191,0.,#192,$,$,$,$,$,$);
|
||||
#194=IFCSURFACESTYLE('Panel',.BOTH.,(#197));
|
||||
#195=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165);
|
||||
#193=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
|
||||
#194=IFCSURFACESTYLE('Panel',.BOTH.,(#195));
|
||||
#195=IFCSURFACESTYLERENDERING(#196,0.,#197,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.5),.NOTDEFINED.);
|
||||
#196=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165);
|
||||
#197=IFCSURFACESTYLERENDERING(#195,0.,#196,$,$,$,$,$,$);
|
||||
#197=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
|
||||
#198=IFCSTYLEDITEM(#187,(#190),'Frame');
|
||||
#199=IFCSTYLEDITEM(#188,(#194),'Panel');
|
||||
#200=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
@@ -223,13 +223,13 @@ DATA;
|
||||
#216=IFCINDEXEDPOLYCURVE(#205,(IFCLINEINDEX((11,15))),$);
|
||||
#217=IFCINDEXEDPOLYCURVE(#205,(IFCLINEINDEX((17,18)),IFCLINEINDEX((18,19)),IFCLINEINDEX((19,20)),IFCLINEINDEX((20,21)),IFCLINEINDEX((21,22)),IFCLINEINDEX((22,23)),IFCLINEINDEX((23,24)),IFCLINEINDEX((24,25)),IFCLINEINDEX((25,26)),IFCLINEINDEX((26,27)),IFCLINEINDEX((27,28)),IFCLINEINDEX((28,29))),$);
|
||||
#218=IFCGEOMETRICCURVESET((#206,#207,#208,#209,#210,#211,#212,#213,#214,#215,#216,#217));
|
||||
#219=IFCSHAPEREPRESENTATION(#18,'Annotation','Annotation2D',(#218));
|
||||
#219=IFCSHAPEREPRESENTATION(#18,'Body','Annotation2D',(#218));
|
||||
#220=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#221=IFCDIRECTION((1.,0.,0.));
|
||||
#222=IFCDIRECTION((0.,0.,1.));
|
||||
#223=IFCAXIS2PLACEMENT3D(#220,#222,#221);
|
||||
#224=IFCREPRESENTATIONMAP(#223,#219);
|
||||
#225=IFCFURNITURETYPE('1wgo6Pw4jDJO4F1GA9Qlmx',$,'BUNNY',$,$,$,(#946,#988),$,$,$,$);
|
||||
#225=IFCFURNITURETYPE('2YuVxsKJL9URJtwIhHY2GN',$,'BUN01',$,$,$,(#946,#988),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#226=IFCINDEXEDPOLYGONALFACE((187,278,44));
|
||||
#227=IFCINDEXEDPOLYGONALFACE((21,52,60));
|
||||
#228=IFCINDEXEDPOLYGONALFACE((91,100,31));
|
||||
@@ -987,7 +987,7 @@ DATA;
|
||||
#980=IFCINDEXEDPOLYCURVE(#947,(IFCLINEINDEX((60,61)),IFCLINEINDEX((61,62)),IFCLINEINDEX((62,63))),$);
|
||||
#981=IFCINDEXEDPOLYCURVE(#947,(IFCLINEINDEX((64,31))),$);
|
||||
#982=IFCGEOMETRICCURVESET((#948,#949,#950,#951,#952,#953,#954,#955,#956,#957,#958,#959,#960,#961,#962,#963,#964,#965,#966,#967,#968,#969,#970,#971,#972,#973,#974,#975,#976,#977,#978,#979,#980,#981));
|
||||
#983=IFCSHAPEREPRESENTATION(#18,'Annotation','Annotation2D',(#982));
|
||||
#983=IFCSHAPEREPRESENTATION(#18,'Body','Annotation2D',(#982));
|
||||
#984=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#985=IFCDIRECTION((1.,0.,0.));
|
||||
#986=IFCDIRECTION((0.,0.,1.));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_ENUMERATEDVALUE.,'IfcLabel',$,#4,$,$,$,.READWRITE.);
|
||||
#4=IFCPROPERTYENUMERATION('Symbol',(IFCLABEL('rectangle-tag'), IFCLABEL('door-tag')),$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,16 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -19,11 +19,12 @@
|
||||
*/
|
||||
|
||||
* { stroke-linecap: round; stroke-linejoin: round; }
|
||||
text { fill: black; stroke: none; }
|
||||
text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; font-size: 4.13px; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
|
||||
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
|
||||
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
|
||||
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
|
||||
@@ -37,13 +38,18 @@ text { fill: black; stroke: none; }
|
||||
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
|
||||
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
|
||||
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
|
||||
.PredefinedType-FILLAREA { fill: white; stroke: none; }
|
||||
.PredefinedType-FILLAREA { fill: white; stroke: black; }
|
||||
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.PredefinedType-TEXT { fill: black; stroke: none; }
|
||||
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
|
||||
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
|
||||
text.title { /* 7mm */ font-size: 11.55px; }
|
||||
text.header { /* 5mm */ font-size: 8.25px; }
|
||||
text.large { /* 3.5mm */ font-size: 5.78px; }
|
||||
text.regular { /* 2.5mm */ font-size: 4.13px; }
|
||||
text.small { /* 1.8mm */ font-size: 2.97px; }
|
||||
text.GRID { /* 5mm */ font-size: 8.25px; }
|
||||
.material-blank { fill: white; }
|
||||
.material-diagonal1 { fill: url(#diagonal1); }
|
||||
.material-diagonal2 { fill: url(#diagonal2); }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
|
||||
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
|
||||
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
|
||||
</marker>
|
||||
<!-- Tip: https://www.svgviewer.dev/ -->
|
||||
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -26,8 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
|
||||
from ifcopenshell.api.material.data import Data as MaterialData
|
||||
from ifcopenshell.api.type.data import Data as TypeData
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
|
||||
|
||||
global_subscription_owner = object()
|
||||
@@ -66,7 +65,6 @@ def name_callback(obj, data):
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
|
||||
MaterialData.load_materials()
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
|
||||
refresh_ui_data()
|
||||
@@ -90,8 +88,6 @@ def name_callback(obj, data):
|
||||
break
|
||||
if grid_collection:
|
||||
grid_collection.name = obj.name
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
TypeData.purge()
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
refresh_ui_data()
|
||||
|
||||
@@ -301,3 +297,4 @@ def setDefaultProperties(scene):
|
||||
drawing_style.name = "Blender Default"
|
||||
drawing_style.render_type = "DEFAULT"
|
||||
bpy.ops.bim.save_drawing_style(index="2")
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
@@ -23,8 +23,11 @@ import math
|
||||
import zipfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -41,16 +44,25 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
return
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
|
||||
propis = bpy.context.scene.BIMWorkScheduleProperties
|
||||
for item in propis.durations_attributes:
|
||||
if item.name == attribute.name:
|
||||
duration_props = item
|
||||
layout.label(text=attribute.name)
|
||||
layout.prop(duration_props, "years", text="Y")
|
||||
layout.prop(duration_props, "months", text="M")
|
||||
layout.prop(duration_props, "days", text="D")
|
||||
layout.prop(duration_props, "hours", text="H")
|
||||
layout.prop(duration_props, "minutes", text="Min")
|
||||
layout.prop(duration_props, "seconds", text="S")
|
||||
break
|
||||
else:
|
||||
layout.prop(
|
||||
attribute,
|
||||
value_name,
|
||||
text=attribute.name,
|
||||
)
|
||||
if "ScheduleDuration" in attribute.name:
|
||||
layout.prop(bpy.context.scene.BIMDuration, "duration_days", text="D")
|
||||
layout.prop(bpy.context.scene.BIMDuration, "duration_hours", text="H")
|
||||
layout.prop(bpy.context.scene.BIMDuration, "duration_minutes", text="M")
|
||||
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
@@ -83,6 +95,7 @@ def import_attribute(attribute, props, data, callback=None):
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type if isinstance(data_type, str) else ""
|
||||
new.ifc_class = data["type"]
|
||||
is_handled_by_callback = callback(attribute.name(), new, data) if callback else None
|
||||
|
||||
if is_handled_by_callback:
|
||||
@@ -90,19 +103,66 @@ def import_attribute(attribute, props, data, callback=None):
|
||||
elif is_handled_by_callback is False:
|
||||
props.remove(len(props) - 1)
|
||||
elif data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
new.string_value = "" if new.is_null else str(data[attribute.name()])
|
||||
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
|
||||
new.is_uri = True
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[attribute.name()]
|
||||
new.bool_value = False if new.is_null else bool(data[attribute.name()])
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else data[attribute.name()]
|
||||
new.int_value = 0 if new.is_null else int(data[attribute.name()])
|
||||
elif data_type == "float":
|
||||
new.float_value = 0.0 if new.is_null else data[attribute.name()]
|
||||
new.float_value = 0.0 if new.is_null else float(data[attribute.name()])
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
enum_items = ifcopenshell.util.attribute.get_enum_items(attribute)
|
||||
new.enum_items = json.dumps(enum_items)
|
||||
add_attribute_enum_items_descriptions(new, enum_items)
|
||||
if data[new.name]:
|
||||
new.enum_value = data[new.name]
|
||||
add_attribute_description(new, data)
|
||||
add_attribute_min_max(new)
|
||||
|
||||
|
||||
ATTRIBUTE_MIN_MAX_CONSTRAINTS = {"IfcMaterialLayer": {"Priority": {"value_min": 0, "value_max": 100}}}
|
||||
|
||||
|
||||
def add_attribute_min_max(attribute_blender):
|
||||
if attribute_blender.ifc_class in ATTRIBUTE_MIN_MAX_CONSTRAINTS:
|
||||
constraints = ATTRIBUTE_MIN_MAX_CONSTRAINTS[attribute_blender.ifc_class].get(attribute_blender.name, {})
|
||||
for constraint, value in constraints.items():
|
||||
setattr(attribute_blender, constraint, value)
|
||||
setattr(attribute_blender, constraint + "_constraint", True)
|
||||
|
||||
|
||||
def add_attribute_enum_items_descriptions(attribute_blender, enum_items):
|
||||
attribute_blender.enum_descriptions.clear()
|
||||
if isinstance(enum_items, dict):
|
||||
enum_items = enum_items.keys()
|
||||
version = tool.Ifc.get_schema()
|
||||
for enum_item in enum_items:
|
||||
new_enum_description = attribute_blender.enum_descriptions.add()
|
||||
try:
|
||||
description = get_predefined_type_doc(version, attribute_blender.ifc_class, enum_item) or ""
|
||||
except KeyError: # TODO this only supports predefined type enums. Add support for other types of enums ?
|
||||
description = ""
|
||||
new_enum_description.name = description
|
||||
|
||||
|
||||
def add_attribute_description(attribute_blender, attribute_ifc=None):
|
||||
if not attribute_blender.name:
|
||||
return
|
||||
version = tool.Ifc.get_schema()
|
||||
description = ""
|
||||
try:
|
||||
description = get_attribute_doc(version, attribute_blender.ifc_class, attribute_blender.name)
|
||||
except RuntimeError: # It's not an Entity Attribute. Let's try a Property Set attribute.
|
||||
doc = get_property_doc(version, attribute_blender.ifc_class, attribute_blender.name)
|
||||
if doc:
|
||||
description = doc.get("description", "")
|
||||
else: # It's a custom property set. Check if this attribute has a description
|
||||
if attribute_ifc is not None:
|
||||
description = getattr(attribute_ifc, "Description", "")
|
||||
if description:
|
||||
attribute_blender.description = description
|
||||
|
||||
|
||||
def export_attributes(props, callback=None):
|
||||
@@ -118,14 +178,18 @@ def export_attributes(props, callback=None):
|
||||
def prop_with_search(layout, data, prop_name, **kwargs):
|
||||
# kwargs are layout.prop arguments (text, icon, etc.)
|
||||
row = layout.row(align=True)
|
||||
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
|
||||
row.context_pointer_set(name="data", data=data)
|
||||
row.prop(data, prop_name, **kwargs)
|
||||
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
|
||||
op.prop_name = prop_name
|
||||
try:
|
||||
if len(get_enum_items(data, prop_name)) > 10:
|
||||
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
|
||||
row.context_pointer_set(name="data", data=data)
|
||||
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
|
||||
op.prop_name = prop_name
|
||||
except TypeError: # Prop is not iterable
|
||||
pass
|
||||
|
||||
|
||||
def get_enum_items(data, prop_name, context):
|
||||
def get_enum_items(data, prop_name, context=None):
|
||||
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported
|
||||
# Or throws an error in the console when the items callback returns an empty list
|
||||
# See https://blender.stackexchange.com/q/215781/86891
|
||||
@@ -135,7 +199,7 @@ def get_enum_items(data, prop_name, context):
|
||||
return
|
||||
if not isinstance(items, (list, tuple)):
|
||||
# items are retrieved through a callback, not a static list :
|
||||
items = items(data, context)
|
||||
items = items(data, context or bpy.context)
|
||||
return items
|
||||
|
||||
|
||||
@@ -144,6 +208,12 @@ def get_obj_ifc_definition_id(context, obj, obj_type):
|
||||
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "Material":
|
||||
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "MaterialSet":
|
||||
return ifcopenshell.util.element.get_material(
|
||||
tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
|
||||
).id()
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
|
||||
elif obj_type == "Task":
|
||||
return context.scene.BIMTaskTreeProperties.tasks[
|
||||
context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
@@ -173,6 +243,30 @@ def close_operator_panel(event):
|
||||
bpy.app.timers.register(move_back, first_interval=0.01)
|
||||
|
||||
|
||||
def convert_property_group_from_si(property_group, skip_props=()):
|
||||
"""Method converts property group values from si to current ifc project units
|
||||
|
||||
based on default values of the properties.
|
||||
|
||||
List of properties to skip can be supplied in `skip_props`."""
|
||||
conversion_k = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
skip_props = ("rna_type", "name") + skip_props
|
||||
for prop_name in property_group.bl_rna.properties.keys():
|
||||
if prop_name in skip_props:
|
||||
continue
|
||||
prop_bl_rna = property_group.bl_rna.properties[prop_name]
|
||||
if prop_bl_rna.array_length > 0:
|
||||
prop_value = prop_bl_rna.default_array
|
||||
else:
|
||||
prop_value = prop_bl_rna.default
|
||||
|
||||
if type(prop_value) is float:
|
||||
prop_value = prop_value * conversion_k
|
||||
elif type(prop_value) is bpy.types.bpy_prop_array:
|
||||
prop_value = [el * conversion_k for el in prop_value]
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
|
||||
@@ -51,6 +51,7 @@ class IfcStore:
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
session_files = {}
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
@@ -71,6 +72,7 @@ class IfcStore:
|
||||
IfcStore.history = []
|
||||
IfcStore.future = []
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
IfcStore.session_files = {}
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
@@ -96,7 +98,14 @@ class IfcStore:
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
|
||||
except:
|
||||
return
|
||||
if os.path.exists(IfcStore.cache_path):
|
||||
os.remove(IfcStore.cache_path)
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
|
||||
except:
|
||||
return
|
||||
else:
|
||||
return
|
||||
return IfcStore.cache
|
||||
|
||||
@staticmethod
|
||||
@@ -252,13 +261,19 @@ class IfcStore:
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
return
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
|
||||
except:
|
||||
return
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class MaterialCreator:
|
||||
|
||||
def parse_representation(self, representation):
|
||||
has_parsed = False
|
||||
representation_items = self.resolve_all_representation_items(representation)
|
||||
representation_items = self.resolve_all_stylable_representation_items(representation)
|
||||
for item in representation_items:
|
||||
if self.parse_representation_item(item):
|
||||
has_parsed = True
|
||||
@@ -153,11 +153,19 @@ class MaterialCreator:
|
||||
]
|
||||
self.mesh.polygons.foreach_set("material_index", material_index)
|
||||
|
||||
def resolve_all_representation_items(self, representation):
|
||||
def resolve_all_stylable_representation_items(self, representation):
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcMappedItem"):
|
||||
items.extend(item.MappingSource.MappedRepresentation.Items)
|
||||
if item.is_a("IfcBooleanResult"):
|
||||
operand = item.FirstOperand
|
||||
while True:
|
||||
items.append(operand)
|
||||
if operand.is_a("IfcBooleanResult"):
|
||||
operand = operand.FirstOperand
|
||||
else:
|
||||
break
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
@@ -177,6 +185,7 @@ class IfcImporter:
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
self.elements = set()
|
||||
self.type_collection = None
|
||||
@@ -227,8 +236,6 @@ class IfcImporter:
|
||||
self.profile_code("Create materials")
|
||||
self.create_styles()
|
||||
self.profile_code("Create styles")
|
||||
self.create_annotations()
|
||||
self.profile_code("Create annotation")
|
||||
self.parse_native_elements()
|
||||
self.profile_code("Parsing native elements")
|
||||
self.create_native_elements()
|
||||
@@ -328,11 +335,23 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
|
||||
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
else:
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
)
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
)
|
||||
elif self.file.schema == "IFC4":
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
+ self.file.by_type("IfcSpatialElementType")
|
||||
)
|
||||
else:
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType") + self.file.by_type("IfcSpatialElementType")
|
||||
)
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
@@ -402,7 +421,7 @@ class IfcImporter:
|
||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
return True
|
||||
elif (
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
@@ -516,14 +535,21 @@ class IfcImporter:
|
||||
|
||||
def does_element_likely_have_geometry_far_away(self, element):
|
||||
for representation in element.Representation.Representations:
|
||||
for subelement in self.file.traverse(representation):
|
||||
if subelement.is_a("IfcCartesianPointList3D"):
|
||||
for point in subelement.CoordList:
|
||||
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcMappedItem"):
|
||||
items.extend(item.MappingSource.MappedRepresentation.Items)
|
||||
else:
|
||||
items.append(item)
|
||||
for item in items:
|
||||
for subelement in self.file.traverse(item):
|
||||
if subelement.is_a("IfcCartesianPointList3D"):
|
||||
for point in subelement.CoordList:
|
||||
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
|
||||
return True
|
||||
if subelement.is_a("IfcCartesianPoint"):
|
||||
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
|
||||
return True
|
||||
if subelement.is_a("IfcCartesianPoint"):
|
||||
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
|
||||
return True
|
||||
|
||||
def apply_blender_offset_to_matrix_world(self, obj, matrix):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -567,7 +593,7 @@ class IfcImporter:
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
|
||||
grid_obj.lock_location = (True, True, True)
|
||||
grid_obj.lock_rotation = (True, True, True)
|
||||
collection = bpy.data.collections.new(self.get_name(grid))
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(grid))
|
||||
u_axes = bpy.data.collections.new("UAxes")
|
||||
collection.children.link(u_axes)
|
||||
v_axes = bpy.data.collections.new("VAxes")
|
||||
@@ -609,11 +635,11 @@ class IfcImporter:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
|
||||
mesh = self.create_mesh(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
obj = bpy.data.objects.new(self.get_name(element), mesh)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
@@ -668,9 +694,6 @@ class IfcImporter:
|
||||
def create_spatial_elements(self):
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
|
||||
def create_annotations(self):
|
||||
self.create_generic_elements(self.annotations)
|
||||
|
||||
def create_elements(self):
|
||||
self.create_generic_elements(self.elements)
|
||||
|
||||
@@ -893,27 +916,32 @@ class IfcImporter:
|
||||
if element is None:
|
||||
return
|
||||
|
||||
if self.has_existing_project:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
return obj
|
||||
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
|
||||
if mesh:
|
||||
pass
|
||||
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
mesh = self.create_camera(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
|
||||
mesh = self.create_curve(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
elif shape:
|
||||
mesh_name = self.get_mesh_name(shape.geometry)
|
||||
mesh_name = tool.Loader.get_mesh_name(shape.geometry)
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
else:
|
||||
mesh = None
|
||||
|
||||
obj = bpy.data.objects.new(self.get_name(element), mesh)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
|
||||
if shape:
|
||||
@@ -966,9 +994,37 @@ class IfcImporter:
|
||||
self.convert_representation(representation)
|
||||
|
||||
mesh = bpy.data.meshes.new("Native")
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
if props.has_blender_offset and self.is_point_far_away(self.mesh_data["co"][0:3], is_meters=False):
|
||||
offset_point = np.linalg.inv(mat) @ np.array(
|
||||
(
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
verts = [None] * len(self.mesh_data["co"])
|
||||
for i in range(0, len(self.mesh_data["co"]), 3):
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
self.mesh_data["co"][i] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||
offset_point[0] * self.unit_scale,
|
||||
offset_point[1] * self.unit_scale,
|
||||
offset_point[2] * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
else:
|
||||
verts = [c * self.unit_scale for c in self.mesh_data["co"]]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
mesh.vertices.add(self.mesh_data["total_verts"])
|
||||
mesh.vertices.foreach_set("co", [c * self.unit_scale for c in self.mesh_data["co"]])
|
||||
# mesh.vertices.foreach_set("co", self.mesh_data["co"])
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(len(self.mesh_data["vertex_index"]))
|
||||
mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"])
|
||||
mesh.polygons.add(self.mesh_data["total_polygons"])
|
||||
@@ -1306,7 +1362,13 @@ class IfcImporter:
|
||||
)
|
||||
|
||||
def create_project(self):
|
||||
self.project = {"ifc": self.file.by_type("IfcProject")[0]}
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
self.project = {"ifc": project}
|
||||
obj = tool.Ifc.get_object(project)
|
||||
if obj:
|
||||
self.project["blender"] = obj.users_collection[0]
|
||||
self.has_existing_project = True
|
||||
return
|
||||
self.project["blender"] = bpy.data.collections.new(
|
||||
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
|
||||
)
|
||||
@@ -1327,6 +1389,13 @@ class IfcImporter:
|
||||
def create_spatial_decomposition_collections(self):
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
|
||||
|
||||
# Invalid IFCs may have orphaned spatial structure elements.
|
||||
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
|
||||
while orphaned_spaces:
|
||||
self.create_spatial_decomposition_collection(self.project["blender"], orphaned_spaces)
|
||||
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
|
||||
|
||||
self.create_views_collection()
|
||||
self.create_type_collection()
|
||||
|
||||
@@ -1348,21 +1417,22 @@ class IfcImporter:
|
||||
if not view_collection:
|
||||
view_collection = bpy.data.collections.new("Views")
|
||||
self.project["blender"].children.link(view_collection)
|
||||
for element in self.file.by_type("IfcAnnotation"):
|
||||
if element.ObjectType == "DRAWING":
|
||||
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
|
||||
collection = bpy.data.collections.new("IfcGroup/" + group.Name)
|
||||
self.collections[group.GlobalId] = collection
|
||||
view_collection.children.link(collection)
|
||||
|
||||
def create_spatial_decomposition_collection(self, parent, related_objects):
|
||||
for element in related_objects:
|
||||
if element not in self.spatial_elements:
|
||||
continue
|
||||
global_id = element.GlobalId
|
||||
collection = bpy.data.collections.new(self.get_name(element))
|
||||
self.collections[global_id] = collection
|
||||
parent.children.link(collection)
|
||||
is_existing = False
|
||||
if self.has_existing_project:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
is_existing = True
|
||||
collection = obj.users_collection[0]
|
||||
self.collections[element.GlobalId] = collection
|
||||
if not is_existing:
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
self.collections[element.GlobalId] = collection
|
||||
parent.children.link(collection)
|
||||
if element.IsDecomposedBy:
|
||||
for rel_aggregate in element.IsDecomposedBy:
|
||||
self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects)
|
||||
@@ -1373,7 +1443,11 @@ class IfcImporter:
|
||||
rel_aggregates += [e.Decomposes[0] for e in self.elements if e.Decomposes]
|
||||
rel_aggregates = set(rel_aggregates)
|
||||
else:
|
||||
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
|
||||
rel_aggregates = [
|
||||
a
|
||||
for a in self.file.by_type("IfcRelAggregates")
|
||||
if a.RelatingObject.is_a("IfcElement") or a.RelatingObject.is_a("IfcElementType")
|
||||
]
|
||||
|
||||
if len(rel_aggregates) > 10000:
|
||||
# More than 10,000 collections makes Blender unhappy
|
||||
@@ -1384,7 +1458,7 @@ class IfcImporter:
|
||||
aggregates = {}
|
||||
for rel_aggregate in rel_aggregates:
|
||||
element = rel_aggregate.RelatingObject
|
||||
collection = bpy.data.collections.new(self.get_name(element))
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
aggregates[element.GlobalId] = {"element": element, "collection": collection}
|
||||
self.collections[element.GlobalId] = collection
|
||||
|
||||
@@ -1396,6 +1470,10 @@ class IfcImporter:
|
||||
parent = ifcopenshell.util.element.get_container(aggregate["element"])
|
||||
if parent:
|
||||
self.collections[parent.GlobalId].children.link(aggregate["collection"])
|
||||
continue
|
||||
if aggregate["element"].is_a("IfcElementType"):
|
||||
self.type_collection.children.link(aggregate["collection"])
|
||||
continue
|
||||
|
||||
def create_materials(self):
|
||||
for material in self.file.by_type("IfcMaterial"):
|
||||
@@ -1473,13 +1551,21 @@ class IfcImporter:
|
||||
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
if surface_style.DiffuseColour:
|
||||
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
|
||||
1,
|
||||
)
|
||||
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
|
||||
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
|
||||
@@ -1603,9 +1689,6 @@ class IfcImporter:
|
||||
else:
|
||||
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
|
||||
|
||||
def get_name(self, element):
|
||||
return "{}/{}".format(element.is_a(), element.Name)
|
||||
|
||||
def place_objects_in_collections(self):
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
@@ -1676,7 +1759,7 @@ class IfcImporter:
|
||||
|
||||
def get_drawing_group(self, element):
|
||||
for rel in element.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup"):
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
@@ -1753,16 +1836,6 @@ class IfcImporter:
|
||||
):
|
||||
return representation.Items[0].MappingTarget
|
||||
|
||||
def get_mesh_name(self, geometry):
|
||||
representation_id = geometry.id
|
||||
if "-" in representation_id:
|
||||
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
|
||||
else:
|
||||
representation_id = int(re.sub(r"\D", "", representation_id))
|
||||
representation = self.file.by_id(representation_id)
|
||||
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
|
||||
return "{}/{}".format(context_id, representation_id)
|
||||
|
||||
def create_camera(self, element, shape):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
@@ -1777,7 +1850,7 @@ class IfcImporter:
|
||||
height = max(y) - min(y)
|
||||
depth = max(z) - min(z)
|
||||
|
||||
camera = bpy.data.cameras.new(self.get_mesh_name(geometry))
|
||||
camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry))
|
||||
camera.type = "ORTHO"
|
||||
camera.ortho_scale = width if width > height else height
|
||||
camera.clip_end = depth
|
||||
@@ -1817,7 +1890,7 @@ class IfcImporter:
|
||||
else:
|
||||
geometry = shape
|
||||
|
||||
curve = bpy.data.curves.new(self.get_mesh_name(geometry), type="CURVE")
|
||||
curve = bpy.data.curves.new(tool.Loader.get_mesh_name(geometry), type="CURVE")
|
||||
curve.dimensions = "3D"
|
||||
curve.resolution_u = 2
|
||||
|
||||
@@ -1843,7 +1916,7 @@ class IfcImporter:
|
||||
else:
|
||||
geometry = shape
|
||||
|
||||
mesh = bpy.data.meshes.new(self.get_mesh_name(geometry))
|
||||
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if (
|
||||
@@ -1958,17 +2031,6 @@ class IfcImporter:
|
||||
self.added_data[element.id()] = obj
|
||||
IfcStore.link_element(element, obj)
|
||||
|
||||
def link_mesh(self, shape, mesh):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
if "-" in geometry.id:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
|
||||
else:
|
||||
# TODO: See #2002
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
|
||||
|
||||
def set_matrix_world(self, obj, matrix_world):
|
||||
obj.matrix_world = matrix_world
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
@@ -25,7 +25,6 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.attribute as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -51,8 +50,6 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
if oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes)
|
||||
props.is_editing_attributes = True
|
||||
return {"FINISHED"}
|
||||
@@ -88,33 +85,10 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
attributes = {}
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
blender_attribute = props.attributes.get(attribute["name"])
|
||||
if not blender_attribute:
|
||||
continue
|
||||
if attribute["is_optional"] and blender_attribute.is_null:
|
||||
attributes[attribute["name"]] = None
|
||||
elif attribute["type"] == "string":
|
||||
attributes[attribute["name"]] = blender_attribute.string_value
|
||||
elif attribute["type"] == "list":
|
||||
values = blender_attribute.string_value[1:-1].split(", ")
|
||||
if attribute["list_type"] == "float":
|
||||
values = [float(v) for v in values]
|
||||
elif attribute["list_type"] == "integer":
|
||||
values = [int(v) for v in values]
|
||||
attributes[attribute["name"]] = values
|
||||
elif attribute["type"] == "integer":
|
||||
attributes[attribute["name"]] = blender_attribute.int_value
|
||||
elif attribute["type"] == "float":
|
||||
attributes[attribute["name"]] = blender_attribute.float_value
|
||||
elif attribute["type"] == "enum":
|
||||
attributes[attribute["name"]] = blender_attribute.enum_value
|
||||
product = self.file.by_id(oprops.ifc_definition_id)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes})
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -143,5 +117,5 @@ class CopyAttributeToSelection(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value()
|
||||
for obj in context.selected_objects:
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj)
|
||||
|
||||
@@ -20,43 +20,44 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.NewBcfProject,
|
||||
operator.LoadBcfProject,
|
||||
operator.LoadBcfTopics,
|
||||
operator.LoadBcfTopic,
|
||||
operator.LoadBcfComments,
|
||||
operator.EditBcfProjectName,
|
||||
operator.EditBcfAuthor,
|
||||
operator.EditBcfTopicName,
|
||||
operator.EditBcfTopic,
|
||||
operator.SaveBcfProject,
|
||||
operator.AddBcfTopic,
|
||||
operator.AddBcfHeaderFile,
|
||||
operator.AddBcfBimSnippet,
|
||||
operator.AddBcfReferenceLink,
|
||||
operator.AddBcfDocumentReference,
|
||||
operator.AddBcfLabel,
|
||||
operator.AddBcfRelatedTopic,
|
||||
operator.ViewBcfTopic,
|
||||
operator.RemoveBcfTopic,
|
||||
operator.RemoveBcfComment,
|
||||
operator.RemoveBcfBimSnippet,
|
||||
operator.RemoveBcfReferenceLink,
|
||||
operator.RemoveBcfDocumentReference,
|
||||
operator.RemoveBcfRelatedTopic,
|
||||
operator.EditBcfReferenceLinks,
|
||||
operator.EditBcfLabels,
|
||||
operator.RemoveBcfLabel,
|
||||
operator.EditBcfComment,
|
||||
operator.AddBcfComment,
|
||||
operator.ActivateBcfViewpoint,
|
||||
operator.AddBcfBimSnippet,
|
||||
operator.AddBcfComment,
|
||||
operator.AddBcfDocumentReference,
|
||||
operator.AddBcfHeaderFile,
|
||||
operator.AddBcfLabel,
|
||||
operator.AddBcfReferenceLink,
|
||||
operator.AddBcfRelatedTopic,
|
||||
operator.AddBcfTopic,
|
||||
operator.AddBcfViewpoint,
|
||||
operator.RemoveBcfViewpoint,
|
||||
operator.RemoveBcfFile,
|
||||
operator.EditBcfAuthor,
|
||||
operator.EditBcfComment,
|
||||
operator.EditBcfLabels,
|
||||
operator.EditBcfProjectName,
|
||||
operator.EditBcfReferenceLinks,
|
||||
operator.EditBcfTopic,
|
||||
operator.EditBcfTopicName,
|
||||
operator.ExtractBcfFile,
|
||||
operator.LoadBcfComments,
|
||||
operator.LoadBcfProject,
|
||||
operator.LoadBcfTopic,
|
||||
operator.LoadBcfTopics,
|
||||
operator.NewBcfProject,
|
||||
operator.OpenBcfReferenceLink,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.RemoveBcfBimSnippet,
|
||||
operator.RemoveBcfComment,
|
||||
operator.RemoveBcfDocumentReference,
|
||||
operator.RemoveBcfFile,
|
||||
operator.RemoveBcfLabel,
|
||||
operator.RemoveBcfReferenceLink,
|
||||
operator.RemoveBcfRelatedTopic,
|
||||
operator.RemoveBcfTopic,
|
||||
operator.RemoveBcfViewpoint,
|
||||
operator.SaveBcfProject,
|
||||
operator.SelectBcfBimSnippetReference,
|
||||
operator.SelectBcfDocumentReference,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.ViewBcfTopic,
|
||||
prop.BcfReferenceLink,
|
||||
prop.BcfLabel,
|
||||
prop.BcfBimSnippet,
|
||||
|
||||
@@ -28,3 +28,7 @@ class BcfStore:
|
||||
if not BcfStore.bcfxml:
|
||||
BcfStore.bcfxml = bcf.v2.bcfxml.BcfXml()
|
||||
return BcfStore.bcfxml
|
||||
|
||||
@classmethod
|
||||
def set(cls, bcfxml):
|
||||
cls.bcfxml = bcfxml
|
||||
|
||||
@@ -20,15 +20,20 @@ import os
|
||||
import bpy
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
import bcf.v2.data
|
||||
import bcf.v2.model
|
||||
import uuid
|
||||
import numpy as np
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from . import bcfstore
|
||||
import blenderbim.bim.module.bcf.prop as bcf_prop
|
||||
from . import bcfstore
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
|
||||
class NewBcfProject(bpy.types.Operator):
|
||||
@@ -38,8 +43,8 @@ class NewBcfProject(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.new_project()
|
||||
bcfxml = bcf.v2.bcfxml.BcfXml.create_new("New Project")
|
||||
bcfstore.BcfStore.set(bcfxml)
|
||||
bpy.ops.bim.load_bcf_project()
|
||||
context.scene.BCFProperties.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
@@ -57,7 +62,6 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
if self.filepath:
|
||||
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_project()
|
||||
context.scene.BCFProperties.name = bcfxml.project.name
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
context.scene.BCFProperties.is_loaded = True
|
||||
@@ -75,7 +79,6 @@ class LoadBcfTopics(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_topics()
|
||||
context.scene.BCFProperties.topics.clear()
|
||||
for index, topic_guid in enumerate(bcfxml.topics.keys()):
|
||||
new = context.scene.BCFProperties.topics.add()
|
||||
@@ -92,51 +95,51 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.get_topic(self.topic_guid)
|
||||
topic = bcfxml.topics[self.topic_guid]
|
||||
bcfxml.get_header(self.topic_guid)
|
||||
new = context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
"name": topic.guid,
|
||||
"title": topic.title,
|
||||
"type": topic.topic_type,
|
||||
"status": topic.topic_status,
|
||||
"priority": topic.priority,
|
||||
"stage": topic.stage,
|
||||
"creation_date": topic.creation_date,
|
||||
"creation_author": topic.creation_author,
|
||||
"modified_date": topic.modified_date,
|
||||
"modified_author": topic.modified_author,
|
||||
"assigned_to": topic.assigned_to,
|
||||
"due_date": topic.due_date,
|
||||
"description": topic.description,
|
||||
"title": topic.topic.title,
|
||||
"type": topic.topic.topic_type,
|
||||
"status": topic.topic.topic_status,
|
||||
"priority": topic.topic.priority,
|
||||
"stage": topic.topic.stage,
|
||||
"creation_date": topic.topic.creation_date,
|
||||
"creation_author": topic.topic.creation_author,
|
||||
"modified_date": topic.topic.modified_date,
|
||||
"modified_author": topic.topic.modified_author,
|
||||
"assigned_to": topic.topic.assigned_to,
|
||||
"due_date": topic.topic.due_date,
|
||||
"description": topic.topic.description,
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
setattr(new, key, str(value))
|
||||
|
||||
new.reference_links.clear()
|
||||
for reference_link in topic.reference_links:
|
||||
for reference_link in topic.topic.reference_link:
|
||||
new_reference_link = new.reference_links.add()
|
||||
new_reference_link.name = reference_link
|
||||
|
||||
new.labels.clear()
|
||||
for label in topic.labels:
|
||||
for label in topic.topic.labels:
|
||||
new_label = new.labels.add()
|
||||
new_label.name = label
|
||||
|
||||
if topic.bim_snippet:
|
||||
if topic.topic.bim_snippet:
|
||||
data_map = {
|
||||
"type": topic.bim_snippet.snippet_type,
|
||||
"is_external": topic.bim_snippet.is_external,
|
||||
"reference": topic.bim_snippet.reference,
|
||||
"schema": topic.bim_snippet.reference_schema,
|
||||
"type": topic.topic.bim_snippet.snippet_type,
|
||||
"is_external": topic.topic.bim_snippet.is_external,
|
||||
"reference": topic.topic.bim_snippet.reference,
|
||||
"schema": topic.topic.bim_snippet.reference_schema,
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
setattr(new.bim_snippet, key, value)
|
||||
|
||||
new.document_references.clear()
|
||||
for doc in topic.document_references:
|
||||
for doc in topic.topic.document_reference:
|
||||
new_document_references = new.document_references.add()
|
||||
data_map = {
|
||||
"reference": doc.referenced_document,
|
||||
@@ -149,7 +152,7 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
setattr(new_document_references, key, value)
|
||||
|
||||
new.related_topics.clear()
|
||||
for related_topic in topic.related_topics:
|
||||
for related_topic in topic.topic.related_topic:
|
||||
new_related_topic = new.related_topics.add()
|
||||
new_related_topic.name = related_topic.guid
|
||||
|
||||
@@ -165,10 +168,9 @@ class LoadBcfComments(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_comments(self.topic_guid)
|
||||
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
|
||||
blender_topic.comments.clear()
|
||||
for comment in bcfxml.topics[self.topic_guid].comments.values():
|
||||
for comment in bcfxml.topics[self.topic_guid].comments:
|
||||
new = blender_topic.comments.add()
|
||||
data_map = {
|
||||
"name": comment.guid,
|
||||
@@ -193,7 +195,6 @@ class EditBcfProjectName(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.project.name = context.scene.BCFProperties.name
|
||||
bcfxml.edit_project()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -219,7 +220,6 @@ class EditBcfTopicName(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.title = blender_topic.title
|
||||
bcfxml.edit_topic(topic)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -243,7 +243,6 @@ class EditBcfTopic(bpy.types.Operator):
|
||||
topic.topic_status = blender_topic.status or None
|
||||
topic.topic_type = blender_topic.type or None
|
||||
|
||||
bcfxml.edit_topic(topic)
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -257,7 +256,7 @@ class SaveBcfProject(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.save_project(self.filepath)
|
||||
bcfxml.save(self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -276,7 +275,7 @@ class AddBcfTopic(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.add_topic()
|
||||
bcfxml.add_topic("New Topic", "", context.scene.BCFProperties.author)
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -300,15 +299,17 @@ class AddBcfBimSnippet(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bim_snippet = bcf.v2.data.BimSnippet()
|
||||
bim_snippet.reference = props.bim_snippet_reference
|
||||
bim_snippet.reference_schema = props.bim_snippet_schema
|
||||
bim_snippet.snippet_type = props.bim_snippet_type
|
||||
bcfxml.add_bim_snippet(topic, bim_snippet)
|
||||
is_external = "://" in props.bim_snippet_reference
|
||||
bim_snippet = bcf.v2.model.BimSnippet(
|
||||
reference=props.bim_snippet_reference if is_external else Path(props.bim_snippet_reference).name,
|
||||
reference_schema=props.bim_snippet_schema,
|
||||
snippet_type=props.bim_snippet_type,
|
||||
is_external=is_external,
|
||||
)
|
||||
topic.topic.bim_snippet = bim_snippet
|
||||
with open(props.bim_snippet_reference, "r") as f:
|
||||
topic.bim_snippet = f.read()
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
bim_snippet.reference = ""
|
||||
bim_snippet.reference_schema = ""
|
||||
bim_snippet.snippet_type = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -327,15 +328,14 @@ class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
if props.related_topic == blender_topic.title:
|
||||
# Prevent adding self as related topic
|
||||
return False
|
||||
related_topic = None
|
||||
related_topic_guid = None
|
||||
for topic in bcfxml.topics.values():
|
||||
if topic.title == props.related_topic:
|
||||
related_topic = bcf.v2.data.RelatedTopic()
|
||||
related_topic.guid = topic.guid
|
||||
if topic.topic.title == props.related_topic:
|
||||
related_topic_guid = topic.guid
|
||||
break
|
||||
if not related_topic:
|
||||
if not related_topic_guid:
|
||||
return False
|
||||
if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]:
|
||||
if str(related_topic_guid) in [t.name for t in blender_topic.related_topics]:
|
||||
# Prevent adding the same related topic more than once
|
||||
return False
|
||||
return True
|
||||
@@ -344,11 +344,12 @@ class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
related_topic = bcf.v2.data.RelatedTopic()
|
||||
related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.related_topics.append(related_topic)
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.related_topic.append(
|
||||
bcf.v2.model.TopicRelatedTopic(
|
||||
guid=next((t for t in bcfxml.topics.values() if t.topic.title == props.related_topic)).guid
|
||||
)
|
||||
)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
props.related_topic = ""
|
||||
return {"FINISHED"}
|
||||
@@ -368,15 +369,24 @@ class AddBcfHeaderFile(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
header_file = bcf.v2.data.HeaderFile()
|
||||
header_file.reference = props.file_reference
|
||||
if not os.path.exists(header_file.reference):
|
||||
header_file.filename = header_file.reference
|
||||
if len(props.file_ifc_project) == 22:
|
||||
header_file.ifc_project = props.file_ifc_project
|
||||
if len(props.file_ifc_spatial_structure_element) == 22:
|
||||
header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element
|
||||
bcfxml.add_file(topic, header_file)
|
||||
|
||||
is_external = "://" in props.file_reference
|
||||
filename = Path(props.file_reference).name if os.path.exists(props.file_reference) else None
|
||||
|
||||
if filename:
|
||||
with open(props.file_reference, "r") as f:
|
||||
topic.reference_files[filename] = f.read()
|
||||
|
||||
header_file = bcf.v2.model.HeaderFile(
|
||||
filename=filename,
|
||||
date=XmlDateTime.now(),
|
||||
reference=props.file_reference if is_external else Path(props.file_reference).name,
|
||||
ifc_project=props.file_ifc_project,
|
||||
ifc_spatial_structure_element=props.file_ifc_spatial_structure_element,
|
||||
is_external=is_external,
|
||||
)
|
||||
topic.header.file.append(header_file)
|
||||
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -410,27 +420,34 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
|
||||
if blender_camera.data.type == "ORTHO":
|
||||
camera = bcf.v2.data.OrthogonalCamera()
|
||||
camera.view_to_world_scale = blender_camera.data.ortho_scale
|
||||
viewpoint.orthogonal_camera = camera
|
||||
elif blender_camera.data.type == "PERSP":
|
||||
camera = bcf.v2.data.PerspectiveCamera()
|
||||
camera.field_of_view = degrees(blender_camera.data.angle)
|
||||
viewpoint.perspective_camera = camera
|
||||
camera.camera_view_point.x = blender_camera.location.x
|
||||
camera.camera_view_point.y = blender_camera.location.y
|
||||
camera.camera_view_point.z = blender_camera.location.z
|
||||
direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
|
||||
camera.camera_direction.x = direction.x
|
||||
camera.camera_direction.y = direction.y
|
||||
camera.camera_direction.z = direction.z
|
||||
up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
|
||||
camera.camera_up_vector.x = up.x
|
||||
camera.camera_up_vector.y = up.y
|
||||
camera.camera_up_vector.z = up.z
|
||||
|
||||
camera_view_point = bcf.v2.model.Point(
|
||||
x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
|
||||
)
|
||||
camera_direction = bcf.v2.model.Direction(x=direction.x, y=direction.y, z=direction.z)
|
||||
camera_up_vector = bcf.v2.model.Direction(x=up.x, y=up.y, z=up.z)
|
||||
if blender_camera.data.type == "ORTHO":
|
||||
camera = bcf.v2.model.OrthogonalCamera(
|
||||
view_to_world_scale=blender_camera.data.ortho_scale,
|
||||
camera_view_point=camera_view_point,
|
||||
camera_direction=camera_direction,
|
||||
camera_up_vector=camera_up_vector,
|
||||
)
|
||||
visualization_info = bcf.v2.model.VisualizationInfo(orthogonal_camera=camera)
|
||||
elif blender_camera.data.type == "PERSP":
|
||||
camera = bcf.v2.model.PerspectiveCamera(
|
||||
field_of_view=degrees(blender_camera.data.angle),
|
||||
camera_view_point=camera_view_point,
|
||||
camera_direction=camera_direction,
|
||||
camera_up_vector=camera_up_vector,
|
||||
)
|
||||
visualization_info = bcf.v2.model.VisualizationInfo(guid=str(uuid.uuid4()), perspective_camera=camera)
|
||||
|
||||
# TODO allow the user to enable or disable snapshotting
|
||||
snapshot = None
|
||||
|
||||
blender_render = context.scene.render
|
||||
old_file_format = blender_render.image_settings.file_format
|
||||
@@ -438,9 +455,17 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
old_filepath = blender_render.filepath
|
||||
blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
viewpoint.snapshot = blender_render.filepath
|
||||
with open(blender_render.filepath, "rb") as f:
|
||||
snapshot = f.read()
|
||||
# viewpoint.snapshot = blender_render.filepath
|
||||
|
||||
# TODO we should handle selection and visibility state
|
||||
vizinfo = bcf.v2.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
|
||||
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
|
||||
topic.markup.viewpoints.append(
|
||||
bcf.v2.model.ViewPoint(viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png")
|
||||
)
|
||||
|
||||
bcfxml.add_viewpoint(topic, viewpoint)
|
||||
blender_render.filepath = old_filepath
|
||||
blender_render.image_settings.file_format = old_file_format
|
||||
props.refresh_topic(context)
|
||||
@@ -460,9 +485,15 @@ class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
viewpoint_guid = blender_topic.viewpoints
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_viewpoint(viewpoint_guid, topic)
|
||||
for key, viewpoint in topic.viewpoints.items():
|
||||
if viewpoint.guid == blender_topic.viewpoints:
|
||||
break
|
||||
del topic.viewpoints[key]
|
||||
for i, viewpoint in enumerate(topic.markup.viewpoints):
|
||||
if viewpoint.guid == blender_topic.viewpoints:
|
||||
break
|
||||
del topic.markup.viewpoints[i]
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -478,7 +509,7 @@ class RemoveBcfFile(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_file(topic, self.index)
|
||||
del topic.header.file[self.index]
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -496,8 +527,7 @@ class RemoveBcfTopic(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
topic_to_delete = props.active_topic
|
||||
bcfxml.delete_topic(topic_to_delete.name)
|
||||
del bcfxml.topics[props.active_topic.name]
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -516,8 +546,7 @@ class AddBcfReferenceLink(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.reference_links.append(props.reference_link)
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.reference_link.append(props.reference_link)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
props.reference_link = ""
|
||||
return {"FINISHED"}
|
||||
@@ -537,10 +566,23 @@ class AddBcfDocumentReference(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
document_reference = bcf.v2.data.DocumentReference()
|
||||
document_reference.referenced_document = props.document_reference
|
||||
document_reference.description = props.document_reference_description or None
|
||||
bcfxml.add_document_reference(topic, document_reference)
|
||||
|
||||
is_external = "://" in props.document_reference
|
||||
filename = Path(props.document_reference).name if os.path.exists(props.document_reference) else None
|
||||
|
||||
if filename:
|
||||
with open(props.document_reference, "rb") as f:
|
||||
topic.document_references[filename] = f.read()
|
||||
|
||||
document_reference = bcf.v2.model.TopicDocumentReference(
|
||||
referenced_document=props.document_reference if is_external else filename,
|
||||
description=props.document_reference_description or None,
|
||||
guid=str(uuid.uuid4()),
|
||||
is_external=is_external,
|
||||
)
|
||||
|
||||
topic.topic.document_reference.append(document_reference)
|
||||
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
props.document_reference = ""
|
||||
props.document_reference_description = ""
|
||||
@@ -563,8 +605,7 @@ class AddBcfLabel(bpy.types.Operator):
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
new = blender_topic.labels.add()
|
||||
new.name = props.label
|
||||
topic.labels.append(props.label)
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.labels.append(props.label)
|
||||
props.label = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -579,11 +620,10 @@ class EditBcfReferenceLinks(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, reference_link in enumerate(topic.reference_links):
|
||||
for index, reference_link in enumerate(topic.topic.reference_link):
|
||||
if reference_link == blender_topic.reference_links[index].name:
|
||||
continue
|
||||
topic.reference_links[index] = blender_topic.reference_links[index].name
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.reference_link[index] = blender_topic.reference_links[index].name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -598,12 +638,11 @@ class EditBcfLabels(bpy.types.Operator):
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, label in enumerate(blender_topic.labels):
|
||||
if index >= len(topic.labels):
|
||||
if index >= len(topic.topic.labels):
|
||||
break
|
||||
if label.name == topic.labels[index]:
|
||||
if label.name == topic.topic.labels[index]:
|
||||
continue
|
||||
topic.labels[index] = blender_topic.labels[index].name
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.labels[index] = blender_topic.labels[index].name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -618,8 +657,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.reference_links[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
del topic.topic.reference_link[self.index]
|
||||
blender_topic.reference_links.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -635,8 +673,7 @@ class RemoveBcfLabel(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.labels[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
del topic.topic.labels[self.index]
|
||||
blender_topic.labels.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -651,7 +688,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_bim_snippet(topic)
|
||||
topic.topic.bim_snippet = None
|
||||
blender_topic.bim_snippet.schema = ""
|
||||
blender_topic.bim_snippet.reference = ""
|
||||
blender_topic.bim_snippet.type = ""
|
||||
@@ -669,7 +706,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_document_reference(topic, self.index)
|
||||
del topic.topic.document_reference[self.index]
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -685,8 +722,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.related_topics[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
del topic.topic.related_topic[self.index]
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -702,7 +738,10 @@ class RemoveBcfComment(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_comment(self.comment_guid, topic)
|
||||
for i, comment in enumerate(topic.comments):
|
||||
if comment.guid == self.comment_guid:
|
||||
break
|
||||
del topic.comments[i]
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -719,9 +758,11 @@ class EditBcfComment(bpy.types.Operator):
|
||||
blender_topic = props.active_topic
|
||||
blender_comment = blender_topic.comments.get(self.comment_guid)
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
comment = topic.comments[self.comment_guid]
|
||||
comment.comment = blender_comment.comment
|
||||
bcfxml.edit_comment(comment, topic)
|
||||
for i, comment in enumerate(topic.comments):
|
||||
if comment.guid == self.comment_guid:
|
||||
comment.comment = blender_comment.comment
|
||||
comment.modified_date = XmlDateTime.now()
|
||||
comment.modified_author = context.scene.BCFProperties.author
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -746,12 +787,15 @@ class AddBcfComment(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
comment = bcf.v2.data.Comment()
|
||||
comment.comment = props.comment
|
||||
comment = bcf.v2.model.Comment(
|
||||
date=XmlDateTime.now(),
|
||||
author=context.scene.BCFProperties.author,
|
||||
comment=props.comment,
|
||||
guid=str(uuid.uuid4()),
|
||||
)
|
||||
if props.has_related_viewpoint:
|
||||
comment.viewpoint = bcf.v2.data.Viewpoint()
|
||||
comment.viewpoint.guid = blender_topic.viewpoints
|
||||
bcfxml.add_comment(topic, comment)
|
||||
comment.viewpoint = bcf.v2.model.CommentViewpoint(guid=blender_topic.viewpoints)
|
||||
topic.comments.append(comment)
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
props.comment = ""
|
||||
props.has_related_viewpoint = False
|
||||
@@ -792,11 +836,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
cam_height = context.scene.render.resolution_y
|
||||
cam_aspect = cam_width / cam_height
|
||||
|
||||
obj.data.background_images.clear()
|
||||
if viewpoint.snapshot:
|
||||
obj.data.show_background_images = True
|
||||
obj.data.background_images.clear()
|
||||
background = obj.data.background_images.new()
|
||||
background.image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write(viewpoint.snapshot)
|
||||
background.image = bpy.data.images.load(f.name)
|
||||
src_width = background.image.size[0]
|
||||
src_height = background.image.size[1]
|
||||
src_aspect = src_width / src_height
|
||||
@@ -806,6 +852,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
else:
|
||||
background.frame_method = "CROP"
|
||||
background.display_depth = "FRONT"
|
||||
else:
|
||||
obj.data.show_background_images = False
|
||||
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
|
||||
@@ -815,27 +864,27 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
gp = bpy.data.grease_pencils.get("BCF")
|
||||
if gp:
|
||||
bpy.data.grease_pencils.remove(gp)
|
||||
if viewpoint.lines:
|
||||
if viewpoint.visualization_info.lines:
|
||||
self.draw_lines(viewpoint, context)
|
||||
|
||||
self.delete_clipping_planes(context)
|
||||
if viewpoint.clipping_planes:
|
||||
if viewpoint.visualization_info.clipping_planes:
|
||||
self.create_clipping_planes(viewpoint)
|
||||
|
||||
self.delete_bitmaps(context)
|
||||
if viewpoint.bitmaps:
|
||||
if viewpoint.visualization_info.bitmap:
|
||||
self.create_bitmaps(bcfxml, viewpoint, topic)
|
||||
|
||||
self.setup_camera(viewpoint, obj, cam_aspect, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def setup_camera(self, viewpoint, obj, cam_aspect, context):
|
||||
if viewpoint.orthogonal_camera:
|
||||
camera = viewpoint.orthogonal_camera
|
||||
if viewpoint.visualization_info.orthogonal_camera:
|
||||
camera = viewpoint.visualization_info.orthogonal_camera
|
||||
obj.data.type = "ORTHO"
|
||||
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale
|
||||
elif viewpoint.perspective_camera:
|
||||
camera = viewpoint.perspective_camera
|
||||
obj.data.ortho_scale = viewpoint.visualization_info.orthogonal_camera.view_to_world_scale
|
||||
elif viewpoint.visualization_info.perspective_camera:
|
||||
camera = viewpoint.visualization_info.perspective_camera
|
||||
obj.data.type = "PERSP"
|
||||
if cam_aspect >= 1:
|
||||
obj.data.angle = radians(camera.field_of_view)
|
||||
@@ -876,15 +925,15 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj.matrix_world = Matrix(matrix.tolist())
|
||||
|
||||
def set_viewpoint_components(self, viewpoint, context):
|
||||
if not viewpoint.components:
|
||||
if not viewpoint.visualization_info.components:
|
||||
return
|
||||
|
||||
# Operators with context overrides are used because they are
|
||||
# significantly faster than looping through all objects
|
||||
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.components.visibility.exceptions}
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions or []}
|
||||
|
||||
if viewpoint.components.visibility.default_visibility:
|
||||
if viewpoint.visualization_info.components.visibility.default_visibility:
|
||||
old = context.area.type
|
||||
context.area.type = "VIEW_3D"
|
||||
bpy.ops.object.hide_view_clear()
|
||||
@@ -908,11 +957,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
bpy.ops.object.hide_view_set(context_override, unselected=True)
|
||||
context.area.type = old
|
||||
|
||||
if viewpoint.components.view_setup_hints:
|
||||
if not viewpoint.components.view_setup_hints.spaces_visible:
|
||||
if viewpoint.visualization_info.components.view_setup_hints:
|
||||
if not viewpoint.visualization_info.components.view_setup_hints.spaces_visible:
|
||||
self.hide_spaces(context)
|
||||
if viewpoint.components.view_setup_hints.openings_visible is not None:
|
||||
self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible, context)
|
||||
if viewpoint.visualization_info.components.view_setup_hints.openings_visible is not None:
|
||||
self.set_openings_visibility(
|
||||
viewpoint.visualization_info.components.view_setup_hints.openings_visible, context
|
||||
)
|
||||
else:
|
||||
self.hide_spaces(context)
|
||||
self.set_openings_visibility(False, context)
|
||||
@@ -928,11 +979,12 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
context.area.type = old
|
||||
|
||||
def set_openings_visibility(self, is_visible, context):
|
||||
for collection in self.get_opening_collections(context):
|
||||
collection.hide_viewport = not is_visible
|
||||
pass # We no longer have an openings collection
|
||||
|
||||
def set_selection(self, viewpoint):
|
||||
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
|
||||
if not viewpoint.visualization_info.components or not viewpoint.visualization_info.components.selection:
|
||||
return
|
||||
selected_global_ids = [s.ifc_guid for s in viewpoint.visualization_info.components.selection.component or []]
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for global_id in selected_global_ids:
|
||||
obj = IfcStore.get_element(global_id)
|
||||
@@ -941,7 +993,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
|
||||
def set_colours(self, viewpoint):
|
||||
global_id_colours = {}
|
||||
for coloring in viewpoint.components.coloring:
|
||||
for coloring in viewpoint.visualization_info.components.coloring or []:
|
||||
for component in coloring.components:
|
||||
global_id_colours.setdefault(component.ifc_guid, coloring.color)
|
||||
for global_id, color in global_id_colours.items():
|
||||
@@ -949,14 +1001,6 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if obj:
|
||||
obj.color = self.hex_to_rgb(color)
|
||||
|
||||
def get_opening_collections(self, context):
|
||||
collections = []
|
||||
for collection in context.view_layer.layer_collection.children:
|
||||
opening_collection = collection.children.get("IfcOpeningElements")
|
||||
if opening_collection:
|
||||
collections.append(opening_collection)
|
||||
return collections
|
||||
|
||||
def draw_lines(self, viewpoint, context):
|
||||
gp = bpy.data.grease_pencils.new("BCF")
|
||||
scene = context.scene
|
||||
@@ -968,9 +1012,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
frame = layer.frames.new(1)
|
||||
stroke = frame.strokes.new()
|
||||
stroke.display_mode = "3DSPACE"
|
||||
stroke.points.add(len(viewpoint.lines) * 2)
|
||||
stroke.points.add(len(viewpoint.visualization_info.lines.line) * 2)
|
||||
coords = []
|
||||
for l in viewpoint.lines:
|
||||
for l in viewpoint.visualization_info.lines.line:
|
||||
coords.extend(
|
||||
[l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]
|
||||
)
|
||||
@@ -978,7 +1022,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
|
||||
def create_clipping_planes(self, viewpoint):
|
||||
n = 0
|
||||
for plane in viewpoint.clipping_planes:
|
||||
for plane in viewpoint.visualization_info.clipping_planes.clipping_plane:
|
||||
bpy.ops.bim.add_section_plane()
|
||||
if n == 0:
|
||||
obj = bpy.data.objects["Section"]
|
||||
@@ -1011,10 +1055,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
collection = bpy.data.collections.get("Bitmaps")
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new("Bitmaps")
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
for bitmap in viewpoint.visualization_info.bitmap:
|
||||
obj = bpy.data.objects.new("Bitmap", None)
|
||||
obj.empty_display_type = "IMAGE"
|
||||
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
|
||||
# image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
topic.extract_file(bitmap, f.name)
|
||||
# f.write(bitmap.what)
|
||||
image = bpy.data.images.load(f.name)
|
||||
src_width = image.size[0]
|
||||
src_height = image.size[1]
|
||||
if src_height > src_width:
|
||||
@@ -1053,6 +1101,7 @@ class SelectBcfHeaderFile(bpy.types.Operator):
|
||||
bl_label = "Select BCF Header File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
@@ -1094,3 +1143,25 @@ class SelectBcfDocumentReference(bpy.types.Operator):
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExtractBcfFile(bpy.types.Operator):
|
||||
bl_idname = "bim.extract_bcf_file"
|
||||
bl_label = "Extract BCF Header File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
entity_type: bpy.props.StringProperty()
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
|
||||
|
||||
if self.entity_type == "HEADER_FILE":
|
||||
entity = topic.header.file[self.index]
|
||||
elif self.entity_type == "BIM_SNIPPET":
|
||||
entity = topic.markup.topic.bim_snippet
|
||||
elif self.entity_type == "DOCUMENT_REFERENCE":
|
||||
entity = topic.markup.topic.document_reference[self.index]
|
||||
|
||||
webbrowser.open(str(topic.extract_file(entity).parent))
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -95,8 +95,8 @@ def getBcfViewpoints(self, context, force_update=False):
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.active_topic
|
||||
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {}
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
|
||||
viewpoints = bcfxml.topics[topic.name].viewpoints.keys() if topic else []
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints)])
|
||||
return bcfviewpoints_enum
|
||||
|
||||
|
||||
|
||||
@@ -127,20 +127,21 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
layout.label(text="Header Files:")
|
||||
|
||||
if bcf_topic.header:
|
||||
for index, f in enumerate(bcf_topic.header.files):
|
||||
for index, f in enumerate(bcf_topic.header.file):
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=f.filename, icon="FILE_BLANK")
|
||||
if f.is_external:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
|
||||
else:
|
||||
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
|
||||
op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference)
|
||||
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.entity_type = "HEADER_FILE"
|
||||
row.operator("bim.remove_bcf_file", icon="X", text="").index = index
|
||||
|
||||
row = box.row()
|
||||
row.label(text="Date")
|
||||
row.label(text=f.date)
|
||||
row.label(text=str(f.date))
|
||||
|
||||
if f.ifc_project:
|
||||
row = box.row()
|
||||
@@ -196,8 +197,8 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
if topic.bim_snippet.is_external:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
|
||||
else:
|
||||
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
|
||||
op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference)
|
||||
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
|
||||
op.entity_type = "BIM_SNIPPET"
|
||||
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
@@ -218,8 +219,10 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
if doc.is_external:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
|
||||
else:
|
||||
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
|
||||
op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference)
|
||||
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
|
||||
op.entity_type = "DOCUMENT_REFERENCE"
|
||||
op.index = index
|
||||
|
||||
row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index
|
||||
row = box.row(align=True)
|
||||
row.prop(doc, "description")
|
||||
@@ -236,7 +239,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
try:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator(
|
||||
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].title}"
|
||||
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].topic.title}"
|
||||
)
|
||||
op.topic_guid = related_topic.name
|
||||
row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
|
||||
|
||||
@@ -20,18 +20,21 @@ import bpy
|
||||
from . import ui, operator, prop
|
||||
|
||||
classes = (
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.LoadProjectSpaceBoundaries,
|
||||
operator.LoadSpaceBoundaries,
|
||||
operator.LoadBoundary,
|
||||
operator.SelectRelatedElementBoundaries,
|
||||
operator.SelectProjectBoundaries,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.SelectRelatedElementTypeBoundaries,
|
||||
operator.SelectSpaceBoundaries,
|
||||
operator.UpdateBoundaryGeometry,
|
||||
ui.BIM_PT_Boundary,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
ui.BIM_PT_SceneBoundaries,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
prop.BIMBoundaryProperties,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 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
|
||||
|
||||
|
||||
def refresh():
|
||||
SpaceBoundariesData.is_loaded = False
|
||||
|
||||
|
||||
class SpaceBoundariesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"boundaries": cls.boundaries()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def boundaries(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
for rel in element.BoundedBy or []:
|
||||
description = f"{rel.id()} > {rel.RelatedBuildingElement.is_a()}/{rel.RelatedBuildingElement.Name}"
|
||||
results.append({"id": rel.id(), "description": description})
|
||||
return results
|
||||
@@ -17,11 +17,14 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import mathutils
|
||||
import logging
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
|
||||
|
||||
@@ -40,8 +43,8 @@ class Loader:
|
||||
self.ifc_file = None
|
||||
self.logger = None
|
||||
self.ifc_importer = None
|
||||
self.settings = None
|
||||
self.load_settings()
|
||||
self.settings = self.load_settings()
|
||||
self.fallback_settings = self.load_fallback_settings()
|
||||
self.load_importer()
|
||||
|
||||
def create_mesh(self, boundary):
|
||||
@@ -49,18 +52,50 @@ class Loader:
|
||||
if not boundary.ConnectionGeometry:
|
||||
return None
|
||||
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
# workaround for invalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
|
||||
# Workaround for invalid geometry provided by Revit. See https://github.com/Autodesk/revit-ifc/issues/270
|
||||
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
|
||||
surface.InnerBoundaries = ()
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
self.ifc_importer.link_mesh(shape, mesh)
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
except RuntimeError:
|
||||
# Fallback solution for invalid geometry provided by Revit. (InnerBoundaries cuting OuterBoundary)
|
||||
print(f"Failed to create mesh from IfcRelSpaceBoundary with ID {boundary.id()}. Geometry might be invalid")
|
||||
shape = ifcopenshell.geom.create_shape(self.fallback_settings, surface.OuterBoundary)
|
||||
mesh = bpy.data.meshes.new(str(surface.id()))
|
||||
bm = bmesh.new()
|
||||
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
||||
bm.faces.new(verts)
|
||||
for inner_boundary in surface.InnerBoundaries:
|
||||
shape = ifcopenshell.geom.create_shape(self.fallback_settings, inner_boundary)
|
||||
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
||||
for i in range(len(verts) - 1):
|
||||
bm.edges.new(verts[i : i + 2])
|
||||
bm.edges.new((verts[-1], verts[0]))
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
mesh.BIMMeshProperties.ifc_definition_id = surface.id()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
matrix = mathutils.Matrix(
|
||||
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
|
||||
)
|
||||
matrix[0][3] *= unit_scale
|
||||
matrix[1][3] *= unit_scale
|
||||
matrix[2][3] *= unit_scale
|
||||
mesh.transform(matrix)
|
||||
return mesh
|
||||
|
||||
def load_settings(self):
|
||||
self.settings = ifcopenshell.geom.settings()
|
||||
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
|
||||
self.settings.set(self.settings.USE_BREP_DATA, False)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
|
||||
settings.set(settings.USE_BREP_DATA, False)
|
||||
return settings
|
||||
|
||||
def load_fallback_settings(self):
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
return settings
|
||||
|
||||
def load_importer(self):
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
@@ -69,8 +104,7 @@ class Loader:
|
||||
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
self.ifc_importer.file = self.ifc_file
|
||||
|
||||
def load_boundary(self, boundary_id, blender_space):
|
||||
boundary = self.ifc_file.by_id(boundary_id)
|
||||
def load_boundary(self, boundary, blender_space):
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
if obj:
|
||||
return obj
|
||||
@@ -90,12 +124,8 @@ class LoadProjectSpaceBoundaries(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
loader = Loader()
|
||||
if not Data.is_loaded:
|
||||
Data.load(tool.Ifc.get())
|
||||
for space_id, boundaries_id in Data.spaces.items():
|
||||
blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
|
||||
for boundary_id in boundaries_id:
|
||||
loader.load_boundary(boundary_id, blender_space)
|
||||
for rel in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
|
||||
loader.load_boundary(rel, tool.Ifc.get_object(rel.RelatingSpace))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -107,10 +137,9 @@ class LoadBoundary(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
loader = Loader()
|
||||
blender_space = context.active_object
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
obj = loader.load_boundary(self.boundary_id, blender_space)
|
||||
obj = loader.load_boundary(tool.Ifc.get().by_id(self.boundary_id), context.active_object)
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
return {"FINISHED"}
|
||||
@@ -122,13 +151,74 @@ class LoadSpaceBoundaries(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
blender_space = context.active_object
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
loader = Loader()
|
||||
if not Data.is_loaded:
|
||||
Data.load(tool.Ifc.get())
|
||||
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
|
||||
loader.load_boundary(boundary_id, blender_space)
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
for rel in element.BoundedBy or []:
|
||||
loader.load_boundary(rel, context.active_object)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def get_element_boundaries(element):
|
||||
if not element:
|
||||
return ()
|
||||
if element.is_a("IfcSpace"):
|
||||
return (b for b in element.BoundedBy or ())
|
||||
return (b for b in element.ProvidesBoundaries)
|
||||
|
||||
|
||||
class SelectRelatedElementBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_related_element_boundaries"
|
||||
bl_label = "Select related element space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_element: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get().by_id(self.related_element)
|
||||
for rel in get_element_boundaries(element):
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectRelatedElementTypeBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_related_element_type_boundaries"
|
||||
bl_label = "Select related element type space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_element: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get().by_id(self.related_element)
|
||||
if not element:
|
||||
return {"FINISHED"}
|
||||
element_type = tool.Root.get_element_type(element)
|
||||
if not element_type:
|
||||
return {"FINISHED"}
|
||||
for child in tool.Type.get_type_occurrences(element_type):
|
||||
for rel in get_element_boundaries(child):
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_space_boundaries"
|
||||
bl_label = "Select all space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
for rel in element.BoundedBy or []:
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -140,10 +230,8 @@ class SelectProjectBoundaries(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
ifc_file = tool.Ifc.get()
|
||||
for boundary_id in Data.boundaries:
|
||||
boundary = ifc_file.by_id(boundary_id)
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
for rel in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
@@ -184,11 +272,10 @@ class ColourByRelatedBuildingElement(bpy.types.Operator):
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element.is_a("IfcRelSpaceBoundary"):
|
||||
continue
|
||||
obj.color = get_colour(element)
|
||||
@@ -285,6 +372,7 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
tool.Boundary.move_origin_to_space_origin(context.active_object)
|
||||
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -21,7 +21,7 @@ import blenderbim.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
from blenderbim.bim.module.boundary.data import SpaceBoundariesData
|
||||
|
||||
|
||||
class BIM_PT_SceneBoundaries(Panel):
|
||||
@@ -67,7 +67,6 @@ class BIM_PT_Boundary(Panel):
|
||||
def draw(self, context):
|
||||
props = context.active_object.BIMObjectProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
element = ifc_file.by_id(props.ifc_definition_id)
|
||||
boundary = ifc_file.by_id(props.ifc_definition_id)
|
||||
self.bprops = context.active_object.bim_boundary_properties
|
||||
if self.bprops.is_editing:
|
||||
@@ -101,8 +100,13 @@ class BIM_PT_Boundary(Panel):
|
||||
entity = getattr(boundary, ifc_attribute)
|
||||
if entity:
|
||||
row.label(text=f"{entity.is_a()}/{entity.Name}")
|
||||
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
|
||||
op = row.operator("bim.select_global_id", text="", icon="OBJECT_DATA")
|
||||
op.global_id = entity.GlobalId
|
||||
if ifc_attribute in ("RelatingSpace", "RelatedBuildingElement"):
|
||||
op = row.operator("bim.select_related_element_boundaries", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.related_element = entity.id()
|
||||
op = row.operator("bim.select_related_element_type_boundaries", text="", icon="ASSET_MANAGER")
|
||||
op.related_element = entity.id()
|
||||
else:
|
||||
row.label(text="")
|
||||
|
||||
@@ -130,28 +134,23 @@ class BIM_PT_SpaceBoundaries(Panel):
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
|
||||
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
|
||||
for ifc_class in valid_classes:
|
||||
if entity.is_a(ifc_class):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
for ifc_class in ("IfcSpace", "IfcExternalSpatialElement"):
|
||||
if element.is_a(ifc_class):
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not SpaceBoundariesData.is_loaded:
|
||||
SpaceBoundariesData.load()
|
||||
|
||||
self.props = context.active_object.BIMObjectProperties
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_space_boundaries")
|
||||
if not Data.is_loaded:
|
||||
Data.load(self.ifc_file)
|
||||
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
|
||||
boundary_data = Data.boundaries[boundary_id]
|
||||
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
|
||||
row.operator("bim.select_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
|
||||
for boundary in SpaceBoundariesData.data["boundaries"]:
|
||||
row = self.layout.row()
|
||||
if building_element:
|
||||
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
|
||||
else:
|
||||
bld_el_description = None
|
||||
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
|
||||
row.label(text=boundary["description"], icon="GHOST_ENABLED")
|
||||
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.boundary_id = boundary_id
|
||||
op.boundary_id = boundary["id"]
|
||||
|
||||
@@ -51,7 +51,6 @@ class BIM_PT_brickschema(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
prop_with_search(row, self.props, "brick_equipment_class", text="")
|
||||
row.prop(self.props, "brick_equipment_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
@@ -51,9 +52,14 @@ class CadTool(WorkSpaceTool):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
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")
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
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")
|
||||
else:
|
||||
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")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
@@ -180,7 +186,10 @@ class CadHotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance)
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
if tool.Ifc.get_entity(bpy.context.active_object):
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
else:
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if self.is_profile():
|
||||
|
||||
@@ -22,6 +22,7 @@ import json
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from mathutils import Matrix
|
||||
from math import radians
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -32,6 +33,7 @@ class ExportClashSets(bpy.types.Operator):
|
||||
bl_label = "Export Clash Sets"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
@@ -64,6 +66,7 @@ class ImportClashSets(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
@@ -194,11 +197,12 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
|
||||
class ExecuteIfcClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
filename_ext = ".bcf"
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if ".json" not in bpy.data.filepath:
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
@@ -207,9 +211,10 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
from ifcclash import ifcclash
|
||||
|
||||
settings = ifcclash.ClashSettings()
|
||||
if ".json" not in self.filepath:
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
settings = ifcclash.ClashSettings()
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
@@ -271,6 +276,7 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
@@ -284,10 +290,25 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
for clash in clash_set["clashes"].values():
|
||||
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
|
||||
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
ifc_file = ""
|
||||
for scene in obj.users_scene:
|
||||
if scene.BIMProperties.ifc_file:
|
||||
ifc_file = scene.BIMProperties.ifc_file
|
||||
break
|
||||
|
||||
if ifc_file:
|
||||
if ifc_file not in IfcStore.session_files:
|
||||
IfcStore.session_files[ifc_file] = ifcopenshell.open(ifc_file)
|
||||
element_file = IfcStore.session_files[ifc_file]
|
||||
else:
|
||||
element_file = self.file
|
||||
|
||||
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.GlobalId in global_ids:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
@@ -304,14 +325,14 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
return context.scene.BIMClashProperties.clash_results_path
|
||||
|
||||
def execute(self, context):
|
||||
import ifcclash
|
||||
from ifcclash import ifcclash
|
||||
|
||||
settings = ifcclash.IfcClashSettings()
|
||||
settings = ifcclash.ClashSettings()
|
||||
self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
ifc_clasher = ifcclash.IfcClasher(settings)
|
||||
ifc_clasher = ifcclash.Clasher(settings)
|
||||
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
|
||||
@@ -65,12 +65,13 @@ class ClassificationsData:
|
||||
|
||||
class ReferencesData:
|
||||
@classmethod
|
||||
def is_available_classification_added(cls):
|
||||
def active_classification_library(cls):
|
||||
if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"):
|
||||
return False
|
||||
props = bpy.context.scene.BIMClassificationProperties
|
||||
name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
|
||||
return name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]
|
||||
if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]:
|
||||
return name
|
||||
|
||||
|
||||
class ClassificationReferencesData(ReferencesData):
|
||||
@@ -81,7 +82,7 @@ class ClassificationReferencesData(ReferencesData):
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
@@ -103,7 +104,7 @@ class MaterialClassificationsData(ReferencesData):
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
@@ -125,7 +126,7 @@ class CostClassificationsData(ReferencesData):
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user