mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
635 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cf07a910e | |||
| 4184cff1a0 | |||
| 67740a5f79 | |||
| ecfe8c3799 | |||
| 7403d14001 | |||
| 2b63698d7c | |||
| e0cd74d0ad | |||
| 4fe427558f | |||
| b59a9f5264 | |||
| f25fb0db51 | |||
| bfaa89cdec | |||
| 9625200a2a | |||
| 5562c9d2b5 | |||
| 7c0405fab0 | |||
| 557f8e6d69 | |||
| 2137b34063 | |||
| 86a44c21ac | |||
| a427462646 | |||
| b79d23b3e2 | |||
| 2af1ebc614 | |||
| d2e421382b | |||
| 7b22f9c85b | |||
| e06591bbbd | |||
| 110daf4ca7 | |||
| 6bf692b44d | |||
| c257afbd1c | |||
| cdf18e9f20 | |||
| c715f41972 | |||
| d1e51fb05b | |||
| 99f72a8451 | |||
| 2537349ec2 | |||
| 10b6253a36 | |||
| d88c4fd377 | |||
| 0596e2000c | |||
| b685610900 | |||
| 5cab72deaa | |||
| 52f15d27b1 | |||
| cfa0575263 | |||
| f5e089e0fd | |||
| 5d246a8716 | |||
| 1f241912f2 | |||
| f7765c531b | |||
| 3b401ec28f | |||
| 77206cb761 | |||
| f8e9f81b9b | |||
| 186c4dd118 | |||
| aa9b6b61fa | |||
| 6337d04efc | |||
| c00bcc78e9 | |||
| 07f19ad80a | |||
| 8ca6183313 | |||
| 492df6845b | |||
| 43f8d8547d | |||
| 6e3c72707d | |||
| 08d6c0a2d8 | |||
| 8c1eff5c26 | |||
| fe3ffb7771 | |||
| 6660714308 | |||
| 2420ad2fa7 | |||
| 16fd06a4e9 | |||
| beb5dda6dd | |||
| c04afe1a4b | |||
| d2a1e0193b | |||
| f2f4057c98 | |||
| 347aae9696 | |||
| 1a2d0d702b | |||
| e791449d3d | |||
| 8071e079b3 | |||
| 1c71af058f | |||
| c4f3633ab8 | |||
| c40fd91730 | |||
| a7252273b4 | |||
| 6e0a443cca | |||
| 4574887709 | |||
| f14d349be0 | |||
| 023eb719f5 | |||
| 8ba1b1c9b4 | |||
| 7cb163efef | |||
| e68c57e9ef | |||
| 7d17b3170c | |||
| a968f2b04d | |||
| 9a14406f92 | |||
| 32062e4085 | |||
| 8da17cf4a0 | |||
| 2d6e941c41 | |||
| 0fffbac519 | |||
| db62faa5ae | |||
| 02dc7b5274 | |||
| 1bf136e93c | |||
| cccab9e751 | |||
| 99e74533c3 | |||
| 0f39b09396 | |||
| ac2324a65f | |||
| ca85927a51 | |||
| e3786423c2 | |||
| 8d352ea07a | |||
| fd04066940 | |||
| d34956c2c7 | |||
| ad2585c571 | |||
| a08cf23d63 | |||
| 37fe04ffd2 | |||
| 248b21eab3 | |||
| 63f578315d | |||
| 19b6765994 | |||
| 7456c270ff | |||
| b480b43435 | |||
| 3614587556 | |||
| 86ce367ba8 | |||
| d320c1dac1 | |||
| cf48661a35 | |||
| 5e03846783 | |||
| acddbfd18c | |||
| 791f57072e | |||
| 078f7b3dae | |||
| c02f270993 | |||
| a73178fe3e | |||
| 24808ced60 | |||
| 1d523c9636 | |||
| 6ccb208345 | |||
| af0f0a4f6c | |||
| edd737aa5d | |||
| 5d8f71128e | |||
| 1ea4f50d30 | |||
| 93fad2a5c3 | |||
| 30a03b0c51 | |||
| b0bb2cb58d | |||
| 3837c9d6ff | |||
| d189113b98 | |||
| 6dba96e780 | |||
| c2bdfa9394 | |||
| 519a53dc61 | |||
| 21c4433c75 | |||
| 0e24d4411f | |||
| cb6837b0ac | |||
| 5ec7f65df5 | |||
| db04cc1bb5 | |||
| 1f128fd7e4 | |||
| 56633b8126 | |||
| 6706ad9bd5 | |||
| fb6c670aa6 | |||
| a87637163a | |||
| 4bc5a3ba46 | |||
| 866df1f040 | |||
| 2fa2fddf46 | |||
| 1aeae0bf4d | |||
| 6a2c4aee46 | |||
| 5cfbcf211b | |||
| 3dcd10b478 | |||
| c687e9af05 | |||
| b10c228155 | |||
| b1d731447a | |||
| 32e7083e21 | |||
| 597b5ffbd6 | |||
| f7e550b926 | |||
| 75d67cd719 | |||
| 41a47f14ae | |||
| cca28231ee | |||
| d7b3a1d95a | |||
| 30ba45f6cb | |||
| d832c9780a | |||
| 7f3b7f4e21 | |||
| dc7dea21ce | |||
| d6a9b73657 | |||
| 97cd919a7b | |||
| 41bc109556 | |||
| 4f5d4def1e | |||
| 3a24f03bed | |||
| f3ed96846e | |||
| cbe8165e08 | |||
| 6a7a122de2 | |||
| e44db61fbd | |||
| 72b60e85d4 | |||
| c48263ade9 | |||
| 5ec081c6bc | |||
| cc3f94097c | |||
| 253d478fdc | |||
| 549bc0e1a9 | |||
| 9286de4813 | |||
| b8015644e9 | |||
| 8a917dc050 | |||
| 68b6389794 | |||
| 85bd616f6e | |||
| 4ff60553cc | |||
| dcd6cace03 | |||
| b7cfa53b4d | |||
| e0048f7a3c | |||
| a8dcb2f306 | |||
| a8f0a4807c | |||
| d5539399e8 | |||
| ad8542f244 | |||
| eb89308019 | |||
| e7e7b22fd5 | |||
| b4a422c2b8 | |||
| 30f778e0a7 | |||
| 2032b361c8 | |||
| 529b3a9867 | |||
| cac8c64ac8 | |||
| 3847c9bd7a | |||
| ffbaa6f0e0 | |||
| b5c3812849 | |||
| 30ad915649 | |||
| c2cf3e8639 | |||
| c1527b4199 | |||
| a1032a3bf0 | |||
| 733b7c0e1e | |||
| 6b0a58db7d | |||
| 10bf875e92 | |||
| 2fd2b49709 | |||
| 37a61c549f | |||
| da10a1060e | |||
| ebfb29d6da | |||
| 6e4e6a88a4 | |||
| 312de694b6 | |||
| 788a5a829e | |||
| a4391df9ca | |||
| 1071ac00d4 | |||
| 6ecedaa128 | |||
| 8f154c753c | |||
| e863015e85 | |||
| 2c49b3bde8 | |||
| eaa527f512 | |||
| 99f0e6c93e | |||
| f27fbecc9a | |||
| 650ebeddd6 | |||
| 5d0d3aaa16 | |||
| 54526960c8 | |||
| e0b93a70d3 | |||
| e569b97c56 | |||
| a0b7fe2736 | |||
| 3b270a61df | |||
| 5b94043cdc | |||
| aadb28bb79 | |||
| 2f1f332d31 | |||
| 1ee2f420c8 | |||
| 567bc7b667 | |||
| 2a2f4d692a | |||
| 1b49a10a15 | |||
| a08918fd22 | |||
| 50608d9459 | |||
| 97d0286b57 | |||
| 2a7fbea9c3 | |||
| 257997c2cb | |||
| 2dfc35877c | |||
| 68233a8fb2 | |||
| 76e2f688e5 | |||
| 4d4bedcf9a | |||
| 36a18ea0f9 | |||
| fa1984e89e | |||
| 3be0a74316 | |||
| 90cb56220f | |||
| 8fd4965106 | |||
| ba26772e46 | |||
| 7e02eb923d | |||
| 459149e418 | |||
| 768606571e | |||
| 65fa5768f2 | |||
| 9db01bc6cc | |||
| 4e60ca01c3 | |||
| 8c9ab98697 | |||
| fc05bdf6d6 | |||
| 4c50b2d224 | |||
| 4017097f19 | |||
| b9472c9bae | |||
| 7d841c3f27 | |||
| ca0dac1353 | |||
| 1a962a7f60 | |||
| f3c1d1df31 | |||
| 6a0562d00a | |||
| 137a00225c | |||
| 5a822d5e39 | |||
| d844223367 | |||
| 6a29ab0d61 | |||
| e9c7b21bfc | |||
| ed316ba691 | |||
| cfd5742b5b | |||
| feac601340 | |||
| a8dc734351 | |||
| e0cbbf2eb7 | |||
| 082ee3c95d | |||
| ece6e6c8ca | |||
| 5c1d26ac91 | |||
| 153b6ee24d | |||
| 8b3b1eeb8e | |||
| 8e279ea862 | |||
| 6f8861c030 | |||
| 2ac6fe9eef | |||
| 8338cc72db | |||
| 67b636f5fb | |||
| 3a754c8ecc | |||
| d64d7cf27a | |||
| 6ff4c95ac7 | |||
| 0b6af5f848 | |||
| 3ded4dff5f | |||
| 452631b997 | |||
| c041dd13ad | |||
| 994ccb94f4 | |||
| c7ba643ef7 | |||
| 0b6f3113a7 | |||
| 730efcae49 | |||
| 62b297757e | |||
| 0533a00631 | |||
| 7fe2e93dea | |||
| acc9bd736f | |||
| 478612b6ae | |||
| a24d683613 | |||
| 0b5d7cdbf3 | |||
| b8e20ec1a5 | |||
| 1cd243f13c | |||
| 0a2adbb632 | |||
| 8882c99455 | |||
| 7890ded698 | |||
| 7d2d9b2bcf | |||
| 75ae501ff9 | |||
| 880fbe9533 | |||
| c2684f4fbf | |||
| afdc1e6679 | |||
| 8935a17844 | |||
| 93c59e81c6 | |||
| d423274e62 | |||
| 7958dd837b | |||
| e42a638220 | |||
| f5f314a58f | |||
| 96465bed8f | |||
| 41bdeea981 | |||
| 14e0910e59 | |||
| e0ba1c2183 | |||
| a13b71abf0 | |||
| 32fb359ee2 | |||
| 61661fe6f3 | |||
| 92054b1999 | |||
| 22d9bce591 | |||
| bfe9213f67 | |||
| 1abf854f01 | |||
| 686b2a187d | |||
| 7c2764c98a | |||
| f73a3a8f79 | |||
| b9723c08e4 | |||
| 1e80e67df5 | |||
| 3e8c10b775 | |||
| fd9f209a19 | |||
| 6b72f7322b | |||
| 9b0d9c1f91 | |||
| 26b699be7c | |||
| 294eba24c4 | |||
| 09094c090c | |||
| a0dad0802e | |||
| 97244d1919 | |||
| 1541cbbbb8 | |||
| 82692bb88d | |||
| 77a97233c5 | |||
| 4214a449a8 | |||
| b650c045f5 | |||
| 851ab07192 | |||
| 20a36c39fc | |||
| 38544e2ba9 | |||
| 41a88bcd4d | |||
| 966850c54a | |||
| 319fe13f9e | |||
| 0d5110b7af | |||
| de873461e1 | |||
| cbce3cbec0 | |||
| 0822d1d377 | |||
| e6bf32b832 | |||
| 30c31d694a | |||
| 65c2f9a038 | |||
| 2d42846fa3 | |||
| d3bb895c9d | |||
| aeead1c011 | |||
| ba742d5976 | |||
| 27750e0caf | |||
| 5b02d3214d | |||
| 0f55a98b2f | |||
| 13209b6d03 | |||
| 411855495e | |||
| 79ebbb82ef | |||
| 647a96a655 | |||
| 66d4665498 | |||
| 537efdcb53 | |||
| f2352e9a17 | |||
| e0b3a87fdb | |||
| ff06f452d5 | |||
| 5aec0718f4 | |||
| fdcafa98b3 | |||
| 1776b19c07 | |||
| e1d1dca518 | |||
| e8ab4dcc14 | |||
| 41443d1a64 | |||
| 13c642c6ab | |||
| c40f07f9b5 | |||
| ae7d123aa2 | |||
| 7c6e7996ff | |||
| 6ec23faf26 | |||
| 81ad689478 | |||
| b3c918f15f | |||
| de7ddd9b97 | |||
| 66862a6cba | |||
| 4e014d1a10 | |||
| 3b33977acc | |||
| 5334d2b754 | |||
| 75ed4dcdd8 | |||
| 0276c8271e | |||
| beb813cb21 | |||
| 35ceba2e88 | |||
| 64b8eee077 | |||
| 229ba95d4b | |||
| 3f9e5ca806 | |||
| cf68cf6f5c | |||
| 34386ea27c | |||
| ce76d27acc | |||
| 353281fa4d | |||
| dd6f593e26 | |||
| a9228c0800 | |||
| 5e52abb3cf | |||
| 8384235e10 | |||
| d98ac8ce89 | |||
| 1b4b2a526d | |||
| b1ed85adda | |||
| 02bfbf0144 | |||
| 3224c4fc77 | |||
| 19e67613db | |||
| e98771ae09 | |||
| 58663769c9 | |||
| 28081f7df8 | |||
| a80fbdbc1d | |||
| 46bc688f26 | |||
| f9b79b4beb | |||
| 15927dd501 | |||
| 7919c193b8 | |||
| 6492b93c69 | |||
| 992474fbf2 | |||
| 8a3e023408 | |||
| a13b257452 | |||
| 3f9842e358 | |||
| 764b880ea8 | |||
| a10f4c070a | |||
| 114214e2a1 | |||
| d19d4fca3e | |||
| a031bffe6c | |||
| 6ff792d75c | |||
| 6b3393c003 | |||
| da5a2625a9 | |||
| a1eb2a1ec3 | |||
| 403412e088 | |||
| ee46e76340 | |||
| 925c40c6ba | |||
| dea844dcf6 | |||
| 018d754b2e | |||
| a05d017341 | |||
| f63479c823 | |||
| 8a8bdeeaa3 | |||
| 6b46ff71a6 | |||
| d73a5c46ea | |||
| 037261afb9 | |||
| be87b17909 | |||
| 5419d72a6a | |||
| 9ca1e011e5 | |||
| 932a21309f | |||
| 3b806a5466 | |||
| 7a1b35f7e2 | |||
| 79dcf809ed | |||
| 2f53e43b90 | |||
| cdc9180111 | |||
| a65a34cfe7 | |||
| 2e0d5fa707 | |||
| de40049c9c | |||
| 29fa5bdffa | |||
| d55ad90319 | |||
| 90ece8a058 | |||
| a936a5317d | |||
| f5e5ede0fa | |||
| 2d1ddea3d0 | |||
| df932b90d6 | |||
| 2514e93a8d | |||
| 64e6561828 | |||
| 76685d236c | |||
| fd83564af0 | |||
| 37e3a65ffb | |||
| e5cf0cde2d | |||
| 78e4478242 | |||
| 3497022c78 | |||
| 810d36781c | |||
| f8eb1c178a | |||
| b726ba7816 | |||
| 4ab23d231d | |||
| 00694854af | |||
| fa52ed6dbc | |||
| 319b59a266 | |||
| 357fcded71 | |||
| c3cc5881df | |||
| ca34c95057 | |||
| 5078db8b26 | |||
| 56046dd6f9 | |||
| 9164a7bcc3 | |||
| e0a5c9c8f3 | |||
| 22e087c82a | |||
| 57396e1bb9 | |||
| 389076bff4 | |||
| bfcc067257 | |||
| f88ac5de62 | |||
| cdd456bede | |||
| 7041d17c16 | |||
| de842f7aba | |||
| 6107c39c1c | |||
| 9584687fcf | |||
| b231e9051b | |||
| 40f9875930 | |||
| 328773550d | |||
| 303544dc5c | |||
| 1c66f986cc | |||
| e801629f86 | |||
| 74bbd6d116 | |||
| e06794ef3d | |||
| cd7062c905 | |||
| 42d476cbb0 | |||
| cd0d3908d3 | |||
| cb70f97789 | |||
| 572ed0db0f | |||
| 43d78af767 | |||
| c7aa42bfd5 | |||
| 3104743a02 | |||
| 18d7193efc | |||
| 8da92557cc | |||
| 761b4c035f | |||
| ee0f7f1677 | |||
| dca2e58586 | |||
| 3810255f04 | |||
| d3ca647cd5 | |||
| 8b35a3aadd | |||
| 78314b1ec7 | |||
| ef88b30639 | |||
| 5e815e7832 | |||
| e7b153cbf6 | |||
| 9b855bed2d | |||
| 3143f8c9a5 | |||
| 8dd781aea2 | |||
| 0506475f26 | |||
| ac092fb6f8 | |||
| 9beddca2f7 | |||
| 6d9770542f | |||
| 544d6ee695 | |||
| b8963b3d6e | |||
| f2bd46d699 | |||
| 8ac8ad33b3 | |||
| 44bbd17d97 | |||
| 8f0e67a35b | |||
| d2d4112724 | |||
| 209eea6a18 | |||
| 85426b3fef | |||
| c602f0f233 | |||
| bb6312e174 | |||
| d6d70af5a0 | |||
| 8187c75183 | |||
| 01cfcebb09 | |||
| 75040b6ffb | |||
| 6c8858e51d | |||
| 6bb63817af | |||
| 1e299a0ddd | |||
| 3201c234fa | |||
| f5d27621e7 | |||
| 3e1eb9648f | |||
| 5f9474acb7 | |||
| f4e6e924b1 | |||
| bcfc97037c | |||
| 65d0abd072 | |||
| 776c68e2b0 | |||
| dab2d5ce04 | |||
| 29b33661d7 | |||
| 5dec636584 | |||
| d11b91f152 | |||
| b0fc98e384 | |||
| 56f09d2541 | |||
| 4484b77232 | |||
| f6c054066b | |||
| d5c70e51f2 | |||
| cab9e4d892 | |||
| 14cd8289ea | |||
| 4202c08487 | |||
| a70fb41652 | |||
| df437fd3e3 | |||
| 3102dbcd44 | |||
| 9d838db096 | |||
| 0c9d555693 | |||
| 4b637c7049 | |||
| fd3a873bbd | |||
| 5cea988911 | |||
| 70fcbfa990 | |||
| c541aa78e5 | |||
| b821e62391 | |||
| 41141b8fc9 | |||
| ae0789e4ec | |||
| 13d4a99358 | |||
| becb375ec2 | |||
| a777a558bd | |||
| de37aeb515 | |||
| 23be1a6939 | |||
| 8edced5a0c | |||
| 21937e88e8 | |||
| f7da83737a | |||
| 27663d80ba | |||
| 4ec5d4fc65 | |||
| 2b34ca9eb7 | |||
| 2bb9810072 | |||
| c7e87324de | |||
| ec62d8fbb9 | |||
| 7b5aae9487 | |||
| 12c94fafab | |||
| 09be170820 | |||
| 966af5a757 | |||
| 89557460c8 | |||
| 2bad6b2722 | |||
| e5cd30c6d0 | |||
| 6f2da2896e | |||
| 0387c778f4 | |||
| 76568ea67c | |||
| 8e2a1a1649 | |||
| cb5f57d68a | |||
| e7c6f28312 | |||
| 1820f72bc4 | |||
| 1631dc39f5 | |||
| d2f19e7c28 | |||
| 736703336c | |||
| 44df1f676d | |||
| f1ffb81c86 | |||
| 08ec3fc73f | |||
| f1d7cf1dfb | |||
| 1ee488b148 | |||
| ecb9827e7f | |||
| 88f26eaead | |||
| ce03742aab | |||
| 62540f0b00 | |||
| 406b8b4540 | |||
| ca24838a3f | |||
| ff7219b557 | |||
| 711acfe120 | |||
| f1acda85c9 |
@@ -63,4 +63,4 @@ jobs:
|
||||
asset_name: blender28-bim-${{steps.date.outputs.date}}-${{ matrix.pyver }}-${{ matrix.config.short_name }}.zip
|
||||
tag: "blenderbim-${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
body: "Package release blenderbim-${{steps.date.outputs.date}}"
|
||||
body: "Daily developer testing build blenderbim-${{steps.date.outputs.date}}"
|
||||
|
||||
+13
@@ -1,4 +1,11 @@
|
||||
# Dependency and build folders created by the build scripts
|
||||
/_build-vs2015-x64/
|
||||
/_deps-vs2015-x64-installed/
|
||||
/_build-vs2017-x64/
|
||||
/_deps-vs2017-x64-installed/
|
||||
/_build-vs2019-x64/
|
||||
/_deps-vs2019-x64-installed/
|
||||
/_deps/
|
||||
/deps*/
|
||||
/build*/
|
||||
/install*/
|
||||
@@ -13,6 +20,10 @@ __pycache__
|
||||
# PyCharm files
|
||||
.idea
|
||||
|
||||
#Virtual Env Files
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
|
||||
# Docs
|
||||
/docs/output
|
||||
/docs/rst_files
|
||||
@@ -23,3 +34,5 @@ __pycache__
|
||||
*.mo
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
|
||||
|
||||
+11
-5
@@ -22,10 +22,15 @@ addons:
|
||||
- nlohmann-json3-dev
|
||||
- opencollada-dev
|
||||
- python3-all-dev
|
||||
- python3-pip
|
||||
- swig
|
||||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
|
||||
|
||||
install:
|
||||
# for IDS
|
||||
- python3 -m pip install xmlschema
|
||||
|
||||
script:
|
||||
- pwd
|
||||
@@ -39,23 +44,24 @@ script:
|
||||
-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 \
|
||||
../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
|
||||
# - gltf_validator 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; \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
@@ -618,4 +618,4 @@ an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ Prerequisites
|
||||
Dependencies
|
||||
-------------
|
||||
* [Boost](http://www.boost.org/)
|
||||
* [Open Cascade](http://opencascade.org) - *optional*, but required for building IfcGeom
|
||||
([official](http://www.opencascade.org/getocc/download/loadocc/), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
|
||||
* [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 tesselated meshes
|
||||
* [OpenCOLLADA](https://github.com/khronosGroup/OpenCOLLADA/) - *optional*
|
||||
For IfcConvert to be able to write tessellated Collada (.dae) files
|
||||
@@ -101,7 +101,7 @@ Note: where `make -j` is written, add a number roughly equal to the amount of CP
|
||||
$ make -j
|
||||
$ sudo make install
|
||||
|
||||
**2c)** or obtain and compile OCCT from http://www.opencascade.org/getocc/download/loadocc/
|
||||
**2c)** or obtain and compile OCCT from https://dev.opencascade.org/release
|
||||
|
||||
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
|
||||
|
||||
@@ -217,7 +217,43 @@ Usage examples
|
||||
>>> # Writing IFC-SPF files to disk:
|
||||
>>> f.write("out.ifc")
|
||||
|
||||
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING "LGPL"
|
||||
Extra tools
|
||||
-----------
|
||||
|
||||
Also available are a series of utilities that are based on or related to IfcOpenShell.
|
||||
|
||||
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
|
||||
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\*
|
||||
ifcp6 | 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\*
|
||||
|
||||
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
|
||||
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
|
||||
[IFC2x3 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ "IFC2x3 TC1"
|
||||
[IFC4 Add2 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ "IFC4 Add2 TC1"
|
||||
|
||||
+56
-23
@@ -23,7 +23,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
|
||||
project (IfcOpenShell VERSION 0.6.0)
|
||||
|
||||
# use extra version to make pre-release using eg semver
|
||||
# use extra version to make pre-release using eg semver
|
||||
set( EXTRA_VERSION "-alpha.3")
|
||||
|
||||
foreach(max_year RANGE 2014 2030)
|
||||
@@ -280,7 +280,7 @@ ENDIF()
|
||||
# Use the found libTKernel as a template for all other OCC libraries
|
||||
# TODO Extract this into macro/function
|
||||
foreach(lib ${OPENCASCADE_LIBRARY_NAMES})
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convetion too.
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
|
||||
string(REPLACE TKerneld "${lib}" lib_path "${libTKernel}")
|
||||
string(REPLACE TKernel "${lib}" lib_path "${lib_path}")
|
||||
list(APPEND OPENCASCADE_LIBRARIES "${lib_path}")
|
||||
@@ -355,7 +355,7 @@ IF(COLLADA_SUPPORT AND BUILD_CONVERT)
|
||||
|
||||
# Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries
|
||||
foreach(lib ${OPENCOLLADA_LIBRARY_NAMES})
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convetion too.
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
|
||||
string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}")
|
||||
string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}")
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}")
|
||||
@@ -441,7 +441,7 @@ IF(MSVC)
|
||||
# Disable overeager and false positives causing C4458 ("declaration of 'indentifier' hides class member"), at least for now.
|
||||
ADD_DEFINITIONS(-wd4458)
|
||||
ENDIF()
|
||||
# Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this
|
||||
# Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this
|
||||
if (MSVC_VERSION GREATER 1900 AND (Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66))
|
||||
add_definitions(-permissive-)
|
||||
endif()
|
||||
@@ -466,7 +466,7 @@ ElSE()
|
||||
else()
|
||||
add_definitions(-Wno-maybe-uninitialized)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 9.0)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0))
|
||||
# OpenCascade spews a lot of deprecated-copy warnings
|
||||
add_definitions(-Wno-deprecated-copy)
|
||||
endif()
|
||||
@@ -486,27 +486,33 @@ INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCL
|
||||
|
||||
function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
set(IFC_PARSE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/ifcparse)
|
||||
set(${RESULT_NAME}
|
||||
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.h
|
||||
set(${RESULT_NAME}
|
||||
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.h
|
||||
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}enum.h
|
||||
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
endfunction()
|
||||
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2")
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4")
|
||||
endif()
|
||||
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
add_definitions(-DHAS_SCHEMA_${s})
|
||||
endforeach()
|
||||
|
||||
if(COMPILE_SCHEMA)
|
||||
# @todo, this appears to be untested at the moment
|
||||
|
||||
find_package(PythonInterp)
|
||||
|
||||
|
||||
IF(NOT PYTHONINTERP_FOUND)
|
||||
MESSAGE(FATAL_ERROR "A Python interpreter is necessary when COMPILE_SCHEMA is enabled. Disable COMPILE_SCHEMA or fix Python paths to proceed.")
|
||||
ENDIF()
|
||||
|
||||
|
||||
set(IFC_RELEASE_NOT_USED ${SCHEMA_VERSIONS})
|
||||
|
||||
|
||||
# Install pyparsing if necessary
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip freeze OUTPUT_VARIABLE PYTHON_PACKAGE_LIST)
|
||||
if ("${PYTHON_PACKAGE_LIST}" STREQUAL "")
|
||||
@@ -528,23 +534,23 @@ if(COMPILE_SCHEMA)
|
||||
else()
|
||||
message(STATUS "Python interpreter with pyparsing found")
|
||||
endif()
|
||||
|
||||
|
||||
# Bootstrap the parser
|
||||
message(STATUS "Compiling schema, this will take a while...")
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} bootstrap.py express.bnf
|
||||
WORKING_DIRECTORY ../src/ifcexpressparser
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} bootstrap.py express.bnf
|
||||
WORKING_DIRECTORY ../src/ifcexpressparser
|
||||
OUTPUT_FILE express_parser.py
|
||||
RESULT_VARIABLE SUCCESS)
|
||||
|
||||
if (NOT "${SUCCESS}" STREQUAL "0")
|
||||
MESSAGE(FATAL_ERROR "Failed to bootstrap parser. Make sure pyparsing is installed")
|
||||
endif()
|
||||
|
||||
|
||||
# Generate code
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} ../ifcexpressparser/express_parser.py ../../${COMPILE_SCHEMA}
|
||||
WORKING_DIRECTORY ../src/ifcparse
|
||||
OUTPUT_VARIABLE COMPILED_SCHEMA_NAME)
|
||||
|
||||
|
||||
# Prevent the schema that had just been compiled from being excluded
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
if("${COMPILED_SCHEMA_NAME}" STREQUAL "${s}")
|
||||
@@ -573,8 +579,35 @@ if (BUILD_CONVERT)
|
||||
endif()
|
||||
|
||||
# IfcParse
|
||||
file(GLOB IFCPARSE_H_FILES ../src/ifcparse/*.h)
|
||||
file(GLOB IFCPARSE_CPP_FILES ../src/ifcparse/*.cpp)
|
||||
file(GLOB IFCPARSE_H_FILES_ALL ../src/ifcparse/*.h)
|
||||
file(GLOB IFCPARSE_CPP_FILES_ALL ../src/ifcparse/*.cpp)
|
||||
|
||||
foreach(s ${IFCPARSE_H_FILES_ALL})
|
||||
get_filename_component(p "${s}" NAME)
|
||||
if (NOT "${p}" MATCHES "[0-9]")
|
||||
list(APPEND IFCPARSE_H_FILES "${s}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(s ${IFCPARSE_CPP_FILES_ALL})
|
||||
get_filename_component(p "${s}" NAME)
|
||||
if (NOT "${p}" MATCHES "[0-9]")
|
||||
list(APPEND IFCPARSE_CPP_FILES "${s}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
list(APPEND IFCPARSE_H_FILES
|
||||
../src/ifcparse/Ifc${s}.h
|
||||
../src/ifcparse/Ifc${s}-definitions.h
|
||||
)
|
||||
list(APPEND IFCPARSE_CPP_FILES
|
||||
../src/ifcparse/Ifc${s}.cpp
|
||||
../src/ifcparse/Ifc${s}-schema.cpp
|
||||
)
|
||||
endforeach()
|
||||
|
||||
|
||||
set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
|
||||
|
||||
add_library(IfcParse ${IFCPARSE_FILES})
|
||||
@@ -607,7 +640,7 @@ if (UNIX)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
TARGET_LINK_LIBRARIES(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
@@ -677,7 +710,7 @@ INSTALL(TARGETS IfcGeomServer
|
||||
|
||||
endif()
|
||||
|
||||
# Documentation
|
||||
# Documentation
|
||||
IF(BUILD_DOCUMENTATION)
|
||||
set(CMAKE_MODULE_PATH "../docs/cmake")
|
||||
ADD_SUBDIRECTORY(../docs docs)
|
||||
@@ -696,7 +729,7 @@ IF(BUILD_IFCMAX)
|
||||
ENDIF()
|
||||
|
||||
# CMake installation targets
|
||||
INSTALL(FILES ${IFCPARSE_H_FILES}
|
||||
INSTALL(FILES ${IFCPARSE_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcparse
|
||||
)
|
||||
|
||||
@@ -707,11 +740,11 @@ INSTALL(TARGETS IfcParse
|
||||
)
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
INSTALL(FILES ${IFCGEOM_H_FILES}
|
||||
INSTALL(FILES ${IFCGEOM_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
)
|
||||
|
||||
INSTALL(FILES ${SCHEMA_AGNOSTIC_H_FILES}
|
||||
INSTALL(FILES ${SCHEMA_AGNOSTIC_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -310,7 +310,7 @@ def run(cmds, cwd=None):
|
||||
BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_")
|
||||
|
||||
OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,)
|
||||
BOOST_LOCATION="https://dl.bintray.com/boostorg/release/%s/source/" % (BOOST_VERSION,)
|
||||
BOOST_LOCATION="https://boostorg.jfrog.io/artifactory/main/release/%s/source/" % (BOOST_VERSION,)
|
||||
|
||||
# Helper functions
|
||||
|
||||
|
||||
+621
@@ -0,0 +1,621 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
+48
-6
@@ -4,23 +4,26 @@ A simple Python implementation of BCF. The data model is described in `data.py`.
|
||||
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
|
||||
is available via `bcfapi.py`.
|
||||
|
||||
Currently supports BCF version 2.1.
|
||||
- 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.
|
||||
|
||||
## bcfxml
|
||||
|
||||
The `bcfxml` module lets you interact with the BCF-XML standard.
|
||||
|
||||
```
|
||||
from bcf.bcfxml import BcfXml
|
||||
```python
|
||||
from bcf import bcfxml
|
||||
|
||||
bcfxml = BcfXml()
|
||||
|
||||
# Load a project
|
||||
project = bcfxml.get_project("/path/to/file.bcf")
|
||||
bcfxml = bcfxml.load("/path/to/file.bcf")
|
||||
|
||||
|
||||
# The project is also stored in the module
|
||||
# project == bcfxml.project
|
||||
|
||||
project=bcfxml.get_project()
|
||||
print(project.name)
|
||||
|
||||
# To edit a project, just modify the object directly
|
||||
@@ -57,3 +60,42 @@ topic = bcfxml.get_topic(guid)
|
||||
topic.title = "New title"
|
||||
bcfxml.edit_topic(topic)
|
||||
```
|
||||
|
||||
## bcfapi
|
||||
|
||||
The `bcfapi` module lets you interact with the BCF-API standard.
|
||||
|
||||
```python
|
||||
from bcf.v3.bcfapi import Client
|
||||
|
||||
client_id = "YOUR_CLIENT_ID"
|
||||
client_secret = "YOUR_CLIENT_SECRET"
|
||||
|
||||
client = Client(client_id, client_secret)
|
||||
client.set_urls(base_url="OPENCDE_BASEURL")
|
||||
auth_methods = client.get_auth_methods()
|
||||
|
||||
# Our library currently only implements the authorization_code flow
|
||||
if "authorization_code" in auth_methods:
|
||||
client.login()
|
||||
|
||||
versions = client.get_versions()
|
||||
|
||||
if "3.0" in versions:
|
||||
client.set_version(version="3.0")
|
||||
|
||||
data = client.get_projects()
|
||||
print(data)
|
||||
project_id = data[0]["project_id"]
|
||||
print(project_id)
|
||||
data = client.get_project(project_id)
|
||||
print(data)
|
||||
data = 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.
|
||||
|
||||
|
||||
+30
-753
@@ -1,765 +1,42 @@
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
import os.path
|
||||
import zipfile
|
||||
import logging
|
||||
import tempfile
|
||||
import bcf.data
|
||||
from datetime import datetime
|
||||
from xml.dom import minidom
|
||||
from xmlschema import XMLSchema
|
||||
from contextlib import contextmanager
|
||||
from shutil import copyfile
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
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
|
||||
|
||||
bcfxml = BcfXml()
|
||||
bcfxml.filepath = filepath
|
||||
return bcfxml
|
||||
elif version_id == "3.0":
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
|
||||
@contextmanager
|
||||
def cd(newdir):
|
||||
prevdir = os.getcwd()
|
||||
os.chdir(os.path.expanduser(newdir))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(prevdir)
|
||||
|
||||
|
||||
class BcfXml:
|
||||
def __init__(self):
|
||||
self.filepath = None
|
||||
self.logger = logging.getLogger("bcfxml")
|
||||
self.author = "john@doe.com"
|
||||
self.project = bcf.data.Project()
|
||||
self.version = "2.1"
|
||||
self.topics = {}
|
||||
|
||||
def new_project(self):
|
||||
self.project.project_id = str(uuid.uuid4())
|
||||
self.project.name = "New Project"
|
||||
self.topics = {}
|
||||
if self.filepath:
|
||||
self.close_project()
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
self.edit_project()
|
||||
self.edit_version()
|
||||
|
||||
def get_project(self, filepath=None):
|
||||
if not filepath:
|
||||
return self.project
|
||||
zip_file = zipfile.ZipFile(filepath)
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
zip_file.extractall(self.filepath)
|
||||
if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
|
||||
data = self._read_xml("project.bcfp", "project.xsd")
|
||||
self.project.extension_schema = data["ExtensionSchema"]
|
||||
if "Project" in data:
|
||||
self.project.project_id = data["Project"]["@ProjectId"]
|
||||
self.project.name = data["Project"].get("Name")
|
||||
return self.project
|
||||
|
||||
def edit_project(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "ProjectExtension")
|
||||
project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
|
||||
self._create_element(project, "Name", text=self.project.name)
|
||||
self._create_element(root, "ExtensionSchema", text="extensions.xsd")
|
||||
with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def save_project(self, filepath):
|
||||
with cd(self.filepath):
|
||||
zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
|
||||
for root, dirs, files in os.walk("./"):
|
||||
for file in files:
|
||||
zip_file.write(os.path.join(root, file))
|
||||
zip_file.close()
|
||||
|
||||
def get_version(self):
|
||||
data = self._read_xml("bcf.version", "version.xsd")
|
||||
self.version = data["@VersionId"]
|
||||
return self.version
|
||||
|
||||
def edit_version(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Version", {"VersionId": self.version})
|
||||
version = self._create_element(root, "DetailedVersion", text=self.version)
|
||||
with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def get_topics(self):
|
||||
self.topics = {}
|
||||
topics = []
|
||||
subdirs = []
|
||||
for (dirpath, dirnames, filenames) in os.walk(self.filepath):
|
||||
subdirs = dirnames
|
||||
break
|
||||
for subdir in subdirs:
|
||||
self.topics[subdir] = self.get_topic(subdir)
|
||||
return self.topics
|
||||
|
||||
def get_header(self, guid):
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Header" not in data:
|
||||
return
|
||||
header = bcf.data.Header()
|
||||
for item in data["Header"]["File"]:
|
||||
header_file = bcf.data.HeaderFile()
|
||||
optional_keys = {
|
||||
"filename": "Filename",
|
||||
"date": "Date",
|
||||
"reference": "Reference",
|
||||
"ifc_project": "@IfcProject",
|
||||
"ifc_spatial_structure_element": "@IfcSpatialStructureElement",
|
||||
"is_external": "@isExternal",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(header_file, key, item[value])
|
||||
header.files.append(header_file)
|
||||
self.topics[guid].header = header
|
||||
return header
|
||||
|
||||
def get_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
return self.topics[guid]
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
topic = bcf.data.Topic()
|
||||
self.topics[guid] = topic
|
||||
|
||||
mandatory_keys = {
|
||||
"guid": "@Guid",
|
||||
"title": "Title",
|
||||
"creation_date": "CreationDate",
|
||||
"creation_author": "CreationAuthor",
|
||||
}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
optional_keys = {
|
||||
"priority": "Priority",
|
||||
"index": "Index",
|
||||
"labels": "Labels",
|
||||
"reference_links": "ReferenceLink",
|
||||
"modified_date": "ModifiedDate",
|
||||
"modified_author": "ModifiedAuthor",
|
||||
"due_date": "DueDate",
|
||||
"assigned_to": "AssignedTo",
|
||||
"stage": "Stage",
|
||||
"description": "Description",
|
||||
"topic_status": "@TopicStatus",
|
||||
"topic_type": "@TopicType",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["Topic"]:
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
if "BimSnippet" in data["Topic"]:
|
||||
bim_snippet = bcf.data.BimSnippet()
|
||||
keys = {
|
||||
"snippet_type": "@SnippetType",
|
||||
"is_external": "@IsExternal",
|
||||
"reference": "Reference",
|
||||
"reference_schema": "ReferenceSchema",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in data["Topic"]["BimSnippet"]:
|
||||
setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
|
||||
topic.bim_snippet = bim_snippet
|
||||
|
||||
if "DocumentReference" in data["Topic"]:
|
||||
for item in data["Topic"]["DocumentReference"]:
|
||||
document_reference = bcf.data.DocumentReference()
|
||||
keys = {
|
||||
"referenced_document": "ReferencedDocument",
|
||||
"is_external": "@IsExternal",
|
||||
"guid": "@Guid",
|
||||
"description": "Description",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in item:
|
||||
setattr(document_reference, key, item[value])
|
||||
topic.document_references.append(document_reference)
|
||||
|
||||
if "RelatedTopic" in data["Topic"]:
|
||||
for item in data["Topic"]["RelatedTopic"]:
|
||||
related_topic = bcf.data.RelatedTopic()
|
||||
related_topic.guid = item["@Guid"]
|
||||
topic.related_topics.append(related_topic)
|
||||
return topic
|
||||
|
||||
def add_topic(self, topic=None):
|
||||
if topic is None:
|
||||
topic = bcf.data.Topic()
|
||||
if not topic.guid:
|
||||
topic.guid = str(uuid.uuid4())
|
||||
if not topic.title:
|
||||
topic.title = "New Topic"
|
||||
os.mkdir(os.path.join(self.filepath, topic.guid))
|
||||
self.edit_topic(topic)
|
||||
return topic
|
||||
|
||||
def edit_topic(self, topic):
|
||||
if not topic.creation_date:
|
||||
topic.creation_date = datetime.utcnow().isoformat()
|
||||
topic.creation_author = self.author
|
||||
bcfxml = BcfXml()
|
||||
bcfxml.filepath = filepath
|
||||
return bcfxml
|
||||
else:
|
||||
topic.modified_date = datetime.utcnow().isoformat()
|
||||
topic.modified_author = self.author
|
||||
raise Exception(f"Version {version_id} not supported.")
|
||||
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Markup")
|
||||
|
||||
self.write_header(topic.header, root)
|
||||
def get_version(version_path):
|
||||
xmlparse = minidom.parse(version_path)
|
||||
version_el = xmlparse.getElementsByTagName("Version")[0]
|
||||
version = version_el.getAttribute("VersionId")
|
||||
return version
|
||||
|
||||
topic_el = self._create_element(
|
||||
root,
|
||||
"Topic",
|
||||
{
|
||||
"Guid": topic.guid,
|
||||
"TopicType": topic.topic_type,
|
||||
"TopicStatus": topic.topic_status,
|
||||
},
|
||||
)
|
||||
|
||||
for reference_link in topic.reference_links:
|
||||
self._create_element(topic_el, "ReferenceLink", text=reference_link)
|
||||
|
||||
text_map = {
|
||||
"Title": topic.title,
|
||||
"Priority": topic.priority,
|
||||
"Index": topic.index,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
|
||||
for label in topic.labels:
|
||||
self._create_element(topic_el, "Labels", text=label)
|
||||
|
||||
text_map = {
|
||||
"CreationDate": topic.creation_date,
|
||||
"CreationAuthor": topic.creation_author,
|
||||
"ModifiedDate": topic.modified_date,
|
||||
"ModifiedAuthor": topic.modified_author,
|
||||
"DueDate": topic.due_date,
|
||||
"AssignedTo": topic.assigned_to,
|
||||
"Stage": topic.stage,
|
||||
"Description": topic.description,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
|
||||
if topic.bim_snippet:
|
||||
bim_snippet = self._create_element(
|
||||
topic_el,
|
||||
"BimSnippet",
|
||||
{"SnippetType": topic.bim_snippet.snippet_type, "isExternal": topic.bim_snippet.is_external},
|
||||
)
|
||||
self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
|
||||
self._create_element(bim_snippet, "ReferenceSchema", text=topic.bim_snippet.reference_schema)
|
||||
for reference in topic.document_references:
|
||||
reference_el = self._create_element(
|
||||
topic_el, "DocumentReference", {"Guid": reference.guid, "isExternal": reference.is_external}
|
||||
)
|
||||
self._create_element(reference_el, "ReferencedDocument", text=reference.referenced_document)
|
||||
self._create_element(reference_el, "Description", text=reference.description)
|
||||
for related_topic in topic.related_topics:
|
||||
self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid})
|
||||
|
||||
self.write_comments(topic.comments, root)
|
||||
self.write_viewpoints(topic.viewpoints, root, topic)
|
||||
|
||||
with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_header(self, header, root):
|
||||
if not header or not header.files:
|
||||
return
|
||||
header_el = self._create_element(root, "Header")
|
||||
for f in header.files:
|
||||
file_el = self._create_element(
|
||||
header_el,
|
||||
"File",
|
||||
{
|
||||
"IfcProject": f.ifc_project,
|
||||
"IfcSpatialStructureElement": f.ifc_spatial_structure_element,
|
||||
"isExternal": f.is_external,
|
||||
},
|
||||
)
|
||||
self._create_element(file_el, "Filename", text=f.filename)
|
||||
self._create_element(file_el, "Date", text=f.date)
|
||||
self._create_element(file_el, "Reference", text=f.reference)
|
||||
|
||||
def write_comments(self, comments, root):
|
||||
for comment in comments.values():
|
||||
comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
|
||||
text_map = {
|
||||
"Date": comment.date,
|
||||
"Author": comment.author,
|
||||
"Comment": comment.comment,
|
||||
"ModifiedDate": comment.modified_date,
|
||||
"ModifiedAuthor": comment.modified_author,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(comment_el, key, text=value)
|
||||
if comment.viewpoint:
|
||||
self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
|
||||
|
||||
def add_comment(self, topic, comment=None):
|
||||
if comment is None:
|
||||
comment = bcf.data.Comment()
|
||||
if not comment.guid:
|
||||
comment.guid = str(uuid.uuid4())
|
||||
if not comment.comment:
|
||||
comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
|
||||
topic.comments[comment.guid] = comment
|
||||
self.edit_comment(comment, topic)
|
||||
|
||||
def edit_comment(self, comment, topic):
|
||||
if not comment.date:
|
||||
comment.date = datetime.utcnow().isoformat()
|
||||
comment.author = self.author
|
||||
else:
|
||||
comment.modified_date = datetime.utcnow().isoformat()
|
||||
comment.modified_author = self.author
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_comment(self, guid, topic):
|
||||
if guid in topic.comments:
|
||||
del topic.comments[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
del self.topics[guid]
|
||||
shutil.rmtree(os.path.join(self.filepath, guid))
|
||||
|
||||
def write_viewpoints(self, viewpoints, root, topic):
|
||||
for viewpoint in viewpoints.values():
|
||||
viewpoint_el = self._create_element(root, "Viewpoints", {"Guid": viewpoint.guid})
|
||||
text_map = {"Viewpoint": viewpoint.viewpoint, "Snapshot": viewpoint.snapshot, "Index": viewpoint.index}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(viewpoint_el, key, text=value)
|
||||
self.write_viewpoint(viewpoint, topic)
|
||||
|
||||
def write_viewpoint(self, viewpoint, topic):
|
||||
document = minidom.Document()
|
||||
root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
|
||||
self.write_viewpoint_components(viewpoint, root)
|
||||
self.write_viewpoint_orthogonal_camera(viewpoint, root)
|
||||
self.write_viewpoint_perspective_camera(viewpoint, root)
|
||||
self.write_viewpoint_lines(viewpoint, root)
|
||||
self.write_viewpoint_clipping_planes(viewpoint, root)
|
||||
self.write_viewpoint_bitmaps(viewpoint, root)
|
||||
with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
|
||||
f.write(document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_viewpoint_components(self, viewpoint, parent):
|
||||
if not viewpoint.components:
|
||||
return
|
||||
components_el = self._create_element(parent, "Components")
|
||||
if viewpoint.components.view_setup_hints:
|
||||
view_setup_hints = self._create_element(
|
||||
components_el,
|
||||
"ViewSetupHints",
|
||||
{
|
||||
"SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible,
|
||||
"SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
|
||||
"OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible,
|
||||
},
|
||||
)
|
||||
if viewpoint.components.selection:
|
||||
selection_el = self._create_element(components_el, "Selection")
|
||||
for selection in viewpoint.components.selection:
|
||||
self.write_component(selection, selection_el)
|
||||
visibility = self._create_element(
|
||||
components_el, "Visibility", {"DefaultVisibility": viewpoint.components.visibility.default_visibility}
|
||||
)
|
||||
if viewpoint.components.visibility.exceptions:
|
||||
exceptions_el = self._create_element(visibility, "Exceptions")
|
||||
for exception in viewpoint.components.visibility.exceptions:
|
||||
self.write_component(exception, exceptions_el)
|
||||
if viewpoint.components.coloring:
|
||||
coloring_el = self._create_element(components_el, "Coloring")
|
||||
for color in viewpoint.components.coloring:
|
||||
color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
|
||||
for component in color.components:
|
||||
self.write_component(component, color_el)
|
||||
|
||||
def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
|
||||
if not viewpoint.orthogonal_camera:
|
||||
return
|
||||
camera = viewpoint.orthogonal_camera
|
||||
camera_el = self._create_element(parent, "OrthogonalCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
|
||||
|
||||
def write_viewpoint_perspective_camera(self, viewpoint, parent):
|
||||
if not viewpoint.perspective_camera:
|
||||
return
|
||||
camera = viewpoint.perspective_camera
|
||||
camera_el = self._create_element(parent, "PerspectiveCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
|
||||
|
||||
def write_viewpoint_lines(self, viewpoint, parent):
|
||||
if not viewpoint.lines:
|
||||
return
|
||||
lines_el = self._create_element(parent, "Lines")
|
||||
for line in viewpoint.lines:
|
||||
line_el = self._create_element(lines_el, "Line")
|
||||
start_point_el = self._create_element(line_el, "StartPoint")
|
||||
self.write_vector(start_point_el, line.start_point)
|
||||
end_point_el = self._create_element(line_el, "EndPoint")
|
||||
self.write_vector(end_point_el, line.end_point)
|
||||
|
||||
def write_viewpoint_clipping_planes(self, viewpoint, parent):
|
||||
if not viewpoint.clipping_planes:
|
||||
return
|
||||
planes_el = self._create_element(parent, "ClippingPlanes")
|
||||
for plane in viewpoint.clipping_planes:
|
||||
plane_el = self._create_element(planes_el, "ClippingPlane")
|
||||
location_el = self._create_element(plane_el, "Location")
|
||||
self.write_vector(location_el, plane.location)
|
||||
direction_el = self._create_element(plane_el, "Direction")
|
||||
self.write_vector(direction_el, plane.direction)
|
||||
|
||||
def write_viewpoint_bitmaps(self, viewpoint, parent):
|
||||
if not viewpoint.bitmaps:
|
||||
return
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
bitmap_el = self._create_element(parent, "Bitmap")
|
||||
|
||||
text_map = {"Bitmap": bitmap.bitmap_type, "Reference": bitmap.reference}
|
||||
for key, value in text_map.items():
|
||||
self._create_element(bitmap_el, key, text=value)
|
||||
|
||||
location_el = self._create_element(bitmap_el, "Location")
|
||||
self.write_vector(location_el, bitmap.location)
|
||||
normal_el = self._create_element(bitmap_el, "Normal")
|
||||
self.write_vector(normal_el, bitmap.normal)
|
||||
up_el = self._create_element(bitmap_el, "Up")
|
||||
self.write_vector(up_el, bitmap.up)
|
||||
|
||||
self._create_element(bitmap_el, "Height", text=bitmap.height)
|
||||
|
||||
def write_vector(self, parent, from_obj):
|
||||
self._create_element(parent, "X", text=from_obj.x)
|
||||
self._create_element(parent, "Y", text=from_obj.y)
|
||||
self._create_element(parent, "Z", text=from_obj.z)
|
||||
|
||||
def write_component(self, data, parent):
|
||||
component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
|
||||
text_map = {"OriginatingSystem": data.originating_system, "AuthoringToolId": data.authoring_tool_id}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(component_el, key, text=value)
|
||||
|
||||
def add_viewpoint(self, topic, viewpoint=None):
|
||||
if not viewpoint:
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
if not viewpoint.guid:
|
||||
viewpoint.guid = str(uuid.uuid4())
|
||||
if not viewpoint.viewpoint:
|
||||
viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
|
||||
if viewpoint.snapshot:
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filepath = os.path.join(topic_filepath, viewpoint.snapshot)
|
||||
if not os.path.exists(filepath):
|
||||
filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
|
||||
copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
|
||||
viewpoint.snapshot = filename
|
||||
topic.viewpoints[viewpoint.guid] = viewpoint
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_viewpoint(self, guid, topic):
|
||||
if guid not in topic.viewpoints:
|
||||
return
|
||||
viewpoint = topic.viewpoints[guid]
|
||||
if viewpoint.snapshot:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
if viewpoint.viewpoint:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
if not bitmap.reference:
|
||||
continue
|
||||
filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
del topic.viewpoints[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_file(self, topic, index):
|
||||
if not topic.header:
|
||||
return
|
||||
f = topic.header.files.pop(index)
|
||||
filepath = os.path.join(self.filepath, topic.guid, f.reference)
|
||||
if not f.is_external and os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_bim_snippet(self, topic):
|
||||
if not topic.bim_snippet:
|
||||
return
|
||||
if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
|
||||
filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
topic.bim_snippet = None
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_document_reference(self, topic, index):
|
||||
document_reference = topic.document_references[index]
|
||||
if document_reference.referenced_document and not document_reference.is_external:
|
||||
filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
del topic.document_references[index]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_document_reference(self, topic, document_reference):
|
||||
if os.path.exists(document_reference.referenced_document):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filename = os.path.basename(document_reference.referenced_document)
|
||||
copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename))
|
||||
document_reference.referenced_document = filename
|
||||
document_reference.is_external = False
|
||||
else:
|
||||
document_reference.is_external = True
|
||||
if not document_reference.guid:
|
||||
document_reference.guid = str(uuid.uuid4())
|
||||
topic.document_references.append(document_reference)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_bim_snippet(self, topic, bim_snippet):
|
||||
if topic.bim_snippet:
|
||||
self.delete_bim_snippet(topic)
|
||||
if os.path.exists(bim_snippet.reference):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filename = os.path.basename(bim_snippet.reference)
|
||||
copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
|
||||
bim_snippet.reference = filename
|
||||
bim_snippet.is_external = False
|
||||
else:
|
||||
bim_snippet.is_external = True
|
||||
topic.bim_snippet = bim_snippet
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_file(self, topic, header_file):
|
||||
if os.path.exists(header_file.reference):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
header_file.filename = os.path.basename(header_file.reference)
|
||||
copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
|
||||
header_file.reference = header_file.filename
|
||||
header_file.is_external = False
|
||||
header_file.date = datetime.utcnow().isoformat()
|
||||
if not topic.header:
|
||||
topic.header = bcf.data.Header()
|
||||
topic.header.files.append(header_file)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def get_comments(self, guid):
|
||||
comments = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Comment" not in data:
|
||||
return comments
|
||||
for item in data["Comment"]:
|
||||
comment = bcf.data.Comment()
|
||||
mandatory_keys = {"guid": "@Guid", "date": "Date", "author": "Author", "comment": "Comment"}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(comment, key, item[value])
|
||||
optional_keys = {"modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor"}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(comment, key, item[value])
|
||||
if "Viewpoint" in item:
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
viewpoint.guid = item["Viewpoint"]["@Guid"]
|
||||
comment.viewpoint = viewpoint
|
||||
comments[comment.guid] = comment
|
||||
self.topics[guid].comments = comments
|
||||
return comments
|
||||
|
||||
def get_viewpoints(self, guid):
|
||||
viewpoints = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Viewpoints" not in data:
|
||||
return viewpoints
|
||||
for item in data["Viewpoints"]:
|
||||
viewpoint = self.get_viewpoint(item, guid)
|
||||
viewpoints[viewpoint.guid] = viewpoint
|
||||
self.topics[guid].viewpoints = viewpoints
|
||||
return viewpoints
|
||||
|
||||
def get_viewpoint(self, data, topic_guid):
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
viewpoint.guid = data["@Guid"]
|
||||
optional_keys = {"viewpoint": "Viewpoint", "snapshot": "Snapshot", "index": "Index"}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(viewpoint, key, data[value])
|
||||
visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
|
||||
viewpoint.components = self.get_viewpoint_components(visinfo)
|
||||
viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
|
||||
viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
|
||||
viewpoint.lines = self.get_viewpoint_lines(visinfo)
|
||||
viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
|
||||
viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
|
||||
return viewpoint
|
||||
|
||||
def get_viewpoint_components(self, visinfo):
|
||||
if "Components" not in visinfo:
|
||||
return None
|
||||
components = bcf.data.Components()
|
||||
data = visinfo["Components"]
|
||||
if "ViewSetupHints" in data:
|
||||
view_setup_hints = bcf.data.ViewSetupHints()
|
||||
optional_keys = {
|
||||
"spaces_visible": "@SpacesVisible",
|
||||
"space_boundaries_visible": "@SpaceBoundariesVisible",
|
||||
"openings_visible": "@OpeningsVisible",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["ViewSetupHints"]:
|
||||
setattr(view_setup_hints, key, data["ViewSetupHints"][value])
|
||||
components.view_setup_hints = view_setup_hints
|
||||
if "Selection" in data and "Component" in data["Selection"]:
|
||||
for item in data["Selection"]["Component"]:
|
||||
components.selection.append(self.get_component(item))
|
||||
if "Visibility" in data:
|
||||
component_visibility = bcf.data.ComponentVisibility()
|
||||
if "@DefaultVisibility" in data["Visibility"]:
|
||||
component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
|
||||
if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
|
||||
for item in data["Visibility"]["Exceptions"]["Component"]:
|
||||
component_visibility.exceptions.append(self.get_component(item))
|
||||
components.visibility = component_visibility
|
||||
if "Coloring" in data and "Color" in data["Coloring"]:
|
||||
for item in data["Coloring"]["Color"]:
|
||||
color = bcf.data.Color()
|
||||
color.color = item["@Color"]
|
||||
for item2 in item["Component"]:
|
||||
color.components.append(self.get_component(item2))
|
||||
components.coloring.append(color)
|
||||
return components
|
||||
|
||||
def get_viewpoint_orthogonal_camera(self, visinfo):
|
||||
if "OrthogonalCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.data.OrthogonalCamera()
|
||||
data = visinfo["OrthogonalCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.view_to_world_scale = data["ViewToWorldScale"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_perspective_camera(self, visinfo):
|
||||
if "PerspectiveCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.data.PerspectiveCamera()
|
||||
data = visinfo["PerspectiveCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.field_of_view = data["FieldOfView"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_lines(self, visinfo):
|
||||
if "Lines" not in visinfo:
|
||||
return []
|
||||
lines = []
|
||||
for item in visinfo["Lines"]["Line"]:
|
||||
line = bcf.data.Line()
|
||||
self.set_vector(line.start_point, item["StartPoint"])
|
||||
self.set_vector(line.end_point, item["EndPoint"])
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def get_viewpoint_clipping_planes(self, visinfo):
|
||||
if "ClippingPlanes" not in visinfo:
|
||||
return []
|
||||
planes = []
|
||||
for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
|
||||
plane = bcf.data.ClippingPlane()
|
||||
self.set_vector(plane.location, item["Location"])
|
||||
self.set_vector(plane.direction, item["Direction"])
|
||||
planes.append(plane)
|
||||
return planes
|
||||
|
||||
def get_viewpoint_bitmaps(self, visinfo):
|
||||
if "Bitmap" not in visinfo:
|
||||
return []
|
||||
bitmaps = []
|
||||
for item in visinfo["Bitmap"]:
|
||||
bitmap = bcf.data.Bitmap()
|
||||
bitmap.reference = item["Reference"]
|
||||
bitmap.bitmap_type = item["Bitmap"].upper()
|
||||
self.set_vector(bitmap.location, item["Location"])
|
||||
self.set_vector(bitmap.normal, item["Normal"])
|
||||
self.set_vector(bitmap.up, item["Up"])
|
||||
bitmap.height = item["Height"]
|
||||
bitmaps.append(bitmap)
|
||||
return bitmaps
|
||||
|
||||
def set_vector(self, to_obj, from_xml):
|
||||
to_obj.x = from_xml["X"]
|
||||
to_obj.y = from_xml["Y"]
|
||||
to_obj.z = from_xml["Z"]
|
||||
|
||||
def get_component(self, data):
|
||||
component = bcf.data.Component()
|
||||
optional_keys = {
|
||||
"originating_system": "OriginatingSystem",
|
||||
"authoring_tool_id": "AuthoringToolId",
|
||||
"ifc_guid": "@IfcGuid",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(component, key, data[value])
|
||||
return component
|
||||
|
||||
def close_project(self):
|
||||
shutil.rmtree(self.filepath)
|
||||
|
||||
def _read_xml(self, filename, xsd):
|
||||
schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
|
||||
filepath = os.path.join(self.filepath, filename)
|
||||
(data, errors) = schema.to_dict(filepath, validation="lax")
|
||||
for error in errors:
|
||||
self.logger.error(error)
|
||||
return data
|
||||
|
||||
def _create_element(self, parent, name, attributes={}, text=None):
|
||||
element = self.document.createElement(name)
|
||||
for key, value in attributes.items():
|
||||
if isinstance(value, bool):
|
||||
element.setAttribute(key, str(value).lower())
|
||||
elif value:
|
||||
element.setAttribute(key, value)
|
||||
if text is not None:
|
||||
text = self.document.createTextNode(str(text))
|
||||
element.appendChild(text)
|
||||
parent.appendChild(element)
|
||||
return element
|
||||
|
||||
def __del__(self):
|
||||
self.close_project()
|
||||
def extract_project(filepath):
|
||||
if not filepath:
|
||||
return
|
||||
zip_file = zipfile.ZipFile(filepath)
|
||||
filepath = tempfile.mkdtemp()
|
||||
zip_file.extractall(filepath)
|
||||
return filepath
|
||||
|
||||
@@ -0,0 +1,771 @@
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
import zipfile
|
||||
import logging
|
||||
import tempfile
|
||||
import bcf.v2.data
|
||||
from datetime import datetime
|
||||
from xml.dom import minidom
|
||||
from xmlschema import XMLSchema
|
||||
from contextlib import contextmanager
|
||||
from shutil import copyfile
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cd(newdir):
|
||||
prevdir = os.getcwd()
|
||||
os.chdir(os.path.expanduser(newdir))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(prevdir)
|
||||
|
||||
|
||||
class BcfXml:
|
||||
def __init__(self):
|
||||
self.filepath = None
|
||||
self.logger = logging.getLogger("bcfxml")
|
||||
self.author = "john@doe.com"
|
||||
self.project = bcf.v2.data.Project()
|
||||
self.version = "2.1"
|
||||
self.topics = {}
|
||||
|
||||
def new_project(self):
|
||||
self.project.project_id = str(uuid.uuid4())
|
||||
self.project.name = "New Project"
|
||||
self.topics = {}
|
||||
if self.filepath:
|
||||
self.close_project()
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
self.edit_project()
|
||||
self.edit_version()
|
||||
|
||||
def get_project(self, filepath=None):
|
||||
if not filepath:
|
||||
return self.project
|
||||
if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
|
||||
data = self._read_xml("project.bcfp", "project.xsd")
|
||||
self.project.extension_schema = data["ExtensionSchema"]
|
||||
if "Project" in data:
|
||||
self.project.project_id = data["Project"]["@ProjectId"]
|
||||
self.project.name = data["Project"].get("Name")
|
||||
return self.project
|
||||
|
||||
def edit_project(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "ProjectExtension")
|
||||
project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
|
||||
self._create_element(project, "Name", text=self.project.name)
|
||||
self._create_element(root, "ExtensionSchema", text="extensions.xsd")
|
||||
with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def save_project(self, filepath):
|
||||
with cd(self.filepath):
|
||||
zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
|
||||
for root, dirs, files in os.walk("./"):
|
||||
for file in files:
|
||||
zip_file.write(os.path.join(root, file))
|
||||
zip_file.close()
|
||||
|
||||
def get_version(self):
|
||||
data = self._read_xml("bcf.version", "version.xsd")
|
||||
self.version = data["@VersionId"]
|
||||
return self.version
|
||||
|
||||
def edit_version(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Version", {"VersionId": self.version})
|
||||
version = self._create_element(root, "DetailedVersion", text=self.version)
|
||||
with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def get_topics(self):
|
||||
self.topics = {}
|
||||
topics = []
|
||||
subdirs = []
|
||||
for (dirpath, dirnames, filenames) in os.walk(self.filepath):
|
||||
subdirs = dirnames
|
||||
break
|
||||
for subdir in subdirs:
|
||||
try:
|
||||
uuid.UUID(subdir)
|
||||
except ValueError:
|
||||
continue
|
||||
if not os.path.exists(os.path.join(self.filepath, subdir, "markup.bcf")):
|
||||
continue
|
||||
self.topics[subdir] = self.get_topic(subdir)
|
||||
return self.topics
|
||||
|
||||
def get_header(self, guid):
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Header" not in data:
|
||||
return
|
||||
header = bcf.v2.data.Header()
|
||||
for item in data["Header"]["File"]:
|
||||
header_file = bcf.v2.data.HeaderFile()
|
||||
optional_keys = {
|
||||
"filename": "Filename",
|
||||
"date": "Date",
|
||||
"reference": "Reference",
|
||||
"ifc_project": "@IfcProject",
|
||||
"ifc_spatial_structure_element": "@IfcSpatialStructureElement",
|
||||
"is_external": "@isExternal",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(header_file, key, item[value])
|
||||
header.files.append(header_file)
|
||||
self.topics[guid].header = header
|
||||
return header
|
||||
|
||||
def get_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
return self.topics[guid]
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
topic = bcf.v2.data.Topic()
|
||||
self.topics[guid] = topic
|
||||
|
||||
mandatory_keys = {
|
||||
"guid": "@Guid",
|
||||
"title": "Title",
|
||||
"creation_date": "CreationDate",
|
||||
"creation_author": "CreationAuthor",
|
||||
}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
optional_keys = {
|
||||
"priority": "Priority",
|
||||
"index": "Index",
|
||||
"labels": "Labels",
|
||||
"reference_links": "ReferenceLink",
|
||||
"modified_date": "ModifiedDate",
|
||||
"modified_author": "ModifiedAuthor",
|
||||
"due_date": "DueDate",
|
||||
"assigned_to": "AssignedTo",
|
||||
"stage": "Stage",
|
||||
"description": "Description",
|
||||
"topic_status": "@TopicStatus",
|
||||
"topic_type": "@TopicType",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["Topic"]:
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
if "BimSnippet" in data["Topic"]:
|
||||
bim_snippet = bcf.v2.data.BimSnippet()
|
||||
keys = {
|
||||
"snippet_type": "@SnippetType",
|
||||
"is_external": "@IsExternal",
|
||||
"reference": "Reference",
|
||||
"reference_schema": "ReferenceSchema",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in data["Topic"]["BimSnippet"]:
|
||||
setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
|
||||
topic.bim_snippet = bim_snippet
|
||||
|
||||
if "DocumentReference" in data["Topic"]:
|
||||
for item in data["Topic"]["DocumentReference"]:
|
||||
document_reference = bcf.v2.data.DocumentReference()
|
||||
keys = {
|
||||
"referenced_document": "ReferencedDocument",
|
||||
"is_external": "@IsExternal",
|
||||
"guid": "@Guid",
|
||||
"description": "Description",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in item:
|
||||
setattr(document_reference, key, item[value])
|
||||
topic.document_references.append(document_reference)
|
||||
|
||||
if "RelatedTopic" in data["Topic"]:
|
||||
for item in data["Topic"]["RelatedTopic"]:
|
||||
related_topic = bcf.v2.data.RelatedTopic()
|
||||
related_topic.guid = item["@Guid"]
|
||||
topic.related_topics.append(related_topic)
|
||||
return topic
|
||||
|
||||
def add_topic(self, topic=None):
|
||||
if topic is None:
|
||||
topic = bcf.v2.data.Topic()
|
||||
if not topic.guid:
|
||||
topic.guid = str(uuid.uuid4())
|
||||
if not topic.title:
|
||||
topic.title = "New Topic"
|
||||
os.mkdir(os.path.join(self.filepath, topic.guid))
|
||||
self.edit_topic(topic)
|
||||
return topic
|
||||
|
||||
def edit_topic(self, topic):
|
||||
if not topic.creation_date:
|
||||
topic.creation_date = datetime.utcnow().isoformat()
|
||||
topic.creation_author = self.author
|
||||
else:
|
||||
topic.modified_date = datetime.utcnow().isoformat()
|
||||
topic.modified_author = self.author
|
||||
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Markup")
|
||||
|
||||
self.write_header(topic.header, root)
|
||||
|
||||
topic_el = self._create_element(
|
||||
root,
|
||||
"Topic",
|
||||
{
|
||||
"Guid": topic.guid,
|
||||
"TopicType": topic.topic_type,
|
||||
"TopicStatus": topic.topic_status,
|
||||
},
|
||||
)
|
||||
|
||||
for reference_link in topic.reference_links:
|
||||
self._create_element(topic_el, "ReferenceLink", text=reference_link)
|
||||
|
||||
text_map = {
|
||||
"Title": topic.title,
|
||||
"Priority": topic.priority,
|
||||
"Index": topic.index,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
|
||||
for label in topic.labels:
|
||||
self._create_element(topic_el, "Labels", text=label)
|
||||
|
||||
text_map = {
|
||||
"CreationDate": topic.creation_date,
|
||||
"CreationAuthor": topic.creation_author,
|
||||
"ModifiedDate": topic.modified_date,
|
||||
"ModifiedAuthor": topic.modified_author,
|
||||
"DueDate": topic.due_date,
|
||||
"AssignedTo": topic.assigned_to,
|
||||
"Stage": topic.stage,
|
||||
"Description": topic.description,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
|
||||
if topic.bim_snippet:
|
||||
bim_snippet = self._create_element(
|
||||
topic_el,
|
||||
"BimSnippet",
|
||||
{"SnippetType": topic.bim_snippet.snippet_type, "isExternal": topic.bim_snippet.is_external},
|
||||
)
|
||||
self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
|
||||
self._create_element(bim_snippet, "ReferenceSchema", text=topic.bim_snippet.reference_schema)
|
||||
for reference in topic.document_references:
|
||||
reference_el = self._create_element(
|
||||
topic_el, "DocumentReference", {"Guid": reference.guid, "isExternal": reference.is_external}
|
||||
)
|
||||
self._create_element(reference_el, "ReferencedDocument", text=reference.referenced_document)
|
||||
self._create_element(reference_el, "Description", text=reference.description)
|
||||
for related_topic in topic.related_topics:
|
||||
self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid})
|
||||
|
||||
self.write_comments(topic.comments, root)
|
||||
self.write_viewpoints(topic.viewpoints, root, topic)
|
||||
|
||||
with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_header(self, header, root):
|
||||
if not header or not header.files:
|
||||
return
|
||||
header_el = self._create_element(root, "Header")
|
||||
for f in header.files:
|
||||
file_el = self._create_element(
|
||||
header_el,
|
||||
"File",
|
||||
{
|
||||
"IfcProject": f.ifc_project,
|
||||
"IfcSpatialStructureElement": f.ifc_spatial_structure_element,
|
||||
"isExternal": f.is_external,
|
||||
},
|
||||
)
|
||||
if f.filename:
|
||||
self._create_element(file_el, "Filename", text=f.filename)
|
||||
if f.date:
|
||||
self._create_element(file_el, "Date", text=f.date)
|
||||
if f.reference:
|
||||
self._create_element(file_el, "Reference", text=f.reference)
|
||||
|
||||
def write_comments(self, comments, root):
|
||||
for comment in comments.values():
|
||||
comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
|
||||
text_map = {
|
||||
"Date": comment.date,
|
||||
"Author": comment.author,
|
||||
"Comment": comment.comment,
|
||||
"ModifiedDate": comment.modified_date,
|
||||
"ModifiedAuthor": comment.modified_author,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(comment_el, key, text=value)
|
||||
if comment.viewpoint:
|
||||
self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
|
||||
|
||||
def add_comment(self, topic, comment=None):
|
||||
if comment is None:
|
||||
comment = bcf.v2.data.Comment()
|
||||
if not comment.guid:
|
||||
comment.guid = str(uuid.uuid4())
|
||||
if not comment.comment:
|
||||
comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
|
||||
topic.comments[comment.guid] = comment
|
||||
self.edit_comment(comment, topic)
|
||||
|
||||
def edit_comment(self, comment, topic):
|
||||
if not comment.date:
|
||||
comment.date = datetime.utcnow().isoformat()
|
||||
comment.author = self.author
|
||||
else:
|
||||
comment.modified_date = datetime.utcnow().isoformat()
|
||||
comment.modified_author = self.author
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_comment(self, guid, topic):
|
||||
if guid in topic.comments:
|
||||
del topic.comments[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
del self.topics[guid]
|
||||
shutil.rmtree(os.path.join(self.filepath, guid))
|
||||
|
||||
def write_viewpoints(self, viewpoints, root, topic):
|
||||
for viewpoint in viewpoints.values():
|
||||
viewpoint_el = self._create_element(root, "Viewpoints", {"Guid": viewpoint.guid})
|
||||
text_map = {"Viewpoint": viewpoint.viewpoint, "Snapshot": viewpoint.snapshot, "Index": viewpoint.index}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(viewpoint_el, key, text=value)
|
||||
self.write_viewpoint(viewpoint, topic)
|
||||
|
||||
def write_viewpoint(self, viewpoint, topic):
|
||||
document = minidom.Document()
|
||||
root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
|
||||
self.write_viewpoint_components(viewpoint, root)
|
||||
self.write_viewpoint_orthogonal_camera(viewpoint, root)
|
||||
self.write_viewpoint_perspective_camera(viewpoint, root)
|
||||
self.write_viewpoint_lines(viewpoint, root)
|
||||
self.write_viewpoint_clipping_planes(viewpoint, root)
|
||||
self.write_viewpoint_bitmaps(viewpoint, root)
|
||||
with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
|
||||
f.write(document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_viewpoint_components(self, viewpoint, parent):
|
||||
if not viewpoint.components:
|
||||
return
|
||||
components_el = self._create_element(parent, "Components")
|
||||
if viewpoint.components.view_setup_hints:
|
||||
view_setup_hints = self._create_element(
|
||||
components_el,
|
||||
"ViewSetupHints",
|
||||
{
|
||||
"SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible,
|
||||
"SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
|
||||
"OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible,
|
||||
},
|
||||
)
|
||||
if viewpoint.components.selection:
|
||||
selection_el = self._create_element(components_el, "Selection")
|
||||
for selection in viewpoint.components.selection:
|
||||
self.write_component(selection, selection_el)
|
||||
visibility = self._create_element(
|
||||
components_el, "Visibility", {"DefaultVisibility": viewpoint.components.visibility.default_visibility}
|
||||
)
|
||||
if viewpoint.components.visibility.exceptions:
|
||||
exceptions_el = self._create_element(visibility, "Exceptions")
|
||||
for exception in viewpoint.components.visibility.exceptions:
|
||||
self.write_component(exception, exceptions_el)
|
||||
if viewpoint.components.coloring:
|
||||
coloring_el = self._create_element(components_el, "Coloring")
|
||||
for color in viewpoint.components.coloring:
|
||||
color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
|
||||
for component in color.components:
|
||||
self.write_component(component, color_el)
|
||||
|
||||
def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
|
||||
if not viewpoint.orthogonal_camera:
|
||||
return
|
||||
camera = viewpoint.orthogonal_camera
|
||||
camera_el = self._create_element(parent, "OrthogonalCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
|
||||
|
||||
def write_viewpoint_perspective_camera(self, viewpoint, parent):
|
||||
if not viewpoint.perspective_camera:
|
||||
return
|
||||
camera = viewpoint.perspective_camera
|
||||
camera_el = self._create_element(parent, "PerspectiveCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
|
||||
|
||||
def write_viewpoint_lines(self, viewpoint, parent):
|
||||
if not viewpoint.lines:
|
||||
return
|
||||
lines_el = self._create_element(parent, "Lines")
|
||||
for line in viewpoint.lines:
|
||||
line_el = self._create_element(lines_el, "Line")
|
||||
start_point_el = self._create_element(line_el, "StartPoint")
|
||||
self.write_vector(start_point_el, line.start_point)
|
||||
end_point_el = self._create_element(line_el, "EndPoint")
|
||||
self.write_vector(end_point_el, line.end_point)
|
||||
|
||||
def write_viewpoint_clipping_planes(self, viewpoint, parent):
|
||||
if not viewpoint.clipping_planes:
|
||||
return
|
||||
planes_el = self._create_element(parent, "ClippingPlanes")
|
||||
for plane in viewpoint.clipping_planes:
|
||||
plane_el = self._create_element(planes_el, "ClippingPlane")
|
||||
location_el = self._create_element(plane_el, "Location")
|
||||
self.write_vector(location_el, plane.location)
|
||||
direction_el = self._create_element(plane_el, "Direction")
|
||||
self.write_vector(direction_el, plane.direction)
|
||||
|
||||
def write_viewpoint_bitmaps(self, viewpoint, parent):
|
||||
if not viewpoint.bitmaps:
|
||||
return
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
bitmap_el = self._create_element(parent, "Bitmap")
|
||||
|
||||
text_map = {"Bitmap": bitmap.bitmap_format, "Reference": bitmap.reference}
|
||||
for key, value in text_map.items():
|
||||
self._create_element(bitmap_el, key, text=value)
|
||||
|
||||
location_el = self._create_element(bitmap_el, "Location")
|
||||
self.write_vector(location_el, bitmap.location)
|
||||
normal_el = self._create_element(bitmap_el, "Normal")
|
||||
self.write_vector(normal_el, bitmap.normal)
|
||||
up_el = self._create_element(bitmap_el, "Up")
|
||||
self.write_vector(up_el, bitmap.up)
|
||||
|
||||
self._create_element(bitmap_el, "Height", text=bitmap.height)
|
||||
|
||||
def write_vector(self, parent, from_obj):
|
||||
self._create_element(parent, "X", text=from_obj.x)
|
||||
self._create_element(parent, "Y", text=from_obj.y)
|
||||
self._create_element(parent, "Z", text=from_obj.z)
|
||||
|
||||
def write_component(self, data, parent):
|
||||
component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
|
||||
text_map = {"OriginatingSystem": data.originating_system, "AuthoringToolId": data.authoring_tool_id}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(component_el, key, text=value)
|
||||
|
||||
def add_viewpoint(self, topic, viewpoint=None):
|
||||
if not viewpoint:
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
if not viewpoint.guid:
|
||||
viewpoint.guid = str(uuid.uuid4())
|
||||
if not viewpoint.viewpoint:
|
||||
viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
|
||||
if viewpoint.snapshot:
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filepath = os.path.join(topic_filepath, viewpoint.snapshot)
|
||||
if not os.path.exists(filepath):
|
||||
filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
|
||||
copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
|
||||
viewpoint.snapshot = filename
|
||||
topic.viewpoints[viewpoint.guid] = viewpoint
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_viewpoint(self, guid, topic):
|
||||
if guid not in topic.viewpoints:
|
||||
return
|
||||
viewpoint = topic.viewpoints[guid]
|
||||
if viewpoint.snapshot:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
if viewpoint.viewpoint:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
if not bitmap.reference:
|
||||
continue
|
||||
filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
del topic.viewpoints[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_file(self, topic, index):
|
||||
if not topic.header:
|
||||
return
|
||||
f = topic.header.files.pop(index)
|
||||
filepath = os.path.join(self.filepath, topic.guid, f.reference)
|
||||
if not f.is_external and os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_bim_snippet(self, topic):
|
||||
if not topic.bim_snippet:
|
||||
return
|
||||
if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
|
||||
filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
topic.bim_snippet = None
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_document_reference(self, topic, index):
|
||||
document_reference = topic.document_references[index]
|
||||
if document_reference.referenced_document and not document_reference.is_external:
|
||||
filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
del topic.document_references[index]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_document_reference(self, topic, document_reference):
|
||||
if os.path.exists(document_reference.referenced_document):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filename = os.path.basename(document_reference.referenced_document)
|
||||
copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename))
|
||||
document_reference.referenced_document = filename
|
||||
document_reference.is_external = False
|
||||
else:
|
||||
document_reference.is_external = True
|
||||
if not document_reference.guid:
|
||||
document_reference.guid = str(uuid.uuid4())
|
||||
topic.document_references.append(document_reference)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_bim_snippet(self, topic, bim_snippet):
|
||||
if topic.bim_snippet:
|
||||
self.delete_bim_snippet(topic)
|
||||
if os.path.exists(bim_snippet.reference):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filename = os.path.basename(bim_snippet.reference)
|
||||
copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
|
||||
bim_snippet.reference = filename
|
||||
bim_snippet.is_external = False
|
||||
else:
|
||||
bim_snippet.is_external = True
|
||||
topic.bim_snippet = bim_snippet
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_file(self, topic, header_file):
|
||||
if os.path.exists(header_file.reference):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
header_file.filename = os.path.basename(header_file.reference)
|
||||
copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
|
||||
header_file.reference = header_file.filename
|
||||
header_file.is_external = False
|
||||
header_file.date = datetime.utcnow().isoformat()
|
||||
if not topic.header:
|
||||
topic.header = bcf.v2.data.Header()
|
||||
topic.header.files.append(header_file)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def get_comments(self, guid):
|
||||
comments = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Comment" not in data:
|
||||
return comments
|
||||
for item in data["Comment"]:
|
||||
comment = bcf.v2.data.Comment()
|
||||
mandatory_keys = {"guid": "@Guid", "date": "Date", "author": "Author", "comment": "Comment"}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(comment, key, item[value])
|
||||
optional_keys = {"modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor"}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(comment, key, item[value])
|
||||
if "Viewpoint" in item:
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
viewpoint.guid = item["Viewpoint"]["@Guid"]
|
||||
comment.viewpoint = viewpoint
|
||||
comments[comment.guid] = comment
|
||||
self.topics[guid].comments = comments
|
||||
return comments
|
||||
|
||||
def get_viewpoints(self, guid):
|
||||
viewpoints = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Viewpoints" not in data:
|
||||
return viewpoints
|
||||
for item in data["Viewpoints"]:
|
||||
viewpoint = self.get_viewpoint(item, guid)
|
||||
viewpoints[viewpoint.guid] = viewpoint
|
||||
self.topics[guid].viewpoints = viewpoints
|
||||
return viewpoints
|
||||
|
||||
def get_viewpoint(self, data, topic_guid):
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
viewpoint.guid = data["@Guid"]
|
||||
optional_keys = {"viewpoint": "Viewpoint", "snapshot": "Snapshot", "index": "Index"}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(viewpoint, key, data[value])
|
||||
visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
|
||||
viewpoint.components = self.get_viewpoint_components(visinfo)
|
||||
viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
|
||||
viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
|
||||
viewpoint.lines = self.get_viewpoint_lines(visinfo)
|
||||
viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
|
||||
viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
|
||||
return viewpoint
|
||||
|
||||
def get_viewpoint_components(self, visinfo):
|
||||
if "Components" not in visinfo:
|
||||
return None
|
||||
components = bcf.v2.data.Components()
|
||||
data = visinfo["Components"]
|
||||
if "ViewSetupHints" in data:
|
||||
view_setup_hints = bcf.v2.data.ViewSetupHints()
|
||||
optional_keys = {
|
||||
"spaces_visible": "@SpacesVisible",
|
||||
"space_boundaries_visible": "@SpaceBoundariesVisible",
|
||||
"openings_visible": "@OpeningsVisible",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["ViewSetupHints"]:
|
||||
setattr(view_setup_hints, key, data["ViewSetupHints"][value])
|
||||
components.view_setup_hints = view_setup_hints
|
||||
if "Selection" in data and "Component" in data["Selection"]:
|
||||
for item in data["Selection"]["Component"]:
|
||||
components.selection.append(self.get_component(item))
|
||||
if "Visibility" in data:
|
||||
component_visibility = bcf.v2.data.ComponentVisibility()
|
||||
if "@DefaultVisibility" in data["Visibility"]:
|
||||
component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
|
||||
if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
|
||||
for item in data["Visibility"]["Exceptions"]["Component"]:
|
||||
component_visibility.exceptions.append(self.get_component(item))
|
||||
components.visibility = component_visibility
|
||||
if "Coloring" in data and "Color" in data["Coloring"]:
|
||||
for item in data["Coloring"]["Color"]:
|
||||
color = bcf.v2.data.Color()
|
||||
color.color = item["@Color"]
|
||||
for item2 in item["Component"]:
|
||||
color.components.append(self.get_component(item2))
|
||||
components.coloring.append(color)
|
||||
return components
|
||||
|
||||
def get_viewpoint_orthogonal_camera(self, visinfo):
|
||||
if "OrthogonalCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.v2.data.OrthogonalCamera()
|
||||
data = visinfo["OrthogonalCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.view_to_world_scale = data["ViewToWorldScale"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_perspective_camera(self, visinfo):
|
||||
if "PerspectiveCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.v2.data.PerspectiveCamera()
|
||||
data = visinfo["PerspectiveCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.field_of_view = data["FieldOfView"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_lines(self, visinfo):
|
||||
if "Lines" not in visinfo:
|
||||
return []
|
||||
lines = []
|
||||
for item in visinfo["Lines"]["Line"]:
|
||||
line = bcf.v2.data.Line()
|
||||
self.set_vector(line.start_point, item["StartPoint"])
|
||||
self.set_vector(line.end_point, item["EndPoint"])
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def get_viewpoint_clipping_planes(self, visinfo):
|
||||
if "ClippingPlanes" not in visinfo:
|
||||
return []
|
||||
planes = []
|
||||
for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
|
||||
plane = bcf.v2.data.ClippingPlane()
|
||||
self.set_vector(plane.location, item["Location"])
|
||||
self.set_vector(plane.direction, item["Direction"])
|
||||
planes.append(plane)
|
||||
return planes
|
||||
|
||||
def get_viewpoint_bitmaps(self, visinfo):
|
||||
if "Bitmap" not in visinfo:
|
||||
return []
|
||||
bitmaps = []
|
||||
for item in visinfo["Bitmap"]:
|
||||
bitmap = bcf.v2.data.Bitmap()
|
||||
bitmap.reference = item["Reference"]
|
||||
bitmap.bitmap_format = item["Bitmap"].upper()
|
||||
self.set_vector(bitmap.location, item["Location"])
|
||||
self.set_vector(bitmap.normal, item["Normal"])
|
||||
self.set_vector(bitmap.up, item["Up"])
|
||||
bitmap.height = item["Height"]
|
||||
bitmaps.append(bitmap)
|
||||
return bitmaps
|
||||
|
||||
def set_vector(self, to_obj, from_xml):
|
||||
to_obj.x = from_xml["X"]
|
||||
to_obj.y = from_xml["Y"]
|
||||
to_obj.z = from_xml["Z"]
|
||||
|
||||
def get_component(self, data):
|
||||
component = bcf.v2.data.Component()
|
||||
optional_keys = {
|
||||
"originating_system": "OriginatingSystem",
|
||||
"authoring_tool_id": "AuthoringToolId",
|
||||
"ifc_guid": "@IfcGuid",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(component, key, data[value])
|
||||
return component
|
||||
|
||||
def close_project(self):
|
||||
shutil.rmtree(self.filepath)
|
||||
|
||||
def _read_xml(self, filename, xsd):
|
||||
schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
|
||||
filepath = os.path.join(self.filepath, filename)
|
||||
(data, errors) = schema.to_dict(filepath, validation="lax")
|
||||
for error in errors:
|
||||
self.logger.error(error)
|
||||
return data
|
||||
|
||||
def _create_element(self, parent, name, attributes={}, text=None):
|
||||
element = self.document.createElement(name)
|
||||
for key, value in attributes.items():
|
||||
if isinstance(value, bool):
|
||||
element.setAttribute(key, str(value).lower())
|
||||
elif value:
|
||||
element.setAttribute(key, value)
|
||||
if text is not None:
|
||||
text = self.document.createTextNode(str(text))
|
||||
element.appendChild(text)
|
||||
parent.appendChild(element)
|
||||
return element
|
||||
|
||||
def __del__(self):
|
||||
self.close_project()
|
||||
@@ -157,7 +157,7 @@ class Bitmap:
|
||||
def __init__(self):
|
||||
self.reference = "" # Only in BCF-XML
|
||||
self.bitmap_data = None # Only in BCF-API
|
||||
self.bitmap_type = "PNG" # Enum of png or jpg
|
||||
self.bitmap_format = "PNG" # Enum of png or jpg
|
||||
self.location = Point()
|
||||
self.normal = Direction()
|
||||
self.up = Direction()
|
||||
@@ -0,0 +1,540 @@
|
||||
import uuid
|
||||
import time
|
||||
import json
|
||||
import urllib
|
||||
import requests
|
||||
import webbrowser
|
||||
import http.server
|
||||
import base64
|
||||
|
||||
|
||||
client_id, client_secret = "", ""
|
||||
|
||||
|
||||
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
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.send_response(200)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, client_id, client_secret):
|
||||
self.baseurl = None
|
||||
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.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.version_ids = {}
|
||||
self.version = None
|
||||
self.auth_method = None
|
||||
self.redirect_uri = None
|
||||
self.api_baseurl = None
|
||||
|
||||
def get(self, endpoint, params=None, is_auth_required=False):
|
||||
headers = {"Authorization": "Bearer " + self.get_access_token()}
|
||||
return requests.get(f"{self.api_baseurl}{endpoint}", headers=headers, params=params or None).json()
|
||||
|
||||
def post(self, endpoint, data=None, params=None):
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.get_access_token(),
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
resp = requests.post(
|
||||
f"{self.api_baseurl}{endpoint}",
|
||||
headers=headers,
|
||||
params=params or None,
|
||||
data=data or None,
|
||||
)
|
||||
return resp.status_code, resp.text
|
||||
|
||||
def put(self, endpoint, data=None, params=None):
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.get_access_token(),
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
resp = requests.put(
|
||||
f"{self.baseurl}{endpoint}",
|
||||
headers=headers,
|
||||
params=params or None,
|
||||
data=data or None,
|
||||
)
|
||||
return resp.status_code, resp.text
|
||||
|
||||
def set_urls(self, base_url=None, redirect_uri=None):
|
||||
self.baseurl = base_url
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def delete(self, endpoint, params=None):
|
||||
headers = {"Authorization": "Bearer " + self.get_access_token()}
|
||||
resp = requests.put(
|
||||
f"{self.baseurl}{endpoint}",
|
||||
headers=headers,
|
||||
params=params or None,
|
||||
)
|
||||
return resp.status_code
|
||||
|
||||
def get_access_token(self):
|
||||
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():
|
||||
self.get_refresh_token()
|
||||
else:
|
||||
self.login()
|
||||
return self.access_token
|
||||
|
||||
def get_auth_methods(self):
|
||||
resp = requests.get(f"{self.baseurl}opencde/1.0/auth")
|
||||
return resp.json()["supported_oauth2_flows"]
|
||||
|
||||
def get_versions(self):
|
||||
resp = requests.get(f"{self.baseurl}opencde/versions")
|
||||
resp_values = resp.json()["versions"]
|
||||
for version in resp_values:
|
||||
if "api_base_url" in version:
|
||||
self.version_ids.update({version["version_id"]: version["api_base_url"]})
|
||||
return self.version_ids
|
||||
|
||||
def set_version(self, version=None):
|
||||
self.version = version
|
||||
self.api_baseurl = self.version_ids[self.version]
|
||||
|
||||
def login(self):
|
||||
resp = requests.get(f"{self.baseurl}opencde/1.0/auth")
|
||||
values = resp.json()
|
||||
self.auth_endpoint = values["oauth2_auth_url"]
|
||||
self.token_endpoint = values["oauth2_token_url"]
|
||||
|
||||
with http.server.HTTPServer(("", 8080), OAuthReceiver) as server:
|
||||
state = str(uuid.uuid4())
|
||||
query = urllib.parse.urlencode(
|
||||
{
|
||||
"client_id": self.client_id,
|
||||
"response_type": "code",
|
||||
"state": state,
|
||||
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_uri}",
|
||||
}
|
||||
)
|
||||
if "?" in self.auth_endpoint:
|
||||
webbrowser.open(f"{self.auth_endpoint}&{query}")
|
||||
else:
|
||||
webbrowser.open(f"{self.auth_endpoint}?{query}")
|
||||
server.timeout = 100
|
||||
server.state = state
|
||||
server.handle_request()
|
||||
if server.auth_code and server.auth_state == state:
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": server.auth_code,
|
||||
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_uri}",
|
||||
}
|
||||
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}"}
|
||||
self.set_tokens_from_response(requests.post(self.token_endpoint, data=data, headers=headers))
|
||||
|
||||
def get_refresh_token(self):
|
||||
self.set_tokens_from_response(
|
||||
requests.post(
|
||||
self.token_endpoint,
|
||||
params={
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": self.refresh_token,
|
||||
},
|
||||
).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}"}
|
||||
self.set_tokens_from_response(
|
||||
requests.post(
|
||||
self.token_endpoint,
|
||||
data={
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": self.refresh_token,
|
||||
},
|
||||
headers=headers,
|
||||
).json()
|
||||
)
|
||||
|
||||
def set_auth_method(self, method="authorization_code_grant"):
|
||||
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 get_projects(self) -> list:
|
||||
return self.get(
|
||||
f"/projects",
|
||||
)
|
||||
|
||||
def get_project(
|
||||
self,
|
||||
project_id="",
|
||||
) -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}",
|
||||
{
|
||||
"project_id": project_id,
|
||||
},
|
||||
)
|
||||
|
||||
def update_project(self, project_id="", data=None) -> dict:
|
||||
url = f"{self.baseurl}/projects/{project_id}"
|
||||
headers = {"Authorization": "Bearer " + self.get_access_token()}
|
||||
resp = requests.put(url, headers=headers, data=data)
|
||||
return resp.status_code, resp.text
|
||||
|
||||
def get_extensions(
|
||||
self,
|
||||
project_id="",
|
||||
) -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/extensions",
|
||||
{
|
||||
"project_id": project_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_topics(
|
||||
self,
|
||||
project_id="",
|
||||
topics="",
|
||||
query_string=None,
|
||||
) -> list:
|
||||
# return self.get(
|
||||
# f"/projects/{project_id}/topics",
|
||||
# {
|
||||
# "project_id": project_id,
|
||||
# "topics": topics,
|
||||
# "query_string": query_string,
|
||||
# },
|
||||
# )
|
||||
pass
|
||||
|
||||
def get_topic(self, project_id="", topic_id="") -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def create_topic(self, project_id="", data=None):
|
||||
return self.post(f"/projects/{project_id}/topics", data=data)
|
||||
|
||||
def update_topic(self, project_id="", topic_id="", data=None) -> dict:
|
||||
return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
|
||||
|
||||
def delete_topic(self, project_id="", topic_id=""):
|
||||
return self.delete(f"/projects/{project_id}/topics/{topic_id}")
|
||||
|
||||
def get_snippet(self, project_id="", topic_id="") -> str:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/snippet",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def update_snippet(self, project_id="", topic_id="", data=None):
|
||||
return self.put(f"/projects/{project_id}/topics", data=data)
|
||||
|
||||
def get_files_information(self, project_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/files_information",
|
||||
{
|
||||
"project_id": project_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_files(self, project_id="", topic_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/files",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def update_files(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
params=None,
|
||||
):
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/files",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_comments(self, project_id="", topic_id="") -> list:
|
||||
pass
|
||||
|
||||
def create_comments(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
params=None,
|
||||
):
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_comment(self, project_id="", topic_id="", comment_id="") -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"comment_id": comment_id,
|
||||
},
|
||||
)
|
||||
|
||||
def delete_comment(self, project_id="", topic_id="", comment_id=""):
|
||||
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,
|
||||
):
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_viewpoints(self, project_id="", topic_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def create_viewpoints(self, project_id="", topic_id="", data=None):
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_viewpoint(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"viewpoint_id": viewpoint_id,
|
||||
},
|
||||
)
|
||||
|
||||
def delete_viewpoint(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
viewpoint_id="",
|
||||
):
|
||||
return self.delete(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
|
||||
)
|
||||
|
||||
def get_snapshot(self, project_id="", topic_id="", viewpoint_id="") -> str:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"viewpoint_id": viewpoint_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_bitmap(self, project_id="", topic_id="", viewpoint_id="", bitmap_id="") -> str:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"viewpoint_id": viewpoint_id,
|
||||
"bitmap_id": bitmap_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_selection(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"viewpoint_id": viewpoint_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_coloring(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"viewpoint_id": viewpoint_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_visibility(self, project_id="", topic_id="", viewpoint_id="") -> dict:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"viewpoint_id": viewpoint_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_related_topics(self, project_id="", topic_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/related_topics",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def update_related_topics(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
):
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/related_topics",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def get_document_references(self, project_id="", topic_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def create_document_reference(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
data=None,
|
||||
):
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
data=data,
|
||||
)
|
||||
|
||||
def update_document_references(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
document_reference_id="",
|
||||
data=None,
|
||||
):
|
||||
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:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/documents",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def create_document(
|
||||
self,
|
||||
project_id="",
|
||||
topic_id="",
|
||||
guid=None,
|
||||
data=None,
|
||||
):
|
||||
headers = {
|
||||
"Authorization": "Bearer " + self.get_access_token(),
|
||||
"Content-type": "application/octet-stream",
|
||||
}
|
||||
response = requests.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/documents",
|
||||
data=data,
|
||||
params={guid},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
def get_document(self, project_id="", topic_id="", document_id="") -> str:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/documents/{document_id}",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"document_id": document_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_topics_events(self, project_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/events",
|
||||
{
|
||||
"project_id": project_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_topic_events(self, project_id="", topic_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/events",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_comments_events(self, project_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/comments/events",
|
||||
{
|
||||
"project_id": project_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_comment_events(self, project_id="", topic_id="", comment_id="") -> list:
|
||||
return self.get(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
|
||||
{
|
||||
"project_id": project_id,
|
||||
"topic_id": topic_id,
|
||||
"comment_id": comment_id,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,827 @@
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
import zipfile
|
||||
import logging
|
||||
import tempfile
|
||||
import bcf.v3.data
|
||||
from datetime import datetime
|
||||
from xml.dom import minidom
|
||||
from xmlschema import XMLSchema
|
||||
from contextlib import contextmanager
|
||||
from shutil import copyfile
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cd(newdir):
|
||||
prevdir = os.getcwd()
|
||||
os.chdir(os.path.expanduser(newdir))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(prevdir)
|
||||
|
||||
|
||||
class BcfXml:
|
||||
def __init__(self):
|
||||
self.filepath = None
|
||||
self.logger = logging.getLogger("bcfxml")
|
||||
self.author = "john@doe.com"
|
||||
self.project = bcf.v3.data.Project()
|
||||
self.version = "3.0"
|
||||
self.topics = {}
|
||||
|
||||
def new_project(self):
|
||||
self.project.project_id = str(uuid.uuid4())
|
||||
self.project.name = "New Project"
|
||||
self.topics = {}
|
||||
if self.filepath:
|
||||
self.close_project()
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
self.edit_project()
|
||||
self.edit_version()
|
||||
|
||||
def get_project(self, filepath=None):
|
||||
if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
|
||||
data = self._read_xml("project.bcfp", "project.xsd")
|
||||
self.project.project_id = data["Project"]["@ProjectId"]
|
||||
self.project.name = data["Project"].get("Name")
|
||||
return self.project
|
||||
|
||||
def edit_project(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "ProjectInfo")
|
||||
project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
|
||||
if self.project.name:
|
||||
self._create_element(project, "Name", text=self.project.name)
|
||||
with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def save_project(self, filepath):
|
||||
with cd(self.filepath):
|
||||
zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
|
||||
for root, dirs, files in os.walk("./"):
|
||||
for file in files:
|
||||
zip_file.write(os.path.join(root, file))
|
||||
zip_file.close()
|
||||
|
||||
def get_version(self):
|
||||
data = self._read_xml("bcf.version", "version.xsd")
|
||||
self.version = data["@VersionId"]
|
||||
return self.version
|
||||
|
||||
def edit_version(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Version", {"VersionId": self.version})
|
||||
with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def get_topics(self):
|
||||
self.topics = {}
|
||||
topics = []
|
||||
subdirs = []
|
||||
for (dirpath, dirnames, filenames) in os.walk(self.filepath):
|
||||
subdirs = dirnames
|
||||
break
|
||||
for subdir in subdirs:
|
||||
try:
|
||||
uuid.UUID(subdir)
|
||||
except ValueError:
|
||||
continue
|
||||
if not os.path.exists(os.path.join(self.filepath, subdir, "markup.bcf")):
|
||||
continue
|
||||
self.topics[subdir] = self.get_topic(subdir)
|
||||
return self.topics
|
||||
|
||||
def get_header(self, guid):
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Header" not in data:
|
||||
return
|
||||
header = bcf.v3.data.Header()
|
||||
if data["Header"].get("Files"):
|
||||
for item in data["Header"]["Files"].get("File", []):
|
||||
header_file = bcf.v3.data.HeaderFile()
|
||||
optional_keys = {
|
||||
"filename": "Filename",
|
||||
"date": "Date",
|
||||
"reference": "Reference",
|
||||
"ifc_project": "@IfcProject",
|
||||
"ifc_spatial_structure_element": "@IfcSpatialStructureElement",
|
||||
"is_external": "@IsExternal",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(header_file, key, item[value])
|
||||
header.files.append(header_file)
|
||||
self.topics[guid].header = header
|
||||
return header
|
||||
|
||||
def get_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
return self.topics[guid]
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
topic = bcf.v3.data.Topic()
|
||||
self.topics[guid] = topic
|
||||
|
||||
mandatory_keys = {
|
||||
"guid": "@Guid",
|
||||
"title": "Title",
|
||||
"creation_date": "CreationDate",
|
||||
"creation_author": "CreationAuthor",
|
||||
"topic_status": "@TopicStatus",
|
||||
"topic_type": "@TopicType",
|
||||
}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
optional_keys = {
|
||||
"priority": "Priority",
|
||||
"index": "Index",
|
||||
"modified_date": "ModifiedDate",
|
||||
"modified_author": "ModifiedAuthor",
|
||||
"due_date": "DueDate",
|
||||
"assigned_to": "AssignedTo",
|
||||
"stage": "Stage",
|
||||
"description": "Description",
|
||||
"server_assigned_id": "@ServerAssignedId",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["Topic"]:
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
if data["Topic"].get("ReferenceLinks"):
|
||||
topic.reference_links.extend(data["Topic"]["ReferenceLinks"].get("ReferenceLink", []))
|
||||
|
||||
if data["Topic"].get("Labels"):
|
||||
topic.labels.extend(data["Topic"]["Labels"].get("Label", []))
|
||||
|
||||
if "BimSnippet" in data["Topic"]:
|
||||
bim_snippet = bcf.v3.data.BimSnippet()
|
||||
keys = {
|
||||
"snippet_type": "@SnippetType",
|
||||
"is_external": "@IsExternal",
|
||||
"reference": "Reference",
|
||||
"reference_schema": "ReferenceSchema",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in data["Topic"]["BimSnippet"]:
|
||||
setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
|
||||
topic.bim_snippet = bim_snippet
|
||||
|
||||
if data["Topic"].get("DocumentReferences"):
|
||||
for item in data["Topic"]["DocumentReferences"].get("DocumentReference", []):
|
||||
document_reference = bcf.v3.data.DocumentReference()
|
||||
keys = {
|
||||
"document_guid": "DocumentGuid",
|
||||
"url": "Url",
|
||||
"guid": "@Guid",
|
||||
"description": "Description",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in item:
|
||||
setattr(document_reference, key, item[value])
|
||||
topic.document_references.append(document_reference)
|
||||
|
||||
if data["Topic"].get("RelatedTopics"):
|
||||
for item in data["Topic"]["RelatedTopics"].get("RelatedTopic", []):
|
||||
related_topic = bcf.v3.data.RelatedTopic()
|
||||
related_topic.guid = item["@Guid"]
|
||||
topic.related_topics.append(related_topic)
|
||||
return topic
|
||||
|
||||
def add_topic(self, topic=None):
|
||||
if topic is None:
|
||||
topic = bcf.v3.data.Topic()
|
||||
if not topic.guid:
|
||||
topic.guid = str(uuid.uuid4())
|
||||
if not topic.title:
|
||||
topic.title = "New Topic"
|
||||
os.mkdir(os.path.join(self.filepath, topic.guid))
|
||||
self.edit_topic(topic)
|
||||
return topic
|
||||
|
||||
def edit_topic(self, topic):
|
||||
if not topic.creation_date:
|
||||
topic.creation_date = datetime.utcnow().isoformat()
|
||||
topic.creation_author = self.author
|
||||
else:
|
||||
topic.modified_date = datetime.utcnow().isoformat()
|
||||
topic.modified_author = self.author
|
||||
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Markup")
|
||||
if topic.header:
|
||||
self.write_header(topic.header, root)
|
||||
|
||||
topic_el = self._create_element(
|
||||
root,
|
||||
"Topic",
|
||||
{
|
||||
"Guid": topic.guid,
|
||||
"ServerAssignedId": topic.server_assigned_id,
|
||||
"TopicType": topic.topic_type,
|
||||
"TopicStatus": topic.topic_status,
|
||||
},
|
||||
)
|
||||
if topic.reference_links:
|
||||
reference_Links_el = self._create_element(topic_el, "ReferenceLinks")
|
||||
for reference_link in topic.reference_links:
|
||||
self._create_element(reference_Links_el, "ReferenceLink", text=reference_link)
|
||||
|
||||
text_map = {
|
||||
"Title": topic.title,
|
||||
"Priority": topic.priority,
|
||||
"Index": topic.index,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
if topic.labels:
|
||||
label_el = self._create_element(topic_el, "Labels")
|
||||
for label in topic.labels:
|
||||
self._create_element(label_el, "Label", text=label)
|
||||
|
||||
text_map = {
|
||||
"CreationDate": topic.creation_date,
|
||||
"CreationAuthor": topic.creation_author,
|
||||
"ModifiedDate": topic.modified_date,
|
||||
"ModifiedAuthor": topic.modified_author,
|
||||
"DueDate": topic.due_date,
|
||||
"AssignedTo": topic.assigned_to,
|
||||
"Stage": topic.stage,
|
||||
"Description": topic.description,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
|
||||
if topic.bim_snippet:
|
||||
bim_snippet = self._create_element(
|
||||
topic_el,
|
||||
"BimSnippet",
|
||||
{
|
||||
"SnippetType": topic.bim_snippet.snippet_type,
|
||||
"IsExternal": topic.bim_snippet.is_external,
|
||||
},
|
||||
)
|
||||
self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
|
||||
self._create_element(
|
||||
bim_snippet,
|
||||
"ReferenceSchema",
|
||||
text=topic.bim_snippet.reference_schema,
|
||||
)
|
||||
if topic.document_references:
|
||||
reference_el = self._create_element(topic_el, "DocumentReferences")
|
||||
self.write_document_references(topic.document_references, reference_el)
|
||||
if topic.related_topics:
|
||||
related_topic_el = self._create_element(topic_el, "RelatedTopics")
|
||||
for related_topic in topic.related_topics:
|
||||
self._create_element(related_topic_el, "RelatedTopic", {"Guid": related_topic.guid})
|
||||
if topic.comments:
|
||||
comment_el = self._create_element(topic_el, "Comments")
|
||||
self.write_comments(topic.comments, comment_el)
|
||||
if topic.viewpoints:
|
||||
viewpoint_el = self._create_element(topic_el, "Viewpoints")
|
||||
self.write_viewpoints(topic.viewpoints, viewpoint_el, topic)
|
||||
with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_document_references(self, references, root):
|
||||
for reference in references:
|
||||
document_reference_el = self._create_element(root, "DocumentReference", {"Guid": reference.guid})
|
||||
if reference.document_guid:
|
||||
self._create_element(document_reference_el, "DocumentGuid", text=reference.document_guid)
|
||||
elif reference.url:
|
||||
self._create_element(document_reference_el, "Url", text=reference.url)
|
||||
if reference.description:
|
||||
self._create_element(document_reference_el, "Description", text=reference.description)
|
||||
|
||||
def write_header(self, header, root):
|
||||
if not header or not header.files:
|
||||
return
|
||||
header_el = self._create_element(root, "Header")
|
||||
files_el = self._create_element(header_el, "Files")
|
||||
for f in header.files:
|
||||
file_el = self._create_element(
|
||||
files_el,
|
||||
"File",
|
||||
{
|
||||
"IfcProject": f.ifc_project,
|
||||
"IfcSpatialStructureElement": f.ifc_spatial_structure_element,
|
||||
"IsExternal": f.is_external,
|
||||
},
|
||||
)
|
||||
if f.filename:
|
||||
self._create_element(file_el, "Filename", text=f.filename)
|
||||
if f.date:
|
||||
self._create_element(file_el, "Date", text=f.date)
|
||||
if f.reference:
|
||||
self._create_element(file_el, "Reference", text=f.reference)
|
||||
|
||||
def write_comments(self, comments, root):
|
||||
for comment in comments.values():
|
||||
comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
|
||||
text_map = {
|
||||
"Date": comment.date,
|
||||
"Author": comment.author,
|
||||
"Comment": comment.comment,
|
||||
"ModifiedDate": comment.modified_date,
|
||||
"ModifiedAuthor": comment.modified_author,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(comment_el, key, text=value)
|
||||
if comment.viewpoint:
|
||||
self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
|
||||
|
||||
def add_comment(self, topic, comment=None):
|
||||
if comment is None:
|
||||
comment = bcf.v3.data.Comment()
|
||||
if not comment.guid:
|
||||
comment.guid = str(uuid.uuid4())
|
||||
if not comment.comment:
|
||||
comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
|
||||
topic.comments[comment.guid] = comment
|
||||
self.edit_comment(comment, topic)
|
||||
|
||||
def edit_comment(self, comment, topic):
|
||||
if not comment.date:
|
||||
comment.date = datetime.utcnow().isoformat()
|
||||
comment.author = self.author
|
||||
else:
|
||||
comment.modified_date = datetime.utcnow().isoformat()
|
||||
comment.modified_author = self.author
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_comment(self, guid, topic):
|
||||
if guid in topic.comments:
|
||||
del topic.comments[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
del self.topics[guid]
|
||||
shutil.rmtree(os.path.join(self.filepath, guid))
|
||||
|
||||
def write_viewpoints(self, viewpoints, root, topic):
|
||||
for viewpoint in viewpoints.values():
|
||||
viewpoint_el = self._create_element(root, "ViewPoint", {"Guid": viewpoint.guid})
|
||||
text_map = {
|
||||
"Viewpoint": viewpoint.viewpoint,
|
||||
"Snapshot": viewpoint.snapshot,
|
||||
"Index": viewpoint.index,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(viewpoint_el, key, text=value)
|
||||
self.write_viewpoint(viewpoint, topic)
|
||||
|
||||
def write_viewpoint(self, viewpoint, topic):
|
||||
document = minidom.Document()
|
||||
root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
|
||||
self.write_viewpoint_components(viewpoint, root)
|
||||
self.write_viewpoint_orthogonal_camera(viewpoint, root)
|
||||
self.write_viewpoint_perspective_camera(viewpoint, root)
|
||||
self.write_viewpoint_lines(viewpoint, root)
|
||||
self.write_viewpoint_clipping_planes(viewpoint, root)
|
||||
self.write_viewpoint_bitmaps(viewpoint, root)
|
||||
with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
|
||||
f.write(document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_viewpoint_components(self, viewpoint, parent):
|
||||
if not viewpoint.components:
|
||||
return
|
||||
components_el = self._create_element(parent, "Components")
|
||||
if viewpoint.components.selection:
|
||||
selection_el = self._create_element(components_el, "Selection")
|
||||
for selection in viewpoint.components.selection:
|
||||
self.write_component(selection, selection_el)
|
||||
if viewpoint.components.visibility:
|
||||
visibility = self._create_element(
|
||||
components_el,
|
||||
"Visibility",
|
||||
{"DefaultVisibility": viewpoint.components.visibility.default_visibility},
|
||||
)
|
||||
if viewpoint.components.visibility.view_setup_hints:
|
||||
view_setup_hints = self._create_element(
|
||||
visibility,
|
||||
"ViewSetupHints",
|
||||
{
|
||||
"SpacesVisible": viewpoint.components.visibility.view_setup_hints.spaces_visible,
|
||||
"SpaceBoundariesVisible": viewpoint.components.visibility.view_setup_hints.space_boundaries_visible,
|
||||
"OpeningsVisible": viewpoint.components.visibility.view_setup_hints.openings_visible,
|
||||
},
|
||||
)
|
||||
if viewpoint.components.visibility.exceptions:
|
||||
exceptions_el = self._create_element(visibility, "Exceptions")
|
||||
for exception in viewpoint.components.visibility.exceptions:
|
||||
self.write_component(exception, exceptions_el)
|
||||
if viewpoint.components.coloring:
|
||||
coloring_el = self._create_element(components_el, "Coloring")
|
||||
for color in viewpoint.components.coloring:
|
||||
color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
|
||||
component_el = self._create_element(color_el, "Components")
|
||||
for component in color.components:
|
||||
self.write_component(component, component_el)
|
||||
|
||||
def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
|
||||
if not viewpoint.orthogonal_camera:
|
||||
return
|
||||
camera = viewpoint.orthogonal_camera
|
||||
camera_el = self._create_element(parent, "OrthogonalCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
|
||||
self._create_element(camera_el, "AspectRatio", text=camera.aspect_ratio)
|
||||
|
||||
def write_viewpoint_perspective_camera(self, viewpoint, parent):
|
||||
if not viewpoint.perspective_camera:
|
||||
return
|
||||
camera = viewpoint.perspective_camera
|
||||
camera_el = self._create_element(parent, "PerspectiveCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
|
||||
self._create_element(camera_el, "AspectRatio", text=camera.aspect_ratio)
|
||||
|
||||
def write_viewpoint_lines(self, viewpoint, parent):
|
||||
if not viewpoint.lines:
|
||||
return
|
||||
lines_el = self._create_element(parent, "Lines")
|
||||
for line in viewpoint.lines:
|
||||
line_el = self._create_element(lines_el, "Line")
|
||||
start_point_el = self._create_element(line_el, "StartPoint")
|
||||
self.write_vector(start_point_el, line.start_point)
|
||||
end_point_el = self._create_element(line_el, "EndPoint")
|
||||
self.write_vector(end_point_el, line.end_point)
|
||||
|
||||
def write_viewpoint_clipping_planes(self, viewpoint, parent):
|
||||
if not viewpoint.clipping_planes:
|
||||
return
|
||||
planes_el = self._create_element(parent, "ClippingPlanes")
|
||||
for plane in viewpoint.clipping_planes:
|
||||
plane_el = self._create_element(planes_el, "ClippingPlane")
|
||||
location_el = self._create_element(plane_el, "Location")
|
||||
self.write_vector(location_el, plane.location)
|
||||
direction_el = self._create_element(plane_el, "Direction")
|
||||
self.write_vector(direction_el, plane.direction)
|
||||
|
||||
def write_viewpoint_bitmaps(self, viewpoint, parent):
|
||||
if not viewpoint.bitmaps:
|
||||
return
|
||||
bitmaps_el = self._create_element(parent, "Bitmaps")
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
bitmap_el = self._create_element(bitmaps_el, "Bitmap")
|
||||
|
||||
text_map = {"Format": bitmap.bitmap_format, "Reference": bitmap.reference}
|
||||
for key, value in text_map.items():
|
||||
self._create_element(bitmap_el, key, text=value)
|
||||
|
||||
location_el = self._create_element(bitmap_el, "Location")
|
||||
self.write_vector(location_el, bitmap.location)
|
||||
normal_el = self._create_element(bitmap_el, "Normal")
|
||||
self.write_vector(normal_el, bitmap.normal)
|
||||
up_el = self._create_element(bitmap_el, "Up")
|
||||
self.write_vector(up_el, bitmap.up)
|
||||
|
||||
self._create_element(bitmap_el, "Height", text=bitmap.height)
|
||||
|
||||
def write_vector(self, parent, from_obj):
|
||||
self._create_element(parent, "X", text=from_obj.x)
|
||||
self._create_element(parent, "Y", text=from_obj.y)
|
||||
self._create_element(parent, "Z", text=from_obj.z)
|
||||
|
||||
def write_component(self, data, parent):
|
||||
component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
|
||||
text_map = {
|
||||
"OriginatingSystem": data.originating_system,
|
||||
"AuthoringToolId": data.authoring_tool_id,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(component_el, key, text=value)
|
||||
|
||||
def add_viewpoint(self, topic, viewpoint=None):
|
||||
if not viewpoint:
|
||||
viewpoint = bcf.v3.data.Viewpoint()
|
||||
if not viewpoint.guid:
|
||||
viewpoint.guid = str(uuid.uuid4())
|
||||
if not viewpoint.viewpoint:
|
||||
viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
|
||||
if viewpoint.snapshot:
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filepath = os.path.join(topic_filepath, viewpoint.snapshot)
|
||||
if not os.path.exists(filepath):
|
||||
filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
|
||||
copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
|
||||
viewpoint.snapshot = filename
|
||||
topic.viewpoints[viewpoint.guid] = viewpoint
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_viewpoint(self, guid, topic):
|
||||
if guid not in topic.viewpoints:
|
||||
return
|
||||
viewpoint = topic.viewpoints[guid]
|
||||
if viewpoint.snapshot:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
if viewpoint.viewpoint:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
if not bitmap.reference:
|
||||
continue
|
||||
filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
del topic.viewpoints[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_file(self, topic, index):
|
||||
if not topic.header:
|
||||
return
|
||||
f = topic.header.files.pop(index)
|
||||
filepath = os.path.join(self.filepath, topic.guid, f.reference)
|
||||
if not f.is_external and os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_bim_snippet(self, topic):
|
||||
if not topic.bim_snippet:
|
||||
return
|
||||
if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
|
||||
filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
topic.bim_snippet = None
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_document_reference(self, topic, index):
|
||||
document_reference = topic.document_references[index]
|
||||
if document_reference.referenced_document and not document_reference.is_external:
|
||||
filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
del topic.document_references[index]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_document_reference(self, topic, document_reference):
|
||||
if os.path.exists(document_reference.referenced_document):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filename = os.path.basename(document_reference.referenced_document)
|
||||
copyfile(
|
||||
document_reference.referenced_document,
|
||||
os.path.join(topic_filepath, filename),
|
||||
)
|
||||
document_reference.referenced_document = filename
|
||||
document_reference.is_external = False
|
||||
else:
|
||||
document_reference.is_external = True
|
||||
if not document_reference.guid:
|
||||
document_reference.guid = str(uuid.uuid4())
|
||||
topic.document_references.append(document_reference)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_bim_snippet(self, topic, bim_snippet):
|
||||
if topic.bim_snippet:
|
||||
self.delete_bim_snippet(topic)
|
||||
if os.path.exists(bim_snippet.reference):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
filename = os.path.basename(bim_snippet.reference)
|
||||
copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
|
||||
bim_snippet.reference = filename
|
||||
bim_snippet.is_external = False
|
||||
else:
|
||||
bim_snippet.is_external = True
|
||||
topic.bim_snippet = bim_snippet
|
||||
self.edit_topic(topic)
|
||||
|
||||
def add_file(self, topic, header_file):
|
||||
if os.path.exists(header_file.reference):
|
||||
topic_filepath = os.path.join(self.filepath, topic.guid)
|
||||
header_file.filename = os.path.basename(header_file.reference)
|
||||
copyfile(
|
||||
header_file.reference,
|
||||
os.path.join(topic_filepath, header_file.filename),
|
||||
)
|
||||
header_file.reference = header_file.filename
|
||||
header_file.is_external = False
|
||||
header_file.date = datetime.utcnow().isoformat()
|
||||
if not topic.header:
|
||||
topic.header = bcf.v3.data.Header()
|
||||
topic.header.files.append(header_file)
|
||||
self.edit_topic(topic)
|
||||
|
||||
def get_comments(self, guid):
|
||||
comments = {}
|
||||
if "Comments" not in data["Topics"]:
|
||||
return comments
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
for item in data["Topic"]["Comments"].get("Comment", []):
|
||||
comment = bcf.v3.data.Comment()
|
||||
mandatory_keys = {
|
||||
"guid": "@Guid",
|
||||
"date": "Date",
|
||||
"author": "Author",
|
||||
}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(comment, key, item[value])
|
||||
optional_keys = {
|
||||
"comment": "Comment",
|
||||
"modified_date": "ModifiedDate",
|
||||
"modified_author": "ModifiedAuthor",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(comment, key, item[value])
|
||||
if "Viewpoint" in item:
|
||||
viewpoint = bcf.v3.data.Viewpoint()
|
||||
viewpoint.guid = item["Viewpoint"]["@Guid"]
|
||||
comment.viewpoint = viewpoint
|
||||
comments[comment.guid] = comment
|
||||
self.topics[guid].comments = comments
|
||||
return comments
|
||||
|
||||
def get_viewpoints(self, guid):
|
||||
viewpoints = {}
|
||||
if "Viewpoints" not in data["Topic"]:
|
||||
return viewpoints
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
for item in data["Topic"]["Viewpoints"]:
|
||||
viewpoint = self.get_viewpoint(item, guid)
|
||||
viewpoints[viewpoint.guid] = viewpoint
|
||||
self.topics[guid].viewpoints = viewpoints
|
||||
return viewpoints
|
||||
|
||||
def get_viewpoint(self, data, topic_guid):
|
||||
viewpoint = bcf.v3.data.Viewpoint()
|
||||
viewpoint.guid = data["@Guid"]
|
||||
optional_keys = {
|
||||
"viewpoint": "Viewpoint",
|
||||
"snapshot": "Snapshot",
|
||||
"index": "Index",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(viewpoint, key, data[value])
|
||||
visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
|
||||
viewpoint.components = self.get_viewpoint_components(visinfo)
|
||||
viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
|
||||
viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
|
||||
viewpoint.lines = self.get_viewpoint_lines(visinfo)
|
||||
viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
|
||||
viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
|
||||
return viewpoint
|
||||
|
||||
def get_viewpoint_components(self, visinfo):
|
||||
if "Components" not in visinfo:
|
||||
return None
|
||||
components = bcf.v3.data.Components()
|
||||
data = visinfo["Components"]
|
||||
if "Selection" in data and "Component" in data["Selection"]:
|
||||
for item in data["Selection"].get("Component", []):
|
||||
components.selection.append(self.get_component(item))
|
||||
if "Visibility" in data:
|
||||
component_visibility = bcf.v3.data.ComponentVisibility()
|
||||
if "@DefaultVisibility" in data["Visibility"]:
|
||||
component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
|
||||
if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
|
||||
for item in data["Visibility"]["Exceptions"]["Component"]:
|
||||
component_visibility.exceptions.append(self.get_component(item))
|
||||
if "ViewSetupHints" in data["Visibility"]:
|
||||
view_setup_hints = bcf.v3.data.ViewSetupHints()
|
||||
optional_keys = {
|
||||
"spaces_visible": "@SpacesVisible",
|
||||
"space_boundaries_visible": "@SpaceBoundariesVisible",
|
||||
"openings_visible": "@OpeningsVisible",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["Visibility"]["ViewSetupHints"]:
|
||||
setattr(view_setup_hints, key, data["Visibility"]["ViewSetupHints"][value])
|
||||
component_visibility.view_setup_hints = view_setup_hints
|
||||
components.visibility = component_visibility
|
||||
if "Coloring" in data and "Color" in data["Coloring"]:
|
||||
for item in data["Coloring"]["Color"]:
|
||||
color = bcf.v3.data.Color()
|
||||
color.color = item["@Color"]
|
||||
for item2 in item["Components"]["Component"]:
|
||||
color.components.append(self.get_component(item2))
|
||||
components.coloring.append(color)
|
||||
return components
|
||||
|
||||
def get_viewpoint_orthogonal_camera(self, visinfo):
|
||||
if "OrthogonalCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.v3.data.OrthogonalCamera()
|
||||
data = visinfo["OrthogonalCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.view_to_world_scale = data["ViewToWorldScale"]
|
||||
camera.aspect_ratio = data["AspectRatio"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_perspective_camera(self, visinfo):
|
||||
if "PerspectiveCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.v3.data.PerspectiveCamera()
|
||||
data = visinfo["PerspectiveCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.field_of_view = data["FieldOfView"]
|
||||
camera.aspect_ratio = data["AspectRatio"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_lines(self, visinfo):
|
||||
if "Lines" not in visinfo:
|
||||
return []
|
||||
lines = []
|
||||
for item in visinfo["Lines"].get("Line", []):
|
||||
line = bcf.v3.data.Line()
|
||||
self.set_vector(line.start_point, item["StartPoint"])
|
||||
self.set_vector(line.end_point, item["EndPoint"])
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def get_viewpoint_clipping_planes(self, visinfo):
|
||||
if "ClippingPlanes" not in visinfo:
|
||||
return []
|
||||
planes = []
|
||||
for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
|
||||
plane = bcf.v3.data.ClippingPlane()
|
||||
self.set_vector(plane.location, item["Location"])
|
||||
self.set_vector(plane.direction, item["Direction"])
|
||||
planes.append(plane)
|
||||
return planes
|
||||
|
||||
def get_viewpoint_bitmaps(self, visinfo):
|
||||
if "Bitmaps" not in visinfo:
|
||||
return []
|
||||
bitmaps = []
|
||||
for item in visinfo["Bitmaps"].get("Bitmap"):
|
||||
bitmap = bcf.v3.data.Bitmap()
|
||||
bitmap.reference = item["Reference"]
|
||||
bitmap.bitmap_format = item["Format"].upper()
|
||||
self.set_vector(bitmap.location, item["Location"])
|
||||
self.set_vector(bitmap.normal, item["Normal"])
|
||||
self.set_vector(bitmap.up, item["Up"])
|
||||
bitmap.height = item["Height"]
|
||||
bitmaps.append(bitmap)
|
||||
return bitmaps
|
||||
|
||||
def set_vector(self, to_obj, from_xml):
|
||||
to_obj.x = from_xml["X"]
|
||||
to_obj.y = from_xml["Y"]
|
||||
to_obj.z = from_xml["Z"]
|
||||
|
||||
def get_component(self, data):
|
||||
component = bcf.v3.data.Component()
|
||||
optional_keys = {
|
||||
"originating_system": "OriginatingSystem",
|
||||
"authoring_tool_id": "AuthoringToolId",
|
||||
"ifc_guid": "@IfcGuid",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(component, key, data[value])
|
||||
return component
|
||||
|
||||
def close_project(self):
|
||||
shutil.rmtree(self.filepath)
|
||||
|
||||
def _read_xml(self, filename, xsd):
|
||||
schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
|
||||
filepath = os.path.join(self.filepath, filename)
|
||||
(data, errors) = schema.to_dict(filepath, validation="lax")
|
||||
for error in errors:
|
||||
self.logger.error(error)
|
||||
return data
|
||||
|
||||
def _create_element(self, parent, name, attributes={}, text=None):
|
||||
element = self.document.createElement(name)
|
||||
for key, value in attributes.items():
|
||||
if isinstance(value, bool):
|
||||
element.setAttribute(key, str(value).lower())
|
||||
elif value:
|
||||
element.setAttribute(key, value)
|
||||
if text is not None:
|
||||
text = self.document.createTextNode(str(text))
|
||||
element.appendChild(text)
|
||||
parent.appendChild(element)
|
||||
return element
|
||||
|
||||
def __del__(self):
|
||||
self.close_project()
|
||||
@@ -0,0 +1,181 @@
|
||||
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.file_name = ""
|
||||
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,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:include schemaLocation="shared-types.xsd"/>
|
||||
|
||||
<xs:element name="DocumentInfo">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Documents" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Document" type="Document" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="Document">
|
||||
<xs:sequence>
|
||||
<!-- Filename of the document with the file extension. Not used to store the file in the BCF -->
|
||||
<xs:element name="Filename" type="NonEmptyOrBlankString"/>
|
||||
<!-- Human readable description of the document -->
|
||||
<xs:element name="Description" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="DocumentAttributes"/>
|
||||
</xs:complexType>
|
||||
<xs:attributeGroup name="DocumentAttributes">
|
||||
<!-- Guid of the document. Must match the filename in the BCF -->
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:attributeGroup>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:include schemaLocation="shared-types.xsd"/>
|
||||
|
||||
<xs:element name="Extensions">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="TopicTypes" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="TopicType" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="TopicStatuses" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="TopicStatus" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Priorities" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Priority" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="TopicLabels" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="TopicLabel" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Users" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="User" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="SnippetTypes" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="SnippetType" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Stages" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Stage" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:include schemaLocation="shared-types.xsd"/>
|
||||
<xs:element name="Markup">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Header" type="Header" minOccurs="0"/>
|
||||
<xs:element name="Topic" type="Topic"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="Header">
|
||||
<xs:sequence>
|
||||
<xs:element name="Files" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="File" type="File" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<!-- ISG Jira issue BCF-9. Add support for several viewpoints and snapshots per issue -->
|
||||
<xs:complexType name="ViewPoint">
|
||||
<xs:sequence>
|
||||
<!-- viewpoint file (xml) -->
|
||||
<xs:element name="Viewpoint" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<!-- the snapshot png -->
|
||||
<xs:element name="Snapshot" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<!-- the viewpoint index (sort order) -->
|
||||
<xs:element name="Index" type="xs:int" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
<!-- Guid of the viewpoint -->
|
||||
</xs:complexType>
|
||||
<!-- BimSnippet -->
|
||||
<xs:complexType name="BimSnippet">
|
||||
<xs:sequence>
|
||||
<!--
|
||||
Name of the file in the topic folder containing the snippet or a URL.
|
||||
E.G.- Expresscode containing p.e Issue, Request
|
||||
// Maybe some header infos ?? // IfcEntites // Geometry
|
||||
-->
|
||||
<!-- Reference (name) to the snippet file -->
|
||||
<xs:element name="Reference" type="NonEmptyOrBlankString"/>
|
||||
<xs:element name="ReferenceSchema" type="NonEmptyOrBlankString"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="SnippetType" type="NonEmptyOrBlankString" use="required"/>
|
||||
<xs:attribute name="IsExternal" type="xs:boolean" default="false"/>
|
||||
<!-- This flag is true when the reference is a URL pointing outside of the BCF file-->
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Topic">
|
||||
<xs:sequence>
|
||||
<xs:element name="ReferenceLinks" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="ReferenceLink" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Title" type="NonEmptyOrBlankString"/>
|
||||
<xs:element name="Priority" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<!-- ISG Jira issue BCF-8 Add a way save order the topics -->
|
||||
<!-- This property is deprecated and will be removed in a future release -->
|
||||
<xs:element name="Index" type="xs:int" minOccurs="0"/>
|
||||
<xs:element name="Labels" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Label" type="NonEmptyOrBlankString" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="CreationDate" type="xs:dateTime"/>
|
||||
<xs:element name="CreationAuthor" type="NonEmptyOrBlankString"/>
|
||||
<xs:element name="ModifiedDate" type="xs:dateTime" minOccurs="0"/>
|
||||
<xs:element name="ModifiedAuthor" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<xs:element name="DueDate" type="xs:dateTime" minOccurs="0"/>
|
||||
<xs:element name="AssignedTo" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<xs:element name="Stage" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<xs:element name="Description" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<xs:element name="BimSnippet" type="BimSnippet" minOccurs="0"/>
|
||||
<xs:element name="DocumentReferences" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="DocumentReference" type="DocumentReference" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="RelatedTopics" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="RelatedTopic" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Comments" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Comment" type="Comment" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<!-- ISG Jira issue BCF-9. Add support for several viewpoints and snapshots per issue -->
|
||||
<xs:element name="Viewpoints" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="ViewPoint" type="ViewPoint" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
<xs:attribute name="ServerAssignedId" type="NonEmptyOrBlankString" use="optional"/>
|
||||
<xs:attribute name="TopicType" type="NonEmptyOrBlankString" use="required"/>
|
||||
<xs:attribute name="TopicStatus" type="NonEmptyOrBlankString" use="required"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="File">
|
||||
<xs:sequence>
|
||||
<xs:element name="Filename" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<xs:element name="Date" type="xs:dateTime" minOccurs="0"/>
|
||||
<!-- Reference (URL) of the file -->
|
||||
<xs:element name="Reference" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="FileAttributes"/>
|
||||
</xs:complexType>
|
||||
<xs:attributeGroup name="FileAttributes">
|
||||
<xs:attribute name="IfcProject" type="IfcGuid" use="optional"/>
|
||||
<xs:attribute name="IfcSpatialStructureElement" type="IfcGuid" use="optional"/>
|
||||
<xs:attribute name="IsExternal" type="xs:boolean" default="true"/>
|
||||
</xs:attributeGroup>
|
||||
<!-- Reference to a document inside of the topic folder or a url pointing to the web -->
|
||||
<xs:complexType name="DocumentReference">
|
||||
<xs:sequence>
|
||||
<xs:choice>
|
||||
<!-- Guid of the document: If pointing to an internal document -->
|
||||
<xs:element name="DocumentGuid" type="Guid" minOccurs="0"/>
|
||||
<!-- Url of the reference. If pointing to an external document -->
|
||||
<xs:element name="Url" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:choice>
|
||||
<!-- Human readable description of the document reference -->
|
||||
<xs:element name="Description" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="DocumentReferenceAttributes"/>
|
||||
</xs:complexType>
|
||||
<xs:attributeGroup name="DocumentReferenceAttributes">
|
||||
<!-- Guid of the document reference -->
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:attributeGroup>
|
||||
<xs:complexType name="Comment">
|
||||
<xs:sequence>
|
||||
<xs:element name="Date" type="xs:dateTime"/>
|
||||
<xs:element name="Author" type="NonEmptyOrBlankString"/>
|
||||
<xs:element name="Comment" minOccurs="0" type="NonEmptyOrBlankString"/>
|
||||
<xs:element name="Viewpoint" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="ModifiedDate" type="xs:dateTime" minOccurs="0"/>
|
||||
<xs:element name="ModifiedAuthor" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="IfcGuid">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:length value="22"/>
|
||||
<xs:pattern value="[0-9A-Za-z_$]*"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:include schemaLocation="shared-types.xsd"/>
|
||||
<xs:element name="ProjectInfo">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Project" type="Project"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="Project">
|
||||
<xs:sequence>
|
||||
<xs:element name="Name" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="ProjectId" type="NonEmptyOrBlankString" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:simpleType name="Guid">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern value="[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="NonEmptyOrBlankString">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:whiteSpace value="collapse"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="Version">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="VersionId" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||
<xs:include schemaLocation="shared-types.xsd"/>
|
||||
<xs:element name="VisualizationInfo">
|
||||
<xs:annotation>
|
||||
<xs:documentation>VisualizationInfo documentation</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<!--
|
||||
Although plural, 'Components' is not a collection
|
||||
-->
|
||||
<xs:element name="Components" type="Components" minOccurs="0"/>
|
||||
<xs:choice>
|
||||
<xs:element name="OrthogonalCamera" type="OrthogonalCamera"/>
|
||||
<xs:element name="PerspectiveCamera" type="PerspectiveCamera"/>
|
||||
</xs:choice>
|
||||
<xs:element name="Lines" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Line" type="Line" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="ClippingPlanes" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="ClippingPlane" type="ClippingPlane" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Bitmaps" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Bitmap" type="Bitmap" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<!-- Guid of the viewpoint -->
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="OrthogonalCamera">
|
||||
<xs:sequence>
|
||||
<xs:element name="CameraViewPoint" type="Point"/>
|
||||
<xs:element name="CameraDirection" type="Direction"/>
|
||||
<xs:element name="CameraUpVector" type="Direction"/>
|
||||
<xs:element name="ViewToWorldScale" type="xs:double">
|
||||
<xs:annotation>
|
||||
<xs:documentation>view's visible vertical size in meters</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="AspectRatio" type="PositiveDouble">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
Proportional relationship between the width and the height of the view (w/h).
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="PerspectiveCamera">
|
||||
<xs:sequence>
|
||||
<xs:element name="CameraViewPoint" type="Point"/>
|
||||
<xs:element name="CameraDirection" type="Direction"/>
|
||||
<xs:element name="CameraUpVector" type="Direction"/>
|
||||
<xs:element name="FieldOfView" type="FieldOfView">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
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.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="AspectRatio" type="PositiveDouble">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
Proportional relationship between the width and the height of the view (w/h).
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Point">
|
||||
<xs:sequence>
|
||||
<xs:element name="X" type="xs:double"/>
|
||||
<xs:element name="Y" type="xs:double"/>
|
||||
<xs:element name="Z" type="xs:double"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Direction">
|
||||
<xs:sequence>
|
||||
<xs:element name="X" type="xs:double"/>
|
||||
<xs:element name="Y" type="xs:double"/>
|
||||
<xs:element name="Z" type="xs:double"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="PositiveDouble">
|
||||
<xs:restriction base="xs:double">
|
||||
<xs:minExclusive value="0"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="FieldOfView">
|
||||
<xs:restriction base="xs:double">
|
||||
<xs:minExclusive value="0"/>
|
||||
<xs:maxExclusive value="180"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="Components">
|
||||
<xs:sequence>
|
||||
<!-- Components with relevance to the viewpoint. They should be displayed highlighted or selected in a viewer -->
|
||||
<xs:element name="Selection" type="ComponentSelection" minOccurs="0"/>
|
||||
<xs:element name="Visibility" type="ComponentVisibility" minOccurs="0"/>
|
||||
<xs:element name="Coloring" type="ComponentColoring" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComponentSelection">
|
||||
<xs:sequence>
|
||||
<xs:element name="Component" type="Component" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComponentVisibility">
|
||||
<xs:sequence>
|
||||
<xs:element name="ViewSetupHints" type="ViewSetupHints" minOccurs="0"/>
|
||||
<xs:element name="Exceptions" minOccurs="0">
|
||||
<!-- List Components that are different than the DefaultVisibility. E.g. if DefaultVisibility = false then list
|
||||
Components that should be visible -->
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Component" type="Component" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="DefaultVisibility" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ViewSetupHints">
|
||||
<xs:attribute name="SpacesVisible" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="SpaceBoundariesVisible" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="OpeningsVisible" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComponentColoring">
|
||||
<xs:sequence>
|
||||
<xs:element name="Color" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<!-- At least one component is required for a Color. -->
|
||||
<xs:element name="Components">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute ref="Color" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Component">
|
||||
<xs:sequence>
|
||||
<xs:element name="OriginatingSystem" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
<xs:element name="AuthoringToolId" type="NonEmptyOrBlankString" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute ref="IfcGuid"/>
|
||||
</xs:complexType>
|
||||
<xs:attribute name="Color">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:normalizedString">
|
||||
<!-- Should either match 3 or 4 hex bytes , e.g. "FF00FF" or "FF00FF99" -->
|
||||
<xs:pattern value="[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IfcGuid">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:normalizedString">
|
||||
<xs:length value="22"/>
|
||||
<xs:pattern value="[0-9A-Za-z_$]*"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:complexType name="Line">
|
||||
<xs:sequence>
|
||||
<xs:element name="StartPoint" type="Point"/>
|
||||
<xs:element name="EndPoint" type="Point"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ClippingPlane">
|
||||
<xs:sequence>
|
||||
<xs:element name="Location" type="Point"/>
|
||||
<xs:element name="Direction" type="Direction"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Bitmap">
|
||||
<xs:sequence>
|
||||
<xs:element name="Format" type="BitmapFormat"/>
|
||||
<!-- Name of the bitmap file in the topic folder -->
|
||||
<xs:element name="Reference" type="NonEmptyOrBlankString"/>
|
||||
<!-- Location of the center of the bitmap -->
|
||||
<xs:element name="Location" type="Point"/>
|
||||
<!-- Normal of the bitmap -->
|
||||
<xs:element name="Normal" type="Direction"/>
|
||||
<!-- Upvector of the bitmap -->
|
||||
<xs:element name="Up" type="Direction"/>
|
||||
<!-- Height of the bitmap -->
|
||||
<xs:element name="Height" type="xs:double"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="BitmapFormat">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="png"/>
|
||||
<xs:enumeration value="jpg"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,621 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
+174
-102
@@ -1,43 +1,59 @@
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py37
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/win-64/pythonocc-core-0.17.3-py37he980bc4_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/win-64/oce-0.17.2-he980bc4_14.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/2019.5/download/win-64/tbb-2019.5-he980bc4_0.tar.bz2
|
||||
PYLIBDIR:=python3.7
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/win-64/pythonocc-core-7.4.1-py39h3d1c7c5_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/win-64/occt-7.4.0-h823b557_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/win-64/tbb-2020.2-h2d74725_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/osx-64/pythonocc-core-0.17.3-py37h04f5b5a_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/osx-64/oce-0.17.2-h04f5b5a_12.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/osx-64/tbb-4.3.6-0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/osx-64/pythonocc-core-7.4.1-py39h4d29fe3_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/osx-64/occt-7.4.0-hb9b6dc7_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/osx-64/tbb-2020.2-h940c156_4.tar.bz2
|
||||
endif
|
||||
PYLIBDIR:=python3.9
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/linux-64/pythonocc-core-0.17.3-py37h6bb024c_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/linux-64/oce-0.17.2-h6bb024c_14.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/linux-64/tbb-4.3.6-0.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2
|
||||
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
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/linux-64/pythonocc-core-7.4.1-py39h465cb30_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/linux-64/occt-7.4.0-h9121d39_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/linux-64/tbb-2020.2-h4bd325d_4.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
|
||||
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
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
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
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
|
||||
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
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2
|
||||
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
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
|
||||
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
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
endif
|
||||
|
||||
.PHONY: dist
|
||||
@@ -52,10 +68,10 @@ endif
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-f14d349-$(PLATFORM)64.zip
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-f14d349-$(PLATFORM)64.zip
|
||||
endif
|
||||
cd dist/working && unzip ifcblender*
|
||||
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
@@ -67,7 +83,7 @@ endif
|
||||
|
||||
# Provides IfcConvert for construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.6.0-81ad689-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip IfcConvert*
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/IfcConvert.exe dist/blenderbim/libs/
|
||||
@@ -76,77 +92,19 @@ else
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Python OCC functionality for cutting IFC geometry for construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(PYTHONOCC_URL)
|
||||
cd dist/working && tar -xjvf pythonocc-core*
|
||||
ifeq ($(PLATFORM), win)
|
||||
cd dist/working && cp -r Lib/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
else
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r lib/python3.7/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r lib/python3.9/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
endif
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by Python OCC
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(OCE_URL)
|
||||
cd dist/working && tar -xjvf oc*
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
|
||||
endif
|
||||
else
|
||||
# Unix Conda builds of PythonOCC expect OCE libs to have a RPATH of ../../../
|
||||
cd dist/working && cp -r lib/* ../blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), linux)
|
||||
rm -rf dist/blenderbim/libs/oce-0.17
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by OpenCascade
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(TBB_URL)
|
||||
cd dist/working && tar -xjvf tbb*
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
|
||||
endif
|
||||
else
|
||||
cd dist/working && cp -r lib/* ../blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://blenderbim.org/builds/patch-blender28-bim-macos.zip
|
||||
cd dist/working && unzip patch*
|
||||
cd dist/working && cp -r *.dylib ../blenderbim/libs/
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Provides dependencies that are part of IfcOpenShell
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.6.0.zip
|
||||
cd dist/working && unzip v0.6.0.zip
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCClash functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcclash/* dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcclash/ifcclash dist/blenderbim/libs/site/packages/
|
||||
# Provides BIMTester functionality
|
||||
cd dist/working && python -m venv env
|
||||
cd dist/working && . env/bin/activate && pip install pybabel
|
||||
@@ -161,6 +119,8 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCPatch functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch/ifcpatch dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCP6 functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcp6/ifcp6 dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
@@ -177,6 +137,41 @@ endif
|
||||
cp -r dist/working/svgwrite-1.3.1/svgwrite dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides fuzzy date parsing for construction sequencing
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz
|
||||
cd dist/working && tar -xzvf python-dateutil*
|
||||
cp -r dist/working/python-dateutil-2.8.1/dateutil dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides duration parsing for construction sequencing
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf isodate*
|
||||
cp -r dist/working/isodate-0.6.0/src/isodate dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides networkx graph analysis for project dependency calculations
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/b0/21/adfbf6168631e28577e4af9eb9f26d75fe72b2bb1d33762a5f2c425e6c2a/networkx-2.5.1.tar.gz
|
||||
cd dist/working && tar -xzvf networkx*
|
||||
cp -r dist/working/networkx-2.5.1/networkx dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by networkx
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/4f/51/15a4f6b8154d292e130e5e566c730d8ec6c9802563d58760666f1818ba58/decorator-5.0.9.tar.gz
|
||||
cd dist/working && tar -xzvf decorator*
|
||||
cp -r dist/working/decorator-5.0.9/src/decorator.py 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
|
||||
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
cp dist/working/jsgantt* dist/blenderbim/bim/data/gantt/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCDiff
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
|
||||
@@ -234,20 +229,97 @@ endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
ifeq ($(PLATFORM), linux)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/0c/fa/00d85f893b02289e2942849d97f8818dde8e2111182e825fb3a735677791/python_fcl-0.0.12-cp37-cp37m-manylinux1_x86_64.whl
|
||||
cd dist/working && unzip python_fcl*
|
||||
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
cd dist/working && wget $(HPPFCL_URL)
|
||||
cd dist/working && tar -xf hpp-fcl*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/19/e6/6e9f33fb59e8f27c0e1592bd26e644bc92b85c942b072b2d3854105d5887/python_fcl_win32-0.0.12.post3-py3-none-win_amd64.whl
|
||||
cd dist/working && unzip python_fcl*
|
||||
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ASSIMP_URL)
|
||||
cd dist/working && tar -xf assimp*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && tar -xf eigenpy*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(OCTOMAP_URL)
|
||||
cd dist/working && tar -xf octomap*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
# Uh, do nothing, apparently? No DLLs are shipped.
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(BOOST_URL)
|
||||
cd dist/working && tar -xf boost*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ZLIB_URL)
|
||||
cd dist/working && tar -xf zlib*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by BIMTester
|
||||
mkdir dist/working
|
||||
|
||||
@@ -15,6 +15,7 @@ import site
|
||||
|
||||
# process *.pth in /libs/site/packages to setup globally importable modules
|
||||
# 3 levels deep required by occ static ../../ path
|
||||
# TODO: 3 levels deep is no longer required as we no longer bundle OCC
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
site.addsitedir(os.path.join(cwd, "libs", "site", "packages"))
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ bpy = sys.modules.get("bpy")
|
||||
if bpy is not None:
|
||||
import bpy
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator, gizmos
|
||||
from . import handler, ui, prop, operator
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
@@ -14,6 +14,7 @@ if bpy is not None:
|
||||
"bcf": None,
|
||||
"root": None,
|
||||
"unit": None,
|
||||
"model": None,
|
||||
"georeference": None,
|
||||
"context": None,
|
||||
"drawing": None,
|
||||
@@ -24,13 +25,17 @@ if bpy is not None:
|
||||
"aggregate": None,
|
||||
"geometry": None,
|
||||
"cobie": None,
|
||||
"resource": None,
|
||||
"cost": None,
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"system": None,
|
||||
"structural": None,
|
||||
"boundary": None,
|
||||
"profile": None,
|
||||
"material": None,
|
||||
"style": None,
|
||||
"layer": None,
|
||||
"model": None,
|
||||
"owner": None,
|
||||
"pset": None,
|
||||
"qto": None,
|
||||
@@ -58,98 +63,29 @@ if bpy is not None:
|
||||
operator.SelectIfcFile,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.SelectExternalMaterialDir,
|
||||
operator.AddSweptSolid,
|
||||
operator.RemoveSweptSolid,
|
||||
operator.AssignSweptSolidOuterCurve,
|
||||
operator.SelectSweptSolidOuterCurve,
|
||||
operator.AddSweptSolidInnerCurve,
|
||||
operator.SelectSweptSolidInnerCurves,
|
||||
operator.AssignSweptSolidExtrusion,
|
||||
operator.SelectSweptSolidExtrusion,
|
||||
operator.FetchExternalMaterial,
|
||||
operator.FetchObjectPassport,
|
||||
operator.CutSection,
|
||||
operator.AddSheet,
|
||||
operator.OpenSheet,
|
||||
operator.AddDrawingToSheet,
|
||||
operator.CreateSheets,
|
||||
operator.OpenView,
|
||||
operator.OpenViewCamera,
|
||||
operator.ActivateView,
|
||||
operator.OpenUpstream,
|
||||
operator.AddSectionPlane,
|
||||
operator.RemoveSectionPlane,
|
||||
operator.ReloadIfcFile,
|
||||
operator.AddIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.AddAnnotation,
|
||||
operator.GenerateReferences,
|
||||
operator.ResizeText,
|
||||
operator.AddVariable,
|
||||
operator.RemoveVariable,
|
||||
operator.PropagateTextData,
|
||||
operator.SetOverrideColour,
|
||||
operator.RemoveDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.ActivateDrawingStyle,
|
||||
operator.EditVectorStyle,
|
||||
operator.RemoveSheet,
|
||||
operator.AddSchedule,
|
||||
operator.RemoveSchedule,
|
||||
operator.SelectScheduleFile,
|
||||
operator.BuildSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.SetViewportShadowFromSun,
|
||||
operator.AddDrawingStyleAttribute,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.CopyPropertyToSelection,
|
||||
operator.CopyAttributeToSelection,
|
||||
operator.RefreshDrawingList,
|
||||
operator.CleanWireframes,
|
||||
operator.LinkIfc,
|
||||
operator.SnapSpacesTogether,
|
||||
operator.CopyGrid,
|
||||
operator.AddSectionsAnnotations,
|
||||
prop.StrProperty,
|
||||
prop.Attribute,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
prop.DrawingStyle,
|
||||
prop.Sheet,
|
||||
prop.BIMProperties,
|
||||
prop.DocProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
prop.GlobalId,
|
||||
prop.RepresentationItem,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.SweptSolid,
|
||||
prop.ItemSlotMap,
|
||||
prop.BIMMeshProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.BIMTextProperties,
|
||||
ui.BIM_PT_section_plane,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_misc_utilities,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_drawinglist,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
gizmos.UglyDotGizmo,
|
||||
gizmos.DotGizmo,
|
||||
gizmos.DimensionLabelGizmo,
|
||||
gizmos.ExtrusionGuidesGizmo,
|
||||
gizmos.ExtrusionWidget
|
||||
]
|
||||
|
||||
for module in modules.values():
|
||||
@@ -169,55 +105,49 @@ if bpy is not None:
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||
bpy.app.handlers.undo_pre.append(handler.undo_pre)
|
||||
bpy.app.handlers.undo_post.append(handler.undo_post)
|
||||
bpy.app.handlers.redo_pre.append(handler.redo_pre)
|
||||
bpy.app.handlers.redo_post.append(handler.redo_post)
|
||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
||||
bpy.app.handlers.save_pre.append(handler.storeIdMap)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this
|
||||
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(
|
||||
type=prop.BIMObjectProperties
|
||||
) # Check if we need this
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
|
||||
|
||||
for module in modules.values():
|
||||
module.register()
|
||||
|
||||
bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler)
|
||||
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.remove(handler.storeIdMap)
|
||||
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Scene.DocProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
del bpy.types.Collection.BIMObjectProperties # Check if we need this
|
||||
del bpy.types.Collection.BIMObjectProperties # Check if we need this
|
||||
del bpy.types.Material.BIMMaterialProperties
|
||||
del bpy.types.Mesh.BIMMeshProperties
|
||||
del bpy.types.Curve.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.TextCurve.BIMTextProperties
|
||||
del bpy.types.PointLight.BIMMeshProperties
|
||||
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
|
||||
|
||||
for module in reversed(list(modules.values())):
|
||||
module.unregister()
|
||||
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler)
|
||||
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
import time
|
||||
import numpy
|
||||
import pickle
|
||||
import multiprocessing
|
||||
|
||||
try:
|
||||
from OCC.Core import (
|
||||
gp,
|
||||
Geom,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRep,
|
||||
BRepPrimAPI,
|
||||
BRepAlgoAPI,
|
||||
BRepBuilderAPI,
|
||||
TopOpeBRepTool,
|
||||
TopOpeBRepBuild,
|
||||
ShapeExtend,
|
||||
GProp,
|
||||
BRepGProp,
|
||||
GC,
|
||||
ShapeAnalysis,
|
||||
TopTools,
|
||||
TopExp,
|
||||
TopAbs,
|
||||
HLRAlgo,
|
||||
HLRBRep,
|
||||
TopLoc,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRepTools,
|
||||
TopoDS,
|
||||
GeomLProp,
|
||||
IntCurvesFace,
|
||||
)
|
||||
from OCC.Core.TopoDS import topods
|
||||
except ImportError:
|
||||
from OCC import (
|
||||
gp,
|
||||
Geom,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRep,
|
||||
BRepPrimAPI,
|
||||
BRepAlgoAPI,
|
||||
BRepBuilderAPI,
|
||||
TopOpeBRepTool,
|
||||
TopOpeBRepBuild,
|
||||
ShapeExtend,
|
||||
GProp,
|
||||
BRepGProp,
|
||||
GC,
|
||||
ShapeAnalysis,
|
||||
TopTools,
|
||||
TopExp,
|
||||
TopAbs,
|
||||
HLRAlgo,
|
||||
HLRBRep,
|
||||
TopLoc,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRepTools,
|
||||
TopoDS,
|
||||
GeomLProp,
|
||||
IntCurvesFace,
|
||||
)
|
||||
from OCC.TopoDS import topods
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.element
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
this_file = os.path.join(cwd, "cut_ifc.py")
|
||||
|
||||
|
||||
def get_booleaned_edges(shape):
|
||||
edges = []
|
||||
exp = TopExp.TopExp_Explorer(shape, TopAbs.TopAbs_EDGE)
|
||||
while exp.More():
|
||||
edges.append(topods.Edge(exp.Current()))
|
||||
exp.Next()
|
||||
return edges
|
||||
|
||||
|
||||
def connect_edges_into_wires(unconnected_edges):
|
||||
edges = TopTools.TopTools_HSequenceOfShape()
|
||||
edges_handle = TopTools.Handle_TopTools_HSequenceOfShape(edges)
|
||||
wires = TopTools.TopTools_HSequenceOfShape()
|
||||
wires_handle = TopTools.Handle_TopTools_HSequenceOfShape(wires)
|
||||
|
||||
for edge in unconnected_edges:
|
||||
edges.Append(edge)
|
||||
|
||||
ShapeAnalysis.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(edges_handle, 1e-5, True, wires_handle)
|
||||
return wires_handle.GetObject()
|
||||
|
||||
|
||||
def do_cut(process_data):
|
||||
global_id, shape, section, trsf_data = process_data
|
||||
|
||||
axis = gp.gp_Ax2(
|
||||
gp.gp_Pnt(trsf_data["top_left_corner"][0], trsf_data["top_left_corner"][1], trsf_data["top_left_corner"][2]),
|
||||
gp.gp_Dir(trsf_data["projection"][0], trsf_data["projection"][1], trsf_data["projection"][2]),
|
||||
gp.gp_Dir(trsf_data["x_axis"][0], trsf_data["x_axis"][1], trsf_data["x_axis"][2]),
|
||||
)
|
||||
source = gp.gp_Ax3(axis)
|
||||
destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0))
|
||||
transformation = gp.gp_Trsf()
|
||||
transformation.SetDisplacement(source, destination)
|
||||
|
||||
cut_polygons = []
|
||||
section = BRepAlgoAPI.BRepAlgoAPI_Section(section, shape).Shape()
|
||||
section_edges = get_booleaned_edges(section)
|
||||
if len(section_edges) <= 0:
|
||||
return cut_polygons
|
||||
wires = connect_edges_into_wires(section_edges)
|
||||
for i in range(wires.Length()):
|
||||
wire_shape = wires.Value(i + 1)
|
||||
|
||||
transformed_wire = BRepBuilderAPI.BRepBuilderAPI_Transform(wire_shape, transformation)
|
||||
wire_shape = transformed_wire.Shape()
|
||||
|
||||
wire = topods.Wire(wire_shape)
|
||||
face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(wire).Face()
|
||||
|
||||
points = []
|
||||
exp = BRepTools.BRepTools_WireExplorer(wire)
|
||||
while exp.More():
|
||||
point = BRep.BRep_Tool.Pnt(exp.CurrentVertex())
|
||||
points.append((point.X(), -point.Y()))
|
||||
exp.Next()
|
||||
cut_polygons.append({"global_id": global_id, "metadata": {}, "points": points})
|
||||
return cut_polygons
|
||||
|
||||
|
||||
class IfcCutter:
|
||||
def __init__(self):
|
||||
self.time = None
|
||||
self.selector = ifcopenshell.util.selector.Selector()
|
||||
self.product_shapes = []
|
||||
self.background_elements = []
|
||||
self.cut_polygons = []
|
||||
self.template_variables = {}
|
||||
self.metadata = {}
|
||||
self.data_dir = ""
|
||||
self.vector_style = ""
|
||||
self.ifc_filenames = []
|
||||
self.ifc_files = {}
|
||||
self.resolved_pixels = set()
|
||||
self.text_pickle_file = "text.pickle"
|
||||
self.metadata_pickle_file = "metadata.pickle"
|
||||
self.cut_pickle_file = "cut.pickle"
|
||||
self.should_recut = True
|
||||
self.should_recut_selected = True
|
||||
self.cut_objects = ""
|
||||
self.selected_global_ids = []
|
||||
self.should_extract = True
|
||||
self.diagram_name = None
|
||||
self.background_image = None
|
||||
self.section_box = {
|
||||
"projection": (0, 1, 0),
|
||||
"x_axis": (1, 0, 0),
|
||||
"y_axis": (0, 0, -1),
|
||||
"top_left_corner": (-2, 2, 8),
|
||||
"x": 14,
|
||||
"y": 9,
|
||||
"z": 2,
|
||||
"shape": None,
|
||||
"face": None,
|
||||
}
|
||||
|
||||
def cut(self):
|
||||
self.profile_code("Starting cut process")
|
||||
self.load_ifc_files()
|
||||
self.profile_code("Load IFC files")
|
||||
self.get_template_variables()
|
||||
self.profile_code("Get template variables")
|
||||
self.get_product_shapes()
|
||||
self.profile_code("Get product shapes")
|
||||
self.create_section_box()
|
||||
self.profile_code("Create section box")
|
||||
self.get_cut_polygons()
|
||||
self.profile_code("Get cut polygons")
|
||||
self.get_annotation()
|
||||
self.profile_code("Get annotation")
|
||||
self.get_cut_polygon_metadata()
|
||||
self.profile_code("Get cut polygon metadata")
|
||||
|
||||
def profile_code(self, message):
|
||||
if not self.time:
|
||||
self.time = time.time()
|
||||
print("{} :: {:.2f}".format(message, time.time() - self.time))
|
||||
self.time = time.time()
|
||||
|
||||
def load_ifc_files(self):
|
||||
if not self.should_recut and not self.should_extract:
|
||||
return
|
||||
|
||||
loaded_files = []
|
||||
for filename in self.ifc_filenames:
|
||||
print("Loading file {} ...".format(filename))
|
||||
if filename:
|
||||
self.ifc_files[filename] = ifcopenshell.open(filename)
|
||||
|
||||
def get_template_variables(self):
|
||||
if not self.should_extract:
|
||||
if os.path.isfile(self.text_pickle_file):
|
||||
with open(self.text_pickle_file, "rb") as text_file:
|
||||
self.template_variables = pickle.load(text_file)
|
||||
return
|
||||
|
||||
data = {}
|
||||
for text_obj in self.text_objs:
|
||||
text_obj_data = self.get_text_variables(text_obj)
|
||||
if text_obj_data:
|
||||
data[text_obj.name] = text_obj_data
|
||||
|
||||
with open(self.text_pickle_file, "wb") as text_file:
|
||||
pickle.dump(data, text_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
self.template_variables = data
|
||||
|
||||
def get_text_variables(self, text_obj):
|
||||
text_obj_data = {}
|
||||
text_body = text_obj.data.body
|
||||
related_element = text_obj.data.BIMTextProperties.related_element
|
||||
if not related_element:
|
||||
return
|
||||
global_id = related_element.BIMObjectProperties.attributes.get("GlobalId")
|
||||
if not global_id:
|
||||
return
|
||||
element = self.get_ifc_element(global_id.string_value)
|
||||
for variable in text_obj.data.BIMTextProperties.variables:
|
||||
if element:
|
||||
if "{{" in variable.prop_key:
|
||||
prop_key = variable.prop_key.split("{{")[1].split("}}")[0]
|
||||
prop_value = self.selector.get_element_value(element, prop_key)
|
||||
variable_value = eval(variable.prop_key.replace("{{" + prop_key + "}}", str(prop_value)))
|
||||
else:
|
||||
variable_value = self.selector.get_element_value(element, variable.prop_key)
|
||||
text_obj_data[variable.name] = variable_value
|
||||
return text_obj_data
|
||||
|
||||
def get_product_shapes(self):
|
||||
if not self.should_recut:
|
||||
return
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.USE_PYTHON_OPENCASCADE, True)
|
||||
products = []
|
||||
|
||||
for filename, ifc_file in self.ifc_files.items():
|
||||
shape_pickle = os.path.join(
|
||||
self.data_dir, "cache", "shapes", "{}.pickle".format(os.path.basename(filename))
|
||||
)
|
||||
shape_map = {}
|
||||
if self.should_recut_selected and os.path.isfile(shape_pickle):
|
||||
with open(shape_pickle, "rb") as shape_file:
|
||||
shape_map = pickle.load(shape_file)
|
||||
|
||||
products.extend(self.selector.parse(ifc_file, self.cut_objects))
|
||||
|
||||
selected_elements = []
|
||||
for i, product in enumerate(products):
|
||||
if (
|
||||
product.is_a("IfcOpeningElement")
|
||||
or product.is_a("IfcSite")
|
||||
or product.Representation is None
|
||||
or self.has_annotation(product)
|
||||
):
|
||||
continue
|
||||
try:
|
||||
if self.should_recut_selected and product.GlobalId in self.selected_global_ids:
|
||||
selected_elements.append(product)
|
||||
elif product.GlobalId in shape_map:
|
||||
shape = shape_map[product.GlobalId]
|
||||
self.add_product_shape(product, shape)
|
||||
else:
|
||||
selected_elements.append(product)
|
||||
except:
|
||||
print("Failed to create shape for {}".format(product))
|
||||
|
||||
if selected_elements:
|
||||
total = 0
|
||||
checkpoint = time.time()
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements
|
||||
)
|
||||
valid_file = iterator.initialize()
|
||||
if valid_file:
|
||||
while True:
|
||||
total += 1
|
||||
if total % 250 == 0:
|
||||
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
|
||||
checkpoint = time.time()
|
||||
shape = iterator.get()
|
||||
shape_map[shape.data.guid] = shape.geometry
|
||||
self.add_product_shape(ifc_file.by_guid(shape.data.guid), shape.geometry)
|
||||
if not iterator.next():
|
||||
break
|
||||
|
||||
with open(shape_pickle, "wb") as shape_file:
|
||||
pickle.dump(shape_map, shape_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def add_product_shape(self, product, shape):
|
||||
self.product_shapes.append((product, shape))
|
||||
|
||||
def has_annotation(self, element):
|
||||
for representation in element.Representation.Representations:
|
||||
if (
|
||||
representation.ContextOfItems.ContextType == "Plan"
|
||||
and representation.ContextOfItems.ContextIdentifier == "Annotation"
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_section_box(self):
|
||||
top_left_corner = gp.gp_Pnt(
|
||||
self.section_box["top_left_corner"][0],
|
||||
self.section_box["top_left_corner"][1],
|
||||
self.section_box["top_left_corner"][2],
|
||||
)
|
||||
axis = gp.gp_Ax2(
|
||||
top_left_corner,
|
||||
gp.gp_Dir(
|
||||
self.section_box["projection"][0], self.section_box["projection"][1], self.section_box["projection"][2]
|
||||
),
|
||||
gp.gp_Dir(self.section_box["x_axis"][0], self.section_box["x_axis"][1], self.section_box["x_axis"][2]),
|
||||
)
|
||||
section_box = BRepPrimAPI.BRepPrimAPI_MakeBox(
|
||||
axis, self.section_box["x"], self.section_box["y"], self.section_box["z"]
|
||||
)
|
||||
self.section_box["shape"] = section_box.Shape()
|
||||
self.section_box["face"] = section_box.BottomFace()
|
||||
|
||||
source = gp.gp_Ax3(axis)
|
||||
self.transformation_data = {
|
||||
"top_left_corner": self.section_box["top_left_corner"],
|
||||
"projection": self.section_box["projection"],
|
||||
"x_axis": self.section_box["x_axis"],
|
||||
}
|
||||
destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0))
|
||||
self.transformation_dest = destination
|
||||
self.transformation = gp.gp_Trsf()
|
||||
self.transformation.SetDisplacement(source, destination)
|
||||
|
||||
def get_bbox(self, shape):
|
||||
bbox = Bnd.Bnd_Box()
|
||||
BRepBndLib.brepbndlib_Add(shape, bbox)
|
||||
return bbox
|
||||
|
||||
def calculate_face_zpos(self, face):
|
||||
bbox = self.get_bbox(face)
|
||||
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
||||
zpos = zmin + ((zmax - zmin) / 2)
|
||||
return zpos, zmax
|
||||
|
||||
def get_booleaned_edges(self, shape):
|
||||
edges = []
|
||||
exp = TopExp.TopExp_Explorer(shape, TopAbs.TopAbs_EDGE)
|
||||
while exp.More():
|
||||
edges.append(topods.Edge(exp.Current()))
|
||||
exp.Next()
|
||||
return edges
|
||||
|
||||
def get_cut_polygons(self):
|
||||
if self.should_recut:
|
||||
self.get_fresh_cut_polygons()
|
||||
self.pickle_cut_polygons()
|
||||
else:
|
||||
self.get_pickled_cut_polygons()
|
||||
|
||||
def get_annotation(self):
|
||||
import mathutils
|
||||
|
||||
self.annotation_objs = []
|
||||
settings_2d = ifcopenshell.geom.settings()
|
||||
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
|
||||
settings_py = ifcopenshell.geom.settings()
|
||||
settings_py.set(settings_py.USE_PYTHON_OPENCASCADE, True)
|
||||
for ifc_file in self.ifc_files.values():
|
||||
for element in ifc_file.by_type("IfcElement"):
|
||||
annotation_representation = None
|
||||
box_representation = None
|
||||
if not element.Representation:
|
||||
continue # This can occur for aggregates
|
||||
for representation in element.Representation.Representations:
|
||||
if (
|
||||
representation.ContextOfItems.ContextType == "Plan"
|
||||
and representation.ContextOfItems.ContextIdentifier == "Annotation"
|
||||
):
|
||||
annotation_representation = representation
|
||||
elif (
|
||||
representation.ContextOfItems.ContextType == "Model"
|
||||
and representation.ContextOfItems.ContextIdentifier == "Box"
|
||||
):
|
||||
box_representation = representation
|
||||
if not annotation_representation or not box_representation:
|
||||
continue
|
||||
|
||||
# This is bad code. See bug #85 to make it slightly less bad.
|
||||
# Effectively if the bbox does not intersect with the camera
|
||||
# plane, then we should "continue" and not process the 2D
|
||||
# wireframe. This approach works but is not very smart.
|
||||
for subelement in ifc_file.traverse(box_representation):
|
||||
if subelement.is_a("IfcBoundingBox"):
|
||||
block = ifc_file.createIfcBlock(
|
||||
ifc_file.createIfcAxis2Placement3D(subelement.Corner, None, None),
|
||||
subelement.XDim,
|
||||
subelement.YDim,
|
||||
subelement.ZDim,
|
||||
)
|
||||
for inverse in ifc_file.get_inverse(subelement):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, subelement, block)
|
||||
element.Representation.Representations = [box_representation]
|
||||
shape = ifcopenshell.geom.create_shape(settings_py, element)
|
||||
|
||||
section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box["face"], shape.geometry).Shape()
|
||||
section_edges = get_booleaned_edges(section)
|
||||
|
||||
if len(section_edges) <= 0:
|
||||
# The bounding box of the annotation object does not
|
||||
# intersect with the camera plane, so don't bother drawing
|
||||
# the annotation
|
||||
continue
|
||||
|
||||
# Monkey patch - see bug #771.
|
||||
element.Representation.Representations = [annotation_representation]
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, element)
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
m = shape.transformation.matrix.data
|
||||
mat = mathutils.Matrix(
|
||||
([m[0], m[1], m[2], 0], [m[3], m[4], m[5], 0], [m[6], m[7], m[8], 0], [m[9], m[10], m[11], 1])
|
||||
)
|
||||
mat.transpose()
|
||||
self.annotation_objs.append(
|
||||
{
|
||||
"raw": element,
|
||||
"classes": self.get_classes(element, "annotation"),
|
||||
"edges": [[e[i], e[i + 1]] for i in range(0, len(e), 2)],
|
||||
"vertices": [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)],
|
||||
}
|
||||
)
|
||||
|
||||
def get_cut_polygon_metadata(self):
|
||||
if not self.should_extract:
|
||||
if os.path.isfile(self.metadata_pickle_file):
|
||||
with open(self.metadata_pickle_file, "rb") as metadata_file:
|
||||
self.metadata = pickle.load(metadata_file)
|
||||
|
||||
for polygon in self.cut_polygons:
|
||||
if polygon["global_id"] in self.metadata:
|
||||
polygon["metadata"] = self.metadata[polygon["global_id"]]
|
||||
return
|
||||
|
||||
for polygon in self.cut_polygons:
|
||||
metadata = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), "cut")}
|
||||
self.metadata[polygon["global_id"]] = metadata
|
||||
polygon["metadata"] = metadata
|
||||
|
||||
with open(self.metadata_pickle_file, "wb") as metadata_file:
|
||||
pickle.dump(self.metadata, metadata_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def pickle_cut_polygons(self):
|
||||
with open(self.cut_pickle_file, "wb") as pickle_file:
|
||||
pickle.dump(self.cut_polygons, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def get_fresh_cut_polygons(self):
|
||||
process_data = [
|
||||
(p.GlobalId, s, self.section_box["face"], self.transformation_data) for p, s in self.product_shapes
|
||||
]
|
||||
|
||||
import bpy
|
||||
|
||||
if bpy.app.version > (2, 90, 0) and os.name == 'nt':
|
||||
# See bug #1148
|
||||
for data in process_data:
|
||||
results = do_cut(data)
|
||||
polygons = [r for r in results if r["points"]]
|
||||
self.cut_polygons.extend(polygons)
|
||||
else:
|
||||
multiprocessing.set_executable(bpy.app.binary_path_python)
|
||||
|
||||
with multiprocessing.Pool(9) as p:
|
||||
results = p.map(do_cut, process_data)
|
||||
for result in results:
|
||||
polygons = [p for p in result if p["points"]]
|
||||
self.cut_polygons.extend(polygons)
|
||||
|
||||
def get_polygon_metadata(self, polygon, position):
|
||||
polygon["metadata"] = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), position)}
|
||||
return polygon
|
||||
|
||||
def get_ifc_element(self, global_id):
|
||||
# TODO: make this less bad
|
||||
element = None
|
||||
for ifc_file in self.ifc_files.values():
|
||||
try:
|
||||
element = ifc_file.by_id(global_id)
|
||||
return element
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_classes(self, element, position):
|
||||
classes = [position, element.is_a()]
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material:
|
||||
classes.append(
|
||||
"material-{}".format(
|
||||
re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))
|
||||
)
|
||||
)
|
||||
classes.append("globalid-{}".format(element.GlobalId))
|
||||
for attribute in self.attributes:
|
||||
result = self.selector.get_element_value(element, attribute)
|
||||
if result:
|
||||
classes.append(
|
||||
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
|
||||
)
|
||||
return classes
|
||||
|
||||
def get_material_name(self, element):
|
||||
if hasattr(element, "Name") and element.Name:
|
||||
return element.Name
|
||||
elif hasattr(element, "LayerSetName") and element.LayerSetName:
|
||||
return element.LayerSetName
|
||||
return "mat-" + str(element.id())
|
||||
|
||||
def get_pickled_cut_polygons(self):
|
||||
if os.path.isfile(self.cut_pickle_file):
|
||||
with open(self.cut_pickle_file, "rb") as pickle_file:
|
||||
self.cut_polygons = pickle.load(pickle_file)
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="jsgantt.js" type="text/javascript"></script>
|
||||
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
||||
<script type="text/javascript">
|
||||
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
|
||||
g.setOptions({
|
||||
vCaptionType: 'Complete', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
|
||||
vWeekMinorDateDisplayFormat: 'dd mon', // Set format to display dates in the "Minor" header of the "Week" view
|
||||
vLang: 'en',
|
||||
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vTotalHeight: 1000,
|
||||
});
|
||||
var json_data = `
|
||||
{{{json_data}}}
|
||||
`;
|
||||
JSGantt.parseJSONString(json_data, g);
|
||||
g.Draw();
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
* { stroke-linecap: round; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
.background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
* { stroke-linecap: round; stroke-linejoin: round; }
|
||||
*[id] { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
.solid {}
|
||||
|
||||
@@ -7,7 +7,8 @@ import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.api.root.remove_product as remove_product
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell.api.spatial.data import Data as SpatialData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import addon_utils
|
||||
|
||||
@@ -46,12 +47,8 @@ class IfcExporter:
|
||||
jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json()
|
||||
with open(self.ifc_export_settings.output_file, "w") as outfile:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
if bpy.data.filepath:
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
def set_header(self):
|
||||
# TODO: add all metadata, pending bug #747
|
||||
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.wrapped_data.header.file_name.time_stamp = (
|
||||
datetime.datetime.utcnow()
|
||||
@@ -64,55 +61,54 @@ class IfcExporter:
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
self.get_application_name(), self.get_application_version()
|
||||
)
|
||||
# TODO: reimplement. See #1222.
|
||||
# if self.owner_history:
|
||||
# if self.schema_version == "IFC2X3":
|
||||
# self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id
|
||||
# else:
|
||||
# self.file.wrapped_data.header.file_name.authorization = (
|
||||
# self.owner_history.OwningUser.ThePerson.Identification
|
||||
# )
|
||||
# else:
|
||||
# self.file.wrapped_data.header.file_name.authorization = "Nobody"
|
||||
|
||||
def sync_object_placements_and_deletions(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
to_delete = []
|
||||
|
||||
for guid, obj in IfcStore.guid_map.items():
|
||||
for ifc_definition_id, obj in IfcStore.id_map.items():
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
continue
|
||||
self.sync_object_placement(obj)
|
||||
except:
|
||||
pass
|
||||
if self.should_delete(guid, obj):
|
||||
to_delete.append(guid)
|
||||
self.sync_object_container(ifc_definition_id, obj)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
if self.should_delete(obj):
|
||||
to_delete.append(ifc_definition_id)
|
||||
|
||||
for guid in to_delete:
|
||||
product = self.file.by_id(guid)
|
||||
SpatialData.purge()
|
||||
|
||||
for ifc_definition_id in to_delete:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
IfcStore.unlink_element(product)
|
||||
remove_product.Usecase(self.file, {"product": product}).execute()
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
|
||||
def sync_edited_objects(self):
|
||||
for obj_name in IfcStore.edited_objs.copy():
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
for obj in IfcStore.edited_objs.copy():
|
||||
if not obj:
|
||||
continue
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
bpy.ops.bim.update_mesh_representation(obj=obj.name)
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
bpy.ops.bim.update_style_colours(material=obj.name)
|
||||
else:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
blender_matrix = np.matrix(obj.matrix_world)
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).ObjectPlacement
|
||||
)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not hasattr(element, "ObjectPlacement"):
|
||||
return
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
ifc_matrix[0][3] *= self.unit_scale
|
||||
ifc_matrix[1][3] *= self.unit_scale
|
||||
ifc_matrix[2][3] *= self.unit_scale
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
ifc_matrix = ifcopenshell.util.geolocation.global2local(
|
||||
ifc_matrix,
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
@@ -124,7 +120,41 @@ class IfcExporter:
|
||||
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
def should_delete(self, guid, obj):
|
||||
def sync_object_container(self, guid, obj):
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element_collection = bpy.data.collections.get(obj.name)
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcContext"):
|
||||
return
|
||||
|
||||
if (
|
||||
(element.is_a("IfcElement") and element_collection)
|
||||
or element.is_a("IfcSpatialStructureElement")
|
||||
or element.is_a("IfcGrid")
|
||||
):
|
||||
try:
|
||||
parent_collection = [c for c in bpy.data.collections if c.children.get(element_collection.name)][0]
|
||||
except:
|
||||
return # Out of the spatial tree
|
||||
else:
|
||||
parent_collection = obj.users_collection[0]
|
||||
|
||||
|
||||
parent_obj = bpy.data.objects.get(parent_collection.name)
|
||||
if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
parent = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"):
|
||||
if parent != ifcopenshell.util.element.get_container(element):
|
||||
bpy.ops.bim.assign_container(relating_structure=parent.id(), related_element=obj.name)
|
||||
elif parent != ifcopenshell.util.element.get_aggregate(element):
|
||||
bpy.ops.bim.assign_object(relating_object=parent_obj.name, related_object=obj.name)
|
||||
|
||||
def should_delete(self, obj):
|
||||
try:
|
||||
# This will throw an exception if the Blender object no longer exists
|
||||
foo = obj.name
|
||||
|
||||
@@ -1,40 +1,105 @@
|
||||
import bpy
|
||||
import json
|
||||
import addon_utils
|
||||
import blenderbim.bim.decoration as decoration
|
||||
import ifcopenshell.api.owner.settings
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.owner.prop import getPersons, getOrganisations
|
||||
from ifcopenshell.api.attribute.data import Data as AttributeData
|
||||
from ifcopenshell.api.material.data import Data as MaterialData
|
||||
from ifcopenshell.api.style.data import Data as StyleData
|
||||
from ifcopenshell.api.type.data import Data as TypeData
|
||||
|
||||
|
||||
global_subscription_owner = object()
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
if (
|
||||
obj.mode != "OBJECT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, bpy.types.Mesh)
|
||||
or not obj.data.BIMMeshProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
if not bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
return
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
IfcStore.edited_objs.add(obj.name)
|
||||
objects = bpy.context.selected_objects
|
||||
if bpy.context.active_object:
|
||||
objects += [bpy.context.active_object]
|
||||
for obj in objects:
|
||||
if (
|
||||
obj.mode != "EDIT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
|
||||
or not obj.BIMObjectProperties.ifc_definition_id
|
||||
):
|
||||
continue
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ["Tessellation", "Brep", "Annotation2D"]:
|
||||
IfcStore.edited_objs.add(obj)
|
||||
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
# Blender material names are up to 63 UTF-8 bytes
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name or len(bytes(obj.name, "utf-8")) >= 63:
|
||||
# TODO Do we still need this, now that we are monitoring the undo redo objects?
|
||||
try:
|
||||
obj.name
|
||||
except:
|
||||
return # In case the object RNA is gone during an undo / redo operation
|
||||
# Blender names are up to 63 UTF-8 bytes
|
||||
if len(bytes(obj.name, "utf-8")) >= 63:
|
||||
return
|
||||
|
||||
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
|
||||
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
MaterialData.load_materials()
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
|
||||
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
|
||||
return
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
|
||||
return
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
|
||||
collection = obj.users_collection[0]
|
||||
collection.name = obj.name
|
||||
if element.is_a("IfcGrid"):
|
||||
axis_obj = IfcStore.get_element(element.UAxes[0].id())
|
||||
axis_collection = axis_obj.users_collection[0]
|
||||
grid_collection = None
|
||||
for collection in bpy.data.collections:
|
||||
if axis_collection.name in collection.children.keys():
|
||||
grid_collection = collection
|
||||
break
|
||||
if grid_collection:
|
||||
grid_collection.name = obj.name
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
TypeData.purge()
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
|
||||
def color_callback(obj, data):
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
|
||||
def active_object_callback():
|
||||
obj = bpy.context.active_object
|
||||
for obj in bpy.context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
stored_obj = IfcStore.get_element(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if stored_obj and stored_obj != obj:
|
||||
bpy.ops.bim.copy_class(obj=obj.name)
|
||||
|
||||
|
||||
def subscribe_to(object, data_path, callback):
|
||||
subscribe_to = object.path_resolve(data_path, False)
|
||||
try:
|
||||
subscribe_to = object.path_resolve(data_path, False)
|
||||
except:
|
||||
return
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_to,
|
||||
owner=object,
|
||||
@@ -52,69 +117,81 @@ def subscribe_to(object, data_path, callback):
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
|
||||
for name in modules.keys():
|
||||
for name, value in modules.items():
|
||||
try:
|
||||
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
getattr(value, "prop").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene):
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
props = bpy.context.scene.BIMProperties
|
||||
IfcStore.id_map = (
|
||||
{int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
|
||||
)
|
||||
IfcStore.guid_map = (
|
||||
{k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {}
|
||||
)
|
||||
IfcStore.purge()
|
||||
purge_module_data()
|
||||
ifc_file = IfcStore.get_file()
|
||||
if not ifc_file:
|
||||
return
|
||||
IfcStore.get_schema()
|
||||
IfcStore.reload_linked_elements()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo()
|
||||
purge_module_data()
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo()
|
||||
purge_module_data()
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
|
||||
|
||||
|
||||
@persistent
|
||||
def ensureIfcExported(scene):
|
||||
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
||||
# The invocation pops up a file select window.
|
||||
# This is non-blocking, therefore the Blend file is saved before we export.
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT", should_force_resave=True)
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT")
|
||||
|
||||
|
||||
@persistent
|
||||
def storeIdMap(scene):
|
||||
try:
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
except:
|
||||
# Regenerate maps. Is there a better solution for this? It seems fragile.
|
||||
file = IfcStore.get_file()
|
||||
IfcStore.id_map = {
|
||||
o.ifc_definition_id: o.name for o in bpy.data.objects if o.BIMObjectProperties.ifc_definition_id
|
||||
}
|
||||
IfcStore.guid_map = {
|
||||
file.by_id(i).GlobalId: n for i, n in IfcStore.id_map.items() if file.by_id(i).is_a("IfcRoot")
|
||||
}
|
||||
# Then attempt to store it again
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
ifcopenshell.api.owner.settings.get_person = (
|
||||
lambda: IfcStore.get_file().by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
|
||||
if bpy.context.scene.BIMOwnerProperties.user_person
|
||||
else None
|
||||
def get_application(ifc):
|
||||
version = get_application_version()
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
if element.ApplicationIdentifier == "BlenderBIM" and element.Version == version:
|
||||
return element
|
||||
return ifcopenshell.api.run(
|
||||
"owner.add_application",
|
||||
ifc,
|
||||
version=get_application_version(),
|
||||
application_full_name="BlenderBIM Add-on",
|
||||
application_identifier="BlenderBIM",
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_organisation = (
|
||||
lambda: IfcStore.get_file().by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
|
||||
if bpy.context.scene.BIMOwnerProperties.user_organisation
|
||||
else None
|
||||
)
|
||||
ifcopenshell.api.owner.settings.settings["ApplicationIdentifier"] = "BlenderBIM"
|
||||
ifcopenshell.api.owner.settings.settings["ApplicationFullName"] = "BlenderBIM Add-on"
|
||||
ifcopenshell.api.owner.settings.settings["Version"] = ".".join(
|
||||
|
||||
|
||||
def get_application_version():
|
||||
return ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
@@ -124,6 +201,26 @@ def setDefaultProperties(scene):
|
||||
][0]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
global global_subscription_owner
|
||||
active_object_key = bpy.types.LayerObjects, "active"
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_person = (
|
||||
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
|
||||
if getPersons(None, bpy.context) and bpy.context.scene.BIMOwnerProperties.user_person
|
||||
else None
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_organisation = (
|
||||
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
|
||||
if getOrganisations(None, bpy.context) and bpy.context.scene.BIMOwnerProperties.user_organisation
|
||||
else None
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Technical"
|
||||
@@ -191,12 +288,3 @@ def setDefaultProperties(scene):
|
||||
drawing_style.name = "Blender Default"
|
||||
drawing_style.render_type = "DEFAULT"
|
||||
bpy.ops.bim.save_drawing_style(index="2")
|
||||
|
||||
|
||||
@persistent
|
||||
def toggleDecorationsOnLoad(*args):
|
||||
toggle = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
if toggle:
|
||||
decoration.DecorationsHandler.install(bpy.context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
|
||||
@@ -1,411 +1,84 @@
|
||||
import bpy
|
||||
import json
|
||||
import math
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import bpy
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
# TODO: Deprecate this in favour of ifcopenshell.util.unit
|
||||
def draw_attributes(props, layout, copy_operator=None):
|
||||
for attribute in props:
|
||||
row = layout.row(align=True)
|
||||
value = None
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
value = attribute.string_value
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
value = attribute.bool_value
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
value = attribute.int_value
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
value = attribute.float_value
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
value = attribute.enum_value
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
if copy_operator:
|
||||
op = row.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.data = json.dumps({"name": attribute.name, "value": value, "is_null": attribute.is_null})
|
||||
|
||||
|
||||
class SIUnitHelper:
|
||||
prefixes = {
|
||||
"EXA": 1e18,
|
||||
"PETA": 1e15,
|
||||
"TERA": 1e12,
|
||||
"GIGA": 1e9,
|
||||
"MEGA": 1e6,
|
||||
"KILO": 1e3,
|
||||
"HECTO": 1e2,
|
||||
"DECA": 1e1,
|
||||
"DECI": 1e-1,
|
||||
"CENTI": 1e-2,
|
||||
"MILLI": 1e-3,
|
||||
"MICRO": 1e-6,
|
||||
"NANO": 1e-9,
|
||||
"PICO": 1e-12,
|
||||
"FEMTO": 1e-15,
|
||||
"ATTO": 1e-18,
|
||||
}
|
||||
unit_names = [
|
||||
"AMPERE",
|
||||
"BECQUEREL",
|
||||
"CANDELA",
|
||||
"COULOMB",
|
||||
"CUBIC_METRE",
|
||||
"DEGREE CELSIUS",
|
||||
"FARAD",
|
||||
"GRAM",
|
||||
"GRAY",
|
||||
"HENRY",
|
||||
"HERTZ",
|
||||
"JOULE",
|
||||
"KELVIN",
|
||||
"LUMEN",
|
||||
"LUX",
|
||||
"MOLE",
|
||||
"NEWTON",
|
||||
"OHM",
|
||||
"PASCAL",
|
||||
"RADIAN",
|
||||
"SECOND",
|
||||
"SIEMENS",
|
||||
"SIEVERT",
|
||||
"SQUARE METRE",
|
||||
"METRE",
|
||||
"STERADIAN",
|
||||
"TESLA",
|
||||
"VOLT",
|
||||
"WATT",
|
||||
"WEBER",
|
||||
]
|
||||
si_conversions = {
|
||||
"inch": 0.0254,
|
||||
"foot": 0.3048,
|
||||
"yard": 0.914,
|
||||
"mile": 1609,
|
||||
"square inch": 0.0006452,
|
||||
"square foot": 0.09290304,
|
||||
"square yard": 0.83612736,
|
||||
"acre": 4046.86,
|
||||
"square mile": 2588881,
|
||||
"cubic inch": 0.00001639,
|
||||
"cubic foot": 0.02831684671168849,
|
||||
"cubic yard": 0.7636,
|
||||
"litre": 0.001,
|
||||
"fluid ounce UK": 0.0000284130625,
|
||||
"fluid ounce US": 0.00002957353,
|
||||
"pint UK": 0.000568,
|
||||
"pint US": 0.000473,
|
||||
"gallon UK": 0.004546,
|
||||
"gallon US": 0.003785,
|
||||
"degree": math.pi / 180,
|
||||
"ounce": 0.02835,
|
||||
"pound": 0.454,
|
||||
"ton UK": 1016.0469088,
|
||||
"ton US": 907.18474,
|
||||
"lbf": 4.4482216153,
|
||||
"kip": 4448.2216153,
|
||||
"psi": 6894.7572932,
|
||||
"ksi": 6894757.2932,
|
||||
"minute": 60,
|
||||
"hour": 3600,
|
||||
"day": 86400,
|
||||
"btu": 1055.056,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_prefix(text):
|
||||
for prefix in SIUnitHelper.prefixes.keys():
|
||||
if prefix in text.upper():
|
||||
return prefix
|
||||
|
||||
@staticmethod
|
||||
def get_prefix_multiplier(text):
|
||||
if not text:
|
||||
return 1
|
||||
prefix = SIUnitHelper.get_prefix(text)
|
||||
if prefix:
|
||||
return SIUnitHelper.prefixes[prefix]
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def get_unit_name(text):
|
||||
for name in SIUnitHelper.unit_names:
|
||||
if name in text.upper().replace("METER", "METRE"):
|
||||
return name
|
||||
|
||||
@staticmethod
|
||||
def convert(value, from_prefix, from_unit, to_prefix, to_unit):
|
||||
"""Converts between length, area, and volume units
|
||||
|
||||
:param value: The numeric value you want to convert
|
||||
:type value: float
|
||||
:param from_prefix: A prefix from IfcSIPrefix. Can be None.
|
||||
:type from_prefix: string
|
||||
:param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
|
||||
:type from_unit: string
|
||||
:param to_prefix: A prefix from IfcSIPrefix. Can be None.
|
||||
:type to_prefix: string
|
||||
:param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
|
||||
:type to_unit: string
|
||||
"""
|
||||
if from_unit in SIUnitHelper.si_conversions:
|
||||
value *= SIUnitHelper.si_conversions[from_unit]
|
||||
elif from_prefix:
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
if "SQUARE" in from_unit:
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
elif "CUBIC" in from_unit:
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
if to_unit in SIUnitHelper.si_conversions:
|
||||
return value * (1 / SIUnitHelper.si_conversions[to_unit])
|
||||
elif to_prefix:
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
if "SQUARE" in from_unit:
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
elif "CUBIC" in from_unit:
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
return value
|
||||
def import_attributes(ifc_class, props, data, callback=None):
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)):
|
||||
continue
|
||||
new = props.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type if isinstance(data_type, str) else ""
|
||||
is_handled_by_callback = callback(attribute.name(), new, data) if callback else None
|
||||
if is_handled_by_callback:
|
||||
pass # Our job is done
|
||||
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()]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[attribute.name()]
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else data[attribute.name()]
|
||||
elif data_type == "float":
|
||||
new.float_value = 0.0 if new.is_null else 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()]
|
||||
|
||||
|
||||
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
|
||||
# MeasureIt-ARCH is GPL-v3
|
||||
# In the future I will need to rewrite this to allow the user to have custom
|
||||
# settings for each annotation object, not read from Blender.
|
||||
def format_distance(value, isArea=False, hide_units=True):
|
||||
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
|
||||
|
||||
# Get Scene Unit Settings
|
||||
scaleFactor = bpy.context.scene.unit_settings.scale_length
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
unit_length = bpy.context.scene.unit_settings.length_unit
|
||||
|
||||
toInches = 39.3700787401574887
|
||||
inPerFoot = 11.999
|
||||
|
||||
if isArea:
|
||||
toInches = 1550
|
||||
inPerFoot = 143.999
|
||||
|
||||
value *= scaleFactor
|
||||
|
||||
# Imperial Formating
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = bpy.context.scene.BIMProperties.imperial_precision
|
||||
if precision == "NONE":
|
||||
precision = 256
|
||||
elif precision == "1":
|
||||
precision = 1
|
||||
elif "/" in precision:
|
||||
precision = int(precision.split("/")[1])
|
||||
|
||||
base = int(precision)
|
||||
decInches = value * toInches
|
||||
|
||||
# Seperate ft and inches
|
||||
# Unless Inches are the specified Length Unit
|
||||
if unit_length != "INCHES":
|
||||
feet = math.floor(decInches / inPerFoot)
|
||||
decInches -= feet * inPerFoot
|
||||
else:
|
||||
feet = 0
|
||||
|
||||
# Seperate Fractional Inches
|
||||
inches = math.floor(decInches)
|
||||
if inches != 0:
|
||||
frac = round(base * (decInches - inches))
|
||||
else:
|
||||
frac = round(base * (decInches))
|
||||
|
||||
# Set proper numerator and denominator
|
||||
if frac != base:
|
||||
numcycles = int(math.log2(base))
|
||||
for i in range(numcycles):
|
||||
if frac % 2 == 0:
|
||||
frac = int(frac / 2)
|
||||
base = int(base / 2)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
frac = 0
|
||||
inches += 1
|
||||
|
||||
# Check values and compose string
|
||||
if inches == 12:
|
||||
feet += 1
|
||||
inches = 0
|
||||
|
||||
if not isArea:
|
||||
tx_dist = ""
|
||||
if feet:
|
||||
tx_dist += str(feet) + "'"
|
||||
if feet and inches:
|
||||
tx_dist += " - "
|
||||
if inches:
|
||||
tx_dist += str(inches)
|
||||
if inches and frac:
|
||||
tx_dist += " "
|
||||
if frac:
|
||||
tx_dist += str(frac) + "/" + str(base)
|
||||
if inches or frac:
|
||||
tx_dist += '"'
|
||||
else:
|
||||
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
|
||||
|
||||
# METRIC FORMATING
|
||||
elif unit_system == "METRIC":
|
||||
precision = bpy.context.scene.BIMProperties.metric_precision
|
||||
if precision != 0:
|
||||
value = precision * round(float(value) / precision)
|
||||
|
||||
# Meters
|
||||
if unit_length == "METERS":
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
# Centimeters
|
||||
elif unit_length == "CENTIMETERS":
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
# Millimeters
|
||||
elif unit_length == "MILLIMETERS":
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
tx_dist = fmt % d_mm
|
||||
|
||||
# Otherwise Use Adaptive Units
|
||||
else:
|
||||
if round(value, 2) >= 1.0:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
else:
|
||||
if round(value, 2) >= 0.01:
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
else:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
tx_dist = fmt % d_mm
|
||||
if isArea:
|
||||
tx_dist += s_code
|
||||
else:
|
||||
tx_dist = fmt % value
|
||||
|
||||
return tx_dist
|
||||
|
||||
|
||||
def parse_diagram_scale(camera):
|
||||
"""Returns numeric value of scale"""
|
||||
if camera.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
_, fraction = camera.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
_, fraction = camera.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
|
||||
def get_project_collection(scene):
|
||||
"""Get main project collection"""
|
||||
|
||||
colls = [c for c in scene.collection.children if c.name.startswith('IfcProject')]
|
||||
if len(colls) != 1:
|
||||
raise RuntimeError("project collection missing or not unique")
|
||||
return colls[0]
|
||||
|
||||
|
||||
def get_active_drawing(scene):
|
||||
"""Get active drawing collection and camera"""
|
||||
props = scene.DocProperties
|
||||
if props.active_drawing_index is None or len(props.drawings) == 0:
|
||||
return None, None
|
||||
try:
|
||||
drawing = props.drawings[props.active_drawing_index]
|
||||
return scene.collection.children['Views'].children[f"IfcGroup/{drawing.name}"], drawing.camera
|
||||
except (KeyError, IndexError):
|
||||
raise RuntimeError("missing drawing collection")
|
||||
|
||||
|
||||
def ortho_view_frame(camera, margin=0.015):
|
||||
"""Calculates 2d bounding box of camera view area.
|
||||
|
||||
Similar to `bpy.types.Camera.view_frame`
|
||||
|
||||
:arg camera: camera of drawing
|
||||
:type camera: bpy.types.Camera + BIMCameraProperties
|
||||
:arg margin: margins, in scene units
|
||||
:type margin: float
|
||||
:return: (xmin, xmax, ymin, ymax, zmin, zmax) in local camera coordinates
|
||||
"""
|
||||
aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x
|
||||
size = camera.ortho_scale
|
||||
hwidth = size * .5
|
||||
hheight = size * .5 * aspect
|
||||
scale = parse_diagram_scale(camera)
|
||||
xmarg = margin * scale
|
||||
ymarg = margin * scale * aspect
|
||||
return (-hwidth + xmarg, hwidth - xmarg, -hheight + ymarg, hheight - ymarg, -camera.clip_start, -camera.clip_end)
|
||||
|
||||
|
||||
def almost_zero(v):
|
||||
return abs(v) < 1e-5
|
||||
|
||||
|
||||
def clip_segment(bounds, segm):
|
||||
"""Clipping line segment to bounds
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
# Liang–Barsky algorithm
|
||||
|
||||
xmin, xmax, ymin, ymax, _, _ = bounds
|
||||
p1, p2 = segm
|
||||
|
||||
def clip_side(p, q):
|
||||
if almost_zero(p): # ~= 0, parallel to the side
|
||||
if q < 0:
|
||||
return None # outside
|
||||
else:
|
||||
return 0, 1 # inside
|
||||
|
||||
t = q / p # the intersection point
|
||||
|
||||
if p < 0: # entering
|
||||
return t, 1
|
||||
else: # leaving
|
||||
return 0, t
|
||||
|
||||
dlt = p2 - p1
|
||||
|
||||
tt = (
|
||||
clip_side(-dlt.x, p1.x - xmin), # left
|
||||
clip_side(+dlt.x, xmax - p1.x), # right
|
||||
clip_side(-dlt.y, p1.y - ymin), # bottom
|
||||
clip_side(+dlt.y, ymax - p1.y), # top
|
||||
)
|
||||
|
||||
if None in tt:
|
||||
return None
|
||||
|
||||
t1 = max(0, max(t[0] for t in tt))
|
||||
t2 = min(1, min(t[1] for t in tt))
|
||||
|
||||
if t1 >= t2:
|
||||
return None
|
||||
|
||||
p1c = p1 + dlt * t1
|
||||
p2c = p1 + dlt * t2
|
||||
|
||||
return p1c, p2c
|
||||
|
||||
|
||||
def elevate_segment(bounds, segm):
|
||||
"""Elevate line xy-perpendicular segment vertically
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
_, _, ymin, ymax, zmin, _ = bounds
|
||||
p1, p2 = segm
|
||||
dlt = p2 - p1
|
||||
if not (almost_zero(dlt.x) and almost_zero(dlt.y)):
|
||||
return None
|
||||
x = p1.x
|
||||
return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))]
|
||||
def export_attributes(props, callback=None):
|
||||
attributes = {}
|
||||
for attribute in props:
|
||||
is_handled_by_callback = callback(attributes, attribute) if callback else False
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif is_handled_by_callback:
|
||||
pass # Our job is done
|
||||
elif attribute.data_type == "string":
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
elif attribute.data_type == "boolean":
|
||||
attributes[attribute.name] = attribute.bool_value
|
||||
elif attribute.data_type == "integer":
|
||||
attributes[attribute.name] = attribute.int_value
|
||||
elif attribute.data_type == "float":
|
||||
attributes[attribute.name] = attribute.float_value
|
||||
elif attribute.data_type == "enum":
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
return attributes
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import bpy
|
||||
import uuid
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
|
||||
@@ -12,6 +13,27 @@ class IfcStore:
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
library_path = ""
|
||||
library_file = None
|
||||
element_listeners = set()
|
||||
undo_redo_stack_objects = set()
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
IfcStore.path = ""
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
IfcStore.id_map = {}
|
||||
IfcStore.guid_map = {}
|
||||
IfcStore.edited_objs = set()
|
||||
IfcStore.pset_template_path = ""
|
||||
IfcStore.pset_template_file = None
|
||||
IfcStore.library_path = ""
|
||||
IfcStore.library_file = None
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
@@ -32,19 +54,196 @@ class IfcStore:
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
|
||||
return IfcStore.schema
|
||||
|
||||
@staticmethod
|
||||
def get_element(id_or_guid):
|
||||
if isinstance(id_or_guid, int):
|
||||
map_object = IfcStore.id_map
|
||||
else:
|
||||
map_object = IfcStore.guid_map
|
||||
try:
|
||||
obj = map_object[id_or_guid]
|
||||
obj.name # In case the object has been deleted, this triggers an exception
|
||||
except:
|
||||
return
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_element_listener(callback):
|
||||
IfcStore.element_listeners.add(callback)
|
||||
|
||||
@staticmethod
|
||||
def update_undo_redo_stack_objects():
|
||||
"""Keeps track of selected object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
only occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos.
|
||||
|
||||
So if I first select o1, then o2, then o3, then press undo, o3 will be
|
||||
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
|
||||
"""
|
||||
if bpy.context.active_object:
|
||||
objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]])
|
||||
else:
|
||||
objects = set([o.name for o in bpy.context.selected_objects])
|
||||
IfcStore.undo_redo_stack_objects |= objects
|
||||
|
||||
@staticmethod
|
||||
def reload_linked_elements(objects=None):
|
||||
file = IfcStore.get_file()
|
||||
if not file:
|
||||
return
|
||||
if objects is None:
|
||||
objects = bpy.data.objects
|
||||
|
||||
for obj in objects:
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
IfcStore.id_map[element.id()] = obj
|
||||
if hasattr(element, "GlobalId"):
|
||||
IfcStore.guid_map[element.GlobalId] = obj
|
||||
obj.BIMObjectProperties.ifc_definition_id = element.id()
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
|
||||
if element.is_a("IfcSurfaceStyle"):
|
||||
obj.BIMMaterialProperties.ifc_style_id = element.id()
|
||||
else:
|
||||
obj.BIMObjectProperties.ifc_definition_id = element.id()
|
||||
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
|
||||
for listener in IfcStore.element_listeners:
|
||||
listener(element, obj)
|
||||
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
{"rollback": IfcStore.rollback_link_element, "commit": IfcStore.commit_link_element, "data": data}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(element, obj=None):
|
||||
del IfcStore.id_map[element.id()]
|
||||
if hasattr(element, "GlobalId"):
|
||||
del IfcStore.guid_map[element.GlobalId]
|
||||
def rollback_link_element(data):
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if data["guid"]:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
|
||||
@staticmethod
|
||||
def commit_link_element(data):
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
IfcStore.id_map[data["id"]] = obj
|
||||
if data["guid"]:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(element=None, obj=None):
|
||||
if element is None:
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if element:
|
||||
del IfcStore.id_map[element.id()]
|
||||
else:
|
||||
del IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id]
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if element and hasattr(element, "GlobalId"):
|
||||
del IfcStore.guid_map[element.GlobalId]
|
||||
except:
|
||||
pass
|
||||
|
||||
if obj:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator, context):
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
if is_top_level_operator:
|
||||
IfcStore.begin_transaction(operator)
|
||||
IfcStore.get_file().begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
|
||||
else:
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
result = getattr(operator, "_execute")(context)
|
||||
|
||||
if is_top_level_operator:
|
||||
IfcStore.get_file().end_transaction()
|
||||
IfcStore.add_transaction_operation(
|
||||
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
|
||||
)
|
||||
IfcStore.end_transaction(operator)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
IfcStore.undo_redo_stack_objects = set()
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@staticmethod
|
||||
def end_transaction(operator):
|
||||
IfcStore.current_transaction = ""
|
||||
operator.transaction_key = ""
|
||||
|
||||
@staticmethod
|
||||
def add_transaction_operation(operator, rollback=None, commit=None):
|
||||
key = getattr(operator, "transaction_key", None)
|
||||
data = getattr(operator, "transaction_data", None)
|
||||
bpy.context.scene.BIMProperties.last_transaction = key
|
||||
IfcStore.last_transaction = key
|
||||
rollback = rollback or getattr(operator, "rollback", lambda data: True)
|
||||
commit = commit or getattr(operator, "commit", lambda data: True)
|
||||
if IfcStore.history and IfcStore.history[-1]["key"] == key:
|
||||
IfcStore.history[-1]["operations"].append({"rollback": rollback, "commit": commit, "data": data})
|
||||
else:
|
||||
IfcStore.history.append(
|
||||
{"key": key, "operations": [{"rollback": rollback, "commit": commit, "data": data}]}
|
||||
)
|
||||
IfcStore.future = []
|
||||
|
||||
@staticmethod
|
||||
def undo():
|
||||
if not IfcStore.history:
|
||||
return
|
||||
event = IfcStore.history.pop()
|
||||
for transaction in event["operations"][::-1]:
|
||||
transaction["rollback"](transaction["data"])
|
||||
IfcStore.future.append(event)
|
||||
|
||||
@staticmethod
|
||||
def redo():
|
||||
if not IfcStore.future:
|
||||
return
|
||||
event = IfcStore.future.pop()
|
||||
for transaction in event["operations"]:
|
||||
transaction["commit"](transaction["data"])
|
||||
IfcStore.history.append(event)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import bpy
|
||||
import ifcopenshell.api.aggregate.assign_object as assign_object
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.aggregate.data import Data
|
||||
|
||||
@@ -7,41 +7,51 @@ from ifcopenshell.api.aggregate.data import Data
|
||||
class AssignObject(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_object"
|
||||
bl_label = "Assign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_object: bpy.props.StringProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
|
||||
props = related_object.BIMObjectProperties
|
||||
relating_object = bpy.data.objects.get(self.relating_object) if self.relating_object else props.relating_object
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
)
|
||||
relating_object = bpy.data.objects.get(self.relating_object)
|
||||
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
product = self.file.by_id(props.ifc_definition_id)
|
||||
assign_object.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": product,
|
||||
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
).execute()
|
||||
bpy.ops.bim.edit_object_placement(obj=related_object.name)
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
|
||||
for related_object in related_objects:
|
||||
oprops = related_object.BIMObjectProperties
|
||||
product = self.file.by_id(oprops.ifc_definition_id)
|
||||
ifcopenshell.api.run(
|
||||
"aggregate.assign_object",
|
||||
self.file,
|
||||
**{
|
||||
"product": product,
|
||||
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
)
|
||||
bpy.ops.bim.edit_object_placement(obj=related_object.name)
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
|
||||
|
||||
spatial_collection = bpy.data.collections.get(related_object.name)
|
||||
relating_collection = bpy.data.collections.get(relating_object.name)
|
||||
if spatial_collection:
|
||||
self.remove_collection(bpy.context.scene.collection, spatial_collection)
|
||||
for collection in bpy.data.collections:
|
||||
if collection == relating_collection:
|
||||
collection.children.link(spatial_collection)
|
||||
continue
|
||||
self.remove_collection(collection, spatial_collection)
|
||||
else:
|
||||
for collection in related_object.users_collection:
|
||||
collection.objects.unlink(related_object)
|
||||
relating_collection.objects.link(related_object)
|
||||
spatial_collection = bpy.data.collections.get(related_object.name)
|
||||
relating_collection = bpy.data.collections.get(relating_object.name)
|
||||
if spatial_collection:
|
||||
self.remove_collection(context.scene.collection, spatial_collection)
|
||||
for collection in bpy.data.collections:
|
||||
if collection == relating_collection:
|
||||
if not collection.children.get(spatial_collection.name):
|
||||
collection.children.link(spatial_collection)
|
||||
continue
|
||||
self.remove_collection(collection, spatial_collection)
|
||||
else:
|
||||
for collection in related_object.users_collection:
|
||||
if collection.name.startswith("Ifc"):
|
||||
collection.objects.unlink(related_object)
|
||||
relating_collection.objects.link(related_object)
|
||||
return {"FINISHED"}
|
||||
|
||||
def remove_collection(self, parent, child):
|
||||
@@ -54,10 +64,11 @@ class AssignObject(bpy.types.Operator):
|
||||
class EnableEditingAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_aggregate"
|
||||
bl_label = "Enable Editing Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.active_object.BIMObjectProperties.relating_object = None
|
||||
bpy.context.active_object.BIMObjectProperties.is_editing_aggregate = True
|
||||
context.active_object.BIMObjectProperties.relating_object = None
|
||||
context.active_object.BIMObjectProperties.is_editing_aggregate = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -65,9 +76,10 @@ class DisableEditingAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_aggregate"
|
||||
bl_label = "Disable Editing Aggregate"
|
||||
obj: bpy.props.StringProperty()
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
obj.BIMObjectProperties.is_editing_aggregate = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -75,12 +87,16 @@ class DisableEditingAggregate(bpy.types.Operator):
|
||||
class AddAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.add_aggregate"
|
||||
bl_label = "Add Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
bpy.context.scene.collection.children.link(aggregate_collection)
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
|
||||
|
||||
@@ -16,6 +16,8 @@ class BIM_PT_aggregate(Panel):
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
return False
|
||||
if props.ifc_definition_id not in Data.products:
|
||||
@@ -34,7 +36,8 @@ class BIM_PT_aggregate(Panel):
|
||||
if props.is_editing_aggregate:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "relating_object", text="")
|
||||
row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
if props.relating_object:
|
||||
row.operator("bim.assign_object", icon="CHECKMARK", text="").relating_object = props.relating_object.name
|
||||
row.operator("bim.disable_editing_aggregate", icon="X", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.attribute.edit_attributes as edit_attributes
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
|
||||
@@ -9,6 +9,7 @@ from ifcopenshell.api.attribute.data import Data
|
||||
class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_attributes"
|
||||
bl_label = "Enable Editing Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
@@ -24,7 +25,7 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
props.attributes.remove(0)
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
new = props.attributes.add()
|
||||
if attribute["type"] == "entity":
|
||||
if attribute["type"] == "entity" or (attribute["type"] == "list" and attribute["list_type"] == "entity"):
|
||||
continue
|
||||
new.name = attribute["name"]
|
||||
new.is_null = attribute["is_null"]
|
||||
@@ -33,7 +34,7 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
elif attribute["type"] == "integer":
|
||||
new.int_value = attribute["value"] or 0
|
||||
elif attribute["type"] == "float":
|
||||
new.float_value = attribute["value"] or 0.
|
||||
new.float_value = attribute["value"] or 0.0
|
||||
elif attribute["type"] == "enum":
|
||||
new.enum_items = json.dumps(attribute["enum_items"])
|
||||
if attribute["value"]:
|
||||
@@ -45,6 +46,7 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
class DisableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_attributes"
|
||||
bl_label = "Disable Editing Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
@@ -61,10 +63,14 @@ class DisableEditingAttributes(bpy.types.Operator):
|
||||
class EditAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_attributes"
|
||||
bl_label = "Edit Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
@@ -95,16 +101,9 @@ class EditAttributes(bpy.types.Operator):
|
||||
elif attribute["type"] == "enum":
|
||||
attributes[attribute["name"]] = blender_attribute.enum_value
|
||||
product = self.file.by_id(oprops.ifc_definition_id)
|
||||
edit_attributes.Usecase(self.file, {
|
||||
"product": product,
|
||||
"attributes": attributes
|
||||
}).execute()
|
||||
if "Name" in attributes:
|
||||
new_name = "{}/{}".format(product.is_a(), product.Name or "Unnamed")
|
||||
collection = bpy.data.collections.get(obj.name)
|
||||
if collection:
|
||||
collection.name = new_name
|
||||
obj.name = new_name
|
||||
ifcopenshell.api.run(
|
||||
"attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
@@ -113,13 +112,14 @@ class EditAttributes(bpy.types.Operator):
|
||||
class GenerateGlobalId(bpy.types.Operator):
|
||||
bl_idname = "bim.generate_global_id"
|
||||
bl_label = "Regenerate GlobalId"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
|
||||
index = context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
|
||||
if index >= 0:
|
||||
global_id = bpy.context.active_object.BIMAttributeProperties.attributes[index]
|
||||
global_id = context.active_object.BIMAttributeProperties.attributes[index]
|
||||
else:
|
||||
global_id = bpy.context.active_object.BIMAttributeProperties.attributes.add()
|
||||
global_id = context.active_object.BIMAttributeProperties.attributes.add()
|
||||
global_id.name = "GlobalId"
|
||||
global_id.data_type = "string"
|
||||
global_id.string_value = ifcopenshell.guid.new()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import bpy
|
||||
import blenderbim.bim.schema # refactor
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
|
||||
@@ -15,7 +15,7 @@ def draw_ui(context, layout, obj_type):
|
||||
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
op = row.operator("bim.disable_editing_attributes", icon="X", text="")
|
||||
op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
@@ -42,10 +42,6 @@ def draw_ui(context, layout, obj_type):
|
||||
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
|
||||
text="",
|
||||
)
|
||||
# TODO: reimplement, see #1222
|
||||
# op = row.operator("bim.copy_attribute_to_selection", icon="COPYDOWN", text="")
|
||||
# op.attribute_name = attribute.name
|
||||
# op.attribute_value = attribute.string_value
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
@@ -78,6 +74,8 @@ class BIM_PT_object_attributes(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
@@ -85,7 +83,7 @@ class BIM_PT_object_attributes(Panel):
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
bl_label = "IFC Attributes"
|
||||
bl_label = "IFC Material Attributes"
|
||||
bl_idname = "BIM_PT_material_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
@@ -13,9 +13,10 @@ import addon_utils
|
||||
class AuginLogin(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_login"
|
||||
bl_label = "Login to Augin"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.AuginProperties
|
||||
props = context.scene.AuginProperties
|
||||
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/user_login"
|
||||
payload = {"email": props.username, "password": props.password}
|
||||
@@ -32,9 +33,10 @@ class AuginLogin(bpy.types.Operator):
|
||||
class AuginReset(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_reset"
|
||||
bl_label = "Upload Another Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.AuginProperties
|
||||
props = context.scene.AuginProperties
|
||||
props.is_success = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -42,11 +44,12 @@ class AuginReset(bpy.types.Operator):
|
||||
class AuginCreateNewModel(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_create_new_model"
|
||||
bl_label = "Create New Augin Model"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
props = bpy.context.scene.AuginProperties
|
||||
props = context.scene.AuginProperties
|
||||
|
||||
# Create project
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/new_model"
|
||||
@@ -110,7 +113,7 @@ class AuginCreateNewModel(bpy.types.Operator):
|
||||
context.scene.render.image_settings.file_format = old_file_format
|
||||
context.scene.render.filepath = old_filepath
|
||||
|
||||
client.upload_file(bpy.context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
|
||||
client.upload_file(context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
|
||||
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
|
||||
|
||||
|
||||
@@ -118,8 +121,8 @@ class AuginCreateNewModel(bpy.types.Operator):
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
|
||||
payload = {
|
||||
"user_token": props.token,
|
||||
"ifc_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
|
||||
"model_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
|
||||
"ifc_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
|
||||
"model_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
|
||||
"thumb_filesize": os.path.getsize(thumb_path),
|
||||
"model_upload_path": result["model_path"],
|
||||
"thumb_upload_path": result["thumb_path"],
|
||||
|
||||
@@ -28,7 +28,7 @@ class BIM_PT_augin(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.label(text="Logged in as " + props.username)
|
||||
|
||||
if not bpy.context.scene.BIMProperties.ifc_file:
|
||||
if not context.scene.BIMProperties.ifc_file:
|
||||
row = layout.row()
|
||||
row.label(text="No IFC Found")
|
||||
return
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
import bcf.v2.bcfxml
|
||||
|
||||
class BcfStore:
|
||||
bcfxml = None
|
||||
@@ -7,5 +7,5 @@ class BcfStore:
|
||||
@staticmethod
|
||||
def get_bcfxml():
|
||||
if not BcfStore.bcfxml:
|
||||
BcfStore.bcfxml = bcf.bcfxml.BcfXml()
|
||||
BcfStore.bcfxml = bcf.v2.bcfxml.BcfXml()
|
||||
return BcfStore.bcfxml
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
import bpy
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
import bcf.v2.data
|
||||
from . import bcfstore
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
@@ -9,29 +11,33 @@ from mathutils import Vector, Matrix, Euler, geometry
|
||||
class NewBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.new_bcf_project"
|
||||
bl_label = "New BCF Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BCFProperties.is_loaded = False
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.new_project()
|
||||
bpy.ops.bim.load_bcf_project()
|
||||
bpy.context.scene.BCFProperties.is_loaded = True
|
||||
context.scene.BCFProperties.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_project"
|
||||
bl_label = "Load BCF Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BCFProperties.is_loaded = False
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
if self.filepath:
|
||||
bcfxml.get_project(self.filepath)
|
||||
bpy.context.scene.BCFProperties.name = bcfxml.project.name
|
||||
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()
|
||||
bpy.context.scene.BCFProperties.is_loaded = True
|
||||
context.scene.BCFProperties.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -42,15 +48,16 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
class LoadBcfTopics(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topics"
|
||||
bl_label = "Load BCF Topics"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_topics()
|
||||
while len(bpy.context.scene.BCFProperties.topics) > 0:
|
||||
bpy.context.scene.BCFProperties.topics.remove(0)
|
||||
while len(context.scene.BCFProperties.topics) > 0:
|
||||
context.scene.BCFProperties.topics.remove(0)
|
||||
index = 0
|
||||
for topic_guid in bcfxml.topics.keys():
|
||||
new = bpy.context.scene.BCFProperties.topics.add()
|
||||
new = context.scene.BCFProperties.topics.add()
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index)
|
||||
index += 1
|
||||
return {"FINISHED"}
|
||||
@@ -59,13 +66,14 @@ class LoadBcfTopics(bpy.types.Operator):
|
||||
class LoadBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topic"
|
||||
bl_label = "Load BCF Topics"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
topic_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.get_topic(self.topic_guid)
|
||||
new = bpy.context.scene.BCFProperties.topics[self.topic_index]
|
||||
new = context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
"name": topic.guid,
|
||||
"title": topic.title,
|
||||
@@ -129,12 +137,13 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
class LoadBcfComments(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_comments"
|
||||
bl_label = "Load BCF Comments"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_comments(self.topic_guid)
|
||||
blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid)
|
||||
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
|
||||
while len(blender_topic.comments) > 0:
|
||||
blender_topic.comments.remove(0)
|
||||
for comment in bcfxml.topics[self.topic_guid].comments.values():
|
||||
@@ -157,10 +166,11 @@ class LoadBcfComments(bpy.types.Operator):
|
||||
class EditBcfProjectName(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_project_name"
|
||||
bl_label = "Edit BCF Project Name"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.project.name = bpy.context.scene.BCFProperties.name
|
||||
bcfxml.project.name = context.scene.BCFProperties.name
|
||||
bcfxml.edit_project()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -168,19 +178,21 @@ class EditBcfProjectName(bpy.types.Operator):
|
||||
class EditBcfAuthor(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_author"
|
||||
bl_label = "Edit BCF Author"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.author = bpy.context.scene.BCFProperties.author
|
||||
bcfxml.author = context.scene.BCFProperties.author
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfTopicName(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_topic_name"
|
||||
bl_label = "Edit BCF Topic Name"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
@@ -192,9 +204,10 @@ class EditBcfTopicName(bpy.types.Operator):
|
||||
class EditBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_topic"
|
||||
bl_label = "Edit BCF Topic"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
|
||||
@@ -216,6 +229,7 @@ class EditBcfTopic(bpy.types.Operator):
|
||||
class SaveBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.save_bcf_project"
|
||||
bl_label = "Save BCF Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
@@ -231,11 +245,12 @@ class SaveBcfProject(bpy.types.Operator):
|
||||
class AddBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_topic"
|
||||
bl_label = "Add BCF Topic"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.add_topic()
|
||||
new = bpy.context.scene.BCFProperties.topics.add()
|
||||
new = context.scene.BCFProperties.topics.add()
|
||||
new.name = "New Topic"
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
@@ -244,13 +259,14 @@ class AddBcfTopic(bpy.types.Operator):
|
||||
class AddBcfBimSnippet(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_bim_snippet"
|
||||
bl_label = "Add BCF BIM Snippet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bim_snippet = bcf.data.BimSnippet()
|
||||
bim_snippet = bcf.v2.data.BimSnippet()
|
||||
bim_snippet.reference = blender_topic.bim_snippet_reference
|
||||
bim_snippet.reference_schema = blender_topic.bim_snippet_schema
|
||||
bim_snippet.snippet_type = blender_topic.bim_snippet_type
|
||||
@@ -262,15 +278,16 @@ class AddBcfBimSnippet(bpy.types.Operator):
|
||||
class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_related_topic"
|
||||
bl_label = "Add BCF Related Topic"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
related_topic = None
|
||||
for topic in bcfxml.topics.values():
|
||||
if topic.title == blender_topic.related_topic:
|
||||
related_topic = bcf.data.RelatedTopic()
|
||||
related_topic = bcf.v2.data.RelatedTopic()
|
||||
related_topic.guid = topic.guid
|
||||
break
|
||||
if not related_topic:
|
||||
@@ -285,13 +302,14 @@ class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
class AddBcfHeaderFile(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_header_file"
|
||||
bl_label = "Add BCF Header File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
header_file = bcf.data.HeaderFile()
|
||||
header_file = bcf.v2.data.HeaderFile()
|
||||
header_file.reference = blender_topic.file_reference
|
||||
if not os.path.exists(header_file.reference):
|
||||
header_file.filename = header_file.reference
|
||||
@@ -307,58 +325,60 @@ class AddBcfHeaderFile(bpy.types.Operator):
|
||||
class ViewBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.view_bcf_topic"
|
||||
bl_label = "Get BCF Topic"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
|
||||
for index, topic in enumerate(context.scene.BCFProperties.topics):
|
||||
if topic.guid.lower() == self.topic_guid.lower():
|
||||
bpy.context.scene.BCFProperties.active_topic_index = index
|
||||
context.scene.BCFProperties.active_topic_index = index
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfViewpoint(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_viewpoint"
|
||||
bl_label = "Add BCF Viewpoint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
if not bpy.context.scene.camera:
|
||||
if not context.scene.camera:
|
||||
return {"FINISHED"}
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
|
||||
if bpy.context.scene.camera.data.type == "ORTHO":
|
||||
camera = bcf.data.OrthogonalCamera()
|
||||
camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale
|
||||
if context.scene.camera.data.type == "ORTHO":
|
||||
camera = bcf.v2.data.OrthogonalCamera()
|
||||
camera.view_to_world_scale = context.scene.camera.data.ortho_scale
|
||||
viewpoint.orthogonal_camera = camera
|
||||
elif bpy.context.scene.camera.data.type == "PERSP":
|
||||
camera = bcf.data.PerspectiveCamera()
|
||||
camera.field_of_view = degrees(bpy.context.scene.camera.data.angle)
|
||||
elif context.scene.camera.data.type == "PERSP":
|
||||
camera = bcf.v2.data.PerspectiveCamera()
|
||||
camera.field_of_view = degrees(context.scene.camera.data.angle)
|
||||
viewpoint.perspective_camera = camera
|
||||
camera.camera_view_point.x = bpy.context.scene.camera.location.x
|
||||
camera.camera_view_point.y = bpy.context.scene.camera.location.y
|
||||
camera.camera_view_point.z = bpy.context.scene.camera.location.z
|
||||
direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
|
||||
camera.camera_view_point.x = context.scene.camera.location.x
|
||||
camera.camera_view_point.y = context.scene.camera.location.y
|
||||
camera.camera_view_point.z = context.scene.camera.location.z
|
||||
direction = context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
|
||||
camera.camera_direction.x = direction.x
|
||||
camera.camera_direction.y = direction.y
|
||||
camera.camera_direction.z = direction.z
|
||||
up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
|
||||
up = context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
|
||||
camera.camera_up_vector.x = up.x
|
||||
camera.camera_up_vector.y = up.y
|
||||
camera.camera_up_vector.z = up.z
|
||||
|
||||
old_file_format = bpy.context.scene.render.image_settings.file_format
|
||||
bpy.context.scene.render.image_settings.file_format = "PNG"
|
||||
old_filepath = bpy.context.scene.render.filepath
|
||||
bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
old_file_format = context.scene.render.image_settings.file_format
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
old_filepath = context.scene.render.filepath
|
||||
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
viewpoint.snapshot = bpy.context.scene.render.filepath
|
||||
viewpoint.snapshot = context.scene.render.filepath
|
||||
|
||||
bcfxml.add_viewpoint(topic, viewpoint)
|
||||
bpy.context.scene.render.filepath = old_filepath
|
||||
bpy.context.scene.render.image_settings.file_format = old_file_format
|
||||
context.scene.render.filepath = old_filepath
|
||||
context.scene.render.image_settings.file_format = old_file_format
|
||||
props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -366,10 +386,11 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_viewpoint"
|
||||
bl_label = "Remove BCF Viewpoint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
viewpoint_guid = blender_topic.viewpoints
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
@@ -381,11 +402,12 @@ class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
class RemoveBcfFile(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_file"
|
||||
bl_label = "Remove BCF File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_file(topic, self.index)
|
||||
@@ -396,10 +418,11 @@ class RemoveBcfFile(bpy.types.Operator):
|
||||
class AddBcfReferenceLink(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_reference_link"
|
||||
bl_label = "Add BCF Reference Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.reference_link:
|
||||
@@ -414,15 +437,16 @@ class AddBcfReferenceLink(bpy.types.Operator):
|
||||
class AddBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_document_reference"
|
||||
bl_label = "Add BCF Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.document_reference:
|
||||
return {"FINISHED"}
|
||||
document_reference = bcf.data.DocumentReference()
|
||||
document_reference = bcf.v2.data.DocumentReference()
|
||||
document_reference.referenced_document = blender_topic.document_reference
|
||||
document_reference.description = blender_topic.document_reference_description or None
|
||||
bcfxml.add_document_reference(topic, document_reference)
|
||||
@@ -435,10 +459,11 @@ class AddBcfDocumentReference(bpy.types.Operator):
|
||||
class AddBcfLabel(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_label"
|
||||
bl_label = "Add BCF Label"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.label:
|
||||
@@ -454,10 +479,11 @@ class AddBcfLabel(bpy.types.Operator):
|
||||
class EditBcfReferenceLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_reference_links"
|
||||
bl_label = "Edit BCF Reference Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, reference_link in enumerate(topic.reference_links):
|
||||
@@ -471,10 +497,11 @@ class EditBcfReferenceLinks(bpy.types.Operator):
|
||||
class EditBcfLabels(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_labels"
|
||||
bl_label = "Edit BCF Labels"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, label in enumerate(blender_topic.labels):
|
||||
@@ -488,11 +515,12 @@ class EditBcfLabels(bpy.types.Operator):
|
||||
class RemoveBcfReferenceLink(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_reference_link"
|
||||
bl_label = "Remove BCF Reference Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.reference_links[self.index]
|
||||
@@ -504,11 +532,12 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
|
||||
class RemoveBcfLabel(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_label"
|
||||
bl_label = "Remove BCF Label"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.labels[self.index]
|
||||
@@ -520,10 +549,11 @@ class RemoveBcfLabel(bpy.types.Operator):
|
||||
class RemoveBcfBimSnippet(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_bim_snippet"
|
||||
bl_label = "Remove BCF BIM Snippet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_bim_snippet(topic)
|
||||
@@ -536,11 +566,12 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
|
||||
class RemoveBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_document_reference"
|
||||
bl_label = "Remove BCF Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_document_reference(topic, self.index)
|
||||
@@ -551,11 +582,12 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
|
||||
class RemoveBcfRelatedTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_related_topic"
|
||||
bl_label = "Remove BCF Related Topic"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.related_topics[self.index]
|
||||
@@ -567,11 +599,12 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
|
||||
class RemoveBcfComment(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_comment"
|
||||
bl_label = "Remove BCF Comment"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
comment_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_comment(self.comment_guid, topic)
|
||||
@@ -582,11 +615,12 @@ class RemoveBcfComment(bpy.types.Operator):
|
||||
class EditBcfComment(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_comment"
|
||||
bl_label = "Edit BCF Comment"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
comment_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
blender_comment = blender_topic.comments.get(self.comment_guid)
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
@@ -600,34 +634,35 @@ class EditBcfComment(bpy.types.Operator):
|
||||
class AddBcfComment(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_comment"
|
||||
bl_label = "Add BCF Comment"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
comment_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.comment:
|
||||
return {"FINISHED"}
|
||||
comment = bcf.data.Comment()
|
||||
comment = bcf.v2.data.Comment()
|
||||
comment.comment = blender_topic.comment
|
||||
if blender_topic.has_related_viewpoint and blender_topic.viewpoints:
|
||||
comment.viewpoint = bcf.data.Viewpoint()
|
||||
comment.viewpoint = bcf.v2.data.Viewpoint()
|
||||
comment.viewpoint.guid = blender_topic.viewpoints
|
||||
bcfxml.add_comment(topic, comment)
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
|
||||
print(bcfxml.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_bcf_viewpoint"
|
||||
bl_label = "Activate BCF Viewpoint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not topic.viewpoints:
|
||||
@@ -638,11 +673,11 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj = bpy.data.objects.get("Viewpoint")
|
||||
if not obj:
|
||||
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
bpy.context.scene.camera = obj
|
||||
context.scene.collection.objects.link(obj)
|
||||
context.scene.camera = obj
|
||||
|
||||
cam_width = bpy.context.scene.render.resolution_x
|
||||
cam_height = bpy.context.scene.render.resolution_y
|
||||
cam_width = context.scene.render.resolution_x
|
||||
cam_height = context.scene.render.resolution_y
|
||||
cam_aspect = cam_width / cam_height
|
||||
|
||||
if viewpoint.snapshot:
|
||||
@@ -662,7 +697,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
else:
|
||||
background.frame_method = "CROP"
|
||||
background.display_depth = "FRONT"
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
|
||||
if viewpoint.orthogonal_camera:
|
||||
@@ -684,13 +719,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if gp:
|
||||
bpy.data.grease_pencils.remove(gp)
|
||||
if viewpoint.lines:
|
||||
self.draw_lines(viewpoint)
|
||||
self.draw_lines(viewpoint, context)
|
||||
|
||||
self.delete_clipping_planes()
|
||||
self.delete_clipping_planes(context)
|
||||
if viewpoint.clipping_planes:
|
||||
self.create_clipping_planes(viewpoint)
|
||||
|
||||
self.delete_bitmaps()
|
||||
self.delete_bitmaps(context)
|
||||
if viewpoint.bitmaps:
|
||||
self.create_bitmaps(bcfxml, viewpoint, topic)
|
||||
|
||||
@@ -741,9 +776,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if global_id in global_id_colours:
|
||||
obj.color = self.hex_to_rgb(global_id_colours[global_id])
|
||||
|
||||
def draw_lines(self, viewpoint):
|
||||
def draw_lines(self, viewpoint, context):
|
||||
gp = bpy.data.grease_pencils.new("BCF")
|
||||
scene = bpy.context.scene
|
||||
scene = context.scene
|
||||
scene.grease_pencil = gp
|
||||
scene.frame_set(1)
|
||||
layer = gp.layers.new("BCF Annotation", set_active=True)
|
||||
@@ -773,19 +808,19 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
)
|
||||
n += 1
|
||||
|
||||
def delete_clipping_planes(self):
|
||||
def delete_clipping_planes(self, context):
|
||||
collection = bpy.data.collections.get("Sections")
|
||||
if not collection:
|
||||
return
|
||||
for section in collection.objects:
|
||||
bpy.context.view_layer.objects.active = section
|
||||
context.view_layer.objects.active = section
|
||||
bpy.ops.bim.remove_section_plane()
|
||||
|
||||
def delete_bitmaps(self):
|
||||
def delete_bitmaps(self, context):
|
||||
collection = bpy.data.collections.get("Bitmaps")
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new("Bitmaps")
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
context.scene.collection.children.link(collection)
|
||||
for bitmap in collection.objects:
|
||||
bpy.data.objects.remove(bitmap)
|
||||
|
||||
@@ -825,18 +860,19 @@ class OpenBcfReferenceLink(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name)
|
||||
webbrowser.open(context.scene.BCFProperties.topic_links[self.index].name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectBcfHeaderFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_header_file"
|
||||
bl_label = "Select BCF Header File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
topic = props.topics[props.active_topic_index]
|
||||
topic.file_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
@@ -849,11 +885,12 @@ class SelectBcfHeaderFile(bpy.types.Operator):
|
||||
class SelectBcfBimSnippetReference(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_bim_snippet_reference"
|
||||
bl_label = "Select BCF BIM Snippet Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
topic = props.topics[props.active_topic_index]
|
||||
topic.bim_snippet_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
@@ -866,11 +903,12 @@ class SelectBcfBimSnippetReference(bpy.types.Operator):
|
||||
class SelectBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_document_reference"
|
||||
bl_label = "Select BCF Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
topic = props.topics[props.active_topic_index]
|
||||
topic.document_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -17,38 +17,43 @@ from bpy.props import (
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
|
||||
def purge():
|
||||
global bcfviewpoints_enum
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
|
||||
def updateBcfReferenceLink(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
if context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_reference_links()
|
||||
|
||||
|
||||
def updateBcfLabel(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
if context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_labels()
|
||||
|
||||
|
||||
def updateBcfProjectName(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
if context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_project_name()
|
||||
|
||||
|
||||
def updateBcfAuthor(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
if context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_author()
|
||||
|
||||
|
||||
def updateBcfTopicName(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
if context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_topic_name()
|
||||
|
||||
|
||||
def updateBcfTopicIsEditable(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
if context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
bpy.ops.bim.edit_bcf_topic()
|
||||
|
||||
|
||||
def updateBcfCommentIsEditable(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
if context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
bpy.ops.bim.edit_bcf_comment(comment_guid = self.name)
|
||||
|
||||
|
||||
@@ -56,7 +61,7 @@ def refreshBcfTopic(self, context):
|
||||
global bcfviewpoints_enum
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.topics[props.active_topic_index]
|
||||
header = bcfxml.get_header(topic.name)
|
||||
@@ -75,7 +80,7 @@ def getBcfViewpoints(self, context):
|
||||
global bcfviewpoints_enum
|
||||
if bcfviewpoints_enum is None:
|
||||
bcfviewpoints_enum = []
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.topics[props.active_topic_index]
|
||||
viewpoints = bcfxml.get_viewpoints(topic.name)
|
||||
|
||||
@@ -17,7 +17,7 @@ class BIM_PT_bcf(Panel):
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = scene.BCFProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
@@ -34,7 +34,7 @@ class BIM_PT_bcf(Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "author")
|
||||
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = context.scene.BCFProperties
|
||||
row = layout.row()
|
||||
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
|
||||
col = row.column(align=True)
|
||||
@@ -93,7 +93,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = scene.BCFProperties
|
||||
|
||||
if props.active_topic_index >= len(props.topics):
|
||||
layout.label(text="No BCF project is loaded")
|
||||
@@ -236,7 +236,7 @@ class BIM_PT_bcf_comments(Panel):
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props = scene.BCFProperties
|
||||
|
||||
if props.active_topic_index >= len(props.topics):
|
||||
layout.label(text="No BCF project is loaded")
|
||||
|
||||
@@ -52,11 +52,11 @@ class BIMTesterPurge(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
filename = os.path.join(
|
||||
bpy.context.scene.BimTesterProperties.features_dir,
|
||||
bpy.context.scene.BimTesterProperties.features_file + ".feature",
|
||||
context.scene.BimTesterProperties.features_dir,
|
||||
context.scene.BimTesterProperties.features_file + ".feature",
|
||||
)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
|
||||
os.chdir(context.scene.BimTesterProperties.features_dir)
|
||||
bimtester.clean.TestPurger().purge()
|
||||
os.chdir(cwd)
|
||||
return {"FINISHED"}
|
||||
@@ -65,12 +65,13 @@ class BIMTesterPurge(bpy.types.Operator):
|
||||
class SelectFeature(bpy.types.Operator):
|
||||
bl_idname = "bim.select_feature"
|
||||
bl_label = "Select Feature / IDS"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".feature"
|
||||
filter_glob: bpy.props.StringProperty(default="*.feature;*.xml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BimTesterProperties.feature = self.filepath
|
||||
context.scene.BimTesterProperties.feature = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -81,12 +82,13 @@ class SelectFeature(bpy.types.Operator):
|
||||
class SelectSteps(bpy.types.Operator):
|
||||
bl_idname = "bim.select_steps"
|
||||
bl_label = "Select Steps"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".py"
|
||||
filter_glob: bpy.props.StringProperty(default="*.py", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BimTesterProperties.steps = self.filepath
|
||||
context.scene.BimTesterProperties.steps = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -97,6 +99,7 @@ class SelectSteps(bpy.types.Operator):
|
||||
class SelectBIMTesterIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bimtester_ifc_file"
|
||||
bl_label = "Select BIMTester IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
@@ -117,14 +120,14 @@ class RejectElement(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
lines = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
lines.append(
|
||||
" * The element {} should not exist because {}".format(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
|
||||
bpy.context.scene.BimTesterProperties.qa_reject_element_reason,
|
||||
context.scene.BimTesterProperties.qa_reject_element_reason,
|
||||
)
|
||||
)
|
||||
QAHelper.append_to_scenario(lines)
|
||||
QAHelper.append_to_scenario(lines, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -135,12 +138,12 @@ class ApproveClass(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
lines = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
lines.append(" * The element {} is an {}".format(element.GlobalId, element.is_a()))
|
||||
QAHelper.append_to_scenario(lines)
|
||||
QAHelper.append_to_scenario(lines, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -151,27 +154,28 @@ class RejectClass(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
lines = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
lines.append(
|
||||
" * The element {} is an {}".format(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
|
||||
bpy.context.scene.BimTesterProperties.audit_ifc_class,
|
||||
context.scene.BimTesterProperties.audit_ifc_class,
|
||||
)
|
||||
)
|
||||
QAHelper.append_to_scenario(lines)
|
||||
QAHelper.append_to_scenario(lines, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectAudited(bpy.types.Operator):
|
||||
bl_idname = "bim.select_audited"
|
||||
bl_label = "Select Audited"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
audited_global_ids = []
|
||||
self.file = IfcStore.get_file()
|
||||
for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"):
|
||||
for filename in Path(context.scene.BimTesterProperties.features_dir).glob("*.feature"):
|
||||
with open(filename, "r") as feature_file:
|
||||
lines = feature_file.readlines()
|
||||
for line in lines:
|
||||
@@ -179,7 +183,7 @@ class SelectAudited(bpy.types.Operator):
|
||||
for word in words:
|
||||
if self.is_a_global_id(word):
|
||||
audited_global_ids.append(word)
|
||||
for obj in bpy.context.visible_objects:
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
if self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId in audited_global_ids:
|
||||
@@ -192,10 +196,10 @@ class SelectAudited(bpy.types.Operator):
|
||||
|
||||
class QAHelper:
|
||||
@classmethod
|
||||
def append_to_scenario(cls, lines):
|
||||
def append_to_scenario(cls, lines, context):
|
||||
filename = os.path.join(
|
||||
bpy.context.scene.BimTesterProperties.features_dir,
|
||||
bpy.context.scene.BimTesterProperties.features_file + ".feature",
|
||||
context.scene.BimTesterProperties.features_dir,
|
||||
context.scene.BimTesterProperties.features_file + ".feature",
|
||||
)
|
||||
if os.path.exists(filename + "~"):
|
||||
os.remove(filename + "~")
|
||||
@@ -206,7 +210,7 @@ class QAHelper:
|
||||
for source_line in source:
|
||||
if (
|
||||
"Scenario: " in source_line
|
||||
and bpy.context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
|
||||
and context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
|
||||
):
|
||||
is_in_scenario = True
|
||||
elif is_in_scenario:
|
||||
|
||||
@@ -18,6 +18,13 @@ scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global scenarios_enum
|
||||
global classes_enum
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def getScenarios(self, context):
|
||||
global scenarios_enum
|
||||
if len(scenarios_enum) < 1:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import bpy
|
||||
from . import ui
|
||||
|
||||
classes = (
|
||||
ui.BIM_PT_boundary,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,36 @@
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
|
||||
|
||||
class BIM_PT_boundary(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_idname = "BIM_PT_boundary"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a() not in ["IfcSpace", "IfcExternalSpatialElement"]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
self.oprops = context.active_object.BIMObjectProperties
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
|
||||
boundary = Data.boundaries[boundary_id]
|
||||
row = self.layout.row()
|
||||
row.label(text=f"{boundary_id}", icon="GHOST_ENABLED")
|
||||
@@ -23,7 +23,7 @@ class ExportClashSets(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
clash_sets = []
|
||||
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
|
||||
for clash_set in context.scene.BIMClashProperties.clash_sets:
|
||||
self.a = []
|
||||
self.b = []
|
||||
for ab in ["a", "b"]:
|
||||
@@ -42,6 +42,7 @@ class ExportClashSets(bpy.types.Operator):
|
||||
class ImportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.import_clash_sets"
|
||||
bl_label = "Import Clash Sets"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
@@ -55,7 +56,7 @@ class ImportClashSets(bpy.types.Operator):
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
for clash_set in clash_sets:
|
||||
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = clash_set["name"]
|
||||
new.tolerance = clash_set["tolerance"]
|
||||
for clash_source in clash_set["a"]:
|
||||
@@ -77,9 +78,10 @@ class ImportClashSets(bpy.types.Operator):
|
||||
class AddClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.add_clash_set"
|
||||
bl_label = "Add Clash Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = "New Clash Set"
|
||||
new.tolerance = 0.01
|
||||
return {"FINISHED"}
|
||||
@@ -88,20 +90,22 @@ class AddClashSet(bpy.types.Operator):
|
||||
class RemoveClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_clash_set"
|
||||
bl_label = "Remove Clash Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMClashProperties.clash_sets.remove(self.index)
|
||||
context.scene.BIMClashProperties.clash_sets.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.add_clash_source"
|
||||
bl_label = "Add Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
|
||||
clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
|
||||
source = getattr(clash_set, self.group).add()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -109,11 +113,12 @@ class AddClashSource(bpy.types.Operator):
|
||||
class RemoveClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_clash_source"
|
||||
bl_label = "Remove Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
|
||||
clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
|
||||
getattr(clash_set, self.group).remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -121,12 +126,14 @@ class RemoveClashSource(bpy.types.Operator):
|
||||
class SelectClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_source"
|
||||
bl_label = "Select Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
|
||||
clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
|
||||
getattr(clash_set, self.group)[self.index].name = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -138,10 +145,11 @@ class SelectClashSource(bpy.types.Operator):
|
||||
class SelectClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_results"
|
||||
bl_label = "Select Clash Results"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMClashProperties.clash_results_path = self.filepath
|
||||
context.scene.BIMClashProperties.clash_results_path = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -152,10 +160,11 @@ class SelectClashResults(bpy.types.Operator):
|
||||
class SelectSmartGroupedClashesPath(bpy.types.Operator):
|
||||
bl_idname = "bim.select_smart_grouped_clashes_path"
|
||||
bl_label = "Select Smart-Grouped Clashes Path"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
|
||||
context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -177,42 +186,42 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
import ifcclash
|
||||
from ifcclash import ifcclash
|
||||
|
||||
settings = ifcclash.IfcClashSettings()
|
||||
settings = ifcclash.ClashSettings()
|
||||
if ".json" not in self.filepath:
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
ifc_clasher = ifcclash.IfcClasher(settings)
|
||||
clasher = ifcclash.Clasher(settings)
|
||||
|
||||
if bpy.context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
if context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(self, viewpoint, mat):
|
||||
def get_viewpoint_snapshot(viewpoint, mat):
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
bpy.context.scene.collection.objects.link(camera)
|
||||
context.scene.collection.objects.link(camera)
|
||||
camera.matrix_world = Matrix(mat)
|
||||
bpy.context.scene.camera = camera
|
||||
context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
area.spaces[0].shading.show_xray = True
|
||||
bpy.context.scene.render.resolution_x = 480
|
||||
bpy.context.scene.render.resolution_y = 270
|
||||
bpy.context.scene.render.image_settings.file_format = "PNG"
|
||||
bpy.context.scene.render.filepath = os.path.join(
|
||||
bpy.context.scene.BIMProperties.data_dir, "snapshot.png"
|
||||
context.scene.render.resolution_x = 480
|
||||
context.scene.render.resolution_y = 270
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = os.path.join(
|
||||
context.scene.BIMProperties.data_dir, "snapshot.png"
|
||||
)
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
return bpy.context.scene.render.filepath
|
||||
return context.scene.render.filepath
|
||||
|
||||
ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
|
||||
ifc_clasher.clash_sets = []
|
||||
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
|
||||
clasher.clash_sets = []
|
||||
for clash_set in context.scene.BIMClashProperties.clash_sets:
|
||||
self.a = []
|
||||
self.b = []
|
||||
for ab in ["a", "b"]:
|
||||
@@ -222,17 +231,19 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
clash_source["selector"] = data.selector
|
||||
clash_source["mode"] = data.mode
|
||||
getattr(self, ab).append(clash_source)
|
||||
ifc_clasher.clash_sets.append(
|
||||
{"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}
|
||||
)
|
||||
ifc_clasher.clash()
|
||||
ifc_clasher.export()
|
||||
clash_set_data = {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a}
|
||||
if self.b:
|
||||
clash_set_data["b"] = self.b
|
||||
clasher.clash_sets.append(clash_set_data)
|
||||
clasher.clash()
|
||||
clasher.export()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectIfcClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_ifc_clash_results"
|
||||
bl_label = "Select IFC Clash Results"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
@@ -246,8 +257,8 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
|
||||
bpy.context.scene.BIMClashProperties.active_clash_set_index
|
||||
clash_set_name = context.scene.BIMClashProperties.clash_sets[
|
||||
context.scene.BIMClashProperties.active_clash_set_index
|
||||
].name
|
||||
global_ids = []
|
||||
for clash_set in clash_sets:
|
||||
@@ -258,7 +269,7 @@ 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 bpy.context.visible_objects:
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
@@ -270,13 +281,14 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
class SmartClashGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.smart_clash_group"
|
||||
bl_label = "Smart Group Clashes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
import ifcclash
|
||||
|
||||
settings = ifcclash.IfcClashSettings()
|
||||
self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.clash_results_path, ".json")
|
||||
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)
|
||||
@@ -286,21 +298,21 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
clash_sets = json.load(f)
|
||||
|
||||
# execute the smart grouping
|
||||
save_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
save_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
|
||||
clash_sets, bpy.context.scene.BIMClashProperties.smart_clash_grouping_max_distance
|
||||
clash_sets, context.scene.BIMClashProperties.smart_clash_grouping_max_distance
|
||||
)
|
||||
|
||||
# save smart_groups to json
|
||||
with open(save_path, "w") as f:
|
||||
f.write(json.dumps(smart_grouped_clashes))
|
||||
|
||||
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
|
||||
bpy.context.scene.BIMClashProperties.active_clash_set_index
|
||||
clash_set_name = context.scene.BIMClashProperties.clash_sets[
|
||||
context.scene.BIMClashProperties.active_clash_set_index
|
||||
].name
|
||||
|
||||
# Reset the list of smart_clash_groups for the UI
|
||||
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
|
||||
for clash_set, smart_groups in smart_grouped_clashes.items():
|
||||
# Only select the clashes that correspond to the actively selected IFC Clash Set
|
||||
@@ -308,7 +320,7 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
continue
|
||||
else:
|
||||
for smart_group, global_id_pairs in smart_groups[0].items():
|
||||
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group.number = f"{smart_group}"
|
||||
|
||||
for pair in global_id_pairs:
|
||||
@@ -322,19 +334,20 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.load_smart_groups_for_active_clash_set"
|
||||
bl_label = "Load Smart Groups for Active Clash Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
smart_groups_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
|
||||
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
|
||||
bpy.context.scene.BIMClashProperties.active_clash_set_index
|
||||
clash_set_name = context.scene.BIMClashProperties.clash_sets[
|
||||
context.scene.BIMClashProperties.active_clash_set_index
|
||||
].name
|
||||
|
||||
with open(smart_groups_path) as f:
|
||||
smart_grouped_clashes = json.load(f)
|
||||
|
||||
# Reset the list of smart_clash_groups for the UI
|
||||
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
|
||||
for clash_set, smart_groups in smart_grouped_clashes.items():
|
||||
# Only select the clashes that correspond to the actively selected IFC Clash Set
|
||||
@@ -342,7 +355,7 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
|
||||
continue
|
||||
else:
|
||||
for smart_group, global_id_pairs in smart_groups[0].items():
|
||||
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group.number = f"{smart_group}"
|
||||
for pair in global_id_pairs:
|
||||
for id in pair:
|
||||
@@ -355,15 +368,16 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
|
||||
class SelectSmartGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.select_smart_group"
|
||||
bl_label = "Select Smart Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
# Select smart group in view
|
||||
selected_smart_group = bpy.context.scene.BIMClashProperties.smart_clash_groups[
|
||||
bpy.context.scene.BIMCLashProperties.active_smart_group_index
|
||||
selected_smart_group = context.scene.BIMClashProperties.smart_clash_groups[
|
||||
context.scene.BIMCLashProperties.active_smart_group_index
|
||||
]
|
||||
# print(selected_smart_group.number)
|
||||
|
||||
for obj in bpy.context.visible_objects:
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
@@ -378,13 +392,13 @@ class SelectSmartGroup(bpy.types.Operator):
|
||||
|
||||
|
||||
class BlenderClasher:
|
||||
def process_clash_set(self):
|
||||
def process_clash_set(self, context):
|
||||
import collision
|
||||
|
||||
a_cm = collision.CollisionManager()
|
||||
b_cm = collision.CollisionManager()
|
||||
self.add_to_cm(a_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_a)
|
||||
self.add_to_cm(b_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_b)
|
||||
self.add_to_cm(a_cm, context.scene.BIMClashProperties.blender_clash_set_a, context)
|
||||
self.add_to_cm(b_cm, context.scene.BIMClashProperties.blender_clash_set_b, context)
|
||||
results = a_cm.in_collision_other(b_cm, return_data=True)
|
||||
if not results[0]:
|
||||
print("No clashes")
|
||||
@@ -397,20 +411,20 @@ class BlenderClasher:
|
||||
print(contact.raw.normal)
|
||||
print(contact.raw.pos)
|
||||
|
||||
def add_to_cm(self, cm, object_names):
|
||||
def add_to_cm(self, cm, object_names, context):
|
||||
import ifcclash
|
||||
|
||||
for object_name in object_names:
|
||||
name = object_name.name
|
||||
obj = bpy.data.objects[name]
|
||||
triangulated_mesh = self.triangulate_mesh(obj)
|
||||
triangulated_mesh = self.triangulate_mesh(obj, context)
|
||||
mesh = ifcclash.Mesh()
|
||||
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
|
||||
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
|
||||
cm.add_object(name, mesh)
|
||||
|
||||
def triangulate_mesh(self, obj):
|
||||
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
def triangulate_mesh(self, obj, context):
|
||||
mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
@@ -423,12 +437,13 @@ class BlenderClasher:
|
||||
class SetBlenderClashSetA(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_clash_set_a"
|
||||
bl_label = "Set Blender Clash Set A"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_a) > 0:
|
||||
bpy.context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
|
||||
for obj in bpy.context.selected_objects:
|
||||
new = bpy.context.scene.BIMClashProperties.blender_clash_set_a.add()
|
||||
while len(context.scene.BIMClashProperties.blender_clash_set_a) > 0:
|
||||
context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
|
||||
for obj in context.selected_objects:
|
||||
new = context.scene.BIMClashProperties.blender_clash_set_a.add()
|
||||
new.name = obj.name
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -436,12 +451,13 @@ class SetBlenderClashSetA(bpy.types.Operator):
|
||||
class SetBlenderClashSetB(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_clash_set_b"
|
||||
bl_label = "Set Blender Clash Set B"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_b) > 0:
|
||||
bpy.context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
|
||||
for obj in bpy.context.selected_objects:
|
||||
new = bpy.context.scene.BIMClashProperties.blender_clash_set_b.add()
|
||||
while len(context.scene.BIMClashProperties.blender_clash_set_b) > 0:
|
||||
context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
|
||||
for obj in context.selected_objects:
|
||||
new = context.scene.BIMClashProperties.blender_clash_set_b.add()
|
||||
new.name = obj.name
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -452,5 +468,5 @@ class ExecuteBlenderClash(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
blender_clasher = BlenderClasher()
|
||||
blender_clasher.process_clash_set()
|
||||
blender_clasher.process_clash_set(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -93,6 +93,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
class BIM_PT_clash_manager(Panel):
|
||||
bl_idname = "BIM_PT_clash_manager"
|
||||
bl_label = "Clash Manager"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api.classification.add_classification as add_classification
|
||||
import ifcopenshell.api.classification.remove_classification as remove_classification
|
||||
import ifcopenshell.api.classification.edit_classification as edit_classification
|
||||
import ifcopenshell.api.classification.add_reference as add_reference
|
||||
import ifcopenshell.api.classification.remove_reference as remove_reference
|
||||
import ifcopenshell.api.classification.edit_reference as edit_reference
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.classification.data import Data
|
||||
from blenderbim.bim.module.classification.prop import getClassifications, getReferences
|
||||
@@ -31,12 +26,18 @@ class LoadClassificationLibrary(bpy.types.Operator):
|
||||
class AddClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.add_classification"
|
||||
bl_label = "Add Classification"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
add_classification.Usecase(
|
||||
IfcStore.get_file(), {"classification": Data.library_file.by_id(int(props.available_classifications))}
|
||||
).execute()
|
||||
ifcopenshell.api.run(
|
||||
"classification.add_classification",
|
||||
IfcStore.get_file(),
|
||||
**{"classification": Data.library_file.by_id(int(props.available_classifications))},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -44,6 +45,7 @@ class AddClassification(bpy.types.Operator):
|
||||
class EnableEditingClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_classification"
|
||||
bl_label = "Enable Editing Classification"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -67,6 +69,7 @@ class EnableEditingClassification(bpy.types.Operator):
|
||||
class DisableEditingClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_classification"
|
||||
bl_label = "Disable Editing Classification"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMClassificationProperties.active_classification_id = 0
|
||||
@@ -76,11 +79,19 @@ class DisableEditingClassification(bpy.types.Operator):
|
||||
class RemoveClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_classification"
|
||||
bl_label = "Remove Classification"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
remove_classification.Usecase(self.file, {"classification": self.file.by_id(self.classification)}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_classification",
|
||||
self.file,
|
||||
**{"classification": self.file.by_id(self.classification)},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -88,8 +99,12 @@ class RemoveClassification(bpy.types.Operator):
|
||||
class EditClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_classification"
|
||||
bl_label = "Edit Classification"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
attributes = {}
|
||||
for attribute in props.classification_attributes:
|
||||
@@ -100,9 +115,11 @@ class EditClassification(bpy.types.Operator):
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_classification.Usecase(
|
||||
self.file, {"classification": self.file.by_id(props.active_classification_id), "attributes": attributes}
|
||||
).execute()
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_classification",
|
||||
self.file,
|
||||
**{"classification": self.file.by_id(props.active_classification_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_classification()
|
||||
return {"FINISHED"}
|
||||
@@ -111,11 +128,12 @@ class EditClassification(bpy.types.Operator):
|
||||
class EnableEditingClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_classification_reference"
|
||||
bl_label = "Enable Editing Classification Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
while len(props.reference_attributes) > 0:
|
||||
props.reference_attributes.remove(0)
|
||||
@@ -135,10 +153,11 @@ class EnableEditingClassificationReference(bpy.types.Operator):
|
||||
class DisableEditingClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_classification_reference"
|
||||
bl_label = "Disable Editing Classification Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
obj.BIMClassificationReferenceProperties.active_reference_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -146,19 +165,24 @@ class DisableEditingClassificationReference(bpy.types.Operator):
|
||||
class RemoveClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_classification_reference"
|
||||
bl_label = "Remove Classification Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
remove_reference.Usecase(
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_reference",
|
||||
self.file,
|
||||
{
|
||||
**{
|
||||
"reference": self.file.by_id(self.reference),
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
).execute()
|
||||
)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
@@ -167,10 +191,14 @@ class RemoveClassificationReference(bpy.types.Operator):
|
||||
class EditClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_classification_reference"
|
||||
bl_label = "Edit Classification Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
attributes = {}
|
||||
for attribute in props.reference_attributes:
|
||||
@@ -179,9 +207,11 @@ class EditClassificationReference(bpy.types.Operator):
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_reference.Usecase(
|
||||
self.file, {"reference": self.file.by_id(props.active_reference_id), "attributes": attributes}
|
||||
).execute()
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_reference",
|
||||
self.file,
|
||||
**{"reference": self.file.by_id(props.active_reference_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_classification_reference()
|
||||
return {"FINISHED"}
|
||||
@@ -190,11 +220,15 @@ class EditClassificationReference(bpy.types.Operator):
|
||||
class AddClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.add_classification_reference"
|
||||
bl_label = "Add Classification Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
classification = None
|
||||
@@ -206,14 +240,15 @@ class AddClassificationReference(bpy.types.Operator):
|
||||
classification = self.file.by_id(classification_id)
|
||||
break
|
||||
|
||||
add_reference.Usecase(
|
||||
ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
self.file,
|
||||
{
|
||||
**{
|
||||
"reference": Data.library_file.by_id(self.reference),
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"classification": classification
|
||||
"classification": classification,
|
||||
},
|
||||
).execute()
|
||||
)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
@@ -222,6 +257,7 @@ class AddClassificationReference(bpy.types.Operator):
|
||||
class ChangeClassificationLevel(bpy.types.Operator):
|
||||
bl_idname = "bim.change_classification_level"
|
||||
bl_label = "Change Classification Level"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -16,6 +16,11 @@ from bpy.props import (
|
||||
classification_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global classification_enum
|
||||
classification_enum = []
|
||||
|
||||
|
||||
def getClassifications(self, context):
|
||||
global classification_enum
|
||||
if len(classification_enum) < 1:
|
||||
|
||||
@@ -70,6 +70,8 @@ class BIM_PT_classification_references(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -11,10 +11,11 @@ from blenderbim.bim.ifc import IfcStore
|
||||
class SelectCobieIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cobie_ifc_file"
|
||||
bl_label = "Select COBie IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.COBieProperties.cobie_ifc_file = self.filepath
|
||||
context.scene.COBieProperties.cobie_ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -25,10 +26,11 @@ class SelectCobieIfcFile(bpy.types.Operator):
|
||||
class SelectCobieJsonFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cobie_json_file"
|
||||
bl_label = "Select COBie JSON File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.COBieProperties.cobie_json_file = self.filepath
|
||||
context.scene.COBieProperties.cobie_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -43,7 +45,7 @@ class ExecuteIfcCobie(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
from cobie import IfcCobieParser
|
||||
props = bpy.context.scene.COBieProperties
|
||||
props = context.scene.COBieProperties
|
||||
|
||||
output_dir = os.path.dirname(props.cobie_ifc_file)
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api.constraint.add_objective as add_objective
|
||||
import ifcopenshell.api.constraint.edit_objective as edit_objective
|
||||
import ifcopenshell.api.constraint.remove_constraint as remove_constraint
|
||||
import ifcopenshell.api.constraint.assign_constraint as assign_constraint
|
||||
import ifcopenshell.api.constraint.unassign_constraint as unassign_constraint
|
||||
import blenderbim.bim.helper
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.constraint.data import Data
|
||||
|
||||
@@ -13,6 +10,7 @@ from ifcopenshell.api.constraint.data import Data
|
||||
class LoadObjectives(bpy.types.Operator):
|
||||
bl_idname = "bim.load_objectives"
|
||||
bl_label = "Load Objectives"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
@@ -30,6 +28,7 @@ class LoadObjectives(bpy.types.Operator):
|
||||
class DisableConstraintEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_constraint_editing_ui"
|
||||
bl_label = "Disable Constraint Editing UI"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMConstraintProperties.is_editing = ""
|
||||
@@ -40,6 +39,7 @@ class DisableConstraintEditingUI(bpy.types.Operator):
|
||||
class EnableEditingConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_constraint"
|
||||
bl_label = "Enable Editing Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -50,21 +50,7 @@ class EnableEditingConstraint(bpy.types.Operator):
|
||||
if props.is_editing == "IfcObjective":
|
||||
data = Data.objectives[self.constraint]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(props.is_editing).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.constraint_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else 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()]
|
||||
blenderbim.bim.helper.import_attributes(props.is_editing, props.constraint_attributes, data)
|
||||
props.active_constraint_id = self.constraint
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -72,6 +58,7 @@ class EnableEditingConstraint(bpy.types.Operator):
|
||||
class DisableEditingConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_constraint"
|
||||
bl_label = "Disable Editing Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMConstraintProperties.active_constraint_id = 0
|
||||
@@ -81,9 +68,13 @@ class DisableEditingConstraint(bpy.types.Operator):
|
||||
class AddObjective(bpy.types.Operator):
|
||||
bl_idname = "bim.add_objective"
|
||||
bl_label = "Add Objective"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
result = add_objective.Usecase(IfcStore.get_file()).execute()
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("constraint.add_objective", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_objectives()
|
||||
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
|
||||
@@ -93,8 +84,12 @@ class AddObjective(bpy.types.Operator):
|
||||
class EditObjective(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_objective"
|
||||
bl_label = "Edit Objective"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
attributes = {}
|
||||
for attribute in props.constraint_attributes:
|
||||
@@ -105,9 +100,11 @@ class EditObjective(bpy.types.Operator):
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_objective.Usecase(
|
||||
self.file, {"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
|
||||
).execute()
|
||||
ifcopenshell.api.run(
|
||||
"constraint.edit_objective",
|
||||
self.file,
|
||||
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_objectives()
|
||||
return {"FINISHED"}
|
||||
@@ -116,12 +113,18 @@ class EditObjective(bpy.types.Operator):
|
||||
class RemoveConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_constraint"
|
||||
bl_label = "Remove Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
self.file = IfcStore.get_file()
|
||||
remove_constraint.Usecase(self.file, {"constraint": self.file.by_id(self.constraint)}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"constraint.remove_constraint", self.file, **{"constraint": self.file.by_id(self.constraint)}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.is_editing == "IfcObjective":
|
||||
bpy.ops.bim.load_objectives()
|
||||
@@ -131,6 +134,7 @@ class RemoveConstraint(bpy.types.Operator):
|
||||
class EnableAssigningConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_assigning_constraint"
|
||||
bl_label = "Enable Assigning Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -145,6 +149,7 @@ class EnableAssigningConstraint(bpy.types.Operator):
|
||||
class DisableAssigningConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_assigning_constraint"
|
||||
bl_label = "Disable Assigning Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -157,16 +162,24 @@ class DisableAssigningConstraint(bpy.types.Operator):
|
||||
class AssignConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_constraint"
|
||||
bl_label = "Assign Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
assign_constraint.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"constraint": self.file.by_id(self.constraint)
|
||||
}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"constraint.assign_constraint",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"constraint": self.file.by_id(self.constraint),
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -174,15 +187,23 @@ class AssignConstraint(bpy.types.Operator):
|
||||
class UnassignConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_constraint"
|
||||
bl_label = "Unassign Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
unassign_constraint.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"constraint": self.file.by_id(self.constraint)
|
||||
}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"constraint.unassign_constraint",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"constraint": self.file.by_id(self.constraint),
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -64,6 +64,8 @@ class BIM_PT_object_constraints(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import bpy
|
||||
import ifcopenshell.api.context.add_context as add_context
|
||||
import ifcopenshell.api.context.remove_context as remove_context
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.context.data import Data
|
||||
|
||||
@@ -8,20 +7,25 @@ from ifcopenshell.api.context.data import Data
|
||||
class AddSubcontext(bpy.types.Operator):
|
||||
bl_idname = "bim.add_subcontext"
|
||||
bl_label = "Add Subcontext"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
context: bpy.props.StringProperty()
|
||||
subcontext: bpy.props.StringProperty()
|
||||
target_view: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
add_context.Usecase(
|
||||
ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
{
|
||||
"context": self.context or bpy.context.scene.BIMProperties.available_contexts,
|
||||
"subcontext": self.subcontext or bpy.context.scene.BIMProperties.available_subcontexts,
|
||||
"target_view": self.target_view or bpy.context.scene.BIMProperties.available_target_views,
|
||||
**{
|
||||
"context": self.context or context.scene.BIMProperties.available_contexts,
|
||||
"subcontext": self.subcontext or context.scene.BIMProperties.available_subcontexts,
|
||||
"target_view": self.target_view or context.scene.BIMProperties.available_target_views,
|
||||
},
|
||||
).execute()
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -29,11 +33,16 @@ class AddSubcontext(bpy.types.Operator):
|
||||
class RemoveSubcontext(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_subcontext"
|
||||
bl_label = "Remove Context"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
usecase = remove_context.Usecase(self.file, {"context": self.file.by_id(self.ifc_definition_id)})
|
||||
usecase.execute()
|
||||
ifcopenshell.api.run(
|
||||
"context.remove_context", self.file, **{"context": self.file.by_id(self.ifc_definition_id)}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCostSchedule,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.EditCostSchedule,
|
||||
operator.EditCostItem,
|
||||
operator.EditCostItemQuantity,
|
||||
operator.EditCostValue,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EnableEditingCostItem,
|
||||
operator.EnableEditingCostItemQuantities,
|
||||
operator.EnableEditingCostItemQuantity,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostItemValue,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.AddCostItem,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.ExpandCostItem,
|
||||
operator.ContractCostItem,
|
||||
operator.RemoveCostItem,
|
||||
operator.AssignCostItemProduct,
|
||||
operator.UnassignCostItemProduct,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.RemoveCostItemQuantity,
|
||||
operator.AddCostValue,
|
||||
operator.RemoveCostItemValue,
|
||||
operator.CopyCostItemValues,
|
||||
operator.SelectCostItemProducts,
|
||||
operator.SelectCostScheduleProducts,
|
||||
prop.CostItem,
|
||||
prop.BIMCostProperties,
|
||||
ui.BIM_PT_cost_schedules,
|
||||
ui.BIM_UL_cost_items,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMCostProperties = bpy.props.PointerProperty(type=prop.BIMCostProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMCostProperties
|
||||
@@ -0,0 +1,652 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
from blenderbim.bim.module.cost.prop import purge
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.cost.data import Data
|
||||
|
||||
|
||||
class AddCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.add_cost_schedule"
|
||||
bl_label = "Add Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run("cost.add_cost_schedule", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_cost_schedule"
|
||||
bl_label = "Edit Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.cost_schedule_attributes)
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_schedule",
|
||||
self.file,
|
||||
**{"cost_schedule": self.file.by_id(props.active_cost_schedule_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_cost_schedule()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_cost_schedule"
|
||||
bl_label = "Remove Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_schedule",
|
||||
IfcStore.get_file(),
|
||||
cost_schedule=IfcStore.get_file().by_id(self.cost_schedule),
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_schedule"
|
||||
bl_label = "Enable Editing Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
self.props.active_cost_schedule_id = self.cost_schedule
|
||||
while len(self.props.cost_schedule_attributes) > 0:
|
||||
self.props.cost_schedule_attributes.remove(0)
|
||||
self.enable_editing_cost_schedule()
|
||||
self.props.is_editing = "COST_SCHEDULE"
|
||||
return {"FINISHED"}
|
||||
|
||||
def enable_editing_cost_schedule(self):
|
||||
data = Data.cost_schedules[self.cost_schedule]
|
||||
blenderbim.bim.helper.import_attributes(
|
||||
"IfcCostSchedule", self.props.cost_schedule_attributes, data, self.import_attributes
|
||||
)
|
||||
|
||||
def import_attributes(self, name, prop, data):
|
||||
if name in ["SubmittedOn", "UpdateDate"]:
|
||||
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
||||
return True
|
||||
|
||||
|
||||
class EnableEditingCostItems(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_items"
|
||||
bl_label = "Enable Editing Cost Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound:
|
||||
# lol
|
||||
# TODO: make pitch higher as costs rise
|
||||
try:
|
||||
import aud
|
||||
|
||||
device = aud.Device()
|
||||
# chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/
|
||||
sound = aud.Sound(os.path.join(context.scene.BIMProperties.data_dir, "chaching.mp3"))
|
||||
handle = device.play(sound)
|
||||
sound_buffered = aud.Sound.buffer(sound)
|
||||
handle_buffered = device.play(sound_buffered)
|
||||
handle.stop()
|
||||
handle_buffered.stop()
|
||||
except:
|
||||
pass # ah well
|
||||
self.props = context.scene.BIMCostProperties
|
||||
self.props.active_cost_schedule_id = self.cost_schedule
|
||||
while len(self.props.cost_items) > 0:
|
||||
self.props.cost_items.remove(0)
|
||||
|
||||
self.contracted_cost_items = json.loads(self.props.contracted_cost_items)
|
||||
for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
|
||||
self.create_new_cost_item_li(related_object_id, 0)
|
||||
self.props.is_editing = "COST_ITEMS"
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_new_cost_item_li(self, related_object_id, level_index):
|
||||
cost_item = Data.cost_items[related_object_id]
|
||||
new = self.props.cost_items.add()
|
||||
new.ifc_definition_id = related_object_id
|
||||
new.name = cost_item["Name"] or "Unnamed"
|
||||
new.is_expanded = related_object_id not in self.contracted_cost_items
|
||||
new.level_index = level_index
|
||||
if cost_item["IsNestedBy"]:
|
||||
new.has_children = True
|
||||
if new.is_expanded:
|
||||
for related_object_id in cost_item["IsNestedBy"]:
|
||||
self.create_new_cost_item_li(related_object_id, level_index + 1)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_cost_schedule"
|
||||
bl_label = "Disable Editing Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMCostProperties.active_cost_schedule_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSummaryCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.add_summary_cost_item"
|
||||
bl_label = "Add Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_schedule": self.file.by_id(self.cost_schedule)})
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=self.cost_schedule)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.add_cost_item"
|
||||
bl_label = "Add Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_item": self.file.by_id(self.cost_item)})
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExpandCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_cost_item"
|
||||
bl_label = "Expand Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
contracted_cost_items.remove(self.cost_item)
|
||||
props.contracted_cost_items = json.dumps(contracted_cost_items)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ContractCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.contract_cost_item"
|
||||
bl_label = "Contract Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
contracted_cost_items.append(self.cost_item)
|
||||
props.contracted_cost_items = json.dumps(contracted_cost_items)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_cost_item"
|
||||
bl_label = "Remove Cost item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_item",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
)
|
||||
contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
if props.active_cost_item_index in contracted_cost_items:
|
||||
contracted_cost_items.remove(props.active_cost_item_index)
|
||||
props.contracted_cost_items = json.dumps(contracted_cost_items)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_item"
|
||||
bl_label = "Enable Editing Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
while len(props.cost_item_attributes) > 0:
|
||||
props.cost_item_attributes.remove(0)
|
||||
|
||||
data = Data.cost_items[self.cost_item]
|
||||
blenderbim.bim.helper.import_attributes("IfcCostItem", props.cost_item_attributes, data)
|
||||
props.active_cost_item_id = self.cost_item
|
||||
props.cost_item_editing_type = "ATTRIBUTES"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_cost_item"
|
||||
bl_label = "Disable Editing Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMCostProperties.active_cost_item_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_cost_item"
|
||||
bl_label = "Edit Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.cost_item_attributes)
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item",
|
||||
self.file,
|
||||
**{"cost_item": self.file.by_id(props.active_cost_item_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_cost_item()
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignCostItemProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_cost_item_product"
|
||||
bl_label = "Assign Control"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
)
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.assign_cost_item_product",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
products=[
|
||||
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
|
||||
for o in related_objects
|
||||
if o.BIMObjectProperties.ifc_definition_id
|
||||
],
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignCostItemProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_cost_item_product"
|
||||
bl_label = "Unassign Control"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
)
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.unassign_cost_item_product",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
products=[
|
||||
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
|
||||
for o in related_objects
|
||||
if o.BIMObjectProperties.ifc_definition_id
|
||||
],
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostItemQuantities(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_item_quantities"
|
||||
bl_label = "Enable Editing Cost Item Quantities"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
props.active_cost_item_id = self.cost_item
|
||||
props.cost_item_editing_type = "QUANTITIES"
|
||||
purge()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostItemValues(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_item_values"
|
||||
bl_label = "Enable Editing Cost Item Values"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
props.active_cost_item_id = self.cost_item
|
||||
props.cost_item_editing_type = "VALUES"
|
||||
bpy.ops.bim.disable_editing_cost_item_value()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddCostItemQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.add_cost_item_quantity"
|
||||
bl_label = "Add Cost Item Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.props = context.scene.BIMCostProperties
|
||||
if self.props.quantity_types == "QTO":
|
||||
self.add_quantities_from_qto_filter()
|
||||
else:
|
||||
self.add_manual_quantity()
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
def add_quantities_from_qto_filter(self):
|
||||
ifcopenshell.api.run(
|
||||
"cost.assign_cost_item_product_quantities",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
prop_name=self.props.quantity_names,
|
||||
)
|
||||
|
||||
def add_manual_quantity(self):
|
||||
ifcopenshell.api.run(
|
||||
"cost.add_cost_item_quantity",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
ifc_class=self.ifc_class,
|
||||
)
|
||||
|
||||
|
||||
class RemoveCostItemQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_cost_item_quantity"
|
||||
bl_label = "Add Cost Item Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
physical_quantity: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_item_quantity",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
physical_quantity=self.file.by_id(self.physical_quantity),
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostItemQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_item_quantity"
|
||||
bl_label = "Enable Editing Cost Item Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
physical_quantity: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
while len(self.props.quantity_attributes) > 0:
|
||||
self.props.quantity_attributes.remove(0)
|
||||
self.props.active_cost_item_quantity_id = self.physical_quantity
|
||||
data = Data.physical_quantities[self.physical_quantity]
|
||||
blenderbim.bim.helper.import_attributes(data["type"], self.props.quantity_attributes, data)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingCostItemQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_cost_item_quantity"
|
||||
bl_label = "Disable Editing Cost Item Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
props.active_cost_item_quantity_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditCostItemQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_cost_item_quantity"
|
||||
bl_label = "Edit Cost Item Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
physical_quantity: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.quantity_attributes)
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item_quantity",
|
||||
self.file,
|
||||
**{"physical_quantity": self.file.by_id(self.physical_quantity), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_cost_item_quantity()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddCostValue(bpy.types.Operator):
|
||||
bl_idname = "bim.add_cost_value"
|
||||
bl_label = "Add Cost Value"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.IntProperty()
|
||||
cost_type: bpy.props.StringProperty()
|
||||
cost_category: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.cost_type == "FIXED":
|
||||
category = None
|
||||
elif self.cost_type == "SUM":
|
||||
category = "*"
|
||||
elif self.cost_type == "CATEGORY":
|
||||
category = self.cost_category
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.file.by_id(self.parent))
|
||||
ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes={"Category": category})
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCostItemValue(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_cost_item_value"
|
||||
bl_label = "Add Cost Item Value"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_value: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("cost.remove_cost_item_value", self.file, cost_value=self.file.by_id(self.cost_value))
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostItemValue(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_item_value"
|
||||
bl_label = "Enable Editing Cost Item Value"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_value: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
while len(self.props.cost_value_attributes) > 0:
|
||||
self.props.cost_value_attributes.remove(0)
|
||||
self.props.active_cost_item_value_id = self.cost_value
|
||||
data = Data.cost_values[self.cost_value]
|
||||
|
||||
blenderbim.bim.helper.import_attributes(
|
||||
data["type"], self.props.cost_value_attributes, data, self.import_attributes
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
def import_attributes(self, name, prop, data):
|
||||
if name == "AppliedValue":
|
||||
# TODO: for now, only support simple values
|
||||
prop.data_type = "float"
|
||||
prop.float_value = 0.0 if prop.is_null else data[name]
|
||||
return True
|
||||
|
||||
|
||||
class DisableEditingCostItemValue(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_cost_item_value"
|
||||
bl_label = "Disable Editing Cost Item Value"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
props.active_cost_item_value_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditCostValue(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_cost_value"
|
||||
bl_label = "Edit Cost Item Value"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_value: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.cost_value_attributes)
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_value",
|
||||
self.file,
|
||||
**{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_cost_item_value()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyCostItemValues(bpy.types.Operator):
|
||||
bl_idname = "bim.copy_cost_item_values"
|
||||
bl_label = "Copy Cost Item Values"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
source: bpy.props.IntProperty()
|
||||
destination: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.copy_cost_item_values",
|
||||
self.file,
|
||||
**{"source": self.file.by_id(self.source), "destination": self.file.by_id(self.destination)},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectCostItemProducts(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cost_item_products"
|
||||
bl_label = "Select Cost Item Products"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_products = Data.cost_items[self.cost_item]["Controls"]
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
if obj.BIMObjectProperties.ifc_definition_id in related_products:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectCostScheduleProducts(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cost_schedule_products"
|
||||
bl_label = "Select Cost Schedule Products"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.related_products = []
|
||||
for cost_item_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
|
||||
self.get_related_products(Data.cost_items[cost_item_id])
|
||||
self.related_products = set(self.related_products)
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
if obj.BIMObjectProperties.ifc_definition_id in self.related_products:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_related_products(self, cost_item):
|
||||
self.related_products.extend(cost_item["Controls"])
|
||||
for child_id in cost_item["IsNestedBy"]:
|
||||
self.get_related_products(Data.cost_items[child_id])
|
||||
@@ -0,0 +1,107 @@
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.cost.data import Data
|
||||
from ifcopenshell.api.pset.data import Data as PsetData
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
quantitytypes_enum = []
|
||||
quantitynames_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global quantitytypes_enum
|
||||
global quantitynames_enum
|
||||
quantitytypes_enum = []
|
||||
quantitynames_enum = []
|
||||
|
||||
|
||||
def getQuantityTypes(self, context):
|
||||
global quantitytypes_enum
|
||||
if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
|
||||
quantitytypes_enum = [("QTO", "Qto", "Derive quantities from IFC quantity sets")]
|
||||
quantitytypes_enum.extend(
|
||||
[
|
||||
(t.name(), t.name(), "")
|
||||
for t in IfcStore.get_schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
|
||||
]
|
||||
)
|
||||
return quantitytypes_enum
|
||||
|
||||
|
||||
def getQuantityNames(self, context):
|
||||
global quantitynames_enum
|
||||
ifc_file = IfcStore.get_file()
|
||||
if len(quantitynames_enum) == 0 and ifc_file:
|
||||
names = set()
|
||||
for element_id in Data.cost_items[self.active_cost_item_id]["Controls"]:
|
||||
if element_id not in PsetData.products:
|
||||
PsetData.load(IfcStore.get_file(), element_id)
|
||||
for qto_id in PsetData.products[element_id]["qtos"]:
|
||||
qto = PsetData.qtos[qto_id]
|
||||
[names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
|
||||
quantitynames_enum.extend([(n, n, "") for n in names])
|
||||
return quantitynames_enum
|
||||
|
||||
|
||||
def updateCostItemName(self, context):
|
||||
if self.name == "Unnamed":
|
||||
return
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMCostProperties
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item",
|
||||
self.file,
|
||||
**{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.active_cost_item_id == self.ifc_definition_id:
|
||||
attribute = props.cost_item_attributes.get("Name")
|
||||
attribute.string_value = self.name
|
||||
|
||||
|
||||
class CostItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateCostItemName)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
has_children: BoolProperty(name="Has Children")
|
||||
is_expanded: BoolProperty(name="Is Expanded")
|
||||
level_index: IntProperty(name="Level Index")
|
||||
|
||||
|
||||
class BIMCostProperties(PropertyGroup):
|
||||
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
|
||||
cost_items: CollectionProperty(name="Work Calendar", type=CostItem)
|
||||
active_cost_item_id: IntProperty(name="Active Cost Id")
|
||||
cost_item_editing_type: StringProperty(name="Cost Item Editing Type")
|
||||
active_cost_item_index: IntProperty(name="Active Cost Item Index")
|
||||
cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
|
||||
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
|
||||
quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types")
|
||||
quantity_names: EnumProperty(items=getQuantityNames, name="Quantity Names")
|
||||
active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id")
|
||||
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
|
||||
cost_types: EnumProperty(
|
||||
items=[
|
||||
("FIXED", "Fixed", "The cost value is a fixed number"),
|
||||
("SUM", "Sum", "The cost value is automatically derived from the sum of all nested cost items"),
|
||||
("CATEGORY", "Category", "The cost value represents a single category"),
|
||||
],
|
||||
name="Cost Types",
|
||||
)
|
||||
cost_category: StringProperty(name="Cost Category")
|
||||
active_cost_item_value_id: IntProperty(name="Active Cost Item Value Id")
|
||||
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
|
||||
@@ -0,0 +1,301 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.cost.data import Data
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
bl_label = "IFC Cost Schedules"
|
||||
bl_idname = "BIM_PT_cost_schedules"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_cost_schedule", icon="ADD")
|
||||
|
||||
for cost_schedule_id, cost_schedule in Data.cost_schedules.items():
|
||||
self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule)
|
||||
|
||||
def draw_cost_schedule_ui(self, cost_schedule_id, cost_schedule):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=cost_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
|
||||
|
||||
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule_id:
|
||||
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.cost_schedule = cost_schedule_id
|
||||
if self.props.is_editing == "COST_SCHEDULE":
|
||||
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule_id
|
||||
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
|
||||
elif self.props.active_cost_schedule_id:
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id
|
||||
else:
|
||||
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule_id
|
||||
row.operator(
|
||||
"bim.enable_editing_cost_schedule", text="", icon="GREASEPENCIL"
|
||||
).cost_schedule = cost_schedule_id
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id
|
||||
|
||||
if self.props.active_cost_schedule_id == cost_schedule_id:
|
||||
if self.props.is_editing == "COST_SCHEDULE":
|
||||
self.draw_editable_cost_schedule_ui()
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
self.draw_editable_cost_item_ui(cost_schedule_id)
|
||||
|
||||
def draw_editable_cost_schedule_ui(self):
|
||||
for attribute in self.props.cost_schedule_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_editable_cost_item_ui(self, cost_schedule_id):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_cost_items",
|
||||
"",
|
||||
self.props,
|
||||
"cost_items",
|
||||
self.props,
|
||||
"active_cost_item_index",
|
||||
)
|
||||
if self.props.active_cost_item_id:
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
self.draw_editable_cost_item_attributes_ui()
|
||||
elif self.props.cost_item_editing_type == "QUANTITIES":
|
||||
self.draw_editable_cost_item_quantities_ui()
|
||||
elif self.props.cost_item_editing_type == "VALUES":
|
||||
self.draw_editable_cost_item_values_ui()
|
||||
|
||||
def draw_editable_cost_item_attributes_ui(self):
|
||||
for attribute in self.props.cost_item_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_editable_cost_item_quantities_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
if self.props.quantity_types == "QTO":
|
||||
row.prop(self.props, "quantity_names", text="")
|
||||
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
|
||||
op.cost_item = self.props.active_cost_item_id
|
||||
op.ifc_class = self.props.quantity_types
|
||||
|
||||
for quantity_id in Data.cost_items[self.props.active_cost_item_id]["CostQuantities"]:
|
||||
quantity = Data.physical_quantities[quantity_id]
|
||||
value = quantity[[k for k in quantity.keys() if "Value" in k][0]]
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=quantity["Name"])
|
||||
row.label(text="{0:.2f}".format(value))
|
||||
if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity_id:
|
||||
op = row.operator("bim.edit_cost_item_quantity", text="", icon="CHECKMARK")
|
||||
op.physical_quantity = quantity_id
|
||||
row.operator("bim.disable_editing_cost_item_quantity", text="", icon="CANCEL")
|
||||
elif self.props.active_cost_item_quantity_id:
|
||||
op = row.operator("bim.remove_cost_item_quantity", text="", icon="X")
|
||||
op.cost_item = self.props.active_cost_item_id
|
||||
op.physical_quantity = quantity_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_cost_item_quantity", text="", icon="GREASEPENCIL")
|
||||
op.physical_quantity = quantity_id
|
||||
op = row.operator("bim.remove_cost_item_quantity", text="", icon="X")
|
||||
op.cost_item = self.props.active_cost_item_id
|
||||
op.physical_quantity = quantity_id
|
||||
|
||||
if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity_id:
|
||||
box = self.layout.box()
|
||||
self.draw_editable_cost_item_quantity_ui(box)
|
||||
|
||||
def draw_editable_cost_item_quantity_ui(self, layout):
|
||||
for attribute in self.props.quantity_attributes:
|
||||
row = layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_editable_cost_item_values_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "cost_types", text="")
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
row.prop(self.props, "cost_category", text="")
|
||||
op = row.operator("bim.add_cost_value", text="", icon="ADD")
|
||||
op.parent = self.props.active_cost_item_id
|
||||
op.cost_type = self.props.cost_types
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
op.cost_category = self.props.cost_category
|
||||
|
||||
for cost_value_id in Data.cost_items[self.props.active_cost_item_id]["CostValues"]:
|
||||
row = self.layout.row(align=True)
|
||||
self.draw_readonly_cost_value_ui(row, cost_value_id)
|
||||
|
||||
if self.props.active_cost_item_value_id:
|
||||
box = self.layout.box()
|
||||
self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id])
|
||||
|
||||
|
||||
def draw_readonly_cost_value_ui(self, layout, cost_value_id):
|
||||
cost_value = Data.cost_values[cost_value_id]
|
||||
cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"])
|
||||
if cost_value["Category"]:
|
||||
cost_value_label += " ({})".format(cost_value["Category"])
|
||||
layout.label(text="", icon="DISC")
|
||||
self.draw_cost_value_operator_ui(layout, cost_value_id)
|
||||
layout.label(text=cost_value_label)
|
||||
|
||||
for component_id in cost_value["Components"] or []:
|
||||
self.draw_readonly_component_cost_value_ui(layout, component_id)
|
||||
|
||||
def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, level=1):
|
||||
self.draw_cost_value_operator_ui(layout, cost_value_id)
|
||||
cost_value = Data.cost_values[cost_value_id]
|
||||
cost_value_label = ">" * level
|
||||
cost_value_label += "{0:.2f}".format(cost_value["AppliedValue"])
|
||||
if cost_value["Category"]:
|
||||
cost_value_label += " ({})".format(cost_value["Category"])
|
||||
layout.label(text=cost_value_label)
|
||||
|
||||
for component_id in cost_value["Components"] or []:
|
||||
self.draw_readonly_component_cost_value_ui(layout, component_id, level + 1)
|
||||
|
||||
def draw_cost_value_operator_ui(self, layout, cost_value_id):
|
||||
if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id:
|
||||
op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK")
|
||||
op.cost_value = cost_value_id
|
||||
op = layout.operator("bim.add_cost_value", text="", icon="ADD")
|
||||
op.parent = cost_value_id
|
||||
op.cost_type = self.props.cost_types
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
op.cost_category = self.props.cost_category
|
||||
layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL")
|
||||
elif self.props.active_cost_item_value_id:
|
||||
op = layout.operator("bim.add_cost_value", text="", icon="ADD")
|
||||
op.parent = cost_value_id
|
||||
op.cost_type = self.props.cost_types
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
op.cost_category = self.props.cost_category
|
||||
op = layout.operator("bim.remove_cost_item_value", text="", icon="X")
|
||||
op.cost_value = cost_value_id
|
||||
else:
|
||||
op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL")
|
||||
op.cost_value = cost_value_id
|
||||
op = layout.operator("bim.add_cost_value", text="", icon="ADD")
|
||||
op.parent = cost_value_id
|
||||
op.cost_type = self.props.cost_types
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
op.cost_category = self.props.cost_category
|
||||
op = layout.operator("bim.remove_cost_item_value", text="", icon="X")
|
||||
op.cost_value = cost_value_id
|
||||
|
||||
def draw_editable_cost_value_ui(self, layout, cost_value):
|
||||
for attribute in self.props.cost_value_attributes:
|
||||
row = layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
|
||||
class BIM_UL_cost_items(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
props = context.scene.BIMCostProperties
|
||||
cost_item = Data.cost_items[item.ifc_definition_id]
|
||||
row = layout.row(align=True)
|
||||
|
||||
for i in range(0, item.level_index):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.has_children:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).cost_item = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).cost_item = item.ifc_definition_id
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
|
||||
split1 = row.split(factor=0.7)
|
||||
split1.prop(item, "name", emboss=False, text="")
|
||||
|
||||
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
|
||||
op.cost_item = item.ifc_definition_id
|
||||
row.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + " (M3)")
|
||||
|
||||
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
|
||||
op.cost_item = item.ifc_definition_id
|
||||
row.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"]))
|
||||
row.label(text="{0:.2f}".format(cost_item["TotalCostValue"]), icon="CON_TRANSLIKE")
|
||||
|
||||
if context.active_object:
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
row = layout.row(align=True)
|
||||
if oprops.ifc_definition_id in cost_item["Controls"]:
|
||||
op = row.operator("bim.unassign_cost_item_product", text="", icon="KEYFRAME_HLT", emboss=False)
|
||||
op.cost_item = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.assign_cost_item_product", text="", icon="KEYFRAME", emboss=False)
|
||||
op.cost_item = item.ifc_definition_id
|
||||
|
||||
if props.active_cost_item_id == item.ifc_definition_id:
|
||||
if props.cost_item_editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
elif props.active_cost_item_id:
|
||||
if props.cost_item_editing_type == "VALUES":
|
||||
op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN")
|
||||
op.source = props.active_cost_item_id
|
||||
op.destination = item.ifc_definition_id
|
||||
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
|
||||
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.cost_item = item.ifc_definition_id
|
||||
row.operator(
|
||||
"bim.enable_editing_cost_item", text="", icon="GREASEPENCIL"
|
||||
).cost_item = item.ifc_definition_id
|
||||
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
|
||||
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
|
||||
@@ -13,15 +13,16 @@ api = Api()
|
||||
class Login(bpy.types.Operator):
|
||||
bl_idname = "bim.covetool_login"
|
||||
bl_label = "Login to cove.tool"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
token = api.login(bpy.context.scene.CoveToolProperties.username, bpy.context.scene.CoveToolProperties.password)
|
||||
token = api.login(context.scene.CoveToolProperties.username, context.scene.CoveToolProperties.password)
|
||||
if token:
|
||||
bpy.context.scene.CoveToolProperties.token = token
|
||||
context.scene.CoveToolProperties.token = token
|
||||
|
||||
projects = api.get_request("projects")
|
||||
for project in projects:
|
||||
new_project = bpy.context.scene.CoveToolProperties.projects.add()
|
||||
new_project = context.scene.CoveToolProperties.projects.add()
|
||||
new_project.name = project["name"]
|
||||
new_project.run_set = project["run_set"][0]
|
||||
new_project.url = project["url"]
|
||||
@@ -35,10 +36,10 @@ class RunSimpleAnalysis(bpy.types.Operator):
|
||||
bl_label = "Run Simple Analysis"
|
||||
|
||||
def execute(self, context):
|
||||
simple_analysis = bpy.context.scene.CoveToolProperties.simple_analysis
|
||||
simple_analysis = context.scene.CoveToolProperties.simple_analysis
|
||||
data = {
|
||||
"run": bpy.context.scene.CoveToolProperties.projects[
|
||||
bpy.context.scene.CoveToolProperties.active_project_index
|
||||
"run": context.scene.CoveToolProperties.projects[
|
||||
context.scene.CoveToolProperties.active_project_index
|
||||
].run_set,
|
||||
"si_units": simple_analysis.si_units,
|
||||
"building_height": simple_analysis.building_height,
|
||||
@@ -83,10 +84,10 @@ class RunAnalysis(bpy.types.Operator):
|
||||
"roofs": [],
|
||||
"shading_devices": [],
|
||||
}
|
||||
self.parse_objects()
|
||||
self.parse_objects(context)
|
||||
data = {
|
||||
"run": bpy.context.scene.CoveToolProperties.projects[
|
||||
bpy.context.scene.CoveToolProperties.active_project_index
|
||||
"run": context.scene.CoveToolProperties.projects[
|
||||
context.scene.CoveToolProperties.active_project_index
|
||||
].run_set,
|
||||
"source": "BlenderBIM",
|
||||
"rotation_angle": self.get_rotation_angle(),
|
||||
@@ -111,14 +112,14 @@ class RunAnalysis(bpy.types.Operator):
|
||||
rotation = 360 - rotation
|
||||
return rotation
|
||||
|
||||
def parse_objects(self):
|
||||
for obj in bpy.context.visible_objects:
|
||||
def parse_objects(self, context):
|
||||
for obj in context.visible_objects:
|
||||
covetool_category = self.get_covetool_category(obj)
|
||||
if not covetool_category:
|
||||
continue
|
||||
if not self.has_triangulate_modifier(obj):
|
||||
obj.modifiers.new(name="Triangulate", type="TRIANGULATE")
|
||||
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
|
||||
meshes = {}
|
||||
for polygon in mesh.polygons:
|
||||
normal = "{}|{}|{}".format(
|
||||
@@ -196,5 +197,4 @@ class RunAnalysis(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def is_window_skylight(self, element):
|
||||
predefined_type = element.get_info().get("PredefinedType")
|
||||
return predefined_type and predefined_type.string_value == "SKYLIGHT"
|
||||
return element.get_info().get("PredefinedType") == "SKYLIGHT"
|
||||
|
||||
@@ -13,6 +13,7 @@ from blenderbim.bim.handler import purge_module_data
|
||||
class AddCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_csv_attribute"
|
||||
bl_label = "Add CSV Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
attribute = context.scene.CsvProperties.csv_attributes.add()
|
||||
@@ -22,6 +23,7 @@ class AddCsvAttribute(bpy.types.Operator):
|
||||
class RemoveCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_csv_attribute"
|
||||
bl_label = "Remove CSV Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -98,6 +100,7 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
class EyedropIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.eyedrop_ifccsv"
|
||||
bl_label = "Query Selected Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
global_ids = []
|
||||
@@ -112,11 +115,11 @@ class EyedropIfcCsv(bpy.types.Operator):
|
||||
class SelectCsvIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_csv_ifc_file"
|
||||
bl_label = "Select CSV IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.CsvProperties.csv_ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -2,6 +2,10 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.PrintIfcFile,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.ValidateIfcFile,
|
||||
operator.ProfileImportIFC,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
|
||||
@@ -1,24 +1,78 @@
|
||||
import bpy
|
||||
import logging
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class PrintIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.print_ifc_file"
|
||||
bl_label = "Print IFC File"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
print(IfcStore.get_file().wrapped_data.to_string())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PurgeIfcLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_ifc_links"
|
||||
bl_label = "Purge IFC Links"
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type in {"MESH", "EMPTY"}:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
if obj.data:
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
for material in bpy.data.materials:
|
||||
material.BIMMaterialProperties.ifc_style_id = False
|
||||
context.scene.BIMProperties.ifc_file = ""
|
||||
context.scene.BIMDebugProperties.attributes.clear()
|
||||
IfcStore.purge()
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ValidateIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.validate_ifc_file"
|
||||
bl_label = "Validate IFC File"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
import ifcopenshell.validate
|
||||
|
||||
logger = logging.getLogger("validate")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ProfileImportIFC(bpy.types.Operator):
|
||||
bl_idname = "bim.profile_import_ifc"
|
||||
bl_label = "Profile Import IFC"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file() and context.scene.BIMProperties.ifc_file
|
||||
|
||||
def execute(self, context):
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
# For Windows
|
||||
filepath = bpy.context.scene.BIMProperties.ifc_file.replace('\\', '\\\\')
|
||||
filepath = context.scene.BIMProperties.ifc_file.replace("\\", "\\\\")
|
||||
|
||||
cProfile.run(
|
||||
f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof"
|
||||
)
|
||||
cProfile.run(f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof")
|
||||
p = pstats.Stats("blender.prof")
|
||||
p.sort_stats("cumulative").print_stats(50)
|
||||
return {"FINISHED"}
|
||||
@@ -28,24 +82,28 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
bl_idname = "bim.create_all_shapes"
|
||||
bl_label = "Create All Shapes"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
||||
total = len(elements)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
failures = []
|
||||
excludes = () # For the developer to debug with
|
||||
excludes = () # For the developer to debug with
|
||||
for i, element in enumerate(elements):
|
||||
if element.GlobalId in excludes:
|
||||
continue
|
||||
print(f'{i}/{total}:', element)
|
||||
print(f"{i}/{total}:", element)
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
print("Success", len(shape.geometry.verts), len(shape.geometry.edges), len(shape.geometry.faces))
|
||||
except:
|
||||
failures.append(element)
|
||||
print('***** FAILURE *****')
|
||||
print('Failures:')
|
||||
print("***** FAILURE *****")
|
||||
print("Failures:")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
return {"FINISHED"}
|
||||
@@ -54,12 +112,20 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
class CreateShapeFromStepId(bpy.types.Operator):
|
||||
bl_idname = "bim.create_shape_from_step_id"
|
||||
bl_label = "Create Shape From STEP ID"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
|
||||
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
self.file = IfcStore.get_file()
|
||||
element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id))
|
||||
element = self.file.by_id(int(context.scene.BIMDebugProperties.step_id))
|
||||
settings = ifcopenshell.geom.settings()
|
||||
# settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
@@ -67,26 +133,19 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
obj = bpy.data.objects.new("Debug", mesh)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
context.scene.collection.objects.link(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectHighPolygonMeshes(bpy.types.Operator):
|
||||
bl_idname = "bim.select_high_polygon_meshes"
|
||||
bl_label = "Select High Polygon Meshes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
results = {}
|
||||
for obj in bpy.data.objects:
|
||||
if not isinstance(obj.data, bpy.types.Mesh) or len(obj.data.polygons) < int(
|
||||
bpy.context.scene.BIMDebugProperties.number_of_polygons
|
||||
):
|
||||
continue
|
||||
try:
|
||||
obj.select_set(True)
|
||||
except:
|
||||
# If it is not in the view layer
|
||||
pass
|
||||
[o.select_set(True) for o in context.view_layer.objects
|
||||
if o.type == 'MESH'
|
||||
and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -95,7 +154,7 @@ class RewindInspector(bpy.types.Operator):
|
||||
bl_label = "Rewind Inspector"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.BIMDebugProperties
|
||||
props = context.scene.BIMDebugProperties
|
||||
total_breadcrumbs = len(props.step_id_breadcrumb)
|
||||
if total_breadcrumbs < 2:
|
||||
return {"FINISHED"}
|
||||
@@ -111,20 +170,24 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
bl_label = "Inspect From STEP ID"
|
||||
step_id: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
bpy.context.scene.BIMDebugProperties.active_step_id = self.step_id
|
||||
crumb = bpy.context.scene.BIMDebugProperties.step_id_breadcrumb.add()
|
||||
context.scene.BIMDebugProperties.active_step_id = self.step_id
|
||||
crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add()
|
||||
crumb.name = str(self.step_id)
|
||||
element = self.file.by_id(self.step_id)
|
||||
while len(bpy.context.scene.BIMDebugProperties.attributes) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.attributes.remove(0)
|
||||
while len(bpy.context.scene.BIMDebugProperties.inverse_attributes) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.inverse_attributes.remove(0)
|
||||
while len(bpy.context.scene.BIMDebugProperties.inverse_references) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.inverse_references.remove(0)
|
||||
while len(context.scene.BIMDebugProperties.attributes) > 0:
|
||||
context.scene.BIMDebugProperties.attributes.remove(0)
|
||||
while len(context.scene.BIMDebugProperties.inverse_attributes) > 0:
|
||||
context.scene.BIMDebugProperties.inverse_attributes.remove(0)
|
||||
while len(context.scene.BIMDebugProperties.inverse_references) > 0:
|
||||
context.scene.BIMDebugProperties.inverse_references.remove(0)
|
||||
for key, value in element.get_info().items():
|
||||
self.add_attribute(bpy.context.scene.BIMDebugProperties.attributes, key, value)
|
||||
self.add_attribute(context.scene.BIMDebugProperties.attributes, key, value)
|
||||
for key in dir(element):
|
||||
if (
|
||||
not key[0].isalpha()
|
||||
@@ -133,9 +196,9 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
or not getattr(element, key)
|
||||
):
|
||||
continue
|
||||
self.add_attribute(bpy.context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
|
||||
self.add_attribute(context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
|
||||
for inverse in self.file.get_inverse(element):
|
||||
new = bpy.context.scene.BIMDebugProperties.inverse_references.add()
|
||||
new = context.scene.BIMDebugProperties.inverse_references.add()
|
||||
new.string_value = str(inverse)
|
||||
new.int_value = inverse.id()
|
||||
return {"FINISHED"}
|
||||
@@ -159,8 +222,18 @@ class InspectFromObject(bpy.types.Operator):
|
||||
bl_label = "Inspect From Object"
|
||||
|
||||
def execute(self, context):
|
||||
ifc_definition_id = bpy.context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
ifc_definition_id = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
if not ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PrintObjectPlacement(bpy.types.Operator):
|
||||
bl_idname = "bim.print_object_placement"
|
||||
bl_label = "Print Object Placement"
|
||||
step_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -14,7 +14,7 @@ from bpy.props import (
|
||||
|
||||
class BIMDebugProperties(PropertyGroup):
|
||||
step_id: IntProperty(name="STEP ID")
|
||||
number_of_polygons: IntProperty(name="Number of Polygons")
|
||||
number_of_polygons: IntProperty(name="Number of Polygons", min=0)
|
||||
active_step_id: IntProperty(name="STEP ID")
|
||||
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
|
||||
@@ -20,21 +20,25 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.print_ifc_file")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_ifc_links")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.create_all_shapes")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.profile_import_ifc")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "step_id", text="")
|
||||
row = layout.row()
|
||||
row = layout.split(factor=0.7, align=True)
|
||||
row.operator("bim.create_shape_from_step_id")
|
||||
row.prop(props, "step_id", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "number_of_polygons", text="")
|
||||
row = layout.row()
|
||||
row = layout.split(factor=0.7, align=True)
|
||||
row.operator("bim.select_high_polygon_meshes")
|
||||
row.prop(props, "number_of_polygons", text="")
|
||||
|
||||
layout.label(text="Inspector:")
|
||||
|
||||
@@ -43,7 +47,7 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
|
||||
row.prop(props, "active_step_id", text="")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.inspect_from_step_id").step_id = bpy.context.scene.BIMDebugProperties.active_step_id
|
||||
row.operator("bim.inspect_from_step_id").step_id = context.scene.BIMDebugProperties.active_step_id
|
||||
row.operator("bim.inspect_from_object")
|
||||
|
||||
if props.attributes:
|
||||
@@ -56,6 +60,9 @@ class BIM_PT_debug(Panel):
|
||||
if attribute.name == "GlobalId":
|
||||
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.global_id = attribute.string_value
|
||||
if attribute.name == "ObjectPlacement":
|
||||
op = row.operator("bim.print_object_placement", icon="TRACKER", text="")
|
||||
op.step_id = attribute.int_value
|
||||
if attribute.int_value:
|
||||
row.operator(
|
||||
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
|
||||
|
||||
@@ -9,10 +9,11 @@ from blenderbim.bim.ifc import IfcStore
|
||||
class SelectDiffJsonFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_diff_json_file"
|
||||
bl_label = "Select Diff JSON File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -23,24 +24,25 @@ class SelectDiffJsonFile(bpy.types.Operator):
|
||||
class VisualiseDiff(bpy.types.Operator):
|
||||
bl_idname = "bim.visualise_diff"
|
||||
bl_label = "Visualise Diff"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
#ifc_file = IfcStore.get_file() # In case we get from Store
|
||||
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
|
||||
with open(bpy.context.scene.DiffProperties.diff_json_file, "r") as file:
|
||||
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
|
||||
diff = json.load(file)
|
||||
for obj in bpy.context.visible_objects:
|
||||
for obj in context.visible_objects:
|
||||
obj.color = (1.0, 1.0, 1.0, 0.2)
|
||||
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||
if not global_id:
|
||||
continue
|
||||
if global_id.string_value in diff["deleted"]:
|
||||
if global_id in diff["deleted"]:
|
||||
obj.color = (1.0, 0.0, 0.0, 0.2)
|
||||
elif global_id.string_value in diff["added"]:
|
||||
elif global_id in diff["added"]:
|
||||
obj.color = (0.0, 1.0, 0.0, 0.2)
|
||||
elif global_id.string_value in diff["changed"]:
|
||||
elif global_id in diff["changed"]:
|
||||
obj.color = (0.0, 0.0, 1.0, 0.2)
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "OBJECT"
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -48,10 +50,11 @@ class VisualiseDiff(bpy.types.Operator):
|
||||
class SelectDiffOldFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_diff_old_file"
|
||||
bl_label = "Select Diff Old File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.DiffProperties.diff_old_file = self.filepath
|
||||
context.scene.DiffProperties.diff_old_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -62,10 +65,11 @@ class SelectDiffOldFile(bpy.types.Operator):
|
||||
class SelectDiffNewFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_diff_new_file"
|
||||
bl_label = "Select Diff New File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.DiffProperties.diff_new_file = self.filepath
|
||||
context.scene.DiffProperties.diff_new_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -89,12 +93,12 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
||||
import ifcdiff
|
||||
|
||||
ifc_diff = ifcdiff.IfcDiff(
|
||||
bpy.context.scene.DiffProperties.diff_old_file,
|
||||
bpy.context.scene.DiffProperties.diff_new_file,
|
||||
context.scene.DiffProperties.diff_old_file,
|
||||
context.scene.DiffProperties.diff_new_file,
|
||||
self.filepath,
|
||||
bpy.context.scene.DiffProperties.diff_relationships.split(),
|
||||
context.scene.DiffProperties.diff_relationships.split(),
|
||||
)
|
||||
ifc_diff.diff()
|
||||
ifc_diff.export()
|
||||
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api.document.add_information as add_information
|
||||
import ifcopenshell.api.document.add_reference as add_reference
|
||||
import ifcopenshell.api.document.edit_information as edit_information
|
||||
import ifcopenshell.api.document.edit_reference as edit_reference
|
||||
import ifcopenshell.api.document.remove_document as remove_document
|
||||
import ifcopenshell.api.document.assign_document as assign_document
|
||||
import ifcopenshell.api.document.unassign_document as unassign_document
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.document.data import Data
|
||||
|
||||
@@ -15,6 +9,7 @@ from ifcopenshell.api.document.data import Data
|
||||
class LoadInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.load_information"
|
||||
bl_label = "Load Information"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
@@ -37,6 +32,7 @@ class LoadInformation(bpy.types.Operator):
|
||||
class LoadDocumentReferences(bpy.types.Operator):
|
||||
bl_idname = "bim.load_document_references"
|
||||
bl_label = "Load Document References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
@@ -59,6 +55,7 @@ class LoadDocumentReferences(bpy.types.Operator):
|
||||
class DisableDocumentEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_document_editing_ui"
|
||||
bl_label = "Disable Document Editing UI"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMDocumentProperties.is_editing = ""
|
||||
@@ -69,6 +66,7 @@ class DisableDocumentEditingUI(bpy.types.Operator):
|
||||
class EnableEditingDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_document"
|
||||
bl_label = "Enable Editing Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -105,6 +103,7 @@ class EnableEditingDocument(bpy.types.Operator):
|
||||
class DisableEditingDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_document"
|
||||
bl_label = "Disable Editing Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMDocumentProperties.active_document_id = 0
|
||||
@@ -114,9 +113,13 @@ class DisableEditingDocument(bpy.types.Operator):
|
||||
class AddInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.add_information"
|
||||
bl_label = "Add Information"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
result = add_information.Usecase(IfcStore.get_file()).execute()
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("document.add_information", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_information()
|
||||
bpy.ops.bim.enable_editing_document(document=result.id())
|
||||
@@ -126,9 +129,13 @@ class AddInformation(bpy.types.Operator):
|
||||
class AddDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.add_document_reference"
|
||||
bl_label = "Add Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
result = add_reference.Usecase(IfcStore.get_file()).execute()
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("document.add_reference", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_document_references()
|
||||
bpy.ops.bim.enable_editing_document(document=result.id())
|
||||
@@ -138,8 +145,12 @@ class AddDocumentReference(bpy.types.Operator):
|
||||
class EditInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_information"
|
||||
bl_label = "Edit Information"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
attributes = {}
|
||||
for attribute in props.document_attributes:
|
||||
@@ -150,9 +161,11 @@ class EditInformation(bpy.types.Operator):
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_information.Usecase(
|
||||
self.file, {"information": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
).execute()
|
||||
ifcopenshell.api.run(
|
||||
"document.edit_information",
|
||||
self.file,
|
||||
**{"information": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_information()
|
||||
return {"FINISHED"}
|
||||
@@ -161,8 +174,12 @@ class EditInformation(bpy.types.Operator):
|
||||
class EditDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_document_reference"
|
||||
bl_label = "Edit Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
attributes = {}
|
||||
for attribute in props.document_attributes:
|
||||
@@ -171,9 +188,11 @@ class EditDocumentReference(bpy.types.Operator):
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_reference.Usecase(
|
||||
self.file, {"reference": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
).execute()
|
||||
ifcopenshell.api.run(
|
||||
"document.edit_reference",
|
||||
self.file,
|
||||
**{"reference": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_document_references()
|
||||
return {"FINISHED"}
|
||||
@@ -182,12 +201,16 @@ class EditDocumentReference(bpy.types.Operator):
|
||||
class RemoveDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_document"
|
||||
bl_label = "Remove Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
self.file = IfcStore.get_file()
|
||||
remove_document.Usecase(self.file, {"document": self.file.by_id(self.document)}).execute()
|
||||
ifcopenshell.api.run("document.remove_document", self.file, **{"document": self.file.by_id(self.document)})
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.is_editing == "information":
|
||||
bpy.ops.bim.load_information()
|
||||
@@ -199,6 +222,7 @@ class RemoveDocument(bpy.types.Operator):
|
||||
class EnableAssigningDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_assigning_document"
|
||||
bl_label = "Enable Assigning Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -215,6 +239,7 @@ class EnableAssigningDocument(bpy.types.Operator):
|
||||
class DisableAssigningDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_assigning_document"
|
||||
bl_label = "Disable Assigning Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -227,16 +252,24 @@ class DisableAssigningDocument(bpy.types.Operator):
|
||||
class AssignDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_document"
|
||||
bl_label = "Assign Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
assign_document.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"document": self.file.by_id(self.document)
|
||||
}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"document.assign_document",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"document": self.file.by_id(self.document),
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -244,15 +277,23 @@ class AssignDocument(bpy.types.Operator):
|
||||
class UnassignDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_document"
|
||||
bl_label = "Unassign Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
unassign_document.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"document": self.file.by_id(self.document)
|
||||
}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"document.unassign_document",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"document": self.file.by_id(self.document),
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -68,6 +68,8 @@ class BIM_PT_object_documents(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -1,16 +1,76 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
from . import ui, prop, operator, handler, gizmos
|
||||
|
||||
classes = (
|
||||
operator.AddDrawing,
|
||||
operator.CreateDrawing,
|
||||
operator.AddAnnotation,
|
||||
operator.AddSheet,
|
||||
operator.OpenSheet,
|
||||
operator.AddDrawingToSheet,
|
||||
operator.CreateSheets,
|
||||
operator.OpenView,
|
||||
operator.OpenViewCamera,
|
||||
operator.ActivateView,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.GenerateReferences,
|
||||
operator.ResizeText,
|
||||
operator.AddVariable,
|
||||
operator.RemoveVariable,
|
||||
operator.PropagateTextData,
|
||||
operator.RemoveDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.ActivateDrawingStyle,
|
||||
operator.EditVectorStyle,
|
||||
operator.RemoveSheet,
|
||||
operator.AddSchedule,
|
||||
operator.RemoveSchedule,
|
||||
operator.SelectScheduleFile,
|
||||
operator.BuildSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddDrawingStyleAttribute,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RefreshDrawingList,
|
||||
operator.CleanWireframes,
|
||||
operator.CopyGrid,
|
||||
operator.AddSectionsAnnotations,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
prop.DrawingStyle,
|
||||
prop.Sheet,
|
||||
prop.DocProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.BIMTextProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_UL_drawinglist,
|
||||
gizmos.UglyDotGizmo,
|
||||
gizmos.DotGizmo,
|
||||
gizmos.DimensionLabelGizmo,
|
||||
gizmos.ExtrusionGuidesGizmo,
|
||||
gizmos.ExtrusionWidget,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
|
||||
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
del bpy.types.Scene.DocProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.TextCurve.BIMTextProperties
|
||||
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(handler.depsgraph_update_pre_handler)
|
||||
|
||||
+21
-21
@@ -16,13 +16,13 @@ class Annotator:
|
||||
return float(sizes[str(size)])
|
||||
|
||||
@staticmethod
|
||||
def add_text(related_element=None):
|
||||
curve = bpy.data.curves.new(type="FONT", name="Plan/Annotation/PLAN_VIEW/Text")
|
||||
def add_text(context, related_element=None):
|
||||
curve = bpy.data.curves.new(type="FONT", name="Text")
|
||||
curve.body = "TEXT"
|
||||
obj = bpy.data.objects.new("IfcAnnotation/Text", curve)
|
||||
obj.matrix_world = bpy.context.scene.camera.matrix_world
|
||||
obj = bpy.data.objects.new("Text", curve)
|
||||
obj.matrix_world = context.scene.camera.matrix_world
|
||||
if related_element is None:
|
||||
location, _, _, _ = Annotator.get_placeholder_coords()
|
||||
location, _, _, _ = Annotator.get_placeholder_coords(context)
|
||||
else:
|
||||
obj.data.BIMTextProperties.related_element = related_element
|
||||
location = related_element.location
|
||||
@@ -31,12 +31,12 @@ class Annotator:
|
||||
font = bpy.data.fonts.get("OpenGost TypeB TT")
|
||||
if not font:
|
||||
font = bpy.data.fonts.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
font.name = "OpenGost Type B TT"
|
||||
obj.data.font = font
|
||||
obj.data.BIMTextProperties.font_size = "2.5"
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
@@ -64,9 +64,9 @@ class Annotator:
|
||||
text_obj.data.size = font_size
|
||||
|
||||
@staticmethod
|
||||
def add_line_to_annotation(obj, co1=None, co2=None):
|
||||
def add_line_to_annotation(obj, context, co1=None, co2=None):
|
||||
if co1 is None:
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords()
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords(context)
|
||||
co1 = obj.matrix_world.inverted() @ co1
|
||||
co2 = obj.matrix_world.inverted() @ co2
|
||||
if isinstance(obj.data, bpy.types.Mesh):
|
||||
@@ -83,8 +83,8 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
|
||||
def add_plane_to_annotation(obj, context):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
|
||||
co1 = obj.matrix_world.inverted() @ co1 # bot left
|
||||
co2 = obj.matrix_world.inverted() @ co2 # top left
|
||||
co3 = obj.matrix_world.inverted() @ co3 # bot right
|
||||
@@ -132,29 +132,29 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_annotation_obj(name, data_type):
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
def get_annotation_obj(name, data_type, context):
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
if name in obj.name:
|
||||
return obj
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new("Plan/Annotation/PLAN_VIEW/" + name)
|
||||
data = bpy.data.meshes.new(name)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new("Plan/Annotation/PLAN_VIEW/" + name, type="CURVE")
|
||||
data = bpy.data.curves.new(name, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new("IfcAnnotation/" + name, data)
|
||||
obj = bpy.data.objects.new(name, data)
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_placeholder_coords():
|
||||
camera = bpy.context.scene.camera
|
||||
def get_placeholder_coords(context):
|
||||
camera = context.scene.camera
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
|
||||
if context.scene.render.resolution_x > context.scene.render.resolution_y:
|
||||
y = (
|
||||
camera.data.ortho_scale
|
||||
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
|
||||
* (context.scene.render.resolution_y / context.scene.render.resolution_x)
|
||||
/ 4
|
||||
)
|
||||
else:
|
||||
@@ -164,4 +164,4 @@ class Annotator:
|
||||
return (camera.location + z_offset,
|
||||
camera.location + z_offset + y_offset,
|
||||
camera.location + z_offset + x_offset,
|
||||
camera.location + z_offset + x_offset + y_offset)
|
||||
camera.location + z_offset + x_offset + y_offset)
|
||||
+2
-2
@@ -13,7 +13,7 @@ import gpu
|
||||
import bgl
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from . import helper
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
|
||||
|
||||
class BaseDecorator():
|
||||
@@ -992,7 +992,7 @@ class GridDecorator(BaseDecorator):
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
dir = Vector((1, 0))
|
||||
text = obj.BIMObjectProperties.attributes['AxisTag'].string_value
|
||||
text = obj.name.split("/")[1].split(".")[0]
|
||||
self.draw_label(context, text, p0, dir, vcenter=True, gap=0)
|
||||
self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
|
||||
|
||||
+235
-122
@@ -1,14 +1,12 @@
|
||||
import bpy
|
||||
import blf
|
||||
from bpy import types
|
||||
import math
|
||||
import gpu, bgl
|
||||
from bpy import types
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import geometry
|
||||
import gpu, bgl
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
from .shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
|
||||
|
||||
"""Gizmos under the hood
|
||||
@@ -43,76 +41,196 @@ draw_select -- fake-draw of selection geometry for gpu-side cursor tracking
|
||||
# some geometries for Gizmo.custom_shape shaders
|
||||
|
||||
CUBE = (
|
||||
(+1, +1, +1), (-1, +1, +1), (+1, -1, +1), # top
|
||||
(+1, -1, +1), (-1, +1, +1), (-1, -1, +1),
|
||||
(+1, +1, +1), (+1, -1, +1), (+1, +1, -1), # right
|
||||
(+1, +1, -1), (+1, -1, +1), (+1, -1, -1),
|
||||
(+1, +1, +1), (+1, +1, -1), (-1, +1, +1), # back
|
||||
(-1, +1, +1), (+1, +1, -1), (-1, +1, -1),
|
||||
(-1, -1, -1), (-1, +1, -1), (+1, -1, -1), # bot
|
||||
(+1, -1, -1), (-1, +1, -1), (+1, +1, -1),
|
||||
(-1, -1, -1), (-1, -1, +1), (-1, +1, -1), # left
|
||||
(-1, +1, -1), (-1, -1, +1), (-1, +1, +1),
|
||||
(-1, -1, -1), (+1, -1, -1), (-1, -1, +1), # front
|
||||
(-1, -1, +1), (+1, -1, -1), (+1, -1, +1)
|
||||
(+1, +1, +1),
|
||||
(-1, +1, +1),
|
||||
(+1, -1, +1), # top
|
||||
(+1, -1, +1),
|
||||
(-1, +1, +1),
|
||||
(-1, -1, +1),
|
||||
(+1, +1, +1),
|
||||
(+1, -1, +1),
|
||||
(+1, +1, -1), # right
|
||||
(+1, +1, -1),
|
||||
(+1, -1, +1),
|
||||
(+1, -1, -1),
|
||||
(+1, +1, +1),
|
||||
(+1, +1, -1),
|
||||
(-1, +1, +1), # back
|
||||
(-1, +1, +1),
|
||||
(+1, +1, -1),
|
||||
(-1, +1, -1),
|
||||
(-1, -1, -1),
|
||||
(-1, +1, -1),
|
||||
(+1, -1, -1), # bot
|
||||
(+1, -1, -1),
|
||||
(-1, +1, -1),
|
||||
(+1, +1, -1),
|
||||
(-1, -1, -1),
|
||||
(-1, -1, +1),
|
||||
(-1, +1, -1), # left
|
||||
(-1, +1, -1),
|
||||
(-1, -1, +1),
|
||||
(-1, +1, +1),
|
||||
(-1, -1, -1),
|
||||
(+1, -1, -1),
|
||||
(-1, -1, +1), # front
|
||||
(-1, -1, +1),
|
||||
(+1, -1, -1),
|
||||
(+1, -1, +1),
|
||||
)
|
||||
|
||||
DISC = (
|
||||
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(1.0, 0.0, 0),
|
||||
(0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.5000000000000001, 0.8660254037844386, 0),
|
||||
(6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(6.123233995736766e-17, 1.0, 0),
|
||||
(-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-1.0, 1.2246467991473532e-16, 0),
|
||||
(-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.8660254037844384, -0.5000000000000004, 0),
|
||||
(1.0, 0.0, 0),
|
||||
)
|
||||
|
||||
X3DISC = (
|
||||
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
|
||||
(0.0, 0.0, 0.0), (0, 1.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994),
|
||||
(0.0, 0.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994), (0, 0.5000000000000001, 0.8660254037844386),
|
||||
(0.0, 0.0, 0.0), (0, 0.5000000000000001, 0.8660254037844386), (0, 6.123233995736766e-17, 1.0),
|
||||
(0.0, 0.0, 0.0), (0, 6.123233995736766e-17, 1.0), (0, -0.4999999999999998, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0), (0, -0.4999999999999998, 0.8660254037844387), (0, -0.8660254037844385, 0.5000000000000003),
|
||||
(0.0, 0.0, 0.0), (0, -0.8660254037844385, 0.5000000000000003), (0, -1.0, 1.2246467991473532e-16),
|
||||
(0.0, 0.0, 0.0), (0, -1.0, 1.2246467991473532e-16), (0, -0.8660254037844388, -0.4999999999999997),
|
||||
(0.0, 0.0, 0.0), (0, -0.8660254037844388, -0.4999999999999997), (0, -0.5000000000000004, -0.8660254037844384),
|
||||
(0.0, 0.0, 0.0), (0, -0.5000000000000004, -0.8660254037844384), (0, -1.8369701987210297e-16, -1.0),
|
||||
(0.0, 0.0, 0.0), (0, -1.8369701987210297e-16, -1.0), (0, 0.49999999999999933, -0.866025403784439),
|
||||
(0.0, 0.0, 0.0), (0, 0.49999999999999933, -0.866025403784439), (0, 0.8660254037844384, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0), (0, 0.8660254037844384, -0.5000000000000004), (0, 1.0, 0.0),
|
||||
(0.0, 0.0, 0.0), (0.0, 0, 1.0), (0.49999999999999994, 0, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999994, 0, 0.8660254037844387), (0.8660254037844386, 0, 0.5000000000000001),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844386, 0, 0.5000000000000001), (1.0, 0, 6.123233995736766e-17),
|
||||
(0.0, 0.0, 0.0), (1.0, 0, 6.123233995736766e-17), (0.8660254037844387, 0, -0.4999999999999998),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0, -0.4999999999999998), (0.5000000000000003, 0, -0.8660254037844385),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000003, 0, -0.8660254037844385), (1.2246467991473532e-16, 0, -1.0),
|
||||
(0.0, 0.0, 0.0), (1.2246467991473532e-16, 0, -1.0), (-0.4999999999999997, 0, -0.8660254037844388),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999997, 0, -0.8660254037844388), (-0.8660254037844384, 0, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844384, 0, -0.5000000000000004), (-1.0, 0, -1.8369701987210297e-16),
|
||||
(0.0, 0.0, 0.0), (-1.0, 0, -1.8369701987210297e-16), (-0.866025403784439, 0, 0.49999999999999933),
|
||||
(0.0, 0.0, 0.0), (-0.866025403784439, 0, 0.49999999999999933), (-0.5000000000000004, 0, 0.8660254037844384),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, 0, 0.8660254037844384), (0.0, 0, 1.0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(1.0, 0.0, 0),
|
||||
(0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.5000000000000001, 0.8660254037844386, 0),
|
||||
(6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(6.123233995736766e-17, 1.0, 0),
|
||||
(-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-1.0, 1.2246467991473532e-16, 0),
|
||||
(-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.8660254037844384, -0.5000000000000004, 0),
|
||||
(1.0, 0.0, 0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, 1.0, 0.0),
|
||||
(0, 0.8660254037844387, 0.49999999999999994),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, 0.8660254037844387, 0.49999999999999994),
|
||||
(0, 0.5000000000000001, 0.8660254037844386),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, 0.5000000000000001, 0.8660254037844386),
|
||||
(0, 6.123233995736766e-17, 1.0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, 6.123233995736766e-17, 1.0),
|
||||
(0, -0.4999999999999998, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, -0.4999999999999998, 0.8660254037844387),
|
||||
(0, -0.8660254037844385, 0.5000000000000003),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, -0.8660254037844385, 0.5000000000000003),
|
||||
(0, -1.0, 1.2246467991473532e-16),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, -1.0, 1.2246467991473532e-16),
|
||||
(0, -0.8660254037844388, -0.4999999999999997),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, -0.8660254037844388, -0.4999999999999997),
|
||||
(0, -0.5000000000000004, -0.8660254037844384),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, -0.5000000000000004, -0.8660254037844384),
|
||||
(0, -1.8369701987210297e-16, -1.0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, -1.8369701987210297e-16, -1.0),
|
||||
(0, 0.49999999999999933, -0.866025403784439),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, 0.49999999999999933, -0.866025403784439),
|
||||
(0, 0.8660254037844384, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0, 0.8660254037844384, -0.5000000000000004),
|
||||
(0, 1.0, 0.0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.0, 0, 1.0),
|
||||
(0.49999999999999994, 0, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.49999999999999994, 0, 0.8660254037844387),
|
||||
(0.8660254037844386, 0, 0.5000000000000001),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.8660254037844386, 0, 0.5000000000000001),
|
||||
(1.0, 0, 6.123233995736766e-17),
|
||||
(0.0, 0.0, 0.0),
|
||||
(1.0, 0, 6.123233995736766e-17),
|
||||
(0.8660254037844387, 0, -0.4999999999999998),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.8660254037844387, 0, -0.4999999999999998),
|
||||
(0.5000000000000003, 0, -0.8660254037844385),
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.5000000000000003, 0, -0.8660254037844385),
|
||||
(1.2246467991473532e-16, 0, -1.0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(1.2246467991473532e-16, 0, -1.0),
|
||||
(-0.4999999999999997, 0, -0.8660254037844388),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.4999999999999997, 0, -0.8660254037844388),
|
||||
(-0.8660254037844384, 0, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.8660254037844384, 0, -0.5000000000000004),
|
||||
(-1.0, 0, -1.8369701987210297e-16),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-1.0, 0, -1.8369701987210297e-16),
|
||||
(-0.866025403784439, 0, 0.49999999999999933),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.866025403784439, 0, 0.49999999999999933),
|
||||
(-0.5000000000000004, 0, 0.8660254037844384),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-0.5000000000000004, 0, 0.8660254037844384),
|
||||
(0.0, 0, 1.0),
|
||||
)
|
||||
|
||||
|
||||
class CustomGizmo():
|
||||
class CustomGizmo:
|
||||
# FIXME: highliting/selection doesnt work
|
||||
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
|
||||
# similar to draw_custom_shape
|
||||
@@ -127,7 +245,7 @@ class CustomGizmo():
|
||||
color = (*self.color_highlight, self.alpha_highlight)
|
||||
else:
|
||||
color = (*self.color, self.alpha)
|
||||
shader.uniform_float('color', color)
|
||||
shader.uniform_float("color", color)
|
||||
shape.glenable()
|
||||
|
||||
shape.uniform_region(ctx)
|
||||
@@ -139,31 +257,32 @@ class CustomGizmo():
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
|
||||
|
||||
class OffsetHandle():
|
||||
class OffsetHandle:
|
||||
"""Handling mouse to offset gizmo from base along Z axis"""
|
||||
|
||||
# FIXME: works a bit weird for rotated objects
|
||||
|
||||
def invoke(self, ctx, event):
|
||||
self.init_value = self.target_get_value('offset') / self.scale_value
|
||||
self.init_value = self.target_get_value("offset") / self.scale_value
|
||||
coordz = self.project_mouse(ctx, event)
|
||||
if coordz is None:
|
||||
return {'CANCELLED'}
|
||||
return {"CANCELLED"}
|
||||
self.init_coordz = coordz
|
||||
return {'RUNNING_MODAL'}
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def modal(self, ctx, event, tweak):
|
||||
coordz = self.project_mouse(ctx, event)
|
||||
if coordz is None:
|
||||
return {'CANCELLED'}
|
||||
return {"CANCELLED"}
|
||||
delta = coordz - self.init_coordz
|
||||
if 'PRECISE' in tweak:
|
||||
if "PRECISE" in tweak:
|
||||
delta /= 10.0
|
||||
value = max(0, self.init_value + delta)
|
||||
value *= self.scale_value
|
||||
# ctx.area.header_text_set(f"coords: {self.init_coordz} - {coordz}, delta: {delta}, value: {value}")
|
||||
ctx.area.header_text_set(f"Depth: {value}")
|
||||
self.target_set_value('offset', value)
|
||||
return {'RUNNING_MODAL'}
|
||||
self.target_set_value("offset", value)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def project_mouse(self, ctx, event):
|
||||
"""Projecting mouse coords to local axis Z"""
|
||||
@@ -189,30 +308,29 @@ class OffsetHandle():
|
||||
|
||||
def exit(self, ctx, cancel):
|
||||
if cancel:
|
||||
self.target_set_value('offset', self.init_value)
|
||||
self.target_set_value("offset", self.init_value)
|
||||
else:
|
||||
self.group.update(ctx)
|
||||
|
||||
|
||||
class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
"""three orthogonal circles"""
|
||||
|
||||
bl_idname = "BIM_GT_uglydot_3d"
|
||||
bl_target_properties = (
|
||||
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape',
|
||||
'init_value',
|
||||
'init_coordz',
|
||||
"scale_value",
|
||||
"custom_shape",
|
||||
"init_value",
|
||||
"init_coordz",
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
self.custom_shape = self.new_custom_shape(type='TRIS', verts=X3DISC)
|
||||
self.custom_shape = self.new_custom_shape(type="TRIS", verts=X3DISC)
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value('offset') / self.scale_value
|
||||
offset = self.target_get_value("offset") / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shift
|
||||
|
||||
def draw(self, ctx):
|
||||
@@ -226,15 +344,14 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
|
||||
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
"""Single dot viewport-aligned"""
|
||||
|
||||
# FIXME: make it selectable
|
||||
bl_idname = "BIM_GT_dot_2d"
|
||||
bl_target_properties = (
|
||||
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape',
|
||||
"scale_value",
|
||||
"custom_shape",
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
@@ -243,7 +360,7 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value('offset') / self.scale_value
|
||||
offset = self.target_get_value("offset") / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shifted
|
||||
|
||||
def draw(self, ctx):
|
||||
@@ -265,15 +382,11 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
Noninteractive gizmo to indicate extrusion depth and planes.
|
||||
Draws main segment and orthogonal cross at endpoints.
|
||||
"""
|
||||
bl_idname = "BIM_GT_extrusion_guides"
|
||||
bl_target_properties = (
|
||||
{'id': 'depth', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape'
|
||||
)
|
||||
bl_idname = "BIM_GT_extrusion_guides"
|
||||
bl_target_properties = ({"id": "depth", "type": "FLOAT", "array_length": 1},)
|
||||
|
||||
__slots__ = ("scale_value", "custom_shape")
|
||||
|
||||
def setup(self):
|
||||
shader = ExtrusionGuidesShader()
|
||||
@@ -281,7 +394,7 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value('depth') / self.scale_value
|
||||
depth = self.target_get_value("depth") / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
|
||||
def draw(self, ctx):
|
||||
@@ -291,25 +404,22 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
|
||||
class DimensionLabelGizmo(types.Gizmo):
|
||||
"""Text label for a dimension"""
|
||||
|
||||
# does not work properly, fonts are totally screwed up
|
||||
|
||||
bl_idname = "BIM_GT_dimension_label"
|
||||
bl_target_properties = (
|
||||
{'id': 'value', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
bl_target_properties = ({"id": "value", "type": "FLOAT", "array_length": 1},)
|
||||
|
||||
__slots__ = (
|
||||
'text_label'
|
||||
)
|
||||
__slots__ = "text_label"
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def refresh(self, ctx):
|
||||
value = self.target_get_value('value')
|
||||
self.matrix_offset.col[3][2] = value * .5
|
||||
value = self.target_get_value("value")
|
||||
self.matrix_offset.col[3][2] = value * 0.5
|
||||
unit_system = ctx.scene.unit_settings.system
|
||||
self.text_label = bpy.utils.units.to_string(unit_system, 'LENGTH', value, 3, split_unit=False)
|
||||
self.text_label = bpy.utils.units.to_string(unit_system, "LENGTH", value, 3, split_unit=False)
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh(ctx)
|
||||
@@ -337,41 +447,44 @@ class DimensionLabelGizmo(types.Gizmo):
|
||||
class ExtrusionWidget(types.GizmoGroup):
|
||||
bl_idname = "bim.extrusion_widget"
|
||||
bl_label = "Extrusion Gizmos"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_options = {'3D', 'PERSISTENT', 'SHOW_MODAL_ALL'}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_options = {"3D", "PERSISTENT", "SHOW_MODAL_ALL"}
|
||||
|
||||
# FIXME: use proper scale from ifc value to blender units
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ctx):
|
||||
obj = ctx.object
|
||||
return (obj and obj.type == 'MESH'
|
||||
and obj.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth') is not None)
|
||||
return (
|
||||
obj
|
||||
and obj.type == "MESH"
|
||||
and obj.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None
|
||||
)
|
||||
|
||||
def setup(self, ctx):
|
||||
target = ctx.object
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
|
||||
|
||||
basis = target.matrix_world.normalized()
|
||||
theme = ctx.preferences.themes[0].user_interface
|
||||
|
||||
gz = self.handle = self.gizmos.new('BIM_GT_uglydot_3d')
|
||||
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
|
||||
gz.matrix_basis = basis
|
||||
gz.scale_basis = 0.1
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_primary)
|
||||
gz.alpha = 0.5
|
||||
gz.alpha_highlight = 1.0
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop('offset', prop, 'value')
|
||||
gz.target_set_prop("offset", prop, "value")
|
||||
gz.scale_value = 1000
|
||||
|
||||
gz = self.guides = self.gizmos.new('BIM_GT_extrusion_guides')
|
||||
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
|
||||
gz.matrix_basis = basis
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
|
||||
gz.alpha = gz.alpha_highlight = 0.5
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop('depth', prop, 'value')
|
||||
gz.target_set_prop("depth", prop, "value")
|
||||
gz.scale_value = 1000
|
||||
|
||||
# gz = self.label = self.gizmos.new('GIZMO_GT_dimension_label')
|
||||
@@ -395,6 +508,6 @@ class ExtrusionWidget(types.GizmoGroup):
|
||||
# need to retrieve and rebind them again
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
target = ctx.object
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
|
||||
self.handle.target_set_prop('offset', prop, 'value')
|
||||
self.guides.target_set_prop('depth', prop, 'value')
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
|
||||
self.handle.target_set_prop("offset", prop, "value")
|
||||
self.guides.target_set_prop("depth", prop, "value")
|
||||
@@ -0,0 +1,32 @@
|
||||
import bpy
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def toggleDecorationsOnLoad(*args):
|
||||
toggle = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
if toggle:
|
||||
decoration.DecorationsHandler.install(bpy.context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
|
||||
|
||||
@persistent
|
||||
def depsgraph_update_pre_handler(scene):
|
||||
set_active_camera_resolution(scene)
|
||||
|
||||
|
||||
def set_active_camera_resolution(scene):
|
||||
if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings:
|
||||
return
|
||||
if (
|
||||
scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x
|
||||
or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y
|
||||
):
|
||||
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
|
||||
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
|
||||
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
|
||||
if scene.camera != current_drawing.camera:
|
||||
scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split("/")[1])
|
||||
bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index)
|
||||
@@ -0,0 +1,360 @@
|
||||
import bpy
|
||||
import math
|
||||
import mathutils.geometry
|
||||
from mathutils import Vector
|
||||
|
||||
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
|
||||
|
||||
|
||||
class BoundingEdge:
|
||||
def __init__(self, v0, v1):
|
||||
self.vertex = (v0, v1)
|
||||
self.vector = v1 - v0
|
||||
|
||||
|
||||
class BoundingFace:
|
||||
def __init__(self, v0, v1, v2):
|
||||
self.vertex = (v0, v1, v2)
|
||||
self.normal = mathutils.geometry.normal(v0, v1, v2)
|
||||
|
||||
|
||||
class BoundingBox:
|
||||
def __init__(self, ob, vertex=None):
|
||||
self.vertex = vertex or [ob.matrix_world @ Vector(v) for v in ob.bound_box]
|
||||
if self.vertex != None:
|
||||
self.edge = [
|
||||
BoundingEdge(self.vertex[0], self.vertex[1]),
|
||||
BoundingEdge(self.vertex[1], self.vertex[2]),
|
||||
BoundingEdge(self.vertex[2], self.vertex[3]),
|
||||
BoundingEdge(self.vertex[3], self.vertex[0]),
|
||||
BoundingEdge(self.vertex[4], self.vertex[5]),
|
||||
BoundingEdge(self.vertex[5], self.vertex[6]),
|
||||
BoundingEdge(self.vertex[6], self.vertex[7]),
|
||||
BoundingEdge(self.vertex[7], self.vertex[4]),
|
||||
BoundingEdge(self.vertex[0], self.vertex[4]),
|
||||
BoundingEdge(self.vertex[1], self.vertex[5]),
|
||||
BoundingEdge(self.vertex[2], self.vertex[6]),
|
||||
BoundingEdge(self.vertex[3], self.vertex[7]),
|
||||
]
|
||||
self.face = [
|
||||
BoundingFace(self.vertex[0], self.vertex[1], self.vertex[3]),
|
||||
BoundingFace(self.vertex[0], self.vertex[4], self.vertex[1]),
|
||||
BoundingFace(self.vertex[0], self.vertex[3], self.vertex[4]),
|
||||
BoundingFace(self.vertex[6], self.vertex[5], self.vertex[7]),
|
||||
BoundingFace(self.vertex[6], self.vertex[7], self.vertex[2]),
|
||||
BoundingFace(self.vertex[6], self.vertex[2], self.vertex[5]),
|
||||
]
|
||||
|
||||
def whichSide(self, vtxs, normal, faceVtx):
|
||||
retVal = 0
|
||||
positive = 0
|
||||
negative = 0
|
||||
for v in vtxs:
|
||||
t = normal.dot(v - faceVtx)
|
||||
if t > 0:
|
||||
positive = positive + 1
|
||||
elif t < 0:
|
||||
negative = negative + 1
|
||||
|
||||
if positive != 0 and negative != 0:
|
||||
return 0
|
||||
|
||||
if positive != 0:
|
||||
retVal = 1
|
||||
else:
|
||||
retVal = -1
|
||||
return retVal
|
||||
|
||||
# Taken from: http://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf
|
||||
def intersect(self, bb):
|
||||
retVal = False
|
||||
if self.vertex != None and bb.vertex != None:
|
||||
# check all the faces of this object for a seperation axis
|
||||
for i, f in enumerate(self.face):
|
||||
d = f.normal
|
||||
if self.whichSide(bb.vertex, d, f.vertex[0]) > 0:
|
||||
return False # all the vertexes are on the +ve side of the face
|
||||
|
||||
# now do it again for the other objects faces
|
||||
for i, f in enumerate(bb.face):
|
||||
d = f.normal
|
||||
if self.whichSide(self.vertex, d, f.vertex[0]) > 0:
|
||||
return False # all the vertexes are on the +ve side of the face
|
||||
|
||||
# do edge checks
|
||||
for e1 in self.edge:
|
||||
for e2 in bb.edge:
|
||||
d = e1.vector.cross(e2.vector)
|
||||
side0 = self.whichSide(self.vertex, d, e1.vertex[0])
|
||||
if side0 == 0:
|
||||
continue
|
||||
side1 = self.whichSide(bb.vertex, d, e1.vertex[0])
|
||||
if side1 == 0:
|
||||
continue
|
||||
|
||||
if (side0 * side1) < 0:
|
||||
return False
|
||||
|
||||
retVal = True
|
||||
return retVal
|
||||
|
||||
|
||||
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
|
||||
# MeasureIt-ARCH is GPL-v3
|
||||
# In the future I will need to rewrite this to allow the user to have custom
|
||||
# settings for each annotation object, not read from Blender.
|
||||
def format_distance(value, isArea=False, hide_units=True):
|
||||
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
|
||||
|
||||
# Get Scene Unit Settings
|
||||
scaleFactor = bpy.context.scene.unit_settings.scale_length
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
unit_length = bpy.context.scene.unit_settings.length_unit
|
||||
|
||||
toInches = 39.3700787401574887
|
||||
inPerFoot = 11.999
|
||||
|
||||
if isArea:
|
||||
toInches = 1550
|
||||
inPerFoot = 143.999
|
||||
|
||||
value *= scaleFactor
|
||||
|
||||
# Imperial Formating
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = bpy.context.scene.BIMProperties.imperial_precision
|
||||
if precision == "NONE":
|
||||
precision = 256
|
||||
elif precision == "1":
|
||||
precision = 1
|
||||
elif "/" in precision:
|
||||
precision = int(precision.split("/")[1])
|
||||
|
||||
base = int(precision)
|
||||
decInches = value * toInches
|
||||
|
||||
# Seperate ft and inches
|
||||
# Unless Inches are the specified Length Unit
|
||||
if unit_length != "INCHES":
|
||||
feet = math.floor(decInches / inPerFoot)
|
||||
decInches -= feet * inPerFoot
|
||||
else:
|
||||
feet = 0
|
||||
|
||||
# Seperate Fractional Inches
|
||||
inches = math.floor(decInches)
|
||||
if inches != 0:
|
||||
frac = round(base * (decInches - inches))
|
||||
else:
|
||||
frac = round(base * (decInches))
|
||||
|
||||
# Set proper numerator and denominator
|
||||
if frac != base:
|
||||
numcycles = int(math.log2(base))
|
||||
for i in range(numcycles):
|
||||
if frac % 2 == 0:
|
||||
frac = int(frac / 2)
|
||||
base = int(base / 2)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
frac = 0
|
||||
inches += 1
|
||||
|
||||
# Check values and compose string
|
||||
if inches == 12:
|
||||
feet += 1
|
||||
inches = 0
|
||||
|
||||
if not isArea:
|
||||
tx_dist = ""
|
||||
if feet:
|
||||
tx_dist += str(feet) + "'"
|
||||
if feet and inches:
|
||||
tx_dist += " - "
|
||||
if inches:
|
||||
tx_dist += str(inches)
|
||||
if inches and frac:
|
||||
tx_dist += " "
|
||||
if frac:
|
||||
tx_dist += str(frac) + "/" + str(base)
|
||||
if inches or frac:
|
||||
tx_dist += '"'
|
||||
else:
|
||||
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
|
||||
|
||||
# METRIC FORMATING
|
||||
elif unit_system == "METRIC":
|
||||
precision = bpy.context.scene.BIMProperties.metric_precision
|
||||
if precision != 0:
|
||||
value = precision * round(float(value) / precision)
|
||||
|
||||
# Meters
|
||||
if unit_length == "METERS":
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
# Centimeters
|
||||
elif unit_length == "CENTIMETERS":
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
# Millimeters
|
||||
elif unit_length == "MILLIMETERS":
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
tx_dist = fmt % d_mm
|
||||
|
||||
# Otherwise Use Adaptive Units
|
||||
else:
|
||||
if round(value, 2) >= 1.0:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
else:
|
||||
if round(value, 2) >= 0.01:
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
else:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
tx_dist = fmt % d_mm
|
||||
if isArea:
|
||||
tx_dist += s_code
|
||||
else:
|
||||
tx_dist = fmt % value
|
||||
|
||||
return tx_dist
|
||||
|
||||
|
||||
def get_active_drawing(scene):
|
||||
"""Get active drawing collection and camera"""
|
||||
props = scene.DocProperties
|
||||
if props.active_drawing_index is None or len(props.drawings) == 0:
|
||||
return None, None
|
||||
try:
|
||||
drawing = props.drawings[props.active_drawing_index]
|
||||
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
|
||||
except (KeyError, IndexError):
|
||||
raise RuntimeError("missing drawing collection")
|
||||
|
||||
|
||||
def get_project_collection(scene):
|
||||
"""Get main project collection"""
|
||||
|
||||
colls = [c for c in scene.collection.children if c.name.startswith("IfcProject")]
|
||||
if len(colls) != 1:
|
||||
raise RuntimeError("project collection missing or not unique")
|
||||
return colls[0]
|
||||
|
||||
|
||||
def parse_diagram_scale(camera):
|
||||
"""Returns numeric value of scale"""
|
||||
if camera.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
_, fraction = camera.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
_, fraction = camera.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
|
||||
def ortho_view_frame(camera, margin=0.015):
|
||||
"""Calculates 2d bounding box of camera view area.
|
||||
|
||||
Similar to `bpy.types.Camera.view_frame`
|
||||
|
||||
:arg camera: camera of drawing
|
||||
:type camera: bpy.types.Camera + BIMCameraProperties
|
||||
:arg margin: margins, in scene units
|
||||
:type margin: float
|
||||
:return: (xmin, xmax, ymin, ymax, zmin, zmax) in local camera coordinates
|
||||
"""
|
||||
aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x
|
||||
size = camera.ortho_scale
|
||||
hwidth = size * 0.5
|
||||
hheight = size * 0.5 * aspect
|
||||
scale = parse_diagram_scale(camera)
|
||||
xmarg = margin * scale
|
||||
ymarg = margin * scale * aspect
|
||||
return (-hwidth + xmarg, hwidth - xmarg, -hheight + ymarg, hheight - ymarg, -camera.clip_start, -camera.clip_end)
|
||||
|
||||
|
||||
def almost_zero(v):
|
||||
return abs(v) < 1e-5
|
||||
|
||||
|
||||
def clip_segment(bounds, segm):
|
||||
"""Clipping line segment to bounds
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
# Liang–Barsky algorithm
|
||||
|
||||
xmin, xmax, ymin, ymax, _, _ = bounds
|
||||
p1, p2 = segm
|
||||
|
||||
def clip_side(p, q):
|
||||
if almost_zero(p): # ~= 0, parallel to the side
|
||||
if q < 0:
|
||||
return None # outside
|
||||
else:
|
||||
return 0, 1 # inside
|
||||
|
||||
t = q / p # the intersection point
|
||||
|
||||
if p < 0: # entering
|
||||
return t, 1
|
||||
else: # leaving
|
||||
return 0, t
|
||||
|
||||
dlt = p2 - p1
|
||||
|
||||
tt = (
|
||||
clip_side(-dlt.x, p1.x - xmin), # left
|
||||
clip_side(+dlt.x, xmax - p1.x), # right
|
||||
clip_side(-dlt.y, p1.y - ymin), # bottom
|
||||
clip_side(+dlt.y, ymax - p1.y), # top
|
||||
)
|
||||
|
||||
if None in tt:
|
||||
return None
|
||||
|
||||
t1 = max(0, max(t[0] for t in tt))
|
||||
t2 = min(1, min(t[1] for t in tt))
|
||||
|
||||
if t1 >= t2:
|
||||
return None
|
||||
|
||||
p1c = p1 + dlt * t1
|
||||
p2c = p1 + dlt * t2
|
||||
|
||||
return p1c, p2c
|
||||
|
||||
|
||||
def elevate_segment(bounds, segm):
|
||||
"""Elevate line xy-perpendicular segment vertically
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
_, _, ymin, ymax, zmin, _ = bounds
|
||||
p1, p2 = segm
|
||||
dlt = p2 - p1
|
||||
if not (almost_zero(dlt.x) and almost_zero(dlt.y)):
|
||||
return None
|
||||
x = p1.x
|
||||
return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
diagram_scales_enum = []
|
||||
titleblocks_enum = []
|
||||
sheets_enum = []
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global diagram_scales_enum
|
||||
global titleblocks_enum
|
||||
global sheets_enum
|
||||
global vector_styles_enum
|
||||
diagram_scales_enum = []
|
||||
titleblocks_enum = []
|
||||
sheets_enum = []
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def getDiagramScales(self, context):
|
||||
global diagram_scales_enum
|
||||
if (
|
||||
len(diagram_scales_enum) < 1
|
||||
or (bpy.context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13)
|
||||
or (bpy.context.scene.unit_settings.system == "METRIC" and len(diagram_scales_enum) == 31)
|
||||
):
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
diagram_scales_enum = [
|
||||
("CUSTOM", "Custom", ""),
|
||||
("1'=1'-0\"|1/1", "1'=1'-0\"", ""),
|
||||
('6"=1\'-0"|1/6', '6"=1\'-0"', ""),
|
||||
('1-1/2"=1\'-0"|1/8', '1-1/2"=1\'-0"', ""),
|
||||
('1"=1\'-0"|1/12', '1"=1\'-0"', ""),
|
||||
('3/4"=1\'-0"|1/16', '3/4"=1\'-0"', ""),
|
||||
('1/2"=1\'-0"|1/24', '1/2"=1\'-0"', ""),
|
||||
('3/8"=1\'-0"|1/32', '3/8"=1\'-0"', ""),
|
||||
('1/4"=1\'-0"|1/48', '1/4"=1\'-0"', ""),
|
||||
('3/16"=1\'-0"|1/64', '3/16"=1\'-0"', ""),
|
||||
('1/8"=1\'-0"|1/96', '1/8"=1\'-0"', ""),
|
||||
('3/32"=1\'-0"|1/128', '3/32"=1\'-0"', ""),
|
||||
('1/16"=1\'-0"|1/192', '1/16"=1\'-0"', ""),
|
||||
('1/32"=1\'-0"|1/384', '1/32"=1\'-0"', ""),
|
||||
('1/64"=1\'-0"|1/768', '1/64"=1\'-0"', ""),
|
||||
('1/128"=1\'-0"|1/1536', '1/128"=1\'-0"', ""),
|
||||
("1\"=10'|1/120", "1\"=10'", ""),
|
||||
("1\"=20'|1/240", "1\"=20'", ""),
|
||||
("1\"=30'|1/360", "1\"=30'", ""),
|
||||
("1\"=40'|1/480", "1\"=40'", ""),
|
||||
("1\"=50'|1/600", "1\"=50'", ""),
|
||||
("1\"=60'|1/720", "1\"=60'", ""),
|
||||
("1\"=70'|1/840", "1\"=70'", ""),
|
||||
("1\"=80'|1/960", "1\"=80'", ""),
|
||||
("1\"=90'|1/1080", "1\"=90'", ""),
|
||||
("1\"=100'|1/1200", "1\"=100'", ""),
|
||||
("1\"=150'|1/1800", "1\"=150'", ""),
|
||||
("1\"=200'|1/2400", "1\"=200'", ""),
|
||||
("1\"=300'|1/3600", "1\"=300'", ""),
|
||||
("1\"=400'|1/4800", "1\"=400'", ""),
|
||||
("1\"=500'|1/6000", "1\"=500'", ""),
|
||||
]
|
||||
else:
|
||||
diagram_scales_enum = [
|
||||
("CUSTOM", "Custom", ""),
|
||||
("1:5000|1/5000", "1:5000", ""),
|
||||
("1:2000|1/2000", "1:2000", ""),
|
||||
("1:1000|1/1000", "1:1000", ""),
|
||||
("1:500|1/500", "1:500", ""),
|
||||
("1:200|1/200", "1:200", ""),
|
||||
("1:100|1/100", "1:100", ""),
|
||||
("1:50|1/50", "1:50", ""),
|
||||
("1:20|1/20", "1:20", ""),
|
||||
("1:10|1/10", "1:10", ""),
|
||||
("1:5|1/5", "1:5", ""),
|
||||
("1:2|1/2", "1:2", ""),
|
||||
("1:1|1/1", "1:1", ""),
|
||||
]
|
||||
return diagram_scales_enum
|
||||
|
||||
|
||||
def updateDrawingName(self, context):
|
||||
if not self.camera:
|
||||
return
|
||||
if self.camera.name == self.name:
|
||||
return
|
||||
self.camera.name = "IfcAnnotation/{}".format(self.name)
|
||||
unique_name = "/".join(self.camera.name.split("/")[1:])
|
||||
self.camera.users_collection[0].name = "IfcGroup/{}".format(unique_name)
|
||||
if self.name != unique_name:
|
||||
self.name = unique_name
|
||||
|
||||
|
||||
def refreshActiveDrawingIndex(self, context):
|
||||
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
|
||||
|
||||
|
||||
def getTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
if len(titleblocks_enum) < 1:
|
||||
titleblocks_enum.clear()
|
||||
for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob(
|
||||
"*.svg"
|
||||
):
|
||||
f = str(filename.stem)
|
||||
titleblocks_enum.append((f, f, ""))
|
||||
return titleblocks_enum
|
||||
|
||||
|
||||
def refreshTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
titleblocks_enum.clear()
|
||||
getTitleblocks(self, context)
|
||||
|
||||
|
||||
def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
decoration.DecorationsHandler.install(context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
|
||||
|
||||
def getVectorStyles(self, context):
|
||||
global vector_styles_enum
|
||||
if len(vector_styles_enum) < 1:
|
||||
sheets_enum.clear()
|
||||
for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "styles")).glob("*.css"):
|
||||
f = str(filename.stem)
|
||||
vector_styles_enum.append((f, f, ""))
|
||||
return vector_styles_enum
|
||||
|
||||
|
||||
def refreshFontSize(self, context):
|
||||
annotation.Annotator.resize_text(context.active_object)
|
||||
|
||||
|
||||
class Variable(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
prop_key: StringProperty(name="Property Key")
|
||||
|
||||
|
||||
class Drawing(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateDrawingName)
|
||||
camera: PointerProperty(name="Camera", type=bpy.types.Object)
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
file: StringProperty(name="File")
|
||||
|
||||
|
||||
class Sheet(PropertyGroup):
|
||||
def set_name(self, new):
|
||||
old = self.get("name")
|
||||
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets")
|
||||
if old and os.path.isfile(os.path.join(path, old + ".svg")):
|
||||
os.rename(os.path.join(path, old + ".svg"), os.path.join(path, new + ".svg"))
|
||||
self["name"] = new
|
||||
|
||||
def get_name(self):
|
||||
return self.get("name")
|
||||
|
||||
name: StringProperty(name="Name", get=get_name, set=set_name)
|
||||
drawings: CollectionProperty(name="Drawings", type=Drawing)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
|
||||
|
||||
class DrawingStyle(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
raster_style: StringProperty(name="Raster Style")
|
||||
render_type: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", ""),
|
||||
("DEFAULT", "Default", ""),
|
||||
("VIEWPORT", "Viewport", ""),
|
||||
],
|
||||
name="Render Type",
|
||||
default="VIEWPORT",
|
||||
)
|
||||
vector_style: EnumProperty(items=getVectorStyles, name="Vector Style")
|
||||
include_query: StringProperty(name="Include Query")
|
||||
exclude_query: StringProperty(name="Exclude Query")
|
||||
attributes: CollectionProperty(name="Attributes", type=StrProperty)
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
has_underlay: BoolProperty(name="Underlay", default=False)
|
||||
has_linework: BoolProperty(name="Linework", default=True)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True)
|
||||
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
|
||||
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
|
||||
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
|
||||
should_extract: BoolProperty(name="Should Extract", default=True)
|
||||
drawings: CollectionProperty(name="Drawings", type=Drawing)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
|
||||
current_drawing_index: IntProperty(name="Current Drawing Index")
|
||||
schedules: CollectionProperty(name="Schedules", type=Schedule)
|
||||
active_schedule_index: IntProperty(name="Active Schedule Index")
|
||||
titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks)
|
||||
sheets: CollectionProperty(name="Sheets", type=Sheet)
|
||||
active_sheet_index: IntProperty(name="Active Sheet Index")
|
||||
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
|
||||
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
|
||||
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
|
||||
decorations_colour: FloatVectorProperty(
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
view_name: StringProperty(name="View Name")
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "PLAN_VIEW", ""),
|
||||
("ELEVATION_VIEW", "ELEVATION_VIEW", ""),
|
||||
("SECTION_VIEW", "SECTION_VIEW", ""),
|
||||
("REFLECTED_PLAN_VIEW", "REFLECTED_PLAN_VIEW", ""),
|
||||
("MODEL_VIEW", "MODEL_VIEW", ""),
|
||||
],
|
||||
name="Target View",
|
||||
default="PLAN_VIEW",
|
||||
)
|
||||
diagram_scale: EnumProperty(items=getDiagramScales, name="Drawing Scale")
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale")
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
is_nts: BoolProperty(name="Is NTS")
|
||||
cut_objects: EnumProperty(
|
||||
items=[
|
||||
(
|
||||
".IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace",
|
||||
"Overall Plan / Section",
|
||||
"",
|
||||
),
|
||||
(".IfcElement", "Detail Drawing", ""),
|
||||
("CUSTOM", "Custom", ""),
|
||||
],
|
||||
name="Cut Objects",
|
||||
)
|
||||
cut_objects_custom: StringProperty(name="Custom Cut")
|
||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
font_size: EnumProperty(
|
||||
items=[
|
||||
("1.8", "1.8 - Small", ""),
|
||||
("2.5", "2.5 - Regular", ""),
|
||||
("3.5", "3.5 - Large", ""),
|
||||
("5.0", "5.0 - Header", ""),
|
||||
("7.0", "7.0 - Title", ""),
|
||||
],
|
||||
update=refreshFontSize,
|
||||
name="Font Size",
|
||||
)
|
||||
symbol: EnumProperty(
|
||||
items=[
|
||||
("None", "None", ""),
|
||||
("rectangle-tag", "Rectangle Tag", ""),
|
||||
("door-tag", "Door Tag", ""),
|
||||
],
|
||||
update=refreshFontSize,
|
||||
name="Symbol",
|
||||
)
|
||||
related_element: PointerProperty(name="Related Element", type=bpy.types.Object)
|
||||
variables: CollectionProperty(name="Variables", type=Variable)
|
||||
+29
-18
@@ -1,10 +1,10 @@
|
||||
from mathutils import Matrix
|
||||
import bgl
|
||||
from mathutils import Matrix
|
||||
from gpu.types import GPUShader
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
class BaseShader():
|
||||
class BaseShader:
|
||||
"""Wrapepr for GPUShader
|
||||
To use for viewport decorations with geometry generated on GPU side.
|
||||
|
||||
@@ -85,14 +85,15 @@ class BaseShader():
|
||||
|
||||
def __init__(self):
|
||||
# NB: libcode arg doesn't work
|
||||
self.prog = GPUShader(vertexcode=self.VERT_GLSL,
|
||||
fragcode=self.FRAG_GLSL,
|
||||
geocode=self.LIB_GLSL + self.GEOM_GLSL,
|
||||
defines=self.DEF_GLSL)
|
||||
self.prog = GPUShader(
|
||||
vertexcode=self.VERT_GLSL,
|
||||
fragcode=self.FRAG_GLSL,
|
||||
geocode=self.LIB_GLSL + self.GEOM_GLSL,
|
||||
defines=self.DEF_GLSL,
|
||||
)
|
||||
|
||||
def batch(self, indices=None, **data):
|
||||
"""Returns automatic GPUBatch filled with provided parameters
|
||||
"""
|
||||
"""Returns automatic GPUBatch filled with provided parameters"""
|
||||
batch = batch_for_shader(self.prog, self.TYPE, data, indices=indices)
|
||||
batch.program_set(self.prog)
|
||||
return batch
|
||||
@@ -113,23 +114,26 @@ class BaseShader():
|
||||
region = ctx.region
|
||||
region3d = ctx.region_data
|
||||
try:
|
||||
self.prog.uniform_float('viewMatrix', region3d.perspective_matrix)
|
||||
self.prog.uniform_float("viewMatrix", region3d.perspective_matrix)
|
||||
except ValueError: # unused uniform
|
||||
pass
|
||||
try:
|
||||
self.prog.uniform_float('winSize', (region.width / 2, region.height / 2))
|
||||
self.prog.uniform_float("winSize", (region.width / 2, region.height / 2))
|
||||
except ValueError: # unused uniform
|
||||
pass
|
||||
|
||||
|
||||
class BaseLinesShader(BaseShader):
|
||||
"""Draws line segments with gaps around vertices at endpoints
|
||||
"""
|
||||
TYPE = 'LINES'
|
||||
"""Draws line segments with gaps around vertices at endpoints"""
|
||||
|
||||
DEF_GLSL = BaseShader.DEF_GLSL + """
|
||||
TYPE = "LINES"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseShader.DEF_GLSL
|
||||
+ """
|
||||
#define GAP_SIZE {gap_size}
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
layout(lines) in;
|
||||
@@ -171,6 +175,7 @@ class GizmoShader(BaseShader):
|
||||
|
||||
Scaling to match viewport is partially controlled by user preferences and gizmo code.
|
||||
"""
|
||||
|
||||
# TODO: add some magic to respect gizmo settings/params
|
||||
|
||||
VERT_GLSL = """
|
||||
@@ -187,12 +192,15 @@ class GizmoShader(BaseShader):
|
||||
class DotsGizmoShader(GizmoShader):
|
||||
"""Draws circles of radius 1 around points"""
|
||||
|
||||
TYPE = 'POINTS'
|
||||
TYPE = "POINTS"
|
||||
|
||||
DEF_GLSL = BaseShader.DEF_GLSL + """
|
||||
DEF_GLSL = (
|
||||
BaseShader.DEF_GLSL
|
||||
+ """
|
||||
#define CIRCLE_SEGMENTS 12
|
||||
#define CIRCLE_RADIUS 8
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
layout(points) in;
|
||||
@@ -235,11 +243,14 @@ class DotsGizmoShader(GizmoShader):
|
||||
class ExtrusionGuidesShader(GizmoShader):
|
||||
"""Draws lines and add cross in XY plane at endpoints"""
|
||||
|
||||
TYPE = 'LINES'
|
||||
TYPE = "LINES"
|
||||
|
||||
DEF_GLSL = BaseShader.DEF_GLSL + """
|
||||
DEF_GLSL = (
|
||||
BaseShader.DEF_GLSL
|
||||
+ """
|
||||
#define CROSS_SIZE .5
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
+8
-7
@@ -13,7 +13,7 @@ class SheetBuilder:
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, name, titleblock_name):
|
||||
sheet_path = "{}sheets/{}.svg".format(self.data_dir, name)
|
||||
sheet_path = os.path.join(self.data_dir, f"{name}.svg")
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
@@ -121,9 +121,9 @@ class SheetBuilder:
|
||||
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
|
||||
|
||||
def build(self, sheet_name):
|
||||
os.makedirs("{}build/{}/".format(self.data_dir, sheet_name), exist_ok=True)
|
||||
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
|
||||
|
||||
sheet_path = "{}sheets/{}.svg".format(self.data_dir, sheet_name)
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
@@ -140,7 +140,7 @@ class SheetBuilder:
|
||||
self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name)
|
||||
self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'))
|
||||
|
||||
with open("{}build/{}/{}.svg".format(self.data_dir, sheet_name, sheet_name), "wb") as output:
|
||||
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
|
||||
tree.write(output)
|
||||
|
||||
def build_drawings(self, drawings, sheet_name):
|
||||
@@ -155,8 +155,9 @@ class SheetBuilder:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
|
||||
# Add background
|
||||
background_path = "{}sheets/{}".format(self.data_dir, self.get_href(background))
|
||||
copy(background_path, "{}build/{}/".format(self.data_dir, sheet_name))
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
|
||||
# Add view title
|
||||
foreground_path = self.get_href(foreground)
|
||||
@@ -202,7 +203,7 @@ class SheetBuilder:
|
||||
self.convert_to_mm(image.attrib.get("x")), self.convert_to_mm(image.attrib.get("y"))
|
||||
)
|
||||
svg_path = self.get_href(image)
|
||||
with open("{}sheets/{}".format(self.data_dir, svg_path), "r") as template:
|
||||
with open(os.path.join(self.data_dir, "sheets", svg_path), "r") as template:
|
||||
embedded = ET.fromstring(pystache.render(template.read(), data))
|
||||
# viewBox should not be nested
|
||||
embedded.attrib["viewBox"] = ""
|
||||
+70
-58
@@ -6,16 +6,19 @@ import pystache
|
||||
import xml.etree.ElementTree as ET
|
||||
import svgwrite
|
||||
import ifcopenshell
|
||||
from . import annotation
|
||||
from . import helper
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
try:
|
||||
from OCC.Core import BRep, BRepTools, TopExp, TopAbs
|
||||
except ImportError:
|
||||
from OCC import BRep, BRepTools, TopExp, TopAbs
|
||||
|
||||
def load_occ():
|
||||
# Don't import until we really need to, as a temporary step before we can purge OCC
|
||||
try:
|
||||
from OCC.Core import BRep, BRepTools, TopExp, TopAbs
|
||||
except ImportError:
|
||||
from OCC import BRep, BRepTools, TopExp, TopAbs
|
||||
|
||||
|
||||
class External(svgwrite.container.Group):
|
||||
@@ -25,7 +28,7 @@ class External(svgwrite.container.Group):
|
||||
# Remove namespace
|
||||
ns = u"{http://www.w3.org/2000/svg}"
|
||||
nsl = len(ns)
|
||||
for elem in self.xml.getiterator():
|
||||
for elem in self.xml.iter():
|
||||
if elem.tag.startswith(ns):
|
||||
elem.tag = elem.tag[nsl:]
|
||||
|
||||
@@ -36,14 +39,17 @@ class External(svgwrite.container.Group):
|
||||
|
||||
|
||||
class SvgWriter:
|
||||
def __init__(self, ifc_cutter):
|
||||
self.ifc_cutter = ifc_cutter
|
||||
def __init__(self):
|
||||
self.output = "out.svg"
|
||||
self.data_dir = None
|
||||
self.vector_style = None
|
||||
self.human_scale = "NTS"
|
||||
self.annotations = {}
|
||||
self.background_image = None
|
||||
self.scale = 1 / 100 # 1:100
|
||||
|
||||
def write(self):
|
||||
def write(self, layer):
|
||||
self.calculate_scale()
|
||||
self.output = os.path.join(self.ifc_cutter.data_dir, "diagrams", self.ifc_cutter.diagram_name + ".svg")
|
||||
self.svg = svgwrite.Drawing(
|
||||
self.output,
|
||||
debug=False,
|
||||
@@ -53,54 +59,57 @@ class SvgWriter:
|
||||
data_scale=self.human_scale,
|
||||
)
|
||||
|
||||
self.add_stylesheet()
|
||||
self.add_markers()
|
||||
self.add_symbols()
|
||||
self.add_patterns()
|
||||
self.draw_background_image()
|
||||
self.draw_background_elements()
|
||||
self.draw_cut_polygons()
|
||||
self.draw_annotations()
|
||||
if layer == "underlay":
|
||||
self.draw_background_image()
|
||||
elif layer == "annotation":
|
||||
self.add_stylesheet()
|
||||
self.add_markers()
|
||||
self.add_symbols()
|
||||
self.add_patterns()
|
||||
# self.draw_background_elements()
|
||||
# self.draw_cut_polygons()
|
||||
self.draw_annotations()
|
||||
self.svg.save(pretty=True)
|
||||
|
||||
def calculate_scale(self):
|
||||
self.scale *= 1000 # IFC is in meters, SVG is in mm
|
||||
self.raw_width = self.ifc_cutter.section_box["x"]
|
||||
self.raw_height = self.ifc_cutter.section_box["y"]
|
||||
self.raw_width = self.camera_width
|
||||
self.raw_height = self.camera_height
|
||||
self.width = self.raw_width * self.scale
|
||||
self.height = self.raw_height * self.scale
|
||||
|
||||
def add_stylesheet(self):
|
||||
with open("{}styles/{}.css".format(self.ifc_cutter.data_dir, self.ifc_cutter.vector_style), "r") as stylesheet:
|
||||
with open(os.path.join(self.data_dir, "styles", f"{self.vector_style}.css"), "r") as stylesheet:
|
||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||
|
||||
def add_markers(self):
|
||||
tree = ET.parse("{}templates/markers.svg".format(self.ifc_cutter.data_dir))
|
||||
tree = ET.parse(os.path.join(self.data_dir, "templates", "markers.svg"))
|
||||
root = tree.getroot()
|
||||
for child in root.getchildren():
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_symbols(self):
|
||||
tree = ET.parse("{}templates/symbols.svg".format(self.ifc_cutter.data_dir))
|
||||
tree = ET.parse(os.path.join(self.data_dir, "templates", "symbols.svg"))
|
||||
root = tree.getroot()
|
||||
for child in root.getchildren():
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_patterns(self):
|
||||
tree = ET.parse("{}templates/patterns.svg".format(self.ifc_cutter.data_dir))
|
||||
tree = ET.parse(os.path.join(self.data_dir, "templates", "patterns.svg"))
|
||||
root = tree.getroot()
|
||||
for child in root.getchildren():
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def draw_background_image(self):
|
||||
self.svg.add(
|
||||
self.svg.image(
|
||||
os.path.join("..", "diagrams", os.path.basename(self.ifc_cutter.background_image)),
|
||||
os.path.join("..", "diagrams", os.path.basename(self.background_image)),
|
||||
**{"width": self.width, "height": self.height}
|
||||
)
|
||||
)
|
||||
|
||||
def draw_background_elements(self):
|
||||
return # TODO purge?
|
||||
for element in self.ifc_cutter.background_elements:
|
||||
if element["type"] == "polygon":
|
||||
self.draw_polygon(element, "background")
|
||||
@@ -113,16 +122,16 @@ class SvgWriter:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
for obj in self.ifc_cutter.equal_objs:
|
||||
for obj in self.annotations.get("equal_objs", []):
|
||||
self.draw_dimension_annotations(obj, text_override="EQ")
|
||||
for obj in self.ifc_cutter.dimension_objs:
|
||||
for obj in self.annotations.get("dimension_objs", []):
|
||||
self.draw_dimension_annotations(obj)
|
||||
self.draw_measureit_arch_dimension_annotations()
|
||||
|
||||
if self.ifc_cutter.break_obj:
|
||||
self.draw_break_annotations(self.ifc_cutter.break_obj)
|
||||
if self.annotations.get("break_obj"):
|
||||
self.draw_break_annotations(self.annotations["break_obj"])
|
||||
|
||||
for grid_obj in self.ifc_cutter.grid_objs:
|
||||
for grid_obj in self.annotations.get("grid_objs", []):
|
||||
matrix_world = grid_obj.matrix_world
|
||||
for edge in grid_obj.data.edges:
|
||||
classes = ["annotation", "grid"]
|
||||
@@ -171,21 +180,21 @@ class SvgWriter:
|
||||
|
||||
self.draw_ifc_annotation()
|
||||
|
||||
for obj in self.ifc_cutter.misc_objs:
|
||||
for obj in self.annotations.get("misc_objs", []):
|
||||
self.draw_misc_annotation(obj, ["IfcAnnotation"])
|
||||
|
||||
for obj_data in self.ifc_cutter.hidden_objs:
|
||||
for obj_data in self.annotations.get("hidden_objs", []):
|
||||
self.draw_line_annotation(obj_data, ["hidden"])
|
||||
|
||||
for obj_data in self.ifc_cutter.solid_objs:
|
||||
for obj_data in self.annotations.get("solid_objs", []):
|
||||
self.draw_line_annotation(obj_data, ["solid"])
|
||||
|
||||
if self.ifc_cutter.leader_obj:
|
||||
self.draw_line_annotation(self.ifc_cutter.leader_obj, ["leader"])
|
||||
if self.annotations.get("leader_obj"):
|
||||
self.draw_line_annotation(self.annotations["leader_obj"], ["leader"])
|
||||
|
||||
if self.ifc_cutter.plan_level_obj:
|
||||
matrix_world = self.ifc_cutter.plan_level_obj.matrix_world
|
||||
for spline in self.ifc_cutter.plan_level_obj.data.splines:
|
||||
if self.annotations.get("plan_level_obj"):
|
||||
matrix_world = self.annotations["plan_level_obj"].matrix_world
|
||||
for spline in self.annotations["plan_level_obj"].data.splines:
|
||||
classes = ["annotation", "plan-level"]
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
@@ -205,7 +214,7 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = ((matrix_world @ points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z
|
||||
rl = ((matrix_world @ points[0].co).xyz + self.annotations["plan_level_obj"].location).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl)
|
||||
else:
|
||||
@@ -228,9 +237,9 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
if self.ifc_cutter.section_level_obj:
|
||||
matrix_world = self.ifc_cutter.section_level_obj.matrix_world
|
||||
for spline in self.ifc_cutter.section_level_obj.data.splines:
|
||||
if self.annotations.get("section_level_obj"):
|
||||
matrix_world = self.annotations["section_level_obj"].matrix_world
|
||||
for spline in self.annotations["section_level_obj"].data.splines:
|
||||
classes = ["annotation", "section-level"]
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
@@ -270,9 +279,9 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
if self.ifc_cutter.stair_obj:
|
||||
matrix_world = self.ifc_cutter.stair_obj.matrix_world
|
||||
for spline in self.ifc_cutter.stair_obj.data.splines:
|
||||
if self.annotations.get("stair_obj"):
|
||||
matrix_world = self.annotations["stair_obj"].matrix_world
|
||||
for spline in self.annotations["stair_obj"].data.splines:
|
||||
classes = ["annotation", "stair"]
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
@@ -305,7 +314,7 @@ class SvgWriter:
|
||||
def draw_ifc_annotation(self):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
for annotation in self.ifc_cutter.annotation_objs:
|
||||
for annotation in self.annotations.get("annotation_objs", []):
|
||||
for edge in annotation["edges"]:
|
||||
v0_global = annotation["vertices"][edge[0]]
|
||||
v1_global = annotation["vertices"][edge[1]]
|
||||
@@ -354,7 +363,7 @@ class SvgWriter:
|
||||
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
|
||||
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||
classes.append("globalid-{}".format(global_id))
|
||||
for attribute in self.ifc_cutter.attributes:
|
||||
for attribute in self.annotations.get("attributes", []):
|
||||
result = self.get_obj_value(obj, attribute)
|
||||
if result:
|
||||
classes.append(
|
||||
@@ -432,7 +441,7 @@ class SvgWriter:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
for text_obj in self.ifc_cutter.text_objs:
|
||||
for text_obj in self.annotations.get("text_objs", []):
|
||||
text_position = self.project_point_onto_camera(text_obj.location)
|
||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||
|
||||
@@ -470,8 +479,8 @@ class SvgWriter:
|
||||
alignment_baseline = "baseline"
|
||||
|
||||
text_body = text_obj.data.body
|
||||
if text_obj.name in self.ifc_cutter.template_variables:
|
||||
text_body = pystache.render(text_body, self.ifc_cutter.template_variables[text_obj.name])
|
||||
if text_obj.name in self.annotations.get("template_variables", {}):
|
||||
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
|
||||
|
||||
for line_number, text_line in enumerate(text_body.split("\n")):
|
||||
self.svg.add(
|
||||
@@ -584,21 +593,23 @@ class SvgWriter:
|
||||
)
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
return self.ifc_cutter.camera_obj.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||
point.xyz,
|
||||
point.xyz - Vector(self.ifc_cutter.section_box["projection"]),
|
||||
self.ifc_cutter.camera_obj.location,
|
||||
Vector(self.ifc_cutter.section_box["projection"]),
|
||||
point.xyz - Vector(self.camera_projection),
|
||||
self.camera.location,
|
||||
Vector(self.camera_projection),
|
||||
)
|
||||
|
||||
def get_spline_points(self, spline):
|
||||
return spline.bezier_points if spline.bezier_points else spline.points
|
||||
|
||||
def draw_cut_polygons(self):
|
||||
return # deprecate?
|
||||
for polygon in self.ifc_cutter.cut_polygons:
|
||||
self.draw_polygon(polygon, "cut")
|
||||
|
||||
def draw_polyline(self, element, position):
|
||||
load_occ()
|
||||
classes = self.get_classes(element["raw"], position)
|
||||
exp = BRepTools.BRepTools_WireExplorer(element["geometry"])
|
||||
points = []
|
||||
@@ -609,6 +620,7 @@ class SvgWriter:
|
||||
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes)))
|
||||
|
||||
def draw_line(self, element, position):
|
||||
load_occ()
|
||||
classes = self.get_classes(element["raw"], position)
|
||||
exp = TopExp.TopExp_Explorer(element["geometry"], TopAbs.TopAbs_VERTEX)
|
||||
points = []
|
||||
@@ -24,15 +24,20 @@ class BIM_PT_camera(Panel):
|
||||
return
|
||||
|
||||
layout.use_property_split = True
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
dprops = context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
|
||||
layout.label(text="Generation Options:")
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE")
|
||||
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_linework", icon="IMAGE_DATA")
|
||||
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_recut")
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_recut_selected")
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
|
||||
@@ -64,7 +69,31 @@ class BIM_PT_camera(Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "custom_diagram_scale")
|
||||
|
||||
layout.label(text="Drawing Styles:")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = context.active_object.name.split("/")[1]
|
||||
|
||||
|
||||
class BIM_PT_drawing_underlay(Panel):
|
||||
bl_label = "Drawing Underlay"
|
||||
bl_idname = "BIM_PT_drawing_underlay"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
bl_parent_id = "BIM_PT_camera"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
engine = context.engine
|
||||
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
dprops = context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_style")
|
||||
@@ -101,8 +130,214 @@ class BIM_PT_camera(Panel):
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
|
||||
|
||||
|
||||
class BIM_PT_drawings(Panel):
|
||||
bl_label = "SVG Drawings"
|
||||
bl_idname = "BIM_PT_drawings"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.cut_section", text="Create Drawing")
|
||||
row.operator("bim.create_drawing", text="Create Drawing 2.0")
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = context.active_object.name.split("/")[1]
|
||||
row.operator("bim.add_drawing")
|
||||
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
|
||||
|
||||
if props.drawings:
|
||||
if props.active_drawing_index < len(props.drawings):
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = props.drawings[props.active_drawing_index].name
|
||||
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
|
||||
layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_ifc_file")
|
||||
|
||||
for index, ifc_file in enumerate(props.ifc_files):
|
||||
row = layout.row(align=True)
|
||||
row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
|
||||
row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
|
||||
row.operator("bim.remove_ifc_file", icon="X", text="").index = index
|
||||
|
||||
|
||||
class BIM_PT_schedules(Panel):
|
||||
bl_label = "ODS Schedules"
|
||||
bl_idname = "BIM_PT_schedules"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_schedule")
|
||||
|
||||
if props.schedules:
|
||||
row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="")
|
||||
row.operator("bim.remove_schedule", icon="X", text="").index = props.active_schedule_index
|
||||
|
||||
layout.template_list("BIM_UL_generic", "", props, "schedules", props, "active_schedule_index")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props.schedules[props.active_schedule_index], "file")
|
||||
row.operator("bim.select_schedule_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
|
||||
class BIM_PT_sheets(Panel):
|
||||
bl_label = "SVG Sheets"
|
||||
bl_idname = "BIM_PT_sheets"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "titleblock", text="")
|
||||
row.operator("bim.add_sheet")
|
||||
|
||||
if props.sheets:
|
||||
row.operator("bim.open_sheet", icon="URL", text="")
|
||||
row.operator("bim.remove_sheet", icon="X", text="").index = props.active_sheet_index
|
||||
|
||||
layout.template_list("BIM_UL_generic", "", props, "sheets", props, "active_sheet_index")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_to_sheet")
|
||||
row.operator("bim.add_schedule_to_sheet")
|
||||
row = layout.row()
|
||||
row.operator("bim.create_sheets")
|
||||
|
||||
|
||||
class BIM_PT_text(Panel):
|
||||
bl_label = "Text Paper Space"
|
||||
bl_idname = "BIM_PT_text"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return type(context.curve) is bpy.types.TextCurve
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = context.active_object.data.BIMTextProperties
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.propagate_text_data")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "font_size")
|
||||
row = layout.row()
|
||||
row.prop(props, "symbol")
|
||||
row = layout.row()
|
||||
row.prop(props, "related_element")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_variable")
|
||||
|
||||
for index, variable in enumerate(props.variables):
|
||||
row = layout.row(align=True)
|
||||
row.prop(variable, "name")
|
||||
row.operator("bim.remove_variable", icon="X", text="").index = index
|
||||
row = layout.row()
|
||||
row.prop(variable, "prop_key")
|
||||
|
||||
|
||||
class BIM_PT_annotation_utilities(Panel):
|
||||
bl_idname = "BIM_PT_annotation_utilities"
|
||||
bl_label = "Annotation"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.link_ifc")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_grid")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_sections_annotations")
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT")
|
||||
op.obj_name = "Dimension"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Dim (Eq)", icon="ARROW_LEFTRIGHT")
|
||||
op.obj_name = "Equal"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
|
||||
op.data_type = "text"
|
||||
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
|
||||
op.obj_name = "Leader"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
|
||||
op.obj_name = "Stair"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
|
||||
op.obj_name = "Hidden"
|
||||
op.data_type = "mesh"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
|
||||
op.obj_name = "Plan Level"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
|
||||
op.obj_name = "Section Level"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
|
||||
op.obj_name = "Break"
|
||||
op.data_type = "mesh"
|
||||
op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY")
|
||||
op.obj_name = "Misc"
|
||||
op.data_type = "mesh"
|
||||
|
||||
props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing")
|
||||
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
|
||||
|
||||
if props.drawings:
|
||||
if props.active_drawing_index < len(props.drawings):
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = props.drawings[props.active_drawing_index].name
|
||||
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
|
||||
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
|
||||
|
||||
layout.prop(props, "should_draw_decorations")
|
||||
layout.prop(props, "decorations_colour")
|
||||
|
||||
|
||||
class BIM_UL_drawinglist(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.prop(item, "name", text="", emboss=False)
|
||||
op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="")
|
||||
op.view_name = item.name
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
@@ -4,14 +4,13 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.EditObjectPlacement,
|
||||
operator.AddRepresentation,
|
||||
operator.MapRepresentations,
|
||||
operator.MapRepresentation,
|
||||
operator.SwitchRepresentation,
|
||||
operator.RemoveRepresentation,
|
||||
operator.UpdateMeshRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_derived_placements,
|
||||
ui.BIM_PT_representations,
|
||||
ui.BIM_PT_mesh,
|
||||
ui.BIM_PT_workarounds,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import mathutils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from math import pi
|
||||
@@ -197,18 +198,26 @@ class Helper:
|
||||
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
|
||||
|
||||
# An extrusion edge is an edge that shares a single vertex with a profile
|
||||
# face and is not parallel to the face.
|
||||
# face and is not on the plane of the face.
|
||||
def detect_extrusion_edge(self, bm, profile_face):
|
||||
bm.edges.ensure_lookup_table()
|
||||
extrusion = None
|
||||
face_verts_set = set(profile_face.verts)
|
||||
for edge in bm.edges:
|
||||
edge_vector = edge.verts[1].co - edge.verts[0].co
|
||||
unshared_verts = set(edge.verts) - face_verts_set
|
||||
if len(unshared_verts) == 1 and not (edge_vector.angle(profile_face.normal) - pi / 2 < 0.001):
|
||||
if unshared_verts.pop() == edge.verts[1]:
|
||||
return [edge.verts[0].index, edge.verts[1].index]
|
||||
return [edge.verts[1].index, edge.verts[0].index]
|
||||
if len(unshared_verts) == 1:
|
||||
unshared_vert = unshared_verts.pop()
|
||||
if (
|
||||
abs(
|
||||
mathutils.geometry.distance_point_to_plane(
|
||||
unshared_vert.co, profile_face.verts[0].co, profile_face.normal
|
||||
)
|
||||
)
|
||||
> 0.001
|
||||
):
|
||||
if unshared_vert == edge.verts[1]:
|
||||
return [edge.verts[0].index, edge.verts[1].index]
|
||||
return [edge.verts[1].index, edge.verts[0].index]
|
||||
|
||||
def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def):
|
||||
position = self.create_ifc_axis_2_placement_3d(
|
||||
|
||||
@@ -3,15 +3,9 @@ import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import logging
|
||||
import ifcopenshell.api.geometry.edit_object_placement as edit_object_placement
|
||||
import ifcopenshell.api.geometry.add_representation as add_representation
|
||||
import ifcopenshell.api.geometry.map_representation as map_representation
|
||||
import ifcopenshell.api.geometry.assign_styles as assign_styles
|
||||
import ifcopenshell.api.geometry.assign_representation as assign_representation
|
||||
import ifcopenshell.api.geometry.unassign_representation as unassign_representation
|
||||
import ifcopenshell.api.geometry.remove_representation as remove_representation
|
||||
import ifcopenshell.api.grid.create_axis_curve as create_axis_curve
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim import import_ifc
|
||||
from ifcopenshell.api.geometry.data import Data
|
||||
@@ -20,29 +14,25 @@ from ifcopenshell.api.void.data import Data as VoidData
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def get_context_id(context_type, context_identifier, target_view):
|
||||
for context in ContextData.contexts.values():
|
||||
if context["ContextType"] == context_type:
|
||||
for i, subcontext in context["HasSubContexts"].items():
|
||||
if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
|
||||
return i
|
||||
|
||||
|
||||
class EditObjectPlacement(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_object_placement"
|
||||
bl_label = "Edit Object Placement"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
# TODO: determine how to deal with this module dependency
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
for obj in objs:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
matrix = np.array(obj.matrix_world)
|
||||
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
# TODO: np.array? Why not matrix?
|
||||
matrix = np.array(
|
||||
@@ -55,74 +45,83 @@ class EditObjectPlacement(bpy.types.Operator):
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
)
|
||||
edit_object_placement.Usecase(
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
self.file,
|
||||
{
|
||||
**{
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"matrix": matrix,
|
||||
},
|
||||
).execute()
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
context_id: bpy.props.IntProperty()
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
profile_set_usage: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
if obj.data:
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
|
||||
context_of_items = self.file.by_id(context_id)
|
||||
if not obj.data:
|
||||
return {"FINISHED"}
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
context_id = self.context_id or int(context.scene.BIMProperties.contexts)
|
||||
context_of_items = self.file.by_id(context_id)
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
)
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
}
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
"ifc_representation_class": self.ifc_representation_class,
|
||||
"profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None,
|
||||
}
|
||||
|
||||
result = add_representation.Usecase(self.file, representation_data).execute()
|
||||
result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
|
||||
if not result:
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
if not result:
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = add_representation.Usecase(self.file, representation_data).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": new_box}).execute()
|
||||
[
|
||||
bpy.ops.bim.add_style(material=s.material.name)
|
||||
for s in obj.material_slots
|
||||
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
|
||||
]
|
||||
|
||||
assign_styles.Usecase(
|
||||
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_representation_styles",
|
||||
self.file,
|
||||
{
|
||||
**{
|
||||
"shape_representation": result,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
@@ -131,47 +130,77 @@ class AddRepresentation(bpy.types.Operator):
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, **{"product": product, "representation": result}
|
||||
)
|
||||
|
||||
existing_mesh = obj.data
|
||||
mesh = obj.data.copy()
|
||||
mesh.name = "{}/{}".format(context_id, result.id())
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
|
||||
obj.data = mesh
|
||||
existing_mesh = obj.data
|
||||
mesh = obj.data.copy()
|
||||
mesh.name = "{}/{}".format(context_id, result.id())
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
|
||||
obj.data = mesh
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if product.is_a("IfcTypeProduct"):
|
||||
if self.file.schema == "IFC2X3":
|
||||
types = product.ObjectTypeOf
|
||||
else:
|
||||
types = product.Types
|
||||
if types:
|
||||
for element in types[0].RelatedObjects:
|
||||
Data.load(IfcStore.get_file(), element.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SwitchRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.switch_representation"
|
||||
bl_label = "Switch Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
should_reload: bpy.props.BoolProperty()
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.obj = bpy.context.active_object
|
||||
self.oprops = self.obj.BIMObjectProperties
|
||||
self.element_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.oprops = self.element_obj.BIMObjectProperties
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
|
||||
self.mesh_name = "{}/{}".format(self.context_of_items.id(), self.ifc_definition_id)
|
||||
self.mesh_name = self.get_mesh_name()
|
||||
|
||||
mesh = bpy.data.meshes.get(self.mesh_name)
|
||||
if mesh:
|
||||
self.obj.data.user_remap(mesh)
|
||||
self.pull_mesh_from_ifc()
|
||||
self.switch_mesh(mesh)
|
||||
if not mesh or self.should_reload:
|
||||
self.pull_mesh_from_ifc(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def pull_mesh_from_ifc(self):
|
||||
self.file = IfcStore.get_file()
|
||||
def switch_mesh(self, mesh):
|
||||
if self.should_switch_all_meshes or self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcTypeProduct"):
|
||||
self.element_obj.data.user_remap(mesh)
|
||||
else:
|
||||
self.element_obj.data = mesh
|
||||
|
||||
def get_mesh_name(self):
|
||||
representation = self.resolve_mapped_representation(self.file.by_id(self.ifc_definition_id))
|
||||
return "{}/{}".format(self.context_of_items.id(), representation.id())
|
||||
|
||||
def resolve_mapped_representation(self, representation):
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
return self.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
def pull_mesh_from_ifc(self, context):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
element = self.file.by_id(self.oprops.ifc_definition_id)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
|
||||
if self.context_of_items.ContextIdentifier == "Body":
|
||||
if self.disable_opening_subtractions:
|
||||
if element.is_a("IfcTypeProduct") or self.disable_opening_subtractions:
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
|
||||
else:
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.oprops.ifc_definition_id))
|
||||
@@ -184,35 +213,41 @@ class SwitchRepresentation(bpy.types.Operator):
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
mesh.name = self.mesh_name
|
||||
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
|
||||
self.obj.data.user_remap(mesh)
|
||||
self.switch_mesh(mesh)
|
||||
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
|
||||
material_creator.create(element, self.obj, mesh)
|
||||
material_creator.load_existing_materials()
|
||||
material_creator.create(element, self.element_obj, mesh)
|
||||
|
||||
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
|
||||
if self.oprops.ifc_definition_id not in VoidData.products:
|
||||
VoidData.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
|
||||
if opening_id in IfcStore.id_map:
|
||||
opening = IfcStore.id_map[opening_id]
|
||||
modifier = self.obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
|
||||
modifier.operation = "DIFFERENCE"
|
||||
modifier.object = opening
|
||||
opening = IfcStore.get_element(opening_id)
|
||||
if not opening:
|
||||
continue
|
||||
modifier = self.element_obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
|
||||
modifier.operation = "DIFFERENCE"
|
||||
modifier.object = opening
|
||||
else:
|
||||
for modifier in self.obj.modifiers:
|
||||
for modifier in self.element_obj.modifiers:
|
||||
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
|
||||
self.obj.modifiers.remove(modifier)
|
||||
self.element_obj.modifiers.remove(modifier)
|
||||
|
||||
|
||||
class RemoveRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_representation"
|
||||
bl_label = "Remove Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
representation = self.file.by_id(self.representation_id)
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
|
||||
if is_mapped_representation:
|
||||
mesh_name = "{}/{}".format(
|
||||
@@ -231,88 +266,41 @@ class RemoveRepresentation(bpy.types.Operator):
|
||||
if not is_mapped_representation:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
unassign_representation.Usecase(self.file, {"product": product, "representation": representation}).execute()
|
||||
remove_representation.Usecase(self.file, {"representation": representation}).execute()
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation", self.file, **{"product": product, "representation": representation}
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
|
||||
Data.load(IfcStore.get_file(), product.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MapRepresentations(bpy.types.Operator):
|
||||
bl_idname = "bim.map_representations"
|
||||
bl_label = "Map Representations"
|
||||
product_id: bpy.props.IntProperty()
|
||||
type_product_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_object = IfcStore.id_map[self.product_id]
|
||||
|
||||
if self.product_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.product_id)
|
||||
|
||||
for representation_id in Data.products[self.product_id]:
|
||||
bpy.ops.bim.remove_representation(obj=related_object.name, representation_id=representation_id)
|
||||
|
||||
if self.type_product_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.type_product_id)
|
||||
|
||||
for representation_id in Data.products[self.type_product_id]:
|
||||
bpy.ops.bim.map_representation(
|
||||
obj=related_object.name,
|
||||
representation_id=representation_id,
|
||||
obj_data=IfcStore.id_map[self.type_product_id].data.name,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MapRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.map_representation"
|
||||
bl_label = "Map Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
obj_data: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else None
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if obj_data:
|
||||
obj.data = obj_data
|
||||
result = map_representation.Usecase(
|
||||
self.file, {"representation": self.file.by_id(self.representation_id)}
|
||||
).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_mesh_representation"
|
||||
bl_label = "Update Mesh Representation"
|
||||
class UpdateRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_representation"
|
||||
bl_label = "Update Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
if not ContextData.is_loaded:
|
||||
ContextData.load(IfcStore.get_file())
|
||||
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
self.update_obj_mesh_representation(context, obj)
|
||||
IfcStore.edited_objs.discard(obj.name)
|
||||
IfcStore.edited_objs.discard(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def update_obj_mesh_representation(self, context, obj):
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if product.is_a("IfcGridAxis"):
|
||||
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": product}).execute()
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
|
||||
return
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
@@ -322,7 +310,7 @@ class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
|
||||
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
@@ -339,54 +327,31 @@ class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
"ifc_representation_class": self.ifc_representation_class
|
||||
"ifc_representation_class": self.ifc_representation_class,
|
||||
}
|
||||
|
||||
new_representation = add_representation.Usecase(self.file, representation_data).execute()
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
|
||||
#if product.is_a("IfcWall"):
|
||||
# # Generate axis representation
|
||||
# axis_context_id = get_context_id("Model", "Axis", "MODEL_VIEW")
|
||||
# old_axis = ifcopenshell.util.element.get_representation(product, "Model", "Axis", "MODEL_VIEW")
|
||||
# if (
|
||||
# axis_context_id
|
||||
# and old_axis
|
||||
# and context_of_items.ContextType == "Model"
|
||||
# and context_of_items.ContextIdentifier
|
||||
# and context_of_items.ContextIdentifier == "Body"
|
||||
# ):
|
||||
# has_axis_generator = False
|
||||
# if has_axis_generator:
|
||||
# # TODO, just pseudocode for now
|
||||
# representation_data["geometry"] = axis_generator_function_call
|
||||
# pass
|
||||
[
|
||||
bpy.ops.bim.add_style(material=s.material.name)
|
||||
for s in obj.material_slots
|
||||
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
|
||||
]
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and old_box
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = add_representation.Usecase(self.file, representation_data).execute()
|
||||
for inverse in self.file.get_inverse(old_box):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
|
||||
|
||||
assign_styles.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"shape_representation": new_representation,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
).execute()
|
||||
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_representation_styles",
|
||||
self.file,
|
||||
**{
|
||||
"shape_representation": new_representation,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
)
|
||||
|
||||
# TODO: move this into a replace_representation usecase or something
|
||||
for inverse in self.file.get_inverse(old_representation):
|
||||
@@ -394,32 +359,36 @@ class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||
bpy.ops.bim.remove_representation(representation_id=old_representation.id())
|
||||
bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name)
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
|
||||
class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_parametric_representation"
|
||||
bl_label = "Update Parametric Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.context.active_object
|
||||
obj = context.active_object
|
||||
props = obj.data.BIMMeshProperties
|
||||
parameter = props.ifc_parameters[self.index]
|
||||
element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
|
||||
bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id)
|
||||
bpy.ops.bim.switch_representation(
|
||||
ifc_definition_id=props.ifc_definition_id, should_reload=True, should_switch_all_meshes=True
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetRepresentationIfcParameters(bpy.types.Operator):
|
||||
bl_idname = "bim.get_representation_ifc_parameters"
|
||||
bl_label = "Get Representation IFC Parameters"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.context.active_object
|
||||
obj = context.active_object
|
||||
props = obj.data.BIMMeshProperties
|
||||
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
|
||||
for element in elements:
|
||||
|
||||
@@ -14,6 +14,8 @@ class BIM_PT_representations(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
@@ -28,7 +30,7 @@ class BIM_PT_representations(Panel):
|
||||
layout.label(text="No representations found")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(bpy.context.scene.BIMProperties, "contexts", text="")
|
||||
row.prop(context.scene.BIMProperties, "contexts", text="")
|
||||
row.operator("bim.add_representation", icon="ADD", text="")
|
||||
|
||||
for ifc_definition_id in representations:
|
||||
@@ -39,6 +41,8 @@ class BIM_PT_representations(Panel):
|
||||
row.label(text=representation["ContextOfItems"]["TargetView"])
|
||||
row.label(text=representation["RepresentationType"])
|
||||
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = ifc_definition_id
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id
|
||||
@@ -68,29 +72,33 @@ class BIM_PT_mesh(Panel):
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
|
||||
op.should_switch_all_meshes=True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = props.ifc_definition_id
|
||||
op.disable_opening_subtractions = False
|
||||
op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT")
|
||||
op.should_switch_all_meshes=True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = props.ifc_definition_id
|
||||
op.disable_opening_subtractions = True
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_mesh_representation")
|
||||
row.operator("bim.update_representation")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Rectangle Extrusion")
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Rectangle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Circle Extrusion")
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Circle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcCircleProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion")
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Arbitrary Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion With Voids")
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Arbitrary Extrusion With Voids")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
|
||||
|
||||
row = layout.row()
|
||||
@@ -104,10 +112,44 @@ class BIM_PT_mesh(Panel):
|
||||
|
||||
def BIM_PT_transform(self, context):
|
||||
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Blender Offset")
|
||||
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.edit_object_placement")
|
||||
|
||||
|
||||
class BIM_PT_derived_placements(Panel):
|
||||
bl_label = "IFC Derived Placements"
|
||||
bl_idname = "BIM_PT_derived_placements"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "OBJECT_PT_transform"
|
||||
|
||||
def draw(self, context):
|
||||
z = context.active_object.matrix_world.translation.z
|
||||
z_values = [co[2] for co in context.active_object.bound_box]
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Min Global Z")
|
||||
row.label(text="{0:.3f}".format(min(z_values) + z))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Max Global Z")
|
||||
row.label(text="{0:.3f}".format(max(z_values) + z))
|
||||
|
||||
collection = bpy.data.objects.get(context.active_object.users_collection[0].name)
|
||||
if collection:
|
||||
collection_z = collection.matrix_world.translation.z
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Min Local Z")
|
||||
row.label(text="{0:.3f}".format(min(z_values) + z - collection_z))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Max Local Z")
|
||||
row.label(text="{0:.3f}".format(max(z_values) + z - collection_z))
|
||||
|
||||
|
||||
class BIM_PT_workarounds(Panel):
|
||||
bl_label = "IFC Vendor Workarounds"
|
||||
bl_idname = "BIM_PT_workarounds"
|
||||
|
||||
@@ -5,8 +5,10 @@ classes = (
|
||||
operator.EnableEditingGeoreferencing,
|
||||
operator.DisableEditingGeoreferencing,
|
||||
operator.EditGeoreferencing,
|
||||
operator.SetNorthOffset,
|
||||
operator.GetNorthOffset,
|
||||
operator.SetIfcGridNorth,
|
||||
operator.SetBlenderGridNorth,
|
||||
operator.SetIfcTrueNorth,
|
||||
operator.SetBlenderTrueNorth,
|
||||
operator.RemoveGeoreferencing,
|
||||
operator.AddGeoreferencing,
|
||||
operator.ConvertLocalToGlobal,
|
||||
|
||||
@@ -3,9 +3,7 @@ import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api.georeference.add_georeferencing as add_georeferencing
|
||||
import ifcopenshell.api.georeference.edit_georeferencing as edit_georeferencing
|
||||
import ifcopenshell.api.georeference.remove_georeferencing as remove_georeferencing
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.georeference.data import Data
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
@@ -14,32 +12,12 @@ from math import radians, degrees, atan, tan, cos, sin
|
||||
class EnableEditingGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_georeferencing"
|
||||
bl_label = "Enable Editing Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
while len(props.map_conversion) > 0:
|
||||
props.map_conversion.remove(0)
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.map_conversion.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = Data.map_conversion[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else Data.map_conversion[attribute.name()]
|
||||
elif data_type == "float":
|
||||
new.float_value = 0.0 if new.is_null else Data.map_conversion[attribute.name()]
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else Data.map_conversion[attribute.name()]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else Data.map_conversion[attribute.name()]
|
||||
|
||||
while len(props.projected_crs) > 0:
|
||||
props.projected_crs.remove(0)
|
||||
|
||||
@@ -71,6 +49,26 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
|
||||
elif props.map_unit_type == "IfcConversionBasedUnit":
|
||||
props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"]
|
||||
|
||||
while len(props.map_conversion) > 0:
|
||||
props.map_conversion.remove(0)
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity" or data_type == "select":
|
||||
continue
|
||||
new = props.map_conversion.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = Data.map_conversion[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
# Enforce a string data type to prevent data loss in single-precision Blender props
|
||||
new.data_type = "string"
|
||||
new.string_value = "" if new.is_null else str(Data.map_conversion[attribute.name()])
|
||||
|
||||
props.has_true_north = bool(Data.true_north)
|
||||
if Data.true_north:
|
||||
props.true_north_abscissa = str(Data.true_north[0])
|
||||
props.true_north_ordinate = str(Data.true_north[1])
|
||||
|
||||
props.is_editing = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -78,6 +76,7 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
|
||||
class DisableEditingGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_georeferencing"
|
||||
bl_label = "Disable Editing Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
@@ -88,28 +87,15 @@ class DisableEditingGeoreferencing(bpy.types.Operator):
|
||||
class EditGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_georeferencing"
|
||||
bl_label = "Edit Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
map_conversion = {}
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
blender_attribute = props.map_conversion.get(attribute.name())
|
||||
if blender_attribute.is_null:
|
||||
map_conversion[attribute.name()] = None
|
||||
elif blender_attribute.data_type == "string":
|
||||
map_conversion[attribute.name()] = blender_attribute.string_value
|
||||
elif blender_attribute.data_type == "float":
|
||||
map_conversion[attribute.name()] = blender_attribute.float_value
|
||||
elif blender_attribute.data_type == "integer":
|
||||
map_conversion[attribute.name()] = blender_attribute.int_value
|
||||
elif blender_attribute.data_type == "boolean":
|
||||
map_conversion[attribute.name()] = blender_attribute.bool_value
|
||||
|
||||
projected_crs = {}
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
@@ -131,45 +117,104 @@ class EditGeoreferencing(bpy.types.Operator):
|
||||
if not props.is_map_unit_null:
|
||||
map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial
|
||||
|
||||
edit_georeferencing.Usecase(
|
||||
self.file, {"map_conversion": map_conversion, "projected_crs": projected_crs, "map_unit": map_unit}
|
||||
).execute()
|
||||
map_conversion = {}
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity" or data_type == "select":
|
||||
continue
|
||||
blender_attribute = props.map_conversion.get(attribute.name())
|
||||
if blender_attribute.is_null:
|
||||
map_conversion[attribute.name()] = None
|
||||
elif blender_attribute.data_type == "string":
|
||||
# We store our floats as string to prevent single precision data loss
|
||||
map_conversion[attribute.name()] = float(blender_attribute.string_value)
|
||||
|
||||
true_north = None
|
||||
if props.has_true_north:
|
||||
try:
|
||||
true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)]
|
||||
except:
|
||||
pass
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"georeference.edit_georeferencing",
|
||||
self.file,
|
||||
**{
|
||||
"map_conversion": map_conversion,
|
||||
"projected_crs": projected_crs,
|
||||
"map_unit": map_unit,
|
||||
"true_north": true_north,
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_georeferencing()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetNorthOffset(bpy.types.Operator):
|
||||
bl_idname = "bim.set_north_offset"
|
||||
bl_label = "Set North Offset"
|
||||
class SetBlenderGridNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_grid_north"
|
||||
bl_label = "Set Blender Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sun_pos_properties.north_offset = -radians(
|
||||
ifcopenshell.util.geolocation.xy2angle(
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value,
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value,
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value),
|
||||
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value),
|
||||
)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetNorthOffset(bpy.types.Operator):
|
||||
bl_idname = "bim.get_north_offset"
|
||||
bl_label = "Get North Offset"
|
||||
class SetIfcGridNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_ifc_grid_north"
|
||||
bl_label = "Set IFC Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
x_angle = -context.scene.sun_pos_properties.north_offset
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value = cos(x_angle)
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value = sin(x_angle)
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle))
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetBlenderTrueNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_true_north"
|
||||
bl_label = "Set Blender True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sun_pos_properties.north_offset = -radians(
|
||||
ifcopenshell.util.geolocation.yaxis2angle(
|
||||
float(context.scene.BIMGeoreferenceProperties.true_north_abscissa),
|
||||
float(context.scene.BIMGeoreferenceProperties.true_north_ordinate),
|
||||
)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetIfcTrueNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_ifc_true_north"
|
||||
bl_label = "Set IFC True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
y_angle = -context.scene.sun_pos_properties.north_offset + radians(90)
|
||||
context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(cos(y_angle))
|
||||
context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(sin(y_angle))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_georeferencing"
|
||||
bl_label = "Remove Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
remove_georeferencing.Usecase(IfcStore.get_file()).execute()
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run("georeference.remove_georeferencing", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -177,9 +222,13 @@ class RemoveGeoreferencing(bpy.types.Operator):
|
||||
class AddGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.add_georeferencing"
|
||||
bl_label = "Add Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
add_georeferencing.Usecase(IfcStore.get_file()).execute()
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -187,6 +236,7 @@ class AddGeoreferencing(bpy.types.Operator):
|
||||
class ConvertLocalToGlobal(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_local_to_global"
|
||||
bl_label = "Convert Local To Global"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
if not Data.is_loaded:
|
||||
@@ -194,11 +244,7 @@ class ConvertLocalToGlobal(bpy.types.Operator):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
|
||||
|
||||
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
|
||||
x -= float(props.blender_eastings)
|
||||
y -= float(props.blender_northings)
|
||||
z -= float(props.blender_orthogonal_height)
|
||||
elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
if props.has_blender_offset:
|
||||
results = ifcopenshell.util.geolocation.xyz2enh(
|
||||
x,
|
||||
y,
|
||||
@@ -230,13 +276,14 @@ class ConvertLocalToGlobal(bpy.types.Operator):
|
||||
results = (x, y, z)
|
||||
|
||||
props.coordinate_output = ",".join([str(r) for r in results])
|
||||
bpy.context.scene.cursor.location = results
|
||||
context.scene.cursor.location = results
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ConvertGlobalToLocal(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_global_to_local"
|
||||
bl_label = "Convert Global To Local"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
if not Data.is_loaded:
|
||||
@@ -259,11 +306,7 @@ class ConvertGlobalToLocal(bpy.types.Operator):
|
||||
else:
|
||||
results = (x, y, z)
|
||||
|
||||
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
|
||||
results[0] += float(props.blender_eastings)
|
||||
results[1] += float(props.blender_northings)
|
||||
results[2] += float(props.blender_orthogonal_height)
|
||||
elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
if props.has_blender_offset:
|
||||
results = ifcopenshell.util.geolocation.enh2xyz(
|
||||
results[0],
|
||||
results[1],
|
||||
@@ -279,18 +322,19 @@ class ConvertGlobalToLocal(bpy.types.Operator):
|
||||
props.coordinate_output = ",".join([str(r) for r in results])
|
||||
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
bpy.context.scene.cursor.location = [o * scale for o in results]
|
||||
context.scene.cursor.location = [o * scale for o in results]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetCursorLocation(bpy.types.Operator):
|
||||
bl_idname = "bim.get_cursor_location"
|
||||
bl_label = "Get Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
project_coordinates = [o / scale for o in bpy.context.scene.cursor.location]
|
||||
project_coordinates = [o / scale for o in context.scene.cursor.location]
|
||||
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -298,9 +342,10 @@ class GetCursorLocation(bpy.types.Operator):
|
||||
class SetCursorLocation(bpy.types.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
bpy.context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
|
||||
context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -36,13 +36,11 @@ class BIMGeoreferenceProperties(PropertyGroup):
|
||||
coordinate_input: StringProperty(name="Coordinate Input")
|
||||
coordinate_output: StringProperty(name="Coordinate Output")
|
||||
has_blender_offset: BoolProperty(name="Has Blender Offset")
|
||||
blender_offset_type: EnumProperty(
|
||||
items=[(o, o, "") for o in ["OBJECT_PLACEMENT", "CARTESIAN_POINT"]],
|
||||
name="Blender Offset",
|
||||
default="OBJECT_PLACEMENT",
|
||||
)
|
||||
blender_eastings: StringProperty(name="Blender Eastings", default="0")
|
||||
blender_northings: StringProperty(name="Blender Northings", default="0")
|
||||
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0")
|
||||
blender_x_axis_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1")
|
||||
blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0")
|
||||
has_true_north: BoolProperty(name="Has True North", default=True)
|
||||
true_north_abscissa: StringProperty(name="True North Abscissa")
|
||||
true_north_ordinate: StringProperty(name="True North Ordinate")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import ifcopenshell.util.geolocation
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.georeference.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_gis(Panel):
|
||||
bl_label = "IFC Georeferencing"
|
||||
bl_idname = "BIM_PT_gis"
|
||||
@@ -15,6 +17,8 @@ class BIM_PT_gis(Panel):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_split = True
|
||||
self.layout.use_property_decorate = False
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
@@ -26,30 +30,10 @@ class BIM_PT_gis(Panel):
|
||||
def draw_editable_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_georeferencing", icon="X", text="")
|
||||
|
||||
for attribute in props.map_conversion:
|
||||
if attribute.name == "XAxisAbscissa" and hasattr(context.scene, "sun_pos_properties"):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.get_north_offset", text="Set IFC North")
|
||||
row.operator("bim.set_north_offset", text="Set Blender North")
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
|
||||
for attribute in props.projected_crs:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
@@ -70,21 +54,52 @@ class BIM_PT_gis(Panel):
|
||||
row.prop(props, "map_unit_imperial", text="")
|
||||
row.prop(props, "is_map_unit_null", icon="RADIOBUT_OFF" if props.is_map_unit_null else "RADIOBUT_ON", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
|
||||
for attribute in props.map_conversion:
|
||||
if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
|
||||
row.operator("bim.set_blender_grid_north", text="Set Blender North")
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="True North", icon="LIGHT_SUN")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "has_true_north")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "true_north_abscissa")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "true_north_ordinate")
|
||||
if hasattr(context.scene, "sun_pos_properties"):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.set_ifc_true_north", text="Set IFC North")
|
||||
row.operator("bim.set_blender_true_north", text="Set Blender North")
|
||||
|
||||
def draw_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if not Data.map_conversion and IfcStore.get_file().schema != "IFC2X3":
|
||||
if not Data.projected_crs:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Not Georeferenced")
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
if IfcStore.get_file().schema != "IFC2X3":
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
|
||||
if props.has_blender_offset:
|
||||
row = self.layout.row()
|
||||
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Type")
|
||||
row.label(text=props.blender_offset_type)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Eastings")
|
||||
row.label(text=props.blender_eastings)
|
||||
@@ -100,26 +115,27 @@ class BIM_PT_gis(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="XAxisOrdinate")
|
||||
row.label(text=props.blender_x_axis_ordinate)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Grid North")
|
||||
row.label(
|
||||
text=str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
|
||||
),
|
||||
3,
|
||||
)
|
||||
)
|
||||
)
|
||||
elif IfcStore.get_file().schema == "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.label(text="Not Georeferenced")
|
||||
|
||||
if Data.map_conversion:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
|
||||
for key, value in Data.map_conversion.items():
|
||||
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or not value:
|
||||
continue
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=key)
|
||||
row.label(text=str(value))
|
||||
|
||||
if Data.projected_crs:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
|
||||
for key, value in Data.projected_crs.items():
|
||||
if key == "id" or key == "type" or not value:
|
||||
@@ -132,10 +148,45 @@ class BIM_PT_gis(Panel):
|
||||
row.label(text=key)
|
||||
row.label(text=str(value))
|
||||
|
||||
if Data.map_conversion:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
|
||||
for key, value in Data.map_conversion.items():
|
||||
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or value is None:
|
||||
continue
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=key)
|
||||
row.label(text=str(value))
|
||||
if key == "XAxisOrdinate":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Angle")
|
||||
row.label(
|
||||
text=str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
Data.map_conversion["XAxisAbscissa"], Data.map_conversion["XAxisOrdinate"]
|
||||
),
|
||||
3,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if Data.true_north:
|
||||
row = self.layout.row()
|
||||
row.label(text="True North", icon="LIGHT_SUN")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Vector")
|
||||
row.label(text=str(Data.true_north[0:2])[1:-1])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Angle")
|
||||
row.label(text=str(round(ifcopenshell.util.geolocation.yaxis2angle(*Data.true_north[0:2]), 3)))
|
||||
|
||||
|
||||
class BIM_PT_gis_utilities(Panel):
|
||||
bl_idname = "BIM_PT_gis_utilities"
|
||||
bl_label = "Georeferencing Utilities"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user