Compare commits
880 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c352181f8c | |||
| 3a1416536a | |||
| 4eec863cad | |||
| 96bcd31d99 | |||
| b09ff828de | |||
| 3cb68ca388 | |||
| c95d3693ea | |||
| 67d2ab2577 | |||
| 970b24b63b | |||
| fdaae048c2 | |||
| 4da5a38237 | |||
| 0cf2d7e05e | |||
| 3122cf21db | |||
| 4bf25d5d9c | |||
| dc10c7a360 | |||
| 94e082fc7d | |||
| 9671df8491 | |||
| 8aa38917eb | |||
| 0774100606 | |||
| 95b48b02e4 | |||
| c5de1d8727 | |||
| 7e09ab0ced | |||
| eb3e137a6e | |||
| f528ad9744 | |||
| 650f9eb7f5 | |||
| a78d6f42e5 | |||
| 4326a74c64 | |||
| 08fc62d4d9 | |||
| ae298de8dc | |||
| a24c4b269a | |||
| 083d9d3e3e | |||
| 12e7ed356c | |||
| 3f15dd71b1 | |||
| 42448cae4e | |||
| 7e28d193ec | |||
| 8f2b32532c | |||
| f0a00cbc94 | |||
| fd73b2cf0a | |||
| e5b3455533 | |||
| e677355438 | |||
| 93fd3f97e3 | |||
| 910d189f35 | |||
| c8d6d12442 | |||
| 1f3ba3d09d | |||
| f67941221e | |||
| 8252ef82ad | |||
| 3b825e35ae | |||
| 4a4912e817 | |||
| 5a38604ec2 | |||
| b15e0d0e2b | |||
| 5a5f52a5c2 | |||
| 816a690519 | |||
| 60303c6560 | |||
| cc88e6f112 | |||
| 5f1b5d0c63 | |||
| b54caa8967 | |||
| 73e048a9a8 | |||
| 316a2b7f53 | |||
| 42edcda8bd | |||
| 92432f453d | |||
| 4b6ebe7b1c | |||
| 3f6d25ed43 | |||
| d4b2cc7f2c | |||
| 53c050c6ed | |||
| 3213af2f48 | |||
| 85122ba184 | |||
| b8791d43aa | |||
| 6a82f27d48 | |||
| 4e2400952a | |||
| de1dae8b6e | |||
| dd127db969 | |||
| 194bc53bdb | |||
| 8c3e29c351 | |||
| 6e55a04550 | |||
| 9333839267 | |||
| 280236290e | |||
| c5773b5665 | |||
| 96babc4506 | |||
| f8acde0801 | |||
| 32c101cf74 | |||
| 2a1aa535ba | |||
| 02903c4929 | |||
| d80e26e4ab | |||
| a88b83306e | |||
| 9577c40386 | |||
| f54a5cf6e0 | |||
| abcb492e5a | |||
| 4d447c169f | |||
| b465605b63 | |||
| fb6dc45b1b | |||
| b2aca0ae36 | |||
| 03831d440c | |||
| ac4679a376 | |||
| 21fee721be | |||
| 6bcf00ef31 | |||
| 952d1c3b79 | |||
| 2d7a90bd2b | |||
| b14de15a68 | |||
| 29ecba293b | |||
| 8396c68e75 | |||
| 01c6a0d9be | |||
| 99d8a225a9 | |||
| 89f13aa1e4 | |||
| f089d03dc6 | |||
| a4366ef151 | |||
| 9483abcdd5 | |||
| 200a343746 | |||
| 2313fa00cc | |||
| 99c5f895fc | |||
| fcd5908ac9 | |||
| dfb980e0d1 | |||
| 445e77bac0 | |||
| 265ebaebb8 | |||
| 07fa7003fb | |||
| 3a90d664fa | |||
| 3f28ae18c0 | |||
| 9ea6499b5a | |||
| 11c3a6e547 | |||
| e7f3856d64 | |||
| 7e7d576855 | |||
| cfa1428a75 | |||
| d4ee74455c | |||
| 07d2cc62c1 | |||
| 8aff4badf7 | |||
| aa3325c010 | |||
| 9b13cd8d5f | |||
| 0fe32c0c75 | |||
| efc43c0fe9 | |||
| 5bfaa32c7c | |||
| 38e4ccdb0a | |||
| 7b71a7a866 | |||
| b5ee20cb7a | |||
| cc8da4abf4 | |||
| f2c4b20187 | |||
| 7c460bcf29 | |||
| 31ddb0f2c5 | |||
| c2cad06cfa | |||
| 421b58850c | |||
| 7c89aa1098 | |||
| be7e85fc2d | |||
| d91442de59 | |||
| 9cd3d73a23 | |||
| 0cf3c97eb2 | |||
| d49095844a | |||
| be65f77311 | |||
| 2081dc4c08 | |||
| bd16345b76 | |||
| 3264f338c7 | |||
| 0c06c67f2d | |||
| a66ba6a6f9 | |||
| 80d8665992 | |||
| 337ce89919 | |||
| 1237a8ce97 | |||
| c3c9ed2975 | |||
| 87cb1c69f4 | |||
| 3aa7f8e4c8 | |||
| 2c301d734b | |||
| 1705e6f565 | |||
| 2cd7b08c47 | |||
| 647a5105b5 | |||
| 9fc0baf175 | |||
| ca502cc293 | |||
| f70ab060e3 | |||
| 6fb3c28f1a | |||
| c79eb60959 | |||
| 538419a23f | |||
| eb19165ebc | |||
| 1138ffee7e | |||
| 0f3416774c | |||
| 1ba7977587 | |||
| 5b1b8e8f50 | |||
| 78811d331b | |||
| 4aa91cfa7f | |||
| c099a6cb75 | |||
| 029b402d11 | |||
| a243cdd6c6 | |||
| 1fae835de9 | |||
| f0c7ff1e57 | |||
| 460fd5ca87 | |||
| 054c0a9a27 | |||
| 5af1baa48b | |||
| 0e5d2cc0c9 | |||
| 48148f00a3 | |||
| 564c4b7644 | |||
| 33c53a2c9a | |||
| c049007ee8 | |||
| 05345e8ddf | |||
| 461b78665a | |||
| 6160d65d18 | |||
| d526314190 | |||
| 85bac37bed | |||
| e38184b152 | |||
| a0c8d87ad0 | |||
| 8686462eed | |||
| 6cd4a2d78b | |||
| 2c6cb2fbb2 | |||
| a60dfd8dd5 | |||
| 1b1fd1e6c5 | |||
| 1e60f23880 | |||
| eb7babb1b0 | |||
| 5797937825 | |||
| 28336140ca | |||
| ca03a88601 | |||
| 0303d217bf | |||
| 4184f2bf46 | |||
| 3b0429ace9 | |||
| baf75f552e | |||
| 4ea0c73081 | |||
| 33852be3f1 | |||
| 48d476c2b3 | |||
| 9db07883b7 | |||
| 7af53c543d | |||
| e7f6ed73ac | |||
| 8afa1a4f7d | |||
| 1dd85f6c48 | |||
| 8bd826fb20 | |||
| cbc6eefca7 | |||
| 4f86dd143a | |||
| c45c73c563 | |||
| 38f1c622ac | |||
| 38170db32a | |||
| 43d0c098a0 | |||
| 533111a1fb | |||
| 0bba31e5f9 | |||
| 9b9f0058f9 | |||
| 0c8273eb70 | |||
| c072554f21 | |||
| 18c9a57bea | |||
| eaa3301b8f | |||
| efb89acfa3 | |||
| 96256d365a | |||
| fa6ec25a39 | |||
| 44afa0df5d | |||
| c478e5bf1b | |||
| ea530858bd | |||
| 0b4cfea3a9 | |||
| e909f25432 | |||
| 2985bba157 | |||
| a1ba61636d | |||
| 1b5c20e14f | |||
| a43fae68b6 | |||
| fdd03d7294 | |||
| 2049bdd976 | |||
| d45a439f6c | |||
| 8fa5873e7e | |||
| 3fa8fce994 | |||
| b8aa90de00 | |||
| dcc1ed5607 | |||
| 2bfff12988 | |||
| 074a55b816 | |||
| a53fd97e5a | |||
| b39a0d1a7c | |||
| 44b786a21b | |||
| bddc64cad3 | |||
| 3713ec00f9 | |||
| b5a58998d5 | |||
| ca3b4d17ba | |||
| 983a1530b8 | |||
| 74e18a56ce | |||
| 05834d071f | |||
| 8f70f462cc | |||
| 456f45b346 | |||
| 53a9175802 | |||
| 379d6d9392 | |||
| 38b708ff1f | |||
| 233e73f9f6 | |||
| db85aa90d3 | |||
| 6fb1e34ab4 | |||
| 9cc683ea6e | |||
| e343494ab4 | |||
| bc9e403a58 | |||
| 8ffd9dc1f9 | |||
| 57f6c07240 | |||
| f518bbfb00 | |||
| 0f34420834 | |||
| 3ef6077a0d | |||
| 3c26cb672b | |||
| 673f757a51 | |||
| 578ab140cc | |||
| 96902bf38a | |||
| b5133c6874 | |||
| 5dfe85691b | |||
| 3b7bf8844a | |||
| 8038b77504 | |||
| b64601ee0a | |||
| 320bb7a086 | |||
| ad135f70f1 | |||
| 15b24d8ba4 | |||
| 3c2b25d923 | |||
| 3282d75aa5 | |||
| 72fc617196 | |||
| 47cad88c38 | |||
| e81dd9c7df | |||
| d16f92a445 | |||
| 8be79f9aee | |||
| ccc6f2c4c6 | |||
| deb0597000 | |||
| 348373d47e | |||
| 9e22e9486a | |||
| 3cf9017f27 | |||
| 21479482b4 | |||
| 97c660fec8 | |||
| 32af2025ec | |||
| ffc82dfb1e | |||
| 2a69af43f6 | |||
| 5c9984a12a | |||
| c938d6ae9b | |||
| 63d65f0c39 | |||
| 472a66b7ed | |||
| ddaed9e93d | |||
| 4d6eba0a06 | |||
| ed09b7ba31 | |||
| 2c2954b11a | |||
| 5317746a82 | |||
| 5af7e95bee | |||
| 618d902a57 | |||
| 68baa416fa | |||
| 7f212963d5 | |||
| d3a43c6e81 | |||
| a44a13f04c | |||
| 126ba8d88f | |||
| caf04200ce | |||
| 05f3cd41e2 | |||
| 80bc4cdb1b | |||
| dd7a47ab99 | |||
| 4eaf0e7a9c | |||
| 3db252bcc0 | |||
| d03685abfa | |||
| 3e4b1d95cb | |||
| b151ec2b0e | |||
| f909adc2dc | |||
| 913a428794 | |||
| fe6d9ae926 | |||
| 4a679812ca | |||
| 6be87c035f | |||
| 6ce8a3621a | |||
| 60ea856e51 | |||
| 419502ddb7 | |||
| 9f530d0578 | |||
| 524483e9cc | |||
| 7116a9f71a | |||
| 868ced2990 | |||
| 6a8292d24c | |||
| fca4d9dea0 | |||
| 89d5c44855 | |||
| 3e99393895 | |||
| 098ab2c237 | |||
| 94ff8349d3 | |||
| d333cb1b78 | |||
| 3f4c4a8e52 | |||
| b103e0dde5 | |||
| 883b8a523c | |||
| 8c0b06b36e | |||
| 72397f3aff | |||
| 77821caae4 | |||
| 10138e28c9 | |||
| f9ce0903f7 | |||
| a2c0045a40 | |||
| 3e10f52965 | |||
| fd8aa6d142 | |||
| ddb38b2ea0 | |||
| 74174b1e09 | |||
| 96179db7ad | |||
| 116252f857 | |||
| 538024e035 | |||
| e9670a6906 | |||
| c7428dd160 | |||
| bf9d46b506 | |||
| 7ee589cfc2 | |||
| 3083b66f61 | |||
| 074fe0d3a6 | |||
| 46e1a46dd2 | |||
| 677a9d715c | |||
| a210cf654e | |||
| 44e4ee70ae | |||
| 27e6d702e9 | |||
| fb76f985c7 | |||
| 9279aeccf6 | |||
| 92a1739d93 | |||
| dfba9827a6 | |||
| 19e007a67c | |||
| d3ef3f1501 | |||
| f2e9f76631 | |||
| 52739418d8 | |||
| 7f1fddf426 | |||
| ee4f48b566 | |||
| 1d47ad3cd4 | |||
| 36a7985f5a | |||
| e129f4afa9 | |||
| 8fd4351195 | |||
| 01f25528e8 | |||
| 3222c9ce99 | |||
| 28e6bb9e3d | |||
| 5884af0678 | |||
| f88a1038a8 | |||
| cafe8ce5f2 | |||
| 8569bcf350 | |||
| e38759c39d | |||
| 50ed8eccfd | |||
| d63d62ddd6 | |||
| 0bdb4b9fcf | |||
| 7e7c54a535 | |||
| 817fdead9b | |||
| 68a43b92ee | |||
| 595c72e08d | |||
| 5709efbdcb | |||
| 67952ed159 | |||
| 000e3acb2b | |||
| b337d5a174 | |||
| 6b0bdc564d | |||
| ab5db2fa23 | |||
| 810c71dfa2 | |||
| 5b281260f8 | |||
| 07264dafb1 | |||
| 9572fef222 | |||
| 1bbbc82b72 | |||
| aa6d03ade2 | |||
| 4c40fc1629 | |||
| 90e5b5576d | |||
| 531805fefb | |||
| 2852f53afa | |||
| 8b06aa9e0b | |||
| 52ea69bcec | |||
| 9ebe0aaa95 | |||
| 1a735c4bef | |||
| 482788a46f | |||
| a30a8cd920 | |||
| c2e7c27421 | |||
| 2f24343f8f | |||
| d259e0f490 | |||
| c09ed228c0 | |||
| d63be20c8b | |||
| f8c7f48674 | |||
| 258ca7fb8c | |||
| ec38fe2edc | |||
| 5a87ab8171 | |||
| d23d382854 | |||
| 30270db0c3 | |||
| 6d78ac4918 | |||
| 81016f3110 | |||
| 189e566740 | |||
| 5f7e4793c2 | |||
| be286c4d9d | |||
| 5ba542488a | |||
| 39960998de | |||
| 92642e6a6a | |||
| 78d33437ea | |||
| 65b3960ab3 | |||
| e81848dfba | |||
| fe9cda8ba1 | |||
| 49221739cf | |||
| c6966f9b39 | |||
| f08ed629b5 | |||
| ba675c0d28 | |||
| 3a80722621 | |||
| 9b77bb6e37 | |||
| 7a6df49348 | |||
| 666e484b2b | |||
| 256f40ed44 | |||
| 8eccb1a5ed | |||
| ce3490824d | |||
| 43c74010a5 | |||
| 6c991ed356 | |||
| c01c2a737a | |||
| c9c11a2905 | |||
| e5037dc41a | |||
| acd9250028 | |||
| a3dc477921 | |||
| 6246dda8d6 | |||
| 2683bcba20 | |||
| 97f2aa976e | |||
| 9b96073ac0 | |||
| 7efd9cea2a | |||
| c917881a8e | |||
| fa46eb140f | |||
| b8ea42540e | |||
| 671ab78c76 | |||
| a8550cea26 | |||
| 4f047274bf | |||
| eb04a7d579 | |||
| 84096529e1 | |||
| 3350f31312 | |||
| 0d79963ed8 | |||
| 43a8fa8bcb | |||
| ca8f3cf1e6 | |||
| 313a1c5705 | |||
| 53f6454784 | |||
| 2c0df1d4f5 | |||
| e384dbd548 | |||
| d22a0c7cb8 | |||
| 02a62a943c | |||
| 0625e4153c | |||
| 03f5feca88 | |||
| 85e3c6588f | |||
| 6e913a0ff9 | |||
| ff4e58a8ff | |||
| 1db028fa7b | |||
| 69a7efdab4 | |||
| 20dcb68009 | |||
| 6b0b64b869 | |||
| 09631e9565 | |||
| e9bb70f56b | |||
| bd008bdefe | |||
| 03d4782cb7 | |||
| 49c555c508 | |||
| c51c5651b2 | |||
| 364c2286d1 | |||
| 37d20eb360 | |||
| dd6912bdaa | |||
| a1fe68a17e | |||
| 3ec79cac3f | |||
| dbc6f521fe | |||
| c52f851d79 | |||
| 3380158f55 | |||
| 4e9a5f246f | |||
| 664c49a07d | |||
| 06b9cc6b38 | |||
| b9ea741e5e | |||
| 0098e38c77 | |||
| ece796fc2d | |||
| de4659570a | |||
| 62236c44ae | |||
| ce5db914db | |||
| 12699f29b7 | |||
| 54e326983f | |||
| 5fb40a390c | |||
| 0cb8716c6a | |||
| 099013fa05 | |||
| 4a8843b6bb | |||
| 33d15b42b7 | |||
| 44581c77ac | |||
| e848b249a6 | |||
| f85c3ae24c | |||
| 7c633fab31 | |||
| f716f8a357 | |||
| d156927e13 | |||
| 2ef1f4d812 | |||
| 78a36ab429 | |||
| 763a38bc9c | |||
| a23c88a53e | |||
| f6bf626349 | |||
| b66792f981 | |||
| 195af0c850 | |||
| ee56e06d86 | |||
| 146dd1e74d | |||
| 6d47cf3a51 | |||
| 9b6d53fe38 | |||
| 6902e2e2c6 | |||
| 82d136b9b3 | |||
| b8dd0450d1 | |||
| 85d76c084b | |||
| c658f7610e | |||
| 3fad28572b | |||
| 455fe80497 | |||
| 3f46d54ad9 | |||
| 89e3276c90 | |||
| bb9eba07c0 | |||
| 6d4573fb58 | |||
| 24890dd001 | |||
| 65d599bbd9 | |||
| ebe5460a9f | |||
| 01bc29c5b2 | |||
| 5ad6d455f9 | |||
| cef1c86f6d | |||
| d1caa3242f | |||
| 4f7bfec026 | |||
| df6ad70345 | |||
| a7a3ccb23a | |||
| b65f2ca062 | |||
| f991b053d3 | |||
| 47a25ca8b9 | |||
| a3f27830c5 | |||
| 1a5b5dd303 | |||
| f529bc37ee | |||
| 825ffe03b0 | |||
| 622ff05247 | |||
| d638405ff8 | |||
| c0f547c6e4 | |||
| 3accc1dbd6 | |||
| 6eab9fb593 | |||
| 2708133493 | |||
| c5e6c3e2c7 | |||
| d7b27c0620 | |||
| 75ffc4454d | |||
| a1817e8a61 | |||
| b4b334145f | |||
| 04535d27ec | |||
| 3c41f6b0f8 | |||
| a17c44c076 | |||
| eccf72ee8a | |||
| 9c9bec9c4e | |||
| 71c1cd6a9e | |||
| 0504556e7a | |||
| e9a18d030e | |||
| 6d725f60fa | |||
| 72206bc441 | |||
| c0eb9addfd | |||
| ff3a98234d | |||
| ebf6b9fc88 | |||
| c2e4b5f1fa | |||
| 9728c4d345 | |||
| f0d6500539 | |||
| 124ccf0404 | |||
| df03f7ad29 | |||
| ff8fc898a1 | |||
| 2d62d8181a | |||
| 6853735bb2 | |||
| 1c3980b6f0 | |||
| 5c224150ec | |||
| 18719c16df | |||
| ddb8abff0f | |||
| f9221717ff | |||
| da6945b352 | |||
| 62cdf76a5a | |||
| fae607f8ce | |||
| 468192e849 | |||
| edd01637e2 | |||
| 604fbc4bc9 | |||
| 4617d9f234 | |||
| 41486ba08a | |||
| 2120653104 | |||
| 43869c33f6 | |||
| fcfdc179df | |||
| 30c164d223 | |||
| b28d8b0cca | |||
| 4b5907f5dd | |||
| 78a27ef44c | |||
| 528e27234c | |||
| 8ac885ced1 | |||
| 63872abaef | |||
| 5ba155eb17 | |||
| 2ec3c4c06b | |||
| 6f55cd5169 | |||
| f5f88f4b97 | |||
| d5b92c0fea | |||
| 9355969110 | |||
| 6c998e0b8a | |||
| 51f8d648ad | |||
| a26539d6a5 | |||
| a9f27fcf17 | |||
| 28e5af853d | |||
| a7b3b5261f | |||
| 0dac56dbdf | |||
| 0992f43f11 | |||
| c1c50f8868 | |||
| d733960068 | |||
| 66fa3f0096 | |||
| cbf7b0a77a | |||
| 91f2f9438f | |||
| fcbb6e60ac | |||
| f33d613f9d | |||
| 849dfbed28 | |||
| 8a4bc34fad | |||
| aa18c1b7d6 | |||
| ae45a9b9a5 | |||
| 7aa75d98d3 | |||
| ff6db5f24d | |||
| de7c54b771 | |||
| 7fbf317ad2 | |||
| 0471f17d9a | |||
| bf134eb0f4 | |||
| 7ccb6ee110 | |||
| 9076043a5c | |||
| 1c0fcf7b40 | |||
| 4fdcb87a28 | |||
| 246460dec4 | |||
| a89a40e637 | |||
| c1fcf2528e | |||
| d4f0495caa | |||
| 669295d4ac | |||
| e4a27d49c9 | |||
| e0e8281a1e | |||
| 1cdf695891 | |||
| 580678c2c1 | |||
| a038ae434b | |||
| 698ef40ada | |||
| 276077964d | |||
| 45080a8a1f | |||
| dd348de0b7 | |||
| 61e510e45c | |||
| 05068754b9 | |||
| de561b78e0 | |||
| e6e1978584 | |||
| 0b7bdc0f9d | |||
| e868186897 | |||
| 5450233f8e | |||
| 8e215382f7 | |||
| 4149292a13 | |||
| 117f663f48 | |||
| 960610325f | |||
| ab50609854 | |||
| c203b78d34 | |||
| 5633445008 | |||
| 22ceb35f47 | |||
| 06a988e141 | |||
| 2dc29978f6 | |||
| 10b502512f | |||
| 2d0c44e25a | |||
| 3e52d93db0 | |||
| 07bee4acfa | |||
| 0da5d807b8 | |||
| 657aafbd2e | |||
| 04449d08e8 | |||
| 2b710ffbde | |||
| 7fbe365599 | |||
| f49b9f6781 | |||
| 901318b73b | |||
| 8545d870c0 | |||
| 3124d306d6 | |||
| 8e529f3f10 | |||
| 1ff45165d9 | |||
| 05256fc0f4 | |||
| be9ef33932 | |||
| fc424ebd6b | |||
| 030f3cf307 | |||
| c8eb933453 | |||
| 208132785f | |||
| cfa435e9c8 | |||
| 515e972906 | |||
| 100b0637ac | |||
| 2d8ec8a6bf | |||
| 02461233a6 | |||
| dcac1e69ca | |||
| b118400cc6 | |||
| bc430b80f6 | |||
| d2d3addf10 | |||
| 683e316088 | |||
| e279fee518 | |||
| 93c5906ad7 | |||
| 6a06607f3f | |||
| 9f73bd43b4 | |||
| 5f7d92aeb0 | |||
| c754d15da8 | |||
| 13f5d2d5de | |||
| 280eac6127 | |||
| ea0e70ef0d | |||
| a5af8807ee | |||
| 0bae861b6a | |||
| b32bbb89a1 | |||
| 109149a0be | |||
| 4bf4622498 | |||
| a4c054ef62 | |||
| 1141fd5784 | |||
| 0aa8a19b6d | |||
| b46f1647d2 | |||
| 72d9bc9256 | |||
| b67d56ada1 | |||
| d013d6ed09 | |||
| 4bb549f3e9 | |||
| d772299871 | |||
| 081ca81a7e | |||
| 2ad2a102cb | |||
| 52e8ac386e | |||
| f69de3324a | |||
| 04b3b9b60e | |||
| f64e1dbd7f | |||
| 341451f59b | |||
| 80b8da629d | |||
| 78e5016619 | |||
| 3406c23f98 | |||
| ee308d294f | |||
| 955a9af461 | |||
| 34e8d9563d | |||
| e5b5403eaa | |||
| 47cf2ea29e | |||
| 9d8074f1fe | |||
| 3fd944a956 | |||
| 566512f7bc | |||
| 1a84967b1b | |||
| 41afb26d31 | |||
| 818e8010a4 | |||
| 58ee049a8f | |||
| 54fa45850a | |||
| fbadcc017b | |||
| 6337698494 | |||
| 2e6a70b338 | |||
| e70f675f20 | |||
| 13d0b27ef4 | |||
| 40b112cd48 | |||
| 14c0400f73 | |||
| 4733c82b82 | |||
| 7bccf10fec | |||
| 918915aeae | |||
| 295568453c | |||
| 56ff0e7472 | |||
| e779a57a09 | |||
| 6d817703aa | |||
| c551275db2 | |||
| 9ffe92eae5 | |||
| f19e334cd5 | |||
| a5b8728aec | |||
| a8c99cebaf | |||
| 9ae2c6352a | |||
| 0bc849be5a | |||
| a99df36351 | |||
| 525e6debd5 | |||
| d6cb94c6f1 | |||
| c437ba8f9d | |||
| aee7fbcc79 | |||
| 456adf453d | |||
| d2e9194636 | |||
| 9907651d4a | |||
| bb2f59e2f8 | |||
| fec295d021 | |||
| cb08c19942 | |||
| b1b121ebc8 | |||
| ba9c2a3280 | |||
| 575de12d79 | |||
| 503f0399ff | |||
| 871ad1ab7f | |||
| 58859ef8c3 | |||
| 52062bc7e8 | |||
| 8fdbe30dc5 | |||
| f438c48c23 | |||
| 6478601f79 | |||
| 816059eefd | |||
| 3daf4203d1 | |||
| c1b53859ee | |||
| 63f85beb7e | |||
| bdc6fcb66f | |||
| 4577b7bf60 | |||
| 33f4461a87 | |||
| c78e7b3ba8 | |||
| 699ccddda3 | |||
| a9a48bdd1d | |||
| 8ed395a29f | |||
| 63bfe761bf | |||
| f8c11e3af7 | |||
| 993ae71968 | |||
| 81b8f7e39b | |||
| b1e897197b | |||
| 46cf1362f6 | |||
| f03992dcde | |||
| aedc1751b6 | |||
| 26cbd82da1 | |||
| 516503b9ca | |||
| 23be5858b5 | |||
| 776842a064 | |||
| bb090bd9a0 | |||
| 0abe9f0559 | |||
| 07bfe296dd | |||
| d666a687dc | |||
| acde4144bc | |||
| b41e90ead6 | |||
| 68fd0c0cf5 | |||
| 199be3363f | |||
| dd3fc79fb9 | |||
| d384b3d7df | |||
| 04209c78b9 | |||
| a7719cc4b1 | |||
| 3d0d2ec8fb | |||
| 1fa1a9e3d3 | |||
| 464b0c2ebc | |||
| 179a970974 | |||
| 7fb77a794e | |||
| 1d74306bc2 | |||
| 0c6a3eafd2 | |||
| 292cbf55ad | |||
| e17ae4ef61 | |||
| dba6a8d0b4 | |||
| 13725411c9 | |||
| b0e9f389a5 | |||
| f6128febe9 | |||
| fc2e12bff6 | |||
| e455f87305 | |||
| 84fc198bbe | |||
| d39e97f6a7 | |||
| 96d509698b | |||
| cefa66cdf9 | |||
| 8198df3fa6 | |||
| 4ebdb5e339 | |||
| 8dca84dd07 | |||
| 4cee76ddc3 | |||
| 8b34eb41fd | |||
| 170cd3b5f5 | |||
| b8c4dd971c | |||
| f26d43e0cd | |||
| 9508858fcb | |||
| 3aa36dd4b7 | |||
| f21005c29e |
@@ -28,7 +28,7 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
|
||||
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
-
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DBUILD_PACKAGE=On \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
|
||||
@@ -23,12 +23,13 @@ jobs:
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py37, py39]
|
||||
pyver: [py37, py39, py310]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
python3-all-dev python3-pip \
|
||||
swig libpcre3-dev libxml2-dev \
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
- name: ccache
|
||||
@@ -53,11 +53,10 @@ jobs:
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
@@ -83,6 +82,8 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install src/bcf
|
||||
sudo /usr/bin/python -m pip install pytest
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -18,14 +18,14 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
submodules: recursive
|
||||
-
|
||||
name: Update ubuntu
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
|
||||
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
|
||||
libhdf5-dev
|
||||
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DBUILD_PACKAGE=On \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
/_installed-vs*-x*/
|
||||
/build/
|
||||
|
||||
# output directories
|
||||
/cmake/out/
|
||||
/docs/out/
|
||||
/src/examples/out/
|
||||
/src/ifcmax/out/
|
||||
/src/ifcwrap/out/
|
||||
/src/qtviewer/out/
|
||||
|
||||
|
||||
/win/BuildDepsCache*.txt
|
||||
# IfcExpressParser residue
|
||||
/src/ifcexpressparser/express_parser.py
|
||||
@@ -13,6 +22,7 @@ __pycache__
|
||||
*.py.bak
|
||||
# Visual Studio Code files
|
||||
.vscode
|
||||
.vs
|
||||
# PyCharm files
|
||||
.idea
|
||||
|
||||
@@ -57,3 +67,6 @@ htmlcov
|
||||
|
||||
# Blender
|
||||
*.blend1
|
||||
|
||||
# Sphinx
|
||||
_build/
|
||||
|
||||
@@ -8,3 +8,9 @@
|
||||
[submodule "src/svgfill"]
|
||||
path = src/svgfill
|
||||
url = https://github.com/IfcOpenShell/svgfill
|
||||
[submodule "src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files
|
||||
url = https://github.com/atomczak/Sample-BIM-Files
|
||||
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
||||
url = https://github.com/atomczak/Sample-BIM-Files
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
IfcOpenShell
|
||||
============
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with the Industry Foundation Classes ([IFC])
|
||||
file format. Currently supported IFC releases are [IFC2x3 TC1] and [IFC4 Add2 TC1].
|
||||
file format. Extensive geometric support is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1].
|
||||
Support for parsing is provided for IFC4x1, IFC4x2, and the IFC4x3 release candidates. Extending with support for
|
||||
arbitrary IFC schemas is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
For more information, see
|
||||
* [http://ifcopenshell.org](http://ifcopenshell.org)
|
||||
@@ -9,11 +11,13 @@ For more information, see
|
||||
|
||||
[](https://travis-ci.org/IfcOpenShell/IfcOpenShell)
|
||||
|
||||
[](https://opencollective.com/opensourcebim/)
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
* Git
|
||||
* CMake (2.6 or newer)
|
||||
* Windows: [Visual Studio] 2008 or newer with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
|
||||
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
|
||||
* *nix: GCC 4.7 or newer, or Clang (any version)
|
||||
|
||||
Dependencies
|
||||
@@ -33,7 +37,11 @@ Dependencies
|
||||
Building IfcOpenShell
|
||||
---------------------
|
||||
|
||||
**Note:** The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
|
||||
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
|
||||
|
||||
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
|
||||
|
||||
Note 3: Be careful with special characters is the path when using the nix or win build scripts, because the OpenCASCADE build will fail on paths containing ++ and likely other situations.
|
||||
|
||||
### Compiling on Windows
|
||||
The preferred way to fetch and build this project's dependencies is to use the build scripts
|
||||
@@ -81,17 +89,26 @@ The following instructions are for Ubuntu, modify as required for other operatin
|
||||
can be experimented with and studied for pointers for other operating systems, but note that this script is not currently
|
||||
meant to be used for a typical IfcOpenShell workspace setup.
|
||||
|
||||
Note: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
|
||||
Note 1: It is recommeded to use OCCT for IfcOpenShell. You could use OCE as well, but sometimes it may lag behind OCCT and
|
||||
therefore not compile with the latest IfcOpenShell.
|
||||
|
||||
Note 2: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
|
||||
|
||||
**1)** Install most of the prerequisites and dependencies:
|
||||
|
||||
$ sudo apt-get install git cmake gcc g++ libboost-all-dev
|
||||
$ sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
|
||||
|
||||
**2a)** Either use an OCE package from your operating system's software repository
|
||||
**2a)** Either use an OCCT package from your operating system's software repository
|
||||
|
||||
$ sudo apt-get install libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev
|
||||
|
||||
**2b)** or, if OCCT is not available or the latest code is wanted, obtain and compile OCCT from https://dev.opencascade.org/release
|
||||
|
||||
**2c)** or, if you'd like to try using OCE, use the package from your operating system's software repository
|
||||
|
||||
$ sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
|
||||
|
||||
**2b)** or (if not available, or the latest code is wanted) compile OCE yourself (note that the build takes a long time):
|
||||
**2d)** or if OCE is not available, or the latest code is wanted, compile OCE yourself (note that the build takes a long time)
|
||||
|
||||
$ sudo apt-get install libftgl-dev libtbb2 libtbb-dev libgl1-mesa-dev libfreetype6-dev
|
||||
$ git clone https://github.com/tpaviot/oce.git
|
||||
@@ -101,8 +118,6 @@ 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 https://dev.opencascade.org/release
|
||||
|
||||
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
|
||||
|
||||
$ sudo apt-get install libpcre3-dev libxml2-dev
|
||||
@@ -128,7 +143,13 @@ dependencies' paths. `OCC_INCLUDE_DIR` might be needed to set also. `OPENCOLLADA
|
||||
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
|
||||
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
|
||||
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
|
||||
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/
|
||||
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_SUPPORT=Off
|
||||
$ make -j
|
||||
|
||||
If all worked out correctly you can now use IfcOpenShell. See the examples below.
|
||||
|
||||
@@ -60,9 +60,14 @@ if (${BUILD_CONVERT})
|
||||
OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
|
||||
endif()
|
||||
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
|
||||
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
|
||||
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
SET(CMAKE_BUILD_TYPE "Release")
|
||||
ENDIF()
|
||||
|
||||
# QtViewer requires Qt5
|
||||
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
|
||||
include(GNUInstallDirs)
|
||||
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
|
||||
message(STATUS "'IfcGeom' is required with current outputs")
|
||||
@@ -144,6 +149,7 @@ ELSE()
|
||||
MESSAGE(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
|
||||
ENDIF()
|
||||
add_definitions(-DWITH_GLTF)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Set INSTALL_RPATH for target
|
||||
@@ -200,6 +206,7 @@ find_package(LibXml2 REQUIRED)
|
||||
|
||||
if (IFCXML_SUPPORT)
|
||||
add_definitions(-DWITH_IFCXML)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
|
||||
endif()
|
||||
|
||||
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
@@ -270,6 +277,9 @@ ENDIF()
|
||||
SET(OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
|
||||
|
||||
# @todo investigate the exact conditions when this is necessary
|
||||
TKBin
|
||||
)
|
||||
|
||||
IF("${OCC_LIBRARY_DIR}" STREQUAL "")
|
||||
@@ -368,6 +378,8 @@ IF(COLLADA_SUPPORT)
|
||||
IF(COLLADASWStreamWriter_h)
|
||||
MESSAGE(STATUS "OpenCOLLADA header files found")
|
||||
ADD_DEFINITIONS(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
|
||||
SET(OPENCOLLADA_LIBRARY_NAMES
|
||||
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
|
||||
OpenCOLLADAStreamWriter UTF buffer ftoa
|
||||
@@ -436,13 +448,17 @@ if(HDF5_SUPPORT)
|
||||
else()
|
||||
set(lib_ext a)
|
||||
endif()
|
||||
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(debug_postfix "_debug")
|
||||
endif()
|
||||
|
||||
SET(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -450,21 +466,18 @@ if(HDF5_SUPPORT)
|
||||
# debian default
|
||||
|
||||
SET(HDF5_LIBRARIES
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.a
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.a
|
||||
/usr/lib/x86_64-linux-gnu/libsz.a
|
||||
/usr/lib/x86_64-linux-gnu/libaec.a
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.so
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.so
|
||||
/usr/lib/x86_64-linux-gnu/libsz.so
|
||||
/usr/lib/x86_64-linux-gnu/libaec.so
|
||||
z dl
|
||||
)
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_HDF5)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
|
||||
endif()
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
SET(CMAKE_BUILD_TYPE "Release")
|
||||
ENDIF()
|
||||
|
||||
if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
if(MSVC)
|
||||
# NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default,
|
||||
@@ -689,7 +702,7 @@ TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_
|
||||
if (BUILD_IFCGEOM)
|
||||
|
||||
# IfcGeom
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h)
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h ../src/ifcgeom/*.i)
|
||||
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
|
||||
@@ -781,21 +794,20 @@ INSTALL(TARGETS IfcGeomServer
|
||||
|
||||
endif()
|
||||
|
||||
if (ADD_COMMIT_SHA)
|
||||
find_package (Git)
|
||||
if (GIT_FOUND)
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} show -s --pretty=%D HEAD
|
||||
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
string(REPLACE ", " ";" git_branch_list "${git_branches}")
|
||||
string(REPLACE " -> " ";" git_branch_list "${git_branch_list}")
|
||||
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
|
||||
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
|
||||
if (NOT git_branch_candidate MATCHES "/")
|
||||
if (NOT git_branch_candidate MATCHES "^HEAD$")
|
||||
set(git_branch ${git_branch_candidate})
|
||||
endif()
|
||||
string(STRIP "${git_branch_candidate}" git_branch_candidate_2)
|
||||
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
set(git_branch ${git_branch_candidate_2})
|
||||
endif()
|
||||
endforeach()
|
||||
execute_process(
|
||||
@@ -803,11 +815,12 @@ if (GIT_FOUND)
|
||||
OUTPUT_VARIABLE git_sha
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
message(STATUS "IfcOpenShell branch ${git_branch}")
|
||||
message(STATUS "IfcOpenShell commit ${git_sha}")
|
||||
message(STATUS "IfcOpenShell branch: \"${git_branch}\"")
|
||||
message(STATUS "IfcOpenShell commit: \"${git_sha}\"")
|
||||
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
|
||||
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Documentation
|
||||
IF(BUILD_DOCUMENTATION)
|
||||
@@ -872,6 +885,9 @@ INSTALL(FILES ${SERIALIZERS_S_FILES}
|
||||
)
|
||||
endif()
|
||||
|
||||
IF(BUILD_QTVIEWER)
|
||||
ADD_SUBDIRECTORY(../src/qtviewer qtviewer)
|
||||
endif()
|
||||
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
.git
|
||||
|
||||
@@ -24,13 +24,19 @@ requirements:
|
||||
- swig >=3.0.9
|
||||
- libboost
|
||||
- icu
|
||||
- xmlschema
|
||||
- lxml
|
||||
- numpy
|
||||
run:
|
||||
- libgcc # [osx]
|
||||
- python
|
||||
- oce ==0.18.3
|
||||
- libboost
|
||||
- icu
|
||||
|
||||
- xmlschema
|
||||
- lxml
|
||||
- numpy
|
||||
|
||||
about:
|
||||
home: http://ifcopenshell.org
|
||||
license: LGPL
|
||||
|
||||
@@ -56,16 +56,10 @@ import subprocess as sp
|
||||
import shutil
|
||||
import tarfile
|
||||
import multiprocessing
|
||||
import platform
|
||||
import sysconfig
|
||||
|
||||
PYTHON_MAJOR = sys.version_info[0]
|
||||
|
||||
if PYTHON_MAJOR >= 3:
|
||||
from urllib.request import urlretrieve
|
||||
else:
|
||||
# Not Python 3 - today, it is most likely to be Python 2
|
||||
# But note that this might need an update when Python 4
|
||||
# might be around one day
|
||||
from urllib import urlretrieve
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -74,145 +68,121 @@ ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
logger.addHandler(ch)
|
||||
|
||||
PROJECT_NAME="IfcOpenShell"
|
||||
PYTHON_VERSIONS=["2.7.18", "3.5.5", "3.6.13", "3.7.10", "3.8.8", "3.9.2"]
|
||||
JSON_VERSION="v3.6.1"
|
||||
OCE_VERSION="0.18"
|
||||
OCCT_VERSION="7.5.3"
|
||||
BOOST_VERSION="1.71.0"
|
||||
PCRE_VERSION="8.41"
|
||||
LIBXML2_VERSION="2.9.9"
|
||||
SWIG_VERSION="3.0.12"
|
||||
OPENCOLLADA_VERSION="v1.6.68"
|
||||
HDF5_VERSION="1.8.22"
|
||||
PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
GMP_VERSION="6.1.2"
|
||||
MPFR_VERSION="3.1.5"
|
||||
CGAL_VERSION="5.2"
|
||||
PYTHON_VERSIONS = ["3.6.14", "3.7.12", "3.8.12", "3.9.7", "3.10.0"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.5.3"
|
||||
BOOST_VERSION = "1.71.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
SWIG_VERSION = "4.0.2"
|
||||
OPENCOLLADA_VERSION = "v1.6.68"
|
||||
HDF5_VERSION = "1.12.1"
|
||||
|
||||
GMP_VERSION = "6.1.2"
|
||||
MPFR_VERSION = "3.1.5" # latest is 4.1.0
|
||||
CGAL_VERSION = "5.3"
|
||||
|
||||
# binaries
|
||||
cp="cp"
|
||||
bash="bash"
|
||||
uname="uname"
|
||||
git="git"
|
||||
bunzip2="bunzip2"
|
||||
tar="tar"
|
||||
cc="cc"
|
||||
cplusplus="c++"
|
||||
autoconf="autoconf"
|
||||
automake="automake"
|
||||
yacc="yacc"
|
||||
make="make"
|
||||
cp = "cp"
|
||||
bash = "bash"
|
||||
git = "git"
|
||||
bunzip2 = "bunzip2"
|
||||
tar = "tar"
|
||||
cc = "cc"
|
||||
cplusplus = "c++"
|
||||
autoconf = "autoconf"
|
||||
automake = "automake"
|
||||
yacc = "yacc"
|
||||
make = "make"
|
||||
date = "date"
|
||||
curl="curl"
|
||||
wget="wget"
|
||||
strip="strip"
|
||||
curl = "curl"
|
||||
wget = "wget"
|
||||
strip = "strip"
|
||||
|
||||
# Helper function for coloured printing
|
||||
|
||||
NO_COLOR="\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
|
||||
BLACK_ON_WHITE="\033[0;30;107m"
|
||||
RED="\033[31m"
|
||||
GREEN="\033[32m"
|
||||
YELLOW="\033[33m"
|
||||
MAGENTA="\033[35m"
|
||||
NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
|
||||
BLACK_ON_WHITE = "\033[0;30;107m"
|
||||
RED = "\033[31m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
MAGENTA = "\033[35m"
|
||||
|
||||
def cecho(message, color=NO_COLOR):
|
||||
"""Logs message `message` in color `color`."""
|
||||
logger.info("%s%s\033[0m" % (color, message))
|
||||
logger.info(f"{color}{message}\033[0m")
|
||||
|
||||
def which(cmd):
|
||||
for path in os.environ["PATH"].split(":"):
|
||||
for path in os.getenv("PATH").split(":"):
|
||||
if os.path.exists(path) and cmd in os.listdir(path):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
def get_os():
|
||||
ret_value = sp.check_output([uname, "-s"]).strip()
|
||||
return ret_value
|
||||
|
||||
def to_pystring(x):
|
||||
""" Python 2 & 3 compatibility function for strings handling
|
||||
(to solve TypeError "Can't mix strings and bytes in path components" for Python 3).
|
||||
Reference https://github.com/hugsy/gef/issues/382 """
|
||||
res = str(x, encoding="utf-8") if PYTHON_MAJOR == 3 else x
|
||||
substs = [("\n","\\n"), ("\r","\\r"), ("\t","\\t"), ("\v","\\v"), ("\b","\\b"), ]
|
||||
for x,y in substs: res = res.replace(x,y)
|
||||
return res
|
||||
|
||||
# Set defaults for missing empty environment variables
|
||||
|
||||
USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
|
||||
|
||||
TOOLSET = None
|
||||
if get_os() == "Darwin":
|
||||
if platform.system() == "Darwin":
|
||||
# C++11 features used in OCCT 7+ need a more recent stdlib
|
||||
TOOLSET = "10.9" if USE_OCCT else "10.6"
|
||||
|
||||
try:
|
||||
IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"]
|
||||
except KeyError:
|
||||
IFCOS_NUM_BUILD_PROCS=multiprocessing.cpu_count() + 1
|
||||
|
||||
try:
|
||||
TARGET_ARCH = os.environ["TARGET_ARCH"]
|
||||
except KeyError:
|
||||
TARGET_ARCH = sp.check_output([uname, "-m"]).strip()
|
||||
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
|
||||
|
||||
CMAKE_DIR=os.path.realpath(os.path.join("..", "cmake"))
|
||||
CMAKE_DIR = os.path.realpath(os.path.join("..", "cmake"))
|
||||
|
||||
try:
|
||||
DEPS_DIR = os.environ["DEPS_DIR"]
|
||||
except KeyError:
|
||||
path = [b"..", b"build", sp.check_output(uname).strip(), TARGET_ARCH]
|
||||
if TOOLSET:
|
||||
path.append(TOOLSET)
|
||||
|
||||
DEPS_DIR = to_pystring(os.path.realpath(os.path.join(*path)))
|
||||
path = ["..", "build", platform.system(), platform.machine()]
|
||||
if TOOLSET:
|
||||
path.append(TOOLSET)
|
||||
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
|
||||
|
||||
DEPS_DIR = os.getenv("DEPS_DIR", DEFAULT_DEPS_DIR)
|
||||
|
||||
if not os.path.exists(DEPS_DIR):
|
||||
os.makedirs(DEPS_DIR)
|
||||
|
||||
try:
|
||||
BUILD_CFG=os.environ["BUILD_CFG"]
|
||||
except KeyError:
|
||||
BUILD_CFG="RelWithDebInfo"
|
||||
BUILD_CFG = os.getenv("BUILD_CFG", "RelWithDebInfo")
|
||||
|
||||
|
||||
# Print build configuration information
|
||||
|
||||
cecho ("""This script fetches and builds %s and its dependencies
|
||||
""" % (PROJECT_NAME,), BLACK_ON_WHITE)
|
||||
cecho (f"""This script fetches and builds {PROJECT_NAME} and its dependencies
|
||||
""", BLACK_ON_WHITE)
|
||||
cecho("""Script configuration:
|
||||
|
||||
""", GREEN)
|
||||
cecho("""* Target Architecture = %s""" % (TARGET_ARCH,), MAGENTA)
|
||||
cecho(" - Whether 32-bit (i686) or 64-bit (x86_64) will be built.")
|
||||
cecho("""* USE_OCCT = %r""" % (USE_OCCT,), MAGENTA)
|
||||
cecho(f"""* USE_OCCT = {USE_OCCT}""", MAGENTA)
|
||||
if USE_OCCT:
|
||||
cecho(" - Compiling against official Open Cascade")
|
||||
else:
|
||||
cecho(" - Compiling against Open Cascade Community Edition")
|
||||
cecho("* Dependency Directory = %s" % (DEPS_DIR,), MAGENTA)
|
||||
cecho(" - The directory where %s dependencies are installed." % (PROJECT_NAME,))
|
||||
cecho("* Build Config Type = %s" % (BUILD_CFG,), MAGENTA)
|
||||
cecho(f"* Dependency Directory = {DEPS_DIR}", MAGENTA)
|
||||
cecho(f" - The directory where {PROJECT_NAME} dependencies are installed.")
|
||||
cecho(f"* Build Config Type = {BUILD_CFG}", MAGENTA)
|
||||
cecho(""" - The used build configuration type for the dependencies.
|
||||
Defaults to RelWithDebInfo if not specified.""")
|
||||
|
||||
if BUILD_CFG == "MinSizeRel":
|
||||
cecho(" WARNING: MinSizeRel build can suffer from a significant performance loss.", RED)
|
||||
|
||||
cecho("* IFCOS_NUM_BUILD_PROCS = %s" % (IFCOS_NUM_BUILD_PROCS,), MAGENTA)
|
||||
cecho(f"* IFCOS_NUM_BUILD_PROCS = {IFCOS_NUM_BUILD_PROCS}", MAGENTA)
|
||||
cecho(""" - How many compiler processes may be run in parallel.
|
||||
""")
|
||||
|
||||
dependency_tree = {
|
||||
'IfcParse': ('boost', 'libxml2', 'hdf5'),
|
||||
'IfcGeom': ('IfcParse', 'occ', 'OpenCOLLADA', 'json', 'cgal'),
|
||||
'IfcConvert': ('IfcGeom', ),
|
||||
'OpenCOLLADA': ('libxml2', 'pcre'),
|
||||
'IfcParse': ('boost', 'libxml2', 'hdf5'),
|
||||
'IfcGeom': ('IfcParse', 'occ', 'json', 'cgal'),
|
||||
'IfcConvert': ('IfcGeom',),
|
||||
'OpenCOLLADA': ('libxml2', 'pcre'),
|
||||
'IfcGeomServer': ('IfcGeom',),
|
||||
'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
|
||||
'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
|
||||
'swig': ('pcre',),
|
||||
'boost': (),
|
||||
'libxml2': (),
|
||||
@@ -252,7 +222,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
|
||||
|
||||
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]:
|
||||
if which(cmd) is None:
|
||||
raise ValueError("Required tool '%s' not installed or not added to PATH" % (cmd,))
|
||||
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
|
||||
|
||||
# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
|
||||
download_tool_default = download_tool_py = "py"
|
||||
@@ -263,10 +233,10 @@ download_tool_git = "git"
|
||||
log_dir = os.path.join(DEPS_DIR, "logs")
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
LOG_FILE="%s.log" % (os.path.join(log_dir, to_pystring(sp.check_output([date, "+%Y%m%d"]).strip())),)
|
||||
LOG_FILE = os.path.join(log_dir, sp.check_output([date, "+%Y%m%d"], encoding="utf-8").strip()) + ".log"
|
||||
if not os.path.exists(LOG_FILE):
|
||||
open(LOG_FILE, "w").close()
|
||||
logger.info("using command log file '%s'" % (LOG_FILE,))
|
||||
logger.info(f"using command log file '{LOG_FILE}'")
|
||||
|
||||
def run(cmds, cwd=None):
|
||||
|
||||
@@ -276,43 +246,47 @@ def run(cmds, cwd=None):
|
||||
with leading and trailing whitespace removed.
|
||||
"""
|
||||
|
||||
logger.debug("running command %r in directory %r" % (" ".join(cmds), cwd))
|
||||
log_file_handle = open(LOG_FILE, "ab")
|
||||
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE)
|
||||
logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
|
||||
log_file_handle = open(LOG_FILE, "a")
|
||||
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
|
||||
stdout, stderr = proc.communicate()
|
||||
log_file_handle.write(stdout)
|
||||
log_file_handle.write(stderr)
|
||||
log_file_handle.close()
|
||||
logger.debug("command returned %r" % proc.returncode)
|
||||
logger.debug(f"command returned {proc.returncode}")
|
||||
|
||||
if proc.returncode != 0:
|
||||
print("-" * 70)
|
||||
print(stderr)
|
||||
print("-" * 70)
|
||||
raise Exception("Command `%s` returned exit code %d" % (" ".join(cmds), proc.returncode))
|
||||
raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")
|
||||
|
||||
return stdout.strip()
|
||||
|
||||
BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_")
|
||||
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
|
||||
|
||||
OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,)
|
||||
BOOST_LOCATION="https://boostorg.jfrog.io/artifactory/main/release/%s/source/" % (BOOST_VERSION,)
|
||||
OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
|
||||
BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"
|
||||
|
||||
# Helper functions
|
||||
|
||||
|
||||
def run_autoconf(arg1, configure_args, cwd):
|
||||
configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
|
||||
if not os.path.exists(configure_path):
|
||||
run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
|
||||
# Using `sh` over `bash` fixes issues with building swig
|
||||
run(["/bin/sh", "../configure"]+configure_args+["--prefix=%s" % (os.path.realpath("%s/install/%s" % (DEPS_DIR, arg1)),)], cwd=cwd)
|
||||
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
|
||||
run(["/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
|
||||
|
||||
|
||||
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
if cmake_dir is None:
|
||||
P=".."
|
||||
P = ".."
|
||||
else:
|
||||
P=cmake_dir
|
||||
run(["cmake", P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd)
|
||||
P = cmake_dir
|
||||
run(["cmake", P] + cmake_args + [f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
|
||||
|
||||
|
||||
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
"""Lazily clones the `git` repository denoted by `clone_url` into
|
||||
@@ -321,10 +295,10 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
`revision` after cloning or in the existing clone if `revision` is not
|
||||
`None`."""
|
||||
if not os.path.exists(target_dir):
|
||||
logger.info("cloning '%s' into '%s'" % (clone_url, target_dir))
|
||||
logger.info(f"cloning '{clone_url}' into '{target_dir}'")
|
||||
run([git, "clone", "--recursive", clone_url, target_dir])
|
||||
else:
|
||||
logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,))
|
||||
logger.info(f"directory '{target_dir}' already cloned. Pulling latest changes.")
|
||||
|
||||
# detect whether we are on a branch and pull
|
||||
if run([git, "rev-parse", "--abbrev-ref", "HEAD"], cwd=target_dir) != "HEAD":
|
||||
@@ -341,13 +315,13 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
linker flags."""
|
||||
check_dir = os.path.join(DEPS_DIR, "install", name)
|
||||
if os.path.exists(check_dir):
|
||||
logger.info( "Found existing %s, skipping" % (name,))
|
||||
logger.info(f"Found existing {name}, skipping")
|
||||
return
|
||||
build_dir = os.path.join(DEPS_DIR, "build")
|
||||
if not os.path.exists(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
logger.info("\rFetching %s... " % (name,))
|
||||
logger.info(f"\rFetching {name}... ")
|
||||
|
||||
if download_tool == download_tool_py:
|
||||
if no_append_name:
|
||||
@@ -359,16 +333,16 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
if not os.path.exists(download_path):
|
||||
urlretrieve(url, os.path.join(build_dir, download_path))
|
||||
else:
|
||||
logger.info("Download '%s' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping" % (download_path,))
|
||||
logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
|
||||
elif download_tool == download_tool_git:
|
||||
logger.info("\rChecking %s... " % (name,))
|
||||
logger.info(f"\rChecking {name}... ")
|
||||
git_clone_or_pull_repository(download_url, target_dir=os.path.join(build_dir, download_name), revision=revision)
|
||||
else:
|
||||
raise ValueError("download tool '%s' is not supported" % (download_tool,))
|
||||
raise ValueError(f"download tool '{download_tool}' is not supported")
|
||||
download_dir = os.path.join(build_dir, download_name)
|
||||
|
||||
if os.path.isdir(download_dir):
|
||||
extract_dir_name=download_name
|
||||
extract_dir_name = download_name
|
||||
extract_dir = os.path.join(build_dir, extract_dir_name)
|
||||
else:
|
||||
download_tarfile_path = os.path.join(build_dir, download_name)
|
||||
@@ -378,12 +352,12 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
compr = "bz2"
|
||||
else:
|
||||
raise RuntimeError("fix source for new download type")
|
||||
download_tarfile = tarfile.open(name=download_tarfile_path, mode="r:%s" % (compr,))
|
||||
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
|
||||
# tarfile seriously doesn't have a function to retrieve the root directory more easily
|
||||
extract_dir_name= os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
|
||||
extract_dir_name = os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
|
||||
#run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
|
||||
if extract_dir_name is None:
|
||||
extract_dir_name= run([bash, "-c", "tar -tf %s 2> /dev/null | head -n 1 | cut -f1 -d /" % (download_name,)], cwd=build_dir)
|
||||
extract_dir_name = run([bash, "-c", f"tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d /"], cwd=build_dir)
|
||||
extract_dir = os.path.join(build_dir, extract_dir_name)
|
||||
if not os.path.exists(extract_dir):
|
||||
run([tar, "-xf", download_name], cwd=build_dir)
|
||||
@@ -413,71 +387,66 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
shutil.rmtree(extract_build_dir)
|
||||
os.makedirs(extract_build_dir)
|
||||
|
||||
logger.info("\rConfiguring %s..." % (name,))
|
||||
logger.info(f"\rConfiguring {name}...")
|
||||
if mode == "autoconf":
|
||||
run_autoconf(name, build_tool_args, cwd=extract_build_dir)
|
||||
elif mode == "cmake":
|
||||
run_cmake(name, build_tool_args, cwd=extract_build_dir)
|
||||
else:
|
||||
raise ValueError()
|
||||
logger.info("\rBuilding %s... " % (name,))
|
||||
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "VERBOSE=1"], cwd=extract_build_dir)
|
||||
logger.info( "\rInstalling %s... " % (name,))
|
||||
logger.info(f"\rBuilding {name}... ")
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
|
||||
logger.info(f"\rInstalling {name}... ")
|
||||
run([make, "install"], cwd=extract_build_dir)
|
||||
logger.info( "\rInstalled %s \n" % (name,))
|
||||
logger.info(f"\rInstalled {name} \n")
|
||||
else:
|
||||
logger.info( "\rConfiguring %s..." % (name,))
|
||||
logger.info(f"\rConfiguring {name}...")
|
||||
run([bash, "./bootstrap.sh"], cwd=extract_dir)
|
||||
logger.info("\rBuilding %s... " % (name,))
|
||||
run(["./b2", "-j%s" % (IFCOS_NUM_BUILD_PROCS,)]+build_tool_args, cwd=extract_dir)
|
||||
logger.info("\rInstalling %s... " % (name,))
|
||||
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", "boost-%s" % BOOST_VERSION, "boost"))
|
||||
logger.info("\rInstalled %s \n" % (name,))
|
||||
logger.info(f"\rBuilding {name}... ")
|
||||
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir)
|
||||
logger.info(f"\rInstalling {name}... ")
|
||||
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
|
||||
logger.info(f"\rInstalled {name} \n")
|
||||
|
||||
cecho("Collecting dependencies:", GREEN)
|
||||
|
||||
# Set compiler flags for 32bit builds on 64bit system
|
||||
# TODO: This is untested
|
||||
|
||||
ADDITIONAL_ARGS=[]
|
||||
BOOST_ADDRESS_MODEL=[]
|
||||
if TARGET_ARCH == "i686" and run([uname, "-m"]).strip() == "x86_64":
|
||||
if get_os() == "Darwin":
|
||||
ADDITIONAL_ARGS=["-m32", "-arch i386"]
|
||||
else:
|
||||
ADDITIONAL_ARGS=["-m32"]
|
||||
BOOST_ADDRESS_MODEL=["architecture=x86", "address-model=32"]
|
||||
ADDITIONAL_ARGS = []
|
||||
BOOST_ADDRESS_MODEL = []
|
||||
|
||||
if get_os() == "Darwin":
|
||||
ADDITIONAL_ARGS=["-mmacosx-version-min=%s" % TOOLSET]+ADDITIONAL_ARGS
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
|
||||
|
||||
# If the linker supports GC sections, set it up to reduce binary file size
|
||||
# -fPIC is required for the shared libraries to work
|
||||
|
||||
CXXFLAGS=os.environ.get("CXXFLAGS", "")
|
||||
CFLAGS=os.environ.get("CFLAGS", "")
|
||||
LDFLAGS=os.environ.get("LDFLAGS", "")
|
||||
CXXFLAGS = os.environ.get("CXXFLAGS", "")
|
||||
CFLAGS = os.environ.get("CFLAGS", "")
|
||||
LDFLAGS = os.environ.get("LDFLAGS", "")
|
||||
|
||||
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
|
||||
if sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
|
||||
CXXFLAGS_MINIMAL="%s %s %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS_MINIMAL="%s %s %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
if BUILD_STATIC:
|
||||
CXXFLAGS="%s %s -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS="%s %s -fdata-sections -ffunction-sections -fvisibility=hidden %s"% (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS = f"{CXXFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS = f"{CFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden {ADDITIONAL_ARGS_STR}"
|
||||
else:
|
||||
CXXFLAGS=CXXFLAGS_MINIMAL
|
||||
CFLAGS=CFLAGS_MINIMAL
|
||||
LDFLAGS="%s -Wl,--gc-sections %s" % (LDFLAGS, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS = CXXFLAGS_MINIMAL
|
||||
CFLAGS = CFLAGS_MINIMAL
|
||||
LDFLAGS = f"{LDFLAGS} -Wl,--gc-sections {ADDITIONAL_ARGS_STR}"
|
||||
else:
|
||||
CXXFLAGS_MINIMAL="%s %s %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS_MINIMAL="%s %s %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
if BUILD_STATIC:
|
||||
CXXFLAGS="%s %s -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS="%s %s -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS = f"{CXXFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS = f"{CFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
|
||||
else:
|
||||
CXXFLAGS=CXXFLAGS_MINIMAL
|
||||
CFLAGS=CFLAGS_MINIMAL
|
||||
LDFLAGS="%s %s" % (LDFLAGS, str.join(" ", ADDITIONAL_ARGS))
|
||||
LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
|
||||
|
||||
os.environ["CXXFLAGS"] = CXXFLAGS
|
||||
os.environ["CFLAGS"] = CFLAGS
|
||||
@@ -488,21 +457,20 @@ os.environ["LDFLAGS"] = LDFLAGS
|
||||
# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))
|
||||
|
||||
if 'hdf5' in targets:
|
||||
HDF5_MAJOR=".".join(HDF5_VERSION.split(".")[:-1])
|
||||
get_os_result = get_os()
|
||||
HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
|
||||
build_dependency(
|
||||
name="hdf5-{HDF5_VERSION}".format(**locals()),
|
||||
name=f"hdf5-{HDF5_VERSION}",
|
||||
mode="ctest",
|
||||
build_tool_args=[],
|
||||
download_url="https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/".format(**locals()),
|
||||
download_name="CMake-hdf5-{HDF5_VERSION}.tar.gz".format(**locals()),
|
||||
ctest_result="HDF5-{HDF5_VERSION}-{get_os_result}".format(**locals()),
|
||||
ctest_result_path="HDF_Group/HDF5/{HDF5_VERSION}".format(**locals())
|
||||
download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
|
||||
download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
|
||||
ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
|
||||
ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}"
|
||||
)
|
||||
|
||||
if "json" in targets:
|
||||
json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals())
|
||||
json_install_path = "{DEPS_DIR}/install/json/nlohmann/json.hpp".format(**locals())
|
||||
json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
|
||||
json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
|
||||
if not os.path.exists(os.path.dirname(json_install_path)):
|
||||
os.makedirs(os.path.dirname(json_install_path))
|
||||
if not os.path.exists(json_install_path):
|
||||
@@ -510,11 +478,11 @@ if "json" in targets:
|
||||
|
||||
if "pcre" in targets:
|
||||
build_dependency(
|
||||
name="pcre-{PCRE_VERSION}".format(**locals()),
|
||||
name=f"pcre-{PCRE_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=[DISABLE_FLAG],
|
||||
download_url="https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/".format(**locals()),
|
||||
download_name="pcre-{PCRE_VERSION}.tar.bz2".format(**locals())
|
||||
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/",
|
||||
download_name=f"pcre-{PCRE_VERSION}.tar.bz2"
|
||||
)
|
||||
|
||||
# An issue exists with swig-1.3 and python >= 3.2
|
||||
@@ -523,20 +491,20 @@ if "swig" in targets:
|
||||
build_dependency(
|
||||
name="swig",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-ccache", "--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())],
|
||||
build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
|
||||
download_url="https://github.com/swig/swig.git",
|
||||
download_name="swig",
|
||||
download_tool=download_tool_git,
|
||||
revision="rel-{SWIG_VERSION}".format(**locals())
|
||||
revision=f"rel-{SWIG_VERSION}"
|
||||
)
|
||||
|
||||
if USE_OCCT and "occ" in targets:
|
||||
build_dependency(
|
||||
name="occt-{OCCT_VERSION}".format(**locals()),
|
||||
name=f"occt-{OCCT_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}".format(**locals()),
|
||||
"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}".format(**locals()),
|
||||
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
|
||||
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
|
||||
"-DBUILD_MODULE_Draw=0",
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
|
||||
],
|
||||
@@ -548,7 +516,7 @@ if USE_OCCT and "occ" in targets:
|
||||
)
|
||||
elif "occ" in targets:
|
||||
build_dependency(
|
||||
name="oce-{OCE_VERSION}".format(**locals()),
|
||||
name=f"oce-{OCE_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DOCE_DISABLE_TKSERVICE_FONT=ON",
|
||||
@@ -557,15 +525,15 @@ elif "occ" in targets:
|
||||
"-DOCE_DISABLE_X11=ON",
|
||||
"-DOCE_VISUALISATION=OFF",
|
||||
"-DOCE_OCAF=OFF",
|
||||
"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}".format(**locals())
|
||||
f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}"
|
||||
],
|
||||
download_url="https://github.com/tpaviot/oce/archive/",
|
||||
download_name="OCE-{OCE_VERSION}.tar.gz".format(**locals())
|
||||
download_name=f"OCE-{OCE_VERSION}.tar.gz"
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
build_dependency(
|
||||
"libxml2-{LIBXML2_VERSION}".format(**locals()),
|
||||
f"libxml2-{LIBXML2_VERSION}",
|
||||
"autoconf",
|
||||
build_tool_args=[
|
||||
"--without-python",
|
||||
@@ -576,7 +544,7 @@ if "libxml2" in targets:
|
||||
"--without-lzma"
|
||||
],
|
||||
download_url="ftp://xmlsoft.org/libxml2/",
|
||||
download_name="libxml2-{LIBXML2_VERSION}.tar.gz".format(**locals())
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
|
||||
)
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
@@ -584,12 +552,12 @@ if "OpenCOLLADA" in targets:
|
||||
"OpenCOLLADA",
|
||||
"cmake",
|
||||
build_tool_args=[
|
||||
"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2".format(**locals()),
|
||||
"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}".format(**locals()),
|
||||
"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include".format(**locals()),
|
||||
"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}".format(**locals()),
|
||||
"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}".format(**locals()),
|
||||
"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/".format(**locals())
|
||||
f"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
f"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
|
||||
f"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include",
|
||||
f"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}",
|
||||
f"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/"
|
||||
],
|
||||
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
|
||||
download_name="OpenCOLLADA",
|
||||
@@ -598,158 +566,155 @@ if "OpenCOLLADA" in targets:
|
||||
revision=OPENCOLLADA_VERSION
|
||||
)
|
||||
|
||||
if "python" in targets:
|
||||
if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
|
||||
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
|
||||
OLD_CXX_FLAGS=os.environ["CXXFLAGS"]
|
||||
OLD_C_FLAGS=os.environ["CFLAGS"]
|
||||
os.environ["CXXFLAGS"]=CXXFLAGS_MINIMAL
|
||||
os.environ["CFLAGS"]=CFLAGS_MINIMAL
|
||||
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
|
||||
OLD_C_FLAGS = os.environ["CFLAGS"]
|
||||
os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
|
||||
os.environ["CFLAGS"] = CFLAGS_MINIMAL
|
||||
|
||||
# On OSX a dynamic python library is built or it would not be compatible
|
||||
# with the system python because of some threading initialization
|
||||
PYTHON_CONFIGURE_ARGS=[]
|
||||
if get_os() == "Darwin":
|
||||
PYTHON_CONFIGURE_ARGS=["--disable-static", "--enable-shared"]
|
||||
PYTHON_CONFIGURE_ARGS = []
|
||||
if platform.system() == "Darwin":
|
||||
PYTHON_CONFIGURE_ARGS = ["--disable-static", "--enable-shared"]
|
||||
|
||||
def get_python_unicode_confs(py_ver):
|
||||
if py_ver < "3.3":
|
||||
return [("--enable-unicode=ucs2",""), ("--enable-unicode=ucs4","u")]
|
||||
else: return [("","")]
|
||||
|
||||
def PYTHON_VERSION_CONFS():
|
||||
for v in PYTHON_VERSIONS:
|
||||
for unicode_conf, abi_tag in get_python_unicode_confs(v):
|
||||
yield v, unicode_conf, abi_tag
|
||||
|
||||
for PYTHON_VERSION, unicode_conf, abi_tag in PYTHON_VERSION_CONFS():
|
||||
for PYTHON_VERSION in PYTHON_VERSIONS:
|
||||
try:
|
||||
build_dependency(
|
||||
"python-{PYTHON_VERSION}{abi_tag}".format(**locals()),
|
||||
f"python-{PYTHON_VERSION}",
|
||||
"autoconf",
|
||||
PYTHON_CONFIGURE_ARGS + [unicode_conf],
|
||||
"http://www.python.org/ftp/python/{PYTHON_VERSION}/".format(**locals()),
|
||||
"Python-{PYTHON_VERSION}.tgz".format(**locals())
|
||||
PYTHON_CONFIGURE_ARGS,
|
||||
f"http://www.python.org/ftp/python/{PYTHON_VERSION}/",
|
||||
f"Python-{PYTHON_VERSION}.tgz"
|
||||
)
|
||||
except Exception as e:
|
||||
pyver2 = PYTHON_VERSION[:PYTHON_VERSION.rfind('.')]
|
||||
if os.path.exists("{DEPS_DIR}/install/python-{PYTHON_VERSION}{abi_tag}/bin/python{pyver2}".format(**locals())):
|
||||
print("Installation partially failed")
|
||||
else: raise e
|
||||
except RuntimeError as e:
|
||||
# Sometimes setting up modules such as pip/lzma can cause
|
||||
# the python installer script to return a non zero exit
|
||||
# code where actually the headers and dynamic libraries
|
||||
# are installed correctly. This is all we need so we catch
|
||||
# the exception and only reraise if a partially successful
|
||||
# install is not detected.
|
||||
if not os.path.exists(
|
||||
os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")
|
||||
):
|
||||
raise e
|
||||
|
||||
os.environ["CXXFLAGS"]=OLD_CXX_FLAGS
|
||||
os.environ["CFLAGS"]=OLD_C_FLAGS
|
||||
os.environ["CXXFLAGS"] = OLD_CXX_FLAGS
|
||||
os.environ["CFLAGS"] = OLD_C_FLAGS
|
||||
|
||||
if "boost" in targets:
|
||||
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
|
||||
build_dependency(
|
||||
"boost-{BOOST_VERSION}".format(**locals()),
|
||||
f"boost-{BOOST_VERSION}",
|
||||
mode="bjam",
|
||||
build_tool_args=[
|
||||
"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
|
||||
f"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
"--with-system",
|
||||
"--with-program_options",
|
||||
"--with-regex",
|
||||
"--with-thread",
|
||||
"--with-date_time",
|
||||
"--with-iostreams",
|
||||
"link={LINK_TYPE}".format(**locals())
|
||||
f"link={LINK_TYPE}"
|
||||
] + \
|
||||
BOOST_ADDRESS_MODEL + \
|
||||
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
|
||||
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
|
||||
["stage", "-s", "NO_BZIP2=1"],
|
||||
download_url=BOOST_LOCATION,
|
||||
download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals())
|
||||
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
|
||||
)
|
||||
|
||||
if "cgal" in targets:
|
||||
build_dependency(
|
||||
name="gmp-%s" % (GMP_VERSION,),
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-shared", "--with-pic"],
|
||||
download_url="https://ftp.gnu.org/gnu/gmp/",
|
||||
download_name="gmp-{GMP_VERSION}.tar.bz2".format(**locals())
|
||||
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
|
||||
)
|
||||
|
||||
build_dependency(
|
||||
name="mpfr-%s" % (MPFR_VERSION,),
|
||||
name=f"mpfr-{MPFR_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-shared", "--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}".format(**locals())],
|
||||
download_url="http://www.mpfr.org/mpfr-{MPFR_VERSION}/".format(**locals()),
|
||||
download_name="mpfr-{MPFR_VERSION}.tar.bz2".format(**locals())
|
||||
build_tool_args=["--disable-shared", f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
|
||||
download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
|
||||
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
|
||||
)
|
||||
|
||||
build_dependency(
|
||||
name="cgal-{CGAL_VERSION}".format(**locals()),
|
||||
name=f"cgal-{CGAL_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DGMP_LIBRARIES=%s/install/gmp-%s/lib/libgmp.a" % (DEPS_DIR, GMP_VERSION),
|
||||
"-DGMP_INCLUDE_DIR=%s/install/gmp-%s/include" % (DEPS_DIR, GMP_VERSION),
|
||||
"-DMPFR_LIBRARIES=%s/install/mpfr-%s/lib/libmpfr.a" % (DEPS_DIR, MPFR_VERSION),
|
||||
"-DMPFR_INCLUDE_DIR=%s/install/mpfr-%s/include" % (DEPS_DIR, MPFR_VERSION),
|
||||
"-DBoost_INCLUDE_DIR=%s/install/boost-%s" % (DEPS_DIR, BOOST_VERSION),
|
||||
"-DCMAKE_INSTALL_PREFIX=%s/install/cgal-%s/" % (DEPS_DIR, CGAL_VERSION),
|
||||
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.a",
|
||||
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.a" ,
|
||||
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
|
||||
"-DCGAL_HEADER_ONLY=On",
|
||||
"-DBUILD_SHARED_LIBS=Off"
|
||||
],
|
||||
download_url="https://github.com/CGAL/cgal.git",
|
||||
download_name="cgal",
|
||||
download_tool=download_tool_git,
|
||||
revision="v{CGAL_VERSION}".format(**locals())
|
||||
revision=f"v{CGAL_VERSION}"
|
||||
)
|
||||
|
||||
cecho("Building IfcOpenShell:", GREEN)
|
||||
|
||||
IFCOS_DIR=os.path.join(DEPS_DIR, "build", "ifcopenshell")
|
||||
if os.path.exists(IFCOS_DIR):
|
||||
shutil.rmtree(IFCOS_DIR)
|
||||
os.makedirs(IFCOS_DIR)
|
||||
|
||||
IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
|
||||
if os.environ.get("NO_CLEAN", "").lower() not in {"1", "on", "true"}:
|
||||
if os.path.exists(IFCOS_DIR):
|
||||
shutil.rmtree(IFCOS_DIR)
|
||||
os.makedirs(IFCOS_DIR, exist_ok=True)
|
||||
executables_dir = os.path.join(IFCOS_DIR, "executables")
|
||||
if not os.path.exists(executables_dir):
|
||||
os.makedirs(executables_dir)
|
||||
os.makedirs(executables_dir, exist_ok=True)
|
||||
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
|
||||
exec_args=[
|
||||
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
|
||||
exec_args = [
|
||||
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
|
||||
"-DBUILD_IFCPYTHON=" "OFF",
|
||||
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell".format(**locals()),
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
cmake_args=[
|
||||
cmake_args = [
|
||||
"-DUSE_MMAP=" "OFF",
|
||||
"-DBUILD_EXAMPLES=" "OFF",
|
||||
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
|
||||
"-DBOOST_ROOT=" "{DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
|
||||
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
|
||||
"-DBOOST_ROOT=" f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
"-DGLTF_SUPPORT=" "ON",
|
||||
"-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals()),
|
||||
"-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
|
||||
"-DBoost_NO_BOOST_CMAKE=" "On",
|
||||
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
|
||||
]
|
||||
|
||||
if "cgal" in targets:
|
||||
cmake_args.extend([
|
||||
"-DCGAL_INCLUDE_DIR=" "{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include".format(**locals()),
|
||||
"-DGMP_INCLUDE_DIR=" "{DEPS_DIR}/install/gmp-{GMP_VERSION}/include".format(**locals()),
|
||||
"-DGMP_LIBRARY_DIR=" "{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib".format(**locals()),
|
||||
"-DMPFR_INCLUDE_DIR=" "{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include".format(**locals()),
|
||||
"-DMPFR_LIBRARY_DIR=" "{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib".format(**locals()),
|
||||
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
|
||||
"-DGMP_INCLUDE_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
"-DGMP_LIBRARY_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
|
||||
"-DMPFR_INCLUDE_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
"-DMPFR_LIBRARY_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
|
||||
])
|
||||
|
||||
if "occ" in targets and USE_OCCT:
|
||||
occ_include_dir = "{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade".format(**locals())
|
||||
occ_library_dir = "{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib".format(**locals())
|
||||
occ_include_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
|
||||
occ_library_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
|
||||
cmake_args.extend([
|
||||
"-DOCC_INCLUDE_DIR=" +occ_include_dir,
|
||||
"-DOCC_LIBRARY_DIR=" +occ_library_dir
|
||||
])
|
||||
|
||||
elif "occ" in targets:
|
||||
occ_include_dir = "{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce".format(**locals())
|
||||
occ_library_dir = "{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
|
||||
occ_include_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce"
|
||||
occ_library_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
|
||||
cmake_args.extend([
|
||||
"-DOCC_INCLUDE_DIR=" +occ_include_dir,
|
||||
"-DOCC_LIBRARY_DIR=" +occ_library_dir
|
||||
@@ -757,82 +722,98 @@ elif "occ" in targets:
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
cmake_args.extend([
|
||||
"-DOPENCOLLADA_INCLUDE_DIR=" "{DEPS_DIR}/install/OpenCOLLADA/include/opencollada".format(**locals()),
|
||||
"-DOPENCOLLADA_LIBRARY_DIR=" "{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada".format(**locals())
|
||||
"-DOPENCOLLADA_INCLUDE_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
|
||||
"-DOPENCOLLADA_LIBRARY_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada"
|
||||
])
|
||||
else:
|
||||
cmake_args.extend([
|
||||
"-DCOLLADA_SUPPORT=" "Off",
|
||||
])
|
||||
|
||||
if "pcre" in targets:
|
||||
cmake_args.append(
|
||||
"-DPCRE_LIBRARY_DIR=" "{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib".format(**locals())
|
||||
"-DPCRE_LIBRARY_DIR=" f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib"
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
cmake_args.extend([
|
||||
"-DLIBXML2_INCLUDE_DIR=" "{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2".format(**locals()),
|
||||
"-DLIBXML2_LIBRARIES=" "{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}".format(**locals())
|
||||
"-DLIBXML2_INCLUDE_DIR=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
"-DLIBXML2_LIBRARIES=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}"
|
||||
])
|
||||
|
||||
if "hdf5" in targets:
|
||||
cmake_args.extend([
|
||||
"-DHDF5_INCLUDE_DIR=" "{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include".format(**locals()),
|
||||
"-DHDF5_LIBRARY_DIR=" "{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib".format(**locals())
|
||||
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
|
||||
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
|
||||
])
|
||||
|
||||
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
|
||||
logger.info("\rBuilding executables... ")
|
||||
|
||||
run([make, "-j{IFCOS_NUM_BUILD_PROCS}".format(**locals())], cwd=executables_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
|
||||
|
||||
if "IfcOpenShell-Python" in targets:
|
||||
|
||||
# On OSX the actual Python library is not linked against.
|
||||
ADDITIONAL_ARGS=""
|
||||
if get_os() == "Darwin":
|
||||
ADDITIONAL_ARGS="-Wl,-flat_namespace,-undefined,suppress"
|
||||
ADDITIONAL_ARGS = ""
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
|
||||
|
||||
os.environ["CXXFLAGS"]="%s %s" % (CXXFLAGS_MINIMAL, ADDITIONAL_ARGS)
|
||||
os.environ["CFLAGS"]="%s %s" % (CFLAGS_MINIMAL, ADDITIONAL_ARGS)
|
||||
os.environ["LDFLAGS"]="%s %s" % (LDFLAGS, ADDITIONAL_ARGS)
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
|
||||
|
||||
for PYTHON_VERSION, _, TAG in PYTHON_VERSION_CONFS():
|
||||
logger.info("\rConfiguring python {PYTHON_VERSION}{TAG} wrapper...".format(**locals()))
|
||||
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
|
||||
os.makedirs(python_dir, exist_ok=True)
|
||||
|
||||
# python_dir = os.path.join(IFCOS_DIR, "python-{PYTHON_VERSION}{TAG}".format(**locals()))
|
||||
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
|
||||
if not os.path.exists(python_dir):
|
||||
os.makedirs(python_dir)
|
||||
def compile_python_wrapper(python_version, python_library, python_include, python_executable):
|
||||
logger.info(f"\rConfiguring python {python_version} wrapper...")
|
||||
|
||||
cache_path = os.path.join(python_dir, "CMakeCache.txt")
|
||||
if os.path.exists(cache_path):
|
||||
os.unlink(cache_path)
|
||||
os.remove(cache_path)
|
||||
|
||||
PYTHON_LIBRARY=run([bash, "-c", "ls {DEPS_DIR}/install/python-{PYTHON_VERSION}{TAG}/lib/libpython*.*".format(**locals())])
|
||||
PYTHON_INCLUDE=run([bash, "-c", "ls -d {DEPS_DIR}/install/python-{PYTHON_VERSION}{TAG}/include/python*".format(**locals())])
|
||||
PYTHON_EXECUTABLE=os.path.join(DEPS_DIR, "install", "python-{PYTHON_VERSION}{TAG}".format(**locals()), "bin", "python{PYTHON_VERSION[0]}".format(**locals()))
|
||||
os.environ["PYTHON_LIBRARY_BASENAME"]=os.path.basename(PYTHON_LIBRARY)
|
||||
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
|
||||
|
||||
run_cmake("",
|
||||
cmake_args + [
|
||||
"-DPYTHON_LIBRARY=" +PYTHON_LIBRARY,
|
||||
"-DPYTHON_EXECUTABLE=" +PYTHON_EXECUTABLE,
|
||||
"-DPYTHON_INCLUDE_DIR=" +PYTHON_INCLUDE,
|
||||
"-DSWIG_EXECUTABLE=" "{DEPS_DIR}/install/swig/bin/swig".format(**locals()),
|
||||
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell/tmp".format(**locals()),
|
||||
"-DPYTHON_LIBRARY=" +python_library,
|
||||
"-DPYTHON_EXECUTABLE=" +python_executable,
|
||||
"-DPYTHON_INCLUDE_DIR=" +python_include,
|
||||
"-DSWIG_EXECUTABLE=" f"{DEPS_DIR}/install/swig/bin/swig",
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}]
|
||||
], cmake_dir=CMAKE_DIR, cwd=python_dir)
|
||||
|
||||
logger.info("\rBuilding python %s%s wrapper... " % (PYTHON_VERSION, TAG))
|
||||
|
||||
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
module_dir = os.path.dirname(run([PYTHON_EXECUTABLE, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
|
||||
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
|
||||
|
||||
if get_os() != "Darwin":
|
||||
if platform.system() != "Darwin":
|
||||
# TODO: This symbol name depends on the Python version?
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
|
||||
return module_dir
|
||||
|
||||
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", "python-%s%s" % (PYTHON_VERSION, TAG))])
|
||||
|
||||
if USE_CURRENT_PYTHON_VERSION:
|
||||
python_info = sysconfig.get_paths()
|
||||
python_lib = os.path.join(
|
||||
sysconfig.get_config_var('LIBDIR'),
|
||||
sysconfig.get_config_var('multiarchsubdir').replace("/", ""),
|
||||
sysconfig.get_config_var("INSTSONAME")
|
||||
)
|
||||
compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
|
||||
else:
|
||||
for python_version in PYTHON_VERSIONS:
|
||||
python_library = run([bash, "-c", f"ls {DEPS_DIR}/install/python-{python_version}/lib/libpython*.*"])
|
||||
python_include = run([bash, "-c", f"ls -d {DEPS_DIR}/install/python-{python_version}/include/python*"])
|
||||
python_executable = os.path.join(DEPS_DIR, "install", f"python-{python_version}", "bin", f"python{python_version[0]}")
|
||||
|
||||
module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
|
||||
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
|
||||
|
||||
logger.info("\rBuilt IfcOpenShell...\n\n")
|
||||
|
||||
@@ -645,9 +645,10 @@ class BcfXml:
|
||||
|
||||
def get_comments(self, guid):
|
||||
comments = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
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 = {
|
||||
@@ -675,9 +676,9 @@ class BcfXml:
|
||||
|
||||
def get_viewpoints(self, guid):
|
||||
viewpoints = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
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
|
||||
|
||||
@@ -46,7 +46,7 @@ class RelatedTopic:
|
||||
|
||||
class HeaderFile:
|
||||
def __init__(self):
|
||||
self.file_name = ""
|
||||
self.filename = ""
|
||||
self.date = None
|
||||
self.reference = ""
|
||||
self.ifc_project = None
|
||||
|
||||
@@ -27,6 +27,10 @@ ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYLIBDIR:=python3.10
|
||||
PYNUMBER:=31
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
@@ -41,6 +45,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/ei
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
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
|
||||
@@ -59,6 +69,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eige
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
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
|
||||
@@ -77,6 +93,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eige
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
|
||||
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
|
||||
@@ -93,9 +115,9 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-9853d3d-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcblender*
|
||||
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-1b1fd1e-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
# See bug #812
|
||||
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/blenderbim/occ_utils.py
|
||||
@@ -133,6 +155,8 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc4d/ifc4d dist/blenderbim/libs/site/packages/
|
||||
# Provides IFC5D functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc5d/ifc5d dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCityJSON functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccityjson dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
@@ -240,6 +264,30 @@ endif
|
||||
cp -r dist/working/lark-parser-0.8.5/lark dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFC4D
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/b0/bb/9c4dddd3ca173cb56241cfb2eddfae24690dc676d357ac4cab17d0a36d9d/PyP6Xer-1.13.0.tar.gz
|
||||
cd dist/working && tar -xzvf PyP6Xer*
|
||||
cp -r dist/working/PyP6Xer-1.13.0/xerparser dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by xerparser and IFC4D
|
||||
# TODO: remove this dependency. It's only used to show a progress bar.
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/9f/7b/76c4e5ef1a1b528fcaada4133f972e77d33c252831676cf414119ca6093d/tqdm-4.50.0.tar.gz
|
||||
cd dist/working && tar -xzvf tqdm*
|
||||
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCityJSON
|
||||
mkdir dist/working
|
||||
cd dist/working && python -m venv env
|
||||
cd dist/working && mkdir site-packages
|
||||
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
|
||||
cd dist/working/site-packages/ && rm -r *dist-info*
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(HPPFCL_URL)
|
||||
@@ -345,7 +393,7 @@ endif
|
||||
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by ids
|
||||
# Required by ids - uh, not any more, but I'm trying it out in the drawing module
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(LXML_URL)
|
||||
cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
|
||||
@@ -429,6 +477,47 @@ endif
|
||||
cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Brickschema fucntionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
|
||||
cd dist/working && tar -xzvf brickschema*
|
||||
# This is an evil hack because we don't want to bundle flask
|
||||
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
|
||||
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
|
||||
# For now lets bundle the latest nightly schema
|
||||
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
cd dist/working && cp Brick.ttl ../../blenderbim/bim/schema/Brick.ttl
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
|
||||
cd dist/working && tar -xzvf pyshacl*
|
||||
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf rdflib*
|
||||
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf owlrl*
|
||||
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://blenderbim.org/builds/pkg_resources.tar.gz
|
||||
cd dist/working && tar -xzvf pkg_resources*
|
||||
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
@@ -445,11 +534,19 @@ test-core:
|
||||
|
||||
.PHONY: test-bim
|
||||
test-bim:
|
||||
ifndef MODULE
|
||||
pytest test/bim
|
||||
else
|
||||
pytest test/bim -m "$(MODULE)" ./ --maxfail=1
|
||||
endif
|
||||
|
||||
.PHONY: test-tool
|
||||
test-tool:
|
||||
ifndef MODULE
|
||||
pytest test/tool
|
||||
else
|
||||
pytest test/tool/test_$(MODULE).py
|
||||
endif
|
||||
|
||||
.PHONY: qa
|
||||
qa:
|
||||
@@ -464,7 +561,7 @@ coverage:
|
||||
|
||||
.PHONY: license
|
||||
license:
|
||||
copyright-header --license GPL3 --copyright-holder "Dion Moult <dion@thinkmoult.com>" --copyright-year "2021" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
|
||||
copyright-header --license GPL3 --copyright-holder "Dion Moult <dion@thinkmoult.com>" --copyright-year "2022" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
@@ -1,127 +1,5 @@
|
||||
# Blender BIM IFC
|
||||
# BlenderBIM Add-on
|
||||
|
||||
_Under heavy development!_
|
||||
An add-on to Blender to allow BIM functionality.
|
||||
|
||||
Let's use Blender to create BIM models, because why not?
|
||||
|
||||
## Features
|
||||
|
||||
- IFC4 is a first-class citizen. IFC4 has been an ISO standard since 2013, with
|
||||
the latest published in 2018. It contains many more features than IFC2X3,
|
||||
published back in 2005.
|
||||
- Available for Windows, Mac, and Linux!
|
||||
- Use open-source Blender 2.8 to do digital modeling! Used by NASA, Hollywood,
|
||||
and Ubisoft, Blender allows real-time modeling in rendered view, animations,
|
||||
file linking, proxy objects, Blender Cloud collaboration, lighting analysis,
|
||||
CFD, energy modeling, and a huge ecosystem of add-ons with a thriving
|
||||
community. No licensing, and with a strong Python API to integrate into your
|
||||
custom workflow.
|
||||
- Create complex spatial structures with multiple sites, buildings, facilities,
|
||||
bridges, and building parts. Sites can be broken down into sub-sites and
|
||||
multiple buildings per site. You can categorise different floors to different
|
||||
buildings.
|
||||
- Classify any geometry as any IFC type. Just because it is a wall doesn't
|
||||
mean you should be limited to modeling it in a certain way. Easily change
|
||||
from one type to another, switch relationships and aggregations, reuse or
|
||||
even remove geometry altogether, without damaging your geometry or breaking
|
||||
your BIM model.
|
||||
- Use collections to create element compositions and decompositions with full
|
||||
control over the classifications of their parts. Most BIM programs don't give
|
||||
this flexibility. Create collection instances to efficiently duplicate
|
||||
composite objects.
|
||||
- Allow BIM objects to instance a shared geometric representations. This allows
|
||||
you to place the same shape in multiple objects without drastically
|
||||
increasing your filesize.
|
||||
- Provide a custom value to any simple IFC attribute of an IFC product.
|
||||
- Create product types that regular IFC products can inherit attributes from.
|
||||
- Define your own property sets with control over exactly how the data in the
|
||||
property sets are stored using property set template definitions which you
|
||||
can download or create yourself. Property sets can apply to both types and
|
||||
objects, and object property sets can selectively override type property
|
||||
sets.
|
||||
- Applies array modifiers to create instances.
|
||||
- Basic quantity take-off support for costing.
|
||||
- Settings to toggle export of quantities or geometric representations.
|
||||
Exporting IFC data without geometric representations is much faster and a
|
||||
fraction of the filesize.
|
||||
- Maintain `GlobalId` between exports, so that you can manipulate the IFC
|
||||
reliably in other software while making changes.
|
||||
- Not all objects need full solid geometry. Create representations as 2D or 3D
|
||||
curves / wireframes for rapid and lightweight BIM development.
|
||||
- Create map conversions and coordinate reference system definitions to link
|
||||
BIM to GIS.
|
||||
- Export basic materials with surface and diffuse colours for representation.
|
||||
You can also link materials to an externally defined file, such as a `.blend`
|
||||
Cycles material, V-Ray material, or even Radiance material for lighting
|
||||
simulation.
|
||||
- Switch between different types of metric units when modeling.
|
||||
- Model multiple representations for different purposes for a single BIM
|
||||
object or type, such as footprints, axis, reference, or clearance geometry.
|
||||
You can export just the type of representation required for your usecase, or
|
||||
export multiple for the user to choose from.
|
||||
- Record qualitative objectives such as design intentions, health and safety
|
||||
strategies, or code compliance, performance solutions, and more, in the BIM
|
||||
model and directly associate them to objects or types.
|
||||
- Classify objects and types to different classification systems such as
|
||||
Uniclass, Omniclass, or your custom system.
|
||||
- Associate external document files such as plans, brochures, specifications,
|
||||
warranties, or anything to objects and types.
|
||||
- Specify swept solid geometries, allowing you to round-trip geometry
|
||||
manipulation with other BIM software which have more limited geometric tools,
|
||||
like Revit.
|
||||
- Create material layer sets, for objects which have materials in predefined
|
||||
layers with thicknesses.
|
||||
- Create material constituent sets, for objects which are made up of
|
||||
arbitrarily mixed ingredients.
|
||||
- Specify nested and hosted element relationships.
|
||||
- Specify predefined door attributes as defined in the IFC4 specification.
|
||||
- Specify predefined window attributes as defined in the IFC4 specification.
|
||||
- Specify surface styles either defined as part of a material or assigned
|
||||
directly to an object's representation.
|
||||
- Colour coding by IFC class to visually spot mistakes
|
||||
|
||||
Some more experimental features:
|
||||
|
||||
- Cut sections for documentation with OpenCascade
|
||||
- Export gbXML files that are linked to your IFC file to do energy analysis
|
||||
- QA module to generate Gherkin unit tests to validate your BIM models
|
||||
- HTML formatter to generate reports from QA results
|
||||
- COBie spreadsheet extraction to import into CAFM systems
|
||||
|
||||
## Demo
|
||||
|
||||
[See demo video](https://www.youtube.com/watch?v=iD3v3eu2AjY)
|
||||
|
||||
It's only just started, so everything can and will change. But if you want to
|
||||
check it out:
|
||||
|
||||
1. Install [Blender](https://www.blender.org/) 64-bit version.
|
||||
2. Download the latest packaged version of Blender BIM from
|
||||
[here](https://blenderbim.org/builds/).
|
||||
3. Launch Blender. Go to `Edit->Preferences->Add-ons->Install...`. Browse to
|
||||
the downloaded `.zip` file from step 2.
|
||||
4. Enable the checkbox next to the Import-Export IFC Blender plugin which will
|
||||
show up.
|
||||
5. All done! You will find an import and export IFC option in the `File` menu.
|
||||
|
||||
By default, Blender BIM will export whatever you have selected. The objects you
|
||||
have selected _must_ belong in an `IfcProject` and spatially contained, such as
|
||||
witihn an `IfcBuilding`. It must also follow the naming convention. Please see
|
||||
the screenshot below for an example scene.
|
||||
|
||||
A naming convention of `IfcClass/IfcName` is enforced. If you name your object
|
||||
`IfcWall/Foo`, it will turn into an `IfcWall` with the name attribute set to
|
||||
`Foo`. You can override object attributes using Blender's `Custom Properties`.
|
||||
Your objects must live in a top level `Collection` defined as a context class,
|
||||
usually something like `IfcProject/Foo`. You can then nest spatial structures
|
||||
underneath to create a hierarchy.
|
||||
|
||||
## Pictures
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
... this Blender files creates this IFC file ...
|
||||
|
||||

|
||||
More information on the [BlenderBIM Add-on website](https://blenderbim.org).
|
||||
|
||||
|
Before Width: | Height: | Size: 399 KiB |
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator
|
||||
from . import handler, ui, prop, operator, helper
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
@@ -27,9 +27,11 @@ modules = {
|
||||
"root": None,
|
||||
"unit": None,
|
||||
"model": None,
|
||||
"cad": None,
|
||||
"georeference": None,
|
||||
"context": None,
|
||||
"drawing": None,
|
||||
"misc": None,
|
||||
"attribute": None,
|
||||
"type": None,
|
||||
"spatial": None,
|
||||
@@ -42,6 +44,7 @@ modules = {
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"system": None,
|
||||
"brick": None,
|
||||
"structural": None,
|
||||
"boundary": None,
|
||||
"profile": None,
|
||||
@@ -52,6 +55,7 @@ modules = {
|
||||
"pset": None,
|
||||
"qto": None,
|
||||
"classification": None,
|
||||
"library": None,
|
||||
"constraint": None,
|
||||
"document": None,
|
||||
"pset_template": None,
|
||||
@@ -61,33 +65,37 @@ modules = {
|
||||
"bimtester": None,
|
||||
"diff": None,
|
||||
"patch": None,
|
||||
"gis": None,
|
||||
"covetool": None,
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
# Uncomment this line to enable loading of the demo module. Happy hacking!
|
||||
# The name "demo" must correlate to a folder name in `bim/module/`.
|
||||
# "demo": None,
|
||||
}
|
||||
|
||||
|
||||
for name in modules.keys():
|
||||
modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
|
||||
|
||||
|
||||
classes = [
|
||||
operator.OpenUri,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.OpenUpstream,
|
||||
operator.AddSectionPlane,
|
||||
operator.RemoveSectionPlane,
|
||||
operator.ReloadIfcFile,
|
||||
operator.AddIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.ConfigureVisibility,
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SetOverrideColour,
|
||||
operator.SetViewportShadowFromSun,
|
||||
operator.SnapSpacesTogether,
|
||||
operator.OverrideDelete,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectURIAttribute,
|
||||
prop.StrProperty,
|
||||
prop.ObjProperty,
|
||||
prop.Attribute,
|
||||
prop.ModuleVisibility,
|
||||
prop.BIMProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
@@ -99,23 +107,32 @@ classes = [
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
# Scene panel groups
|
||||
ui.BIM_PT_project_info,
|
||||
ui.BIM_PT_project_setup,
|
||||
ui.BIM_PT_collaboration,
|
||||
ui.BIM_PT_geometry,
|
||||
ui.BIM_PT_services,
|
||||
ui.BIM_PT_structural,
|
||||
ui.BIM_PT_4D5D,
|
||||
ui.BIM_PT_quality_control,
|
||||
ui.BIM_PT_integrations,
|
||||
# Object panel groups
|
||||
ui.BIM_PT_object_metadata,
|
||||
ui.BIM_PT_geometry_object,
|
||||
ui.BIM_PT_services_object,
|
||||
ui.BIM_PT_utilities_object,
|
||||
ui.BIM_PT_misc_object,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
classes.extend(mod.classes)
|
||||
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
|
||||
|
||||
|
||||
def on_register(scene):
|
||||
handler.setDefaultProperties(scene)
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
if not bpy.app.background:
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
|
||||
|
||||
def register():
|
||||
@@ -129,8 +146,6 @@ def register():
|
||||
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.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.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
@@ -151,8 +166,6 @@ def unregister():
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
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.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
|
||||
@@ -1,10 +1,44 @@
|
||||
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="jsgantt.js" type="text/javascript"></script>
|
||||
<style>
|
||||
.gtaskcellwkend,
|
||||
.gtaskcellcurrent,
|
||||
.gminorheadingwkend {
|
||||
background-color: #e1e1e1;
|
||||
}
|
||||
|
||||
.gitemhighlight td {
|
||||
background-color: #ffdaaa;
|
||||
}
|
||||
|
||||
.gtaskblue {
|
||||
background: #4281A4;
|
||||
}
|
||||
|
||||
.gtaskred {
|
||||
background: #C1666B;
|
||||
}
|
||||
|
||||
.gtaskgreen {
|
||||
background: #48A9A6;
|
||||
}
|
||||
|
||||
.gtaskyellow {
|
||||
background: #D4B483;
|
||||
}
|
||||
|
||||
.gmainleft {
|
||||
overflow: visible;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
<a href="#print" id="print">Print Mode</a>
|
||||
<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,
|
||||
vCaptionType: 'Caption', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
|
||||
@@ -14,7 +48,16 @@ g.setOptions({
|
||||
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,
|
||||
vShowRes: false, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {ifcduration: {title: 'Duration'}},
|
||||
vUseToolTip: false, // Disable tooltips.
|
||||
vTotalHeight: 900,
|
||||
});
|
||||
document.getElementById('print').addEventListener('click', function() {
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
});
|
||||
var json_data = `
|
||||
{{{json_data}}}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_ENUMERATEDVALUE.,'IfcLabel',$,#4,$,$,$,.READWRITE.);
|
||||
#4=IFCPROPERTYENUMERATION('Symbol',(IFCLABEL('rectangle-tag'), IFCLABEL('door-tag')),$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,24 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -19,15 +19,31 @@
|
||||
*/
|
||||
|
||||
* { stroke-linecap: round; stroke-linejoin: round; }
|
||||
*[id] { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
text { fill: black; stroke: none; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
.solid {}
|
||||
.leader { marker-end: url(#leader-marker); }
|
||||
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.break { fill: white; }
|
||||
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
|
||||
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
|
||||
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
|
||||
.PredefinedType-SECTION { stroke-dasharray: 12.5, 3, 3, 3; }
|
||||
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
|
||||
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
|
||||
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
|
||||
.PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); }
|
||||
.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
|
||||
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
|
||||
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
|
||||
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
|
||||
.PredefinedType-FILLAREA { fill: white; stroke: none; }
|
||||
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.PredefinedType-TEXT { fill: black; stroke: none; }
|
||||
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
|
||||
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
|
||||
.material-blank { fill: white; }
|
||||
.material-diagonal1 { fill: url(#diagonal1); }
|
||||
.material-diagonal2 { fill: url(#diagonal2); }
|
||||
@@ -43,5 +59,9 @@
|
||||
.material-honeycomb { fill: url(#honeycomb); }
|
||||
.material-sand { fill: url(#sand); }
|
||||
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
|
||||
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
|
||||
.IfcSpace { fill: none; stroke: none; }
|
||||
.PredefinedType-STUD { stroke: black; stroke-width: 1; }
|
||||
.PredefinedType-WOOD { fill: url(#wood); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-STEEL { fill: url(#steel); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-CONCRETE { fill: url(#concrete); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-PLASTERBOARD { fill: url(#sand); stroke: black; stroke-width: 0.25; }
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
|
||||
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
|
||||
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
|
||||
</marker>
|
||||
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
@@ -12,6 +16,35 @@
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="radius-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="angle-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="angle-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="diameter-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="diameter-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="dimension-marker-start" markerHeight="30" markerWidth="20" refX="10" refY="15" orient="auto">
|
||||
<g>
|
||||
<path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" />
|
||||
@@ -53,7 +86,7 @@
|
||||
<path d="M 0 1 L 90 1" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10">
|
||||
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10" orient="auto">
|
||||
<g>
|
||||
<path d="M 0 10 L 5 20 L 7.5 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
<path d="M 7.5 10 L 10 0 L 15 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -3,6 +3,10 @@
|
||||
<pattern id="demolish" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:red; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="steel" width="3" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
<line x1="1" y1="0" x2="1" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="diagonal1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
@@ -80,6 +84,9 @@
|
||||
<path d="M 1.25,2.165 L 2.5,4.33 L 5,4.33 L 6.25,2.165 L 5,0 L 2.5,0 L 1.25,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round;fill:none" />
|
||||
<path d="M 6.25,2.165 L 7.5,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round" />
|
||||
</pattern>
|
||||
<pattern id="wood" width="50" height="50" patternUnits="userSpaceOnUse">
|
||||
<path style="fill:none; stroke: black; stroke-width:0.25;" d="M 49.649999,1.3460737 A 69.019995,69.019995 0 0 0 48.653938,0.34999949 M 49.649999,4.9389583 A 66.554994,66.554994 0 0 0 45.06105,0.34999949 M 49.649999,8.6983527 A 64.089999,64.089999 0 0 0 41.301656,0.34999949 M 49.649999,12.675003 A 61.624998,61.624998 0 0 0 37.325,0.34999949 M 49.649999,16.948067 A 59.159997,59.159997 0 0 0 33.05192,0.34999949 M 49.649999,21.652972 A 56.694997,56.694997 0 0 0 28.347017,0.34999949 M 49.649999,27.057906 A 54.229999,54.229999 0 0 0 22.942096,0.34999949 M 49.649999,33.866298 A 51.764998,51.764998 0 0 0 16.1337,0.34999949 M 49.649999,49.649989 A 49.299997,49.299997 0 0 0 0.34999949,0.34999949 M 47.185,49.649989 A 46.834999,46.834999 0 0 0 0.34999949,2.8149951 M 44.72,49.649989 A 44.369998,44.369998 0 0 0 0.34999949,5.2799906 M 42.254999,49.649989 A 41.904997,41.904997 0 0 0 0.34999949,7.7450123 M 39.790001,49.649989 A 39.439999,39.439999 0 0 0 0.34999949,10.210008 M 37.325,49.649989 A 36.974998,36.974998 0 0 0 0.34999949,12.675003 M 34.859999,49.649989 A 34.509998,34.509998 0 0 0 0.34999949,15.139998 M 32.395001,49.649989 A 32.044999,32.044999 0 0 0 0.34999949,17.604994 M 29.93,49.649989 A 29.579999,29.579999 0 0 0 0.34999949,20.069989 M 27.465,49.649989 A 27.114998,27.114998 0 0 0 0.34999949,22.535011 M 24.999997,49.649989 A 24.649999,24.649999 0 0 0 0.34999949,25.000007 M 22.534998,49.649989 A 22.184999,22.184999 0 0 0 0.34999949,27.465003 M 20.069998,49.649989 A 19.719999,19.719999 0 0 0 0.34999949,29.929999 M 17.604998,49.649989 A 17.254999,17.254999 0 0 0 0.34999949,32.394994 M 15.139998,49.649989 A 14.789999,14.789999 0 0 0 0.34999949,34.85999 M 12.674999,49.649989 A 12.325,12.325 0 0 0 0.34999949,37.325011 M 10.209999,49.649989 A 9.8599994,9.8599994 0 0 0 0.34999949,39.790007 M 7.7449994,49.649989 A 7.3949999,7.3949999 0 0 0 0.34999949,42.255002 M 5.2799992,49.649989 A 4.9299997,4.9299997 0 0 0 0.34999949,44.719998 M 2.8149995,49.649989 A 2.4649999,2.4649999 0 0 0 0.34999949,47.184993 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003" />
|
||||
</pattern>
|
||||
<pattern id="sand" width="50" height="50" patternUnits="userSpaceOnUse">
|
||||
<path style="fill: white;" d="M 0 0 50 0 50 50 0 50" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 67 KiB |
@@ -5,6 +5,20 @@
|
||||
</g>
|
||||
<g id="door-tag">
|
||||
<circle cy="1.75" r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke:black; stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="elevation-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
</g>
|
||||
<g id="elevation-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="section-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
</g>
|
||||
<g id="section-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 1.2 KiB |
@@ -173,7 +173,7 @@
|
||||
style="stroke-width:0.999999px"
|
||||
y="1036.1166"
|
||||
x="3030.9653"
|
||||
id="tspan2527">{{revision}}</tspan></text>
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3918px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
@@ -183,7 +183,7 @@
|
||||
id="tspan2531"
|
||||
x="2804.7021"
|
||||
y="1036.1166"
|
||||
style="stroke-width:0.999999px">{{number}}</tspan></text>
|
||||
style="stroke-width:0.999999px">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -169,7 +169,7 @@
|
||||
style="stroke-width:0.264583px"
|
||||
y="397.13919"
|
||||
x="554.94287"
|
||||
id="tspan2527">{{revision}}</tspan></text>
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
@@ -179,7 +179,7 @@
|
||||
id="tspan2531"
|
||||
x="495.07742"
|
||||
y="397.13919"
|
||||
style="stroke-width:0.264583px">{{number}}</tspan></text>
|
||||
style="stroke-width:0.264583px">{{Identification}} - {{Name}}</tspan></text>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -172,7 +172,7 @@
|
||||
id="tspan2527"
|
||||
x="544.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{revision}}</tspan></text>
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
@@ -182,7 +182,7 @@
|
||||
style="stroke-width:0.264583px"
|
||||
y="284.13919"
|
||||
x="485.07742"
|
||||
id="tspan2531">{{number}}</tspan></text>
|
||||
id="tspan2531">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -70,7 +70,7 @@
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055365px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.26458332px"
|
||||
y="103.10933"
|
||||
x="32.822662"
|
||||
id="tspan857">{{no}}</tspan></text>
|
||||
id="tspan857">{{Identification}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.71626091px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
@@ -80,7 +80,7 @@
|
||||
id="tspan861"
|
||||
x="39.12896"
|
||||
y="100.19173"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{name}}</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{Name}}</tspan></text>
|
||||
<text
|
||||
id="text867"
|
||||
y="105.0553"
|
||||
@@ -90,6 +90,6 @@
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458332px"
|
||||
y="105.0553"
|
||||
x="39.285267"
|
||||
id="tspan865">{{scale}}</tspan></text>
|
||||
id="tspan865">{{Scale}}</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,214 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
presets = {
|
||||
"Basic": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
|
||||
"Admin": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"cobie",
|
||||
"resource",
|
||||
"cost",
|
||||
"sequence",
|
||||
"group",
|
||||
"system",
|
||||
"structural",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"constraint",
|
||||
"document",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"lca",
|
||||
"csv",
|
||||
"bimtester",
|
||||
"diff",
|
||||
"patch",
|
||||
"covetool",
|
||||
"augin",
|
||||
"debug",
|
||||
],
|
||||
"BIM Coordination": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"georeference",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"clash",
|
||||
"csv",
|
||||
"bimtester",
|
||||
"diff",
|
||||
"patch",
|
||||
"debug",
|
||||
],
|
||||
"Architecture": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"group",
|
||||
"system",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"csv",
|
||||
],
|
||||
"Structural Engineering": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"group",
|
||||
"system",
|
||||
"structural",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"csv",
|
||||
],
|
||||
"MEP": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"group",
|
||||
"system",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"csv",
|
||||
],
|
||||
"Quantity Surveying": ["project", "search", "attribute", "spatial", "void", "pset", "qto", "classification", "csv"],
|
||||
"Model Enrichment": [
|
||||
"project",
|
||||
"search",
|
||||
"root",
|
||||
"attribute",
|
||||
"spatial",
|
||||
"pset",
|
||||
"qto",
|
||||
"pset_template",
|
||||
"csv",
|
||||
"patch",
|
||||
],
|
||||
"3D Visualisation": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
|
||||
"4D Scheduling": ["project", "search", "attribute", "spatial", "resource", "cost", "sequence", "pset", "qto"],
|
||||
"5D Cost Management": [
|
||||
"project",
|
||||
"search",
|
||||
"attribute",
|
||||
"spatial",
|
||||
"resource",
|
||||
"cost",
|
||||
"sequence",
|
||||
"pset",
|
||||
"qto",
|
||||
"lca",
|
||||
],
|
||||
"Facility Management": ["project", "search", "attribute", "spatial", "cobie", "pset", "qto", "lca"],
|
||||
}
|
||||
@@ -27,7 +27,13 @@ import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.aggregate
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.style
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
@@ -37,20 +43,22 @@ class IfcExporter:
|
||||
def export(self):
|
||||
self.file = IfcStore.get_file()
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_object_placements_and_deletions()
|
||||
self.sync_deletions()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1]
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
if extension == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
|
||||
tmp_name = "{}.ifc".format(filename)
|
||||
tmp_file = os.path.join(unzipped_path, tmp_name)
|
||||
tmp_filename = f"{filename}.ifc"
|
||||
tmp_file = os.path.join(unzipped_path, tmp_filename)
|
||||
self.file.write(tmp_file)
|
||||
with zipfile.ZipFile(
|
||||
self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
||||
) as zf:
|
||||
zf.write(tmp_file)
|
||||
zf.write(tmp_file, arcname=tmp_filename)
|
||||
elif extension == "ifc":
|
||||
self.file.write(self.ifc_export_settings.output_file)
|
||||
elif extension == "ifcjson":
|
||||
@@ -79,103 +87,90 @@ class IfcExporter:
|
||||
self.get_application_name(), self.get_application_version()
|
||||
)
|
||||
|
||||
def sync_object_placements_and_deletions(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
to_delete = []
|
||||
def sync_deletions(self):
|
||||
results = []
|
||||
for ifc_definition_id in IfcStore.deleted_ids:
|
||||
try:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
if hasattr(product, "GlobalId"):
|
||||
results.append(product.GlobalId)
|
||||
except:
|
||||
continue
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
IfcStore.deleted_ids.clear()
|
||||
return results
|
||||
|
||||
for ifc_definition_id, obj in IfcStore.id_map.items():
|
||||
def sync_all_objects(self):
|
||||
results = []
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
for ifc_definition_id in list(IfcStore.id_map.keys()):
|
||||
obj = IfcStore.id_map[ifc_definition_id]
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
continue
|
||||
self.sync_object_placement(obj)
|
||||
self.sync_object_container(ifc_definition_id, obj)
|
||||
tool.Collector.sync(obj)
|
||||
result = self.sync_object_placement(obj)
|
||||
if result:
|
||||
results.append(result)
|
||||
result = self.sync_object_material(obj)
|
||||
if result:
|
||||
results.append(result)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
if self.should_delete(obj):
|
||||
to_delete.append(ifc_definition_id)
|
||||
|
||||
for ifc_definition_id in to_delete:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
IfcStore.unlink_element(product)
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
return results
|
||||
|
||||
def sync_edited_objects(self):
|
||||
results = []
|
||||
for obj in IfcStore.edited_objs.copy():
|
||||
if not obj:
|
||||
continue
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
bpy.ops.bim.update_style_colours(material=obj.name)
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
|
||||
else:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
results.append(element)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
IfcStore.edited_objs.clear()
|
||||
return results
|
||||
|
||||
def sync_object_material(self, obj):
|
||||
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
if not self.has_changed_materials(obj):
|
||||
return
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
def has_changed_materials(self, obj):
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
if not tool.Ifc.is_moved(obj):
|
||||
return
|
||||
blender_matrix = np.array(obj.matrix_world)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
return element
|
||||
if element.is_a("IfcGridAxis"):
|
||||
return self.sync_grid_axis_object_placement(obj, element)
|
||||
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
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
return element
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
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,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
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
|
||||
foo
|
||||
return False
|
||||
except:
|
||||
return True
|
||||
def sync_grid_axis_object_placement(self, obj, element):
|
||||
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
|
||||
grid_obj = tool.Ifc.get_object(grid)
|
||||
if grid_obj:
|
||||
self.sync_object_placement(grid_obj)
|
||||
if grid_obj.matrix_world != obj.matrix_world:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
def get_application_name(self):
|
||||
return "BlenderBIM"
|
||||
|
||||
@@ -26,9 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
|
||||
from ifcopenshell.api.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
|
||||
|
||||
|
||||
@@ -50,9 +48,7 @@ def mode_callback(obj, data):
|
||||
):
|
||||
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)
|
||||
IfcStore.edited_objs.add(obj)
|
||||
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
@@ -70,11 +66,10 @@ def name_callback(obj, data):
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
|
||||
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)
|
||||
refresh_ui_data()
|
||||
return
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
|
||||
@@ -98,7 +93,7 @@ def name_callback(obj, data):
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
TypeData.purge()
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
def color_callback(obj, data):
|
||||
@@ -107,13 +102,6 @@ def color_callback(obj, data):
|
||||
|
||||
|
||||
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)
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
@@ -169,12 +157,12 @@ def loadIfcStore(scene):
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
IfcStore.get_schema()
|
||||
IfcStore.reload_linked_elements()
|
||||
IfcStore.relink_all_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -183,13 +171,13 @@ def undo_post(scene):
|
||||
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])
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -198,8 +186,8 @@ def redo_post(scene):
|
||||
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])
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -209,6 +197,7 @@ def ensureIfcExported(scene):
|
||||
|
||||
|
||||
def get_application(ifc):
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
version = get_application_version()
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
if element.ApplicationIdentifier == "BlenderBIM" and element.Version == version:
|
||||
@@ -216,7 +205,7 @@ def get_application(ifc):
|
||||
return ifcopenshell.api.run(
|
||||
"owner.add_application",
|
||||
ifc,
|
||||
version=get_application_version(),
|
||||
version=version,
|
||||
application_full_name="BlenderBIM Add-on",
|
||||
application_identifier="BlenderBIM",
|
||||
)
|
||||
@@ -244,6 +233,7 @@ def setDefaultProperties(scene):
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
# TODO: Move to drawing module
|
||||
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Technical"
|
||||
@@ -297,7 +287,7 @@ def setDefaultProperties(scene):
|
||||
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
|
||||
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
|
||||
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
|
||||
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
|
||||
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import json
|
||||
import math
|
||||
import zipfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
from mathutils import geometry
|
||||
@@ -46,37 +47,52 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.data = json.dumps({"name": attribute.name, "value": attribute.get_value(), "is_null": attribute.is_null})
|
||||
op.name = attribute.name
|
||||
if attribute.is_uri:
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.data_path = attribute.path_from_id("string_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 isinstance(data_type, tuple) or data_type == "entity":
|
||||
callback(attribute.name(), None, data) if callback else None
|
||||
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()]
|
||||
import_attribute(attribute, props, data, callback=callback)
|
||||
|
||||
|
||||
# A more elegant attribute importer signature, intended to supersede import_attributes
|
||||
def import_attributes2(element, props, callback=None):
|
||||
for attribute in element.wrapped_data.declaration().as_entity().all_attributes():
|
||||
import_attribute(attribute, props, element.get_info(), callback=callback)
|
||||
|
||||
|
||||
def import_attribute(attribute, props, data, callback=None):
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if isinstance(data_type, tuple) or data_type == "entity":
|
||||
callback(attribute.name(), None, data) if callback else None
|
||||
return
|
||||
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()]
|
||||
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
|
||||
new.is_uri = True
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[attribute.name()]
|
||||
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()]
|
||||
|
||||
|
||||
def export_attributes(props, callback=None):
|
||||
@@ -87,3 +103,45 @@ def export_attributes(props, callback=None):
|
||||
continue # Our job is done
|
||||
attributes[prop.name] = prop.get_value()
|
||||
return attributes
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
|
||||
def extract(self):
|
||||
extension = self.filepath.split(".")[-1]
|
||||
if extension.lower() == "ifc":
|
||||
with open(self.filepath) as ifc_file:
|
||||
return self.extract_ifc_spf(ifc_file)
|
||||
elif extension.lower() == "ifczip":
|
||||
return self.extract_ifc_zip()
|
||||
|
||||
def extract_ifc_spf(self, ifc_file):
|
||||
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
data = {}
|
||||
max_lines_to_parse = 50
|
||||
for _ in range(max_lines_to_parse):
|
||||
line = next(ifc_file)
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode("utf-8")
|
||||
if line.startswith("FILE_DESCRIPTION"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
data["description"] = part
|
||||
elif i == 3:
|
||||
data["implementation_level"] = part
|
||||
elif line.startswith("FILE_NAME"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
data["name"] = part
|
||||
elif i == 3:
|
||||
data["time_stamp"] = part
|
||||
elif line.startswith("FILE_SCHEMA"):
|
||||
data["schema_name"] = line.split("'")[1]
|
||||
break
|
||||
return data
|
||||
|
||||
def extract_ifc_zip(self):
|
||||
archive = zipfile.ZipFile(self.filepath, "r")
|
||||
return self.extract_ifc_spf(archive.open(archive.filelist[0]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -16,8 +16,10 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
@@ -29,8 +31,11 @@ class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
schema = None
|
||||
cache = None
|
||||
cache_path = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
deleted_ids = set()
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
@@ -38,28 +43,36 @@ class IfcStore:
|
||||
library_file = None
|
||||
element_listeners = set()
|
||||
undo_redo_stack_objects = set()
|
||||
undo_redo_stack_object_names = {}
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3"]
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
IfcStore.path = ""
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
IfcStore.cache = None
|
||||
IfcStore.cache_path = None
|
||||
IfcStore.id_map = {}
|
||||
IfcStore.guid_map = {}
|
||||
IfcStore.deleted_ids = set()
|
||||
IfcStore.edited_objs = set()
|
||||
IfcStore.pset_template_path = ""
|
||||
IfcStore.pset_template_file = None
|
||||
IfcStore.library_path = ""
|
||||
IfcStore.library_file = None
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
if IfcStore.file is None:
|
||||
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
|
||||
if not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
@@ -67,6 +80,31 @@ class IfcStore:
|
||||
pass
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
def get_cache():
|
||||
if IfcStore.cache is None and IfcStore.path:
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
cache_settings.set(cache_settings.STRICT_TOLERANCE, True)
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
|
||||
except:
|
||||
return
|
||||
return IfcStore.cache
|
||||
|
||||
@staticmethod
|
||||
def update_cache():
|
||||
if not IfcStore.cache:
|
||||
return
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
os.replace(IfcStore.cache_path, new_cache_path)
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
extension = path.split(".")[-1]
|
||||
@@ -85,7 +123,9 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def get_schema():
|
||||
if IfcStore.file is None:
|
||||
return
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
|
||||
bpy.context.scene.BIMProjectProperties.export_schema
|
||||
)
|
||||
elif IfcStore.schema is None:
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
|
||||
return IfcStore.schema
|
||||
@@ -108,14 +148,32 @@ class IfcStore:
|
||||
IfcStore.element_listeners.add(callback)
|
||||
|
||||
@staticmethod
|
||||
def update_undo_redo_stack_objects():
|
||||
def track_undo_redo_stack_object_map():
|
||||
"""Keeps track of currently mapped 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
|
||||
occur whenever an object is affected during an operation.
|
||||
|
||||
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
|
||||
"""
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
IfcStore.undo_redo_stack_object_names[key] = value.name
|
||||
except:
|
||||
continue
|
||||
|
||||
@staticmethod
|
||||
def track_undo_redo_stack_selected_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, and all children.
|
||||
occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos, and all children. This is
|
||||
important because selected objects are often deleted from the scene.
|
||||
|
||||
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.
|
||||
@@ -130,14 +188,23 @@ class IfcStore:
|
||||
IfcStore.undo_redo_stack_objects |= objects
|
||||
|
||||
@staticmethod
|
||||
def reload_linked_elements(objects=None):
|
||||
def reload_undo_redo_stack_objects():
|
||||
"""Reloads any invalidated objects after undo or redo
|
||||
|
||||
After an undo or redo operation, objects may have been invalidated in
|
||||
our id_map and guid_map. Invalidated objects are typically those that
|
||||
have been manipulated or deleted. This checks the cache of mapped and
|
||||
selected objects prior to the operation and ensures that if the object
|
||||
is invalidated, they are reloaded based on the object name that was
|
||||
tracked prior to the undo / redo.
|
||||
"""
|
||||
file = IfcStore.get_file()
|
||||
if not file:
|
||||
return
|
||||
if objects is None:
|
||||
objects = bpy.data.objects
|
||||
|
||||
for obj in objects:
|
||||
# First, reload objects that were selected or active
|
||||
for name in IfcStore.undo_redo_stack_objects:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -148,6 +215,62 @@ class IfcStore:
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
value.name
|
||||
except:
|
||||
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
|
||||
if not obj or 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 relink_all_objects():
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
for obj in bpy.data.objects:
|
||||
IfcStore.relink_object(obj)
|
||||
for obj in bpy.data.materials:
|
||||
IfcStore.relink_object(obj)
|
||||
|
||||
@staticmethod
|
||||
def relink_object(obj):
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = IfcStore.get_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)
|
||||
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def delete_element(element):
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id()}
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
{"rollback": IfcStore.rollback_delete_element, "commit": IfcStore.commit_delete_element, "data": data}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def rollback_delete_element(data):
|
||||
IfcStore.deleted_ids.remove(data["id"])
|
||||
|
||||
@staticmethod
|
||||
def commit_delete_element(data):
|
||||
IfcStore.deleted_ids.add(data["id"])
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
IfcStore.id_map[element.id()] = obj
|
||||
@@ -184,6 +307,8 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def commit_link_element(data):
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
if not obj:
|
||||
obj = bpy.data.materials.get(data["obj"])
|
||||
IfcStore.id_map[data["id"]] = obj
|
||||
if "guid" in data:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
@@ -224,7 +349,9 @@ class IfcStore:
|
||||
except:
|
||||
pass
|
||||
|
||||
if obj:
|
||||
if element and element.is_a("IfcSurfaceStyle"):
|
||||
obj.BIMMaterialProperties.ifc_style_id = 0
|
||||
elif obj:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
|
||||
@staticmethod
|
||||
@@ -232,7 +359,6 @@ class IfcStore:
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
if is_top_level_operator:
|
||||
active_object = context.active_object
|
||||
IfcStore.begin_transaction(operator)
|
||||
IfcStore.get_file().begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
@@ -248,17 +374,14 @@ class IfcStore:
|
||||
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
|
||||
)
|
||||
IfcStore.end_transaction(operator)
|
||||
try:
|
||||
active_object.name
|
||||
context.view_layer.objects.active = active_object
|
||||
except:
|
||||
pass
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
IfcStore.undo_redo_stack_objects = set()
|
||||
IfcStore.undo_redo_stack_object_names = {}
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
|
||||
@@ -17,20 +17,25 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AssignObject,
|
||||
operator.EnableEditingAggregate,
|
||||
operator.DisableEditingAggregate,
|
||||
operator.AddAggregate,
|
||||
operator.BIM_OT_assign_object,
|
||||
operator.BIM_OT_unassign_object,
|
||||
operator.BIM_OT_enable_editing_aggregate,
|
||||
operator.BIM_OT_disable_editing_aggregate,
|
||||
operator.BIM_OT_add_aggregate,
|
||||
operator.BIM_OT_select_parts,
|
||||
operator.BIM_OT_select_aggregate,
|
||||
operator.BIM_OT_add_part_to_object,
|
||||
prop.BIMObjectAggregateProperties,
|
||||
ui.BIM_PT_aggregate,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
bpy.types.Object.BIMObjectAggregateProperties = bpy.props.PointerProperty(type=prop.BIMObjectAggregateProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
del bpy.types.Object.BIMObjectAggregateProperties
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def refresh():
|
||||
AggregateData.is_loaded = False
|
||||
|
||||
|
||||
class AggregateData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"has_relating_object": cls.has_relating_object(),
|
||||
"relating_object_label": cls.get_relating_object_label(),
|
||||
"relating_object_id": cls.get_relating_object_id(),
|
||||
"has_related_objects": cls.has_related_objects(),
|
||||
"related_objects_amount": cls.get_related_objects_amount(),
|
||||
"ifc_class": cls.ifc_class(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def get_relating_object(cls):
|
||||
return ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
|
||||
@classmethod
|
||||
def has_relating_object(cls) -> bool:
|
||||
return cls.get_relating_object() is not None
|
||||
|
||||
@classmethod
|
||||
def get_relating_object_label(cls) -> str:
|
||||
aggregate = cls.get_relating_object()
|
||||
if aggregate:
|
||||
return f"{aggregate.is_a()}/{aggregate.Name or ''}"
|
||||
|
||||
@classmethod
|
||||
def get_relating_object_id(cls) -> int:
|
||||
aggregate = cls.get_relating_object()
|
||||
if aggregate:
|
||||
return aggregate.id()
|
||||
|
||||
@classmethod
|
||||
def get_related_objects(cls):
|
||||
return ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
|
||||
@classmethod
|
||||
def get_related_objects_amount(cls):
|
||||
parts = cls.get_related_objects()
|
||||
return len(parts) if parts else 0
|
||||
|
||||
@classmethod
|
||||
def has_related_objects(cls) -> bool:
|
||||
return bool(cls.get_related_objects())
|
||||
|
||||
@classmethod
|
||||
def ifc_class(cls) -> str:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
return element.is_a()
|
||||
@@ -17,92 +17,84 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.aggregate as core
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.aggregate.data import Data
|
||||
|
||||
|
||||
class AssignObject(bpy.types.Operator):
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_assign_object(bpy.types.Operator, Operator):
|
||||
"""Create aggregation relationship between two ifc elements"""
|
||||
|
||||
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)
|
||||
relating_object: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
|
||||
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
|
||||
)
|
||||
relating_object = bpy.data.objects.get(self.relating_object)
|
||||
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
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(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):
|
||||
try:
|
||||
parent.children.unlink(child)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class EnableEditingAggregate(bpy.types.Operator):
|
||||
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
|
||||
"""Remove aggregation relationship between two ifc elements"""
|
||||
|
||||
bl_idname = "bim.unassign_object"
|
||||
bl_label = "Unassign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_object: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
|
||||
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
|
||||
)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
"""Enable editing aggregation relationship"""
|
||||
|
||||
bl_idname = "bim.enable_editing_aggregate"
|
||||
bl_label = "Enable Editing Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.active_object.BIMObjectProperties.relating_object = None
|
||||
context.active_object.BIMObjectProperties.is_editing_aggregate = True
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.enable_editing_aggregate(tool.Aggregate, obj=context.active_object)
|
||||
|
||||
|
||||
class DisableEditingAggregate(bpy.types.Operator):
|
||||
class BIM_OT_disable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
"""Disable editing aggregation relationship"""
|
||||
|
||||
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 context.active_object
|
||||
obj.BIMObjectProperties.is_editing_aggregate = False
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_editing_aggregate(tool.Aggregate, obj=context.active_object)
|
||||
|
||||
|
||||
class AddAggregate(bpy.types.Operator):
|
||||
class BIM_OT_add_aggregate(bpy.types.Operator):
|
||||
"""Add aggregate to IFC element"""
|
||||
|
||||
bl_idname = "bim.add_aggregate"
|
||||
bl_label = "Add Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -113,10 +105,99 @@ class AddAggregate(bpy.types.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return {"FINISHED"}
|
||||
|
||||
aggregate = self.create_aggregate(context)
|
||||
tool.Collector.sync(obj)
|
||||
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
current_container = ifcopenshell.util.element.get_container(element)
|
||||
if current_aggregate:
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(current_aggregate),
|
||||
related_obj=aggregate,
|
||||
)
|
||||
elif current_container:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=tool.Ifc.get_object(current_container),
|
||||
element_obj=aggregate,
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_aggregate(self, context):
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
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")
|
||||
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=aggregate.name)
|
||||
return aggregate
|
||||
|
||||
|
||||
class BIM_OT_select_parts(bpy.types.Operator):
|
||||
"""Select Parts"""
|
||||
|
||||
bl_idname = "bim.select_parts"
|
||||
bl_label = "Select Parts"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
|
||||
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
|
||||
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
|
||||
for selectable_part_obj in selectable_parts_objs:
|
||||
selectable_part_obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
"""Select Aggregate"""
|
||||
|
||||
bl_idname = "bim.select_aggregate"
|
||||
bl_label = "Select Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj in context.selectable_objects:
|
||||
aggregate_obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_part_to_object"
|
||||
bl_label = "Add Part to Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"})
|
||||
part_name: bpy.props.StringProperty(name="Name")
|
||||
obj: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.part_name = "My " + self.part_class.lstrip("Ifc")
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
core.add_part_to_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
tool.Blender,
|
||||
obj=obj,
|
||||
part_class=self.part_class,
|
||||
part_name=self.part_name,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.spatial.data import SpatialData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def update_relating_object(self, context):
|
||||
def message(self, context):
|
||||
self.layout.label(text="Please select a valid Ifc Element")
|
||||
|
||||
if self.relating_object is None:
|
||||
return
|
||||
if not self.relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
context.window_manager.popup_menu(message, title="Invalid Element Selected", icon="INFO")
|
||||
self.relating_object = None
|
||||
|
||||
|
||||
class BIMObjectAggregateProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object, update=update_relating_object)
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.aggregate.data import Data
|
||||
from blenderbim.bim.module.aggregate.data import AggregateData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class BIM_PT_aggregate(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -40,34 +41,55 @@ class BIM_PT_aggregate(Panel):
|
||||
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:
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
if not Data.products[props.ifc_definition_id]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return
|
||||
if props.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
layout = self.layout
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
if props.is_editing_aggregate:
|
||||
row = self.layout.row(align=True)
|
||||
props = context.active_object.BIMObjectAggregateProperties
|
||||
|
||||
if props.is_editing:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "relating_object", text="")
|
||||
if props.relating_object:
|
||||
row.operator(
|
||||
"bim.assign_object", icon="CHECKMARK", text=""
|
||||
).relating_object = props.relating_object.name
|
||||
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
name = "{}/{}".format(
|
||||
Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"]
|
||||
)
|
||||
if name == "None/None":
|
||||
name = "No Aggregate Found"
|
||||
row.label(text=name)
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
row = layout.row(align=True)
|
||||
if AggregateData.data["has_relating_object"]:
|
||||
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
|
||||
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.obj = context.active_object.name
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.unassign_object", icon="X", text="")
|
||||
op.relating_object = AggregateData.data["relating_object_id"]
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
else:
|
||||
row.label(text="No Aggregate", icon="TRIA_UP")
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
parts = AggregateData.data["related_objects_amount"]
|
||||
row.label(text=f"{parts or 'No'} Part{'s' if parts > 1 else ''}", icon="TRIA_DOWN")
|
||||
if AggregateData.data["has_related_objects"]:
|
||||
op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.obj = context.active_object.name
|
||||
|
||||
ifc_class = AggregateData.data["ifc_class"]
|
||||
part_class = ""
|
||||
if ifc_class == "IfcBuilding":
|
||||
part_class = "IfcBuildingStorey"
|
||||
elif ifc_class == "IfcSite":
|
||||
part_class = "IfcBuilding"
|
||||
elif ifc_class == "IfcProject":
|
||||
part_class = "IfcSite"
|
||||
if part_class != "":
|
||||
op = layout.operator("bim.add_part_to_object", text="Add " + part_class.lstrip("Ifc"))
|
||||
op.part_class = part_class
|
||||
op.obj = context.active_object.name
|
||||
|
||||
@@ -24,6 +24,7 @@ classes = (
|
||||
operator.DisableEditingAttributes,
|
||||
operator.EditAttributes,
|
||||
operator.GenerateGlobalId,
|
||||
operator.CopyAttributeToSelection,
|
||||
prop.BIMAttributeProperties,
|
||||
ui.BIM_PT_object_attributes,
|
||||
ui.BIM_PT_material_attributes,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
AttributesData.is_loaded = False
|
||||
MaterialAttributesData.is_loaded = False
|
||||
|
||||
|
||||
class AttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
data = element.get_info()
|
||||
if hasattr(element, "GlobalId"):
|
||||
excluded_keys = ["id", "type"]
|
||||
else:
|
||||
excluded_keys = ["type"]
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in excluded_keys:
|
||||
continue
|
||||
if key == "id":
|
||||
key = "STEP ID"
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
|
||||
class MaterialAttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
|
||||
data = element.get_info()
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in ["id", "type"]:
|
||||
continue
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
@@ -20,10 +20,21 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.attribute as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_attributes"
|
||||
bl_label = "Enable Editing Attributes"
|
||||
@@ -40,22 +51,9 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
new = props.attributes.add()
|
||||
if attribute["type"] == "entity" or (attribute["type"] == "list" and attribute["list_type"] == "entity"):
|
||||
continue
|
||||
new.name = attribute["name"]
|
||||
new.is_null = attribute["is_null"]
|
||||
if attribute["type"] == "string" or attribute["type"] == "list":
|
||||
new.string_value = attribute["value"] or ""
|
||||
elif attribute["type"] == "integer":
|
||||
new.int_value = attribute["value"] or 0
|
||||
elif attribute["type"] == "float":
|
||||
new.float_value = attribute["value"] or 0.0
|
||||
elif attribute["type"] == "enum":
|
||||
new.enum_items = json.dumps(attribute["enum_items"])
|
||||
if attribute["value"]:
|
||||
new.enum_value = attribute["value"]
|
||||
if oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes)
|
||||
props.is_editing_attributes = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -77,16 +75,13 @@ class DisableEditingAttributes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditAttributes(bpy.types.Operator):
|
||||
class EditAttributes(bpy.types.Operator, 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":
|
||||
@@ -139,3 +134,14 @@ class GenerateGlobalId(bpy.types.Operator):
|
||||
global_id.data_type = "string"
|
||||
global_id.string_value = ifcopenshell.guid.new()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyAttributeToSelection(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.copy_attribute_to_selection"
|
||||
bl_label = "Copy Attribute To Selection"
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value()
|
||||
for obj in context.selected_objects:
|
||||
core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj)
|
||||
|
||||
@@ -16,17 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
from blenderbim.bim.module.attribute.data import AttributesData, MaterialAttributesData
|
||||
|
||||
|
||||
def draw_ui(context, layout, obj_type):
|
||||
def draw_ui(context, layout, obj_type, attributes):
|
||||
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
if oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
@@ -37,46 +36,17 @@ def draw_ui(context, layout, obj_type):
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["type"] == "entity":
|
||||
continue
|
||||
row = layout.row(align=True)
|
||||
blender_attribute = props.attributes.get(attribute["name"])
|
||||
if attribute["type"] == "string" or attribute["type"] == "list":
|
||||
row.prop(blender_attribute, "string_value", text=attribute["name"])
|
||||
elif attribute["type"] == "integer":
|
||||
row.prop(blender_attribute, "int_value", text=attribute["name"])
|
||||
elif attribute["type"] == "float":
|
||||
row.prop(blender_attribute, "float_value", text=attribute["name"])
|
||||
elif attribute["type"] == "enum":
|
||||
row.prop(blender_attribute, "enum_value", text=attribute["name"])
|
||||
|
||||
if attribute["name"] == "GlobalId":
|
||||
row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
|
||||
if attribute["is_optional"]:
|
||||
row.prop(
|
||||
blender_attribute,
|
||||
"is_null",
|
||||
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
|
||||
text="",
|
||||
)
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
if "GlobalId" not in [a["name"] for a in Data.products[oprops.ifc_definition_id]]:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(oprops.ifc_definition_id))
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["value"] is None or attribute["type"] == "entity":
|
||||
continue
|
||||
for attribute in attributes:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=str(attribute["value"]))
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
# TODO: reimplement, see #1222
|
||||
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
|
||||
@@ -89,6 +59,7 @@ class BIM_PT_object_attributes(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -99,7 +70,9 @@ class BIM_PT_object_attributes(Panel):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
draw_ui(context, self.layout, "Object")
|
||||
if not AttributesData.is_loaded:
|
||||
AttributesData.load()
|
||||
draw_ui(context, self.layout, "Object", AttributesData.data["attributes"])
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
@@ -119,4 +92,6 @@ class BIM_PT_material_attributes(Panel):
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
draw_ui(context, self.layout, "Material")
|
||||
if not MaterialAttributesData.is_loaded:
|
||||
MaterialAttributesData.load()
|
||||
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
|
||||
|
||||
@@ -26,6 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -93,6 +93,7 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.get_topic(self.topic_guid)
|
||||
bcfxml.get_header(self.topic_guid)
|
||||
new = context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
"name": topic.guid,
|
||||
@@ -841,6 +842,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj.data.angle = 2 * atan(
|
||||
(0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
z_axis = Vector(
|
||||
(-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_bcf(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.module.root.prop import getIfcClasses
|
||||
from blenderbim.bim.module.root.prop import get_ifc_classes
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -33,14 +33,11 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global scenarios_enum
|
||||
global classes_enum
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def getScenarios(self, context):
|
||||
@@ -68,7 +65,7 @@ class BimTesterProperties(PropertyGroup):
|
||||
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
|
||||
steps: StringProperty(default="", name="Custom Steps")
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
||||
audit_ifc_class: EnumProperty(items=get_ifc_classes, name="Audit Class")
|
||||
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
|
||||
scenario: EnumProperty(items=getScenarios, name="Scenario")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_qa(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_split = True
|
||||
|
||||
@@ -17,14 +17,28 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui
|
||||
from . import ui, operator, prop
|
||||
|
||||
classes = (ui.BIM_PT_boundary,)
|
||||
classes = (
|
||||
operator.LoadProjectSpaceBoundaries,
|
||||
operator.LoadSpaceBoundaries,
|
||||
operator.LoadBoundary,
|
||||
operator.SelectProjectBoundaries,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.UpdateBoundaryGeometry,
|
||||
ui.BIM_PT_Boundary,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
ui.BIM_PT_SceneBoundaries,
|
||||
prop.BIMBoundaryProperties,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
del bpy.types.Object.bim_boundary_properties
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021, 2022 Cyril Waechter <cyril@biminsight.ch>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import logging
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
space_collection = bpy.data.collections.get(blender_space.name, blender_space.users_collection[0])
|
||||
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
|
||||
boundaries_collection = space_collection.children.get(collection_name)
|
||||
if not boundaries_collection:
|
||||
boundaries_collection = bpy.data.collections.new(collection_name)
|
||||
space_collection.children.link(boundaries_collection)
|
||||
return boundaries_collection
|
||||
|
||||
|
||||
class Loader:
|
||||
def __init__(self):
|
||||
self.ifc_file = None
|
||||
self.logger = None
|
||||
self.ifc_importer = None
|
||||
self.settings = None
|
||||
self.load_settings()
|
||||
self.load_importer()
|
||||
|
||||
def create_mesh(self, boundary):
|
||||
# ConnectionGeometry is optional in IFC schema for some reasons.
|
||||
if not boundary.ConnectionGeometry:
|
||||
return None
|
||||
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
|
||||
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
|
||||
surface.InnerBoundaries = ()
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
self.ifc_importer.link_mesh(shape, mesh)
|
||||
return mesh
|
||||
|
||||
def load_settings(self):
|
||||
self.settings = ifcopenshell.geom.settings()
|
||||
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
|
||||
self.settings.set(self.settings.USE_BREP_DATA, False)
|
||||
|
||||
def load_importer(self):
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
self.logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, self.logger)
|
||||
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
self.ifc_importer.file = self.ifc_file
|
||||
|
||||
def load_boundary(self, boundary_id, blender_space):
|
||||
boundary = self.ifc_file.by_id(boundary_id)
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
if obj:
|
||||
return obj
|
||||
mesh = self.create_mesh(boundary)
|
||||
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
|
||||
obj.matrix_world = blender_space.matrix_world
|
||||
boundaries_collection = get_boundaries_collection(blender_space)
|
||||
boundaries_collection.objects.link(obj)
|
||||
tool.Ifc.link(boundary, obj)
|
||||
return obj
|
||||
|
||||
|
||||
class LoadProjectSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_project_space_boundaries"
|
||||
bl_label = "Load all project space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
loader = Loader()
|
||||
if not Data.is_loaded:
|
||||
Data.load(tool.Ifc.get())
|
||||
for space_id, boundaries_id in Data.spaces.items():
|
||||
blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
|
||||
for boundary_id in boundaries_id:
|
||||
loader.load_boundary(boundary_id, blender_space)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.load_boundary"
|
||||
bl_label = "Load boundary"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
boundary_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
loader = Loader()
|
||||
blender_space = context.active_object
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
obj = loader.load_boundary(self.boundary_id, blender_space)
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_space_boundaries"
|
||||
bl_label = "Load selected space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
blender_space = context.active_object
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
loader = Loader()
|
||||
if not Data.is_loaded:
|
||||
Data.load(tool.Ifc.get())
|
||||
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
|
||||
loader.load_boundary(boundary_id, blender_space)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectProjectBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_project_space_boundaries"
|
||||
bl_label = "Select all project space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
ifc_file = tool.Ifc.get()
|
||||
for boundary_id in Data.boundaries:
|
||||
boundary = ifc_file.by_id(boundary_id)
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def get_colour(ifc_boundary):
|
||||
"""Return a color depending on IfcClass given"""
|
||||
product_colors = {
|
||||
"IfcWall": (0.7, 0.3, 0, 1),
|
||||
"IfcWindow": (0, 0.7, 1, 1),
|
||||
"IfcSlab": (0.7, 0.7, 0.5, 1),
|
||||
"IfcRoof": (0, 0.3, 0, 1),
|
||||
"IfcDoor": (1, 1, 1, 1),
|
||||
}
|
||||
if ifc_boundary.PhysicalOrVirtualBoundary == "VIRTUAL":
|
||||
return (1, 0, 1, 1)
|
||||
|
||||
element = ifc_boundary.RelatedBuildingElement
|
||||
if not element:
|
||||
return (1, 0, 0, 1)
|
||||
for product, colour in product_colors.items():
|
||||
if element.is_a(product):
|
||||
return colour
|
||||
return (0, 0, 0, 1)
|
||||
|
||||
|
||||
class ColourByRelatedBuildingElement(bpy.types.Operator):
|
||||
bl_idname = "bim.colour_by_related_building_element"
|
||||
bl_label = "Apply colour based on related building elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
self.store_state(context)
|
||||
result = self._execute(context)
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRelSpaceBoundary"):
|
||||
continue
|
||||
obj.color = get_colour(element)
|
||||
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
|
||||
if areas:
|
||||
areas[0].spaces[0].shading.color_type = "OBJECT"
|
||||
return {"FINISHED"}
|
||||
|
||||
def store_state(self, context):
|
||||
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
|
||||
if areas:
|
||||
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
|
||||
|
||||
def rollback(self, data):
|
||||
if data:
|
||||
data["area"].spaces[0].shading.color_type = data["color_type"]
|
||||
|
||||
def commit(self, data):
|
||||
if data:
|
||||
data["area"].spaces[0].shading.color_type = "OBJECT"
|
||||
|
||||
|
||||
EDITABLE_ATTRIBUTES = {
|
||||
"RelatingSpace": "relating_space",
|
||||
"RelatedBuildingElement": "related_building_element",
|
||||
"ParentBoundary": "parent_boundary",
|
||||
"CorrespondingBoundary": "corresponding_boundary",
|
||||
}
|
||||
|
||||
|
||||
class EnableEditingBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_boundary"
|
||||
bl_label = "Edit boundary relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
bprops.is_editing = True
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
entity = getattr(boundary, ifc_attribute, None)
|
||||
if not entity:
|
||||
continue
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if entity and obj:
|
||||
setattr(bprops, blender_property, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_boundary"
|
||||
bl_label = "Disable editing boundary relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
bprops.is_editing = False
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
setattr(bprops, blender_property, None)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBoundaryAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_boundary_attributes"
|
||||
bl_label = "Disable editing boundary relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
attributes = dict()
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
obj = getattr(bprops, blender_property, None)
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
attributes[blender_property] = entity
|
||||
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
|
||||
bpy.ops.bim.disable_editing_boundary()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
bl_idname = "bim.update_boundary_geometry"
|
||||
bl_label = "Update boundary geometry"
|
||||
bl_description = """
|
||||
Update boundary connection geometry from mesh.
|
||||
Mesh must lie on a single plane. It should look like a face or a face with holes.
|
||||
"""
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,60 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def space_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
|
||||
return False
|
||||
|
||||
|
||||
def boundary_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
return False
|
||||
|
||||
|
||||
def element_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcElement")
|
||||
return False
|
||||
|
||||
|
||||
class BIMBoundaryProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
|
||||
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
|
||||
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
@@ -20,16 +20,37 @@ import bpy
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
|
||||
|
||||
class BIM_PT_boundary(Panel):
|
||||
class BIM_PT_SceneBoundaries(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_idname = "BIM_PT_boundary"
|
||||
bl_id_name = "BIM_PT_scene_boundaries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.load_project_space_boundaries")
|
||||
row.operator("bim.select_project_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
|
||||
row.operator("bim.colour_by_related_building_element", text="", icon="BRUSH_DATA")
|
||||
|
||||
|
||||
class BIM_PT_Boundary(Panel):
|
||||
bl_label = "IFC Space Boundary"
|
||||
bl_idname = "BIM_PT_Boundary"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -40,15 +61,97 @@ class BIM_PT_boundary(Panel):
|
||||
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
|
||||
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
|
||||
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]
|
||||
props = context.active_object.BIMObjectProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
element = ifc_file.by_id(props.ifc_definition_id)
|
||||
boundary = ifc_file.by_id(props.ifc_definition_id)
|
||||
self.bprops = context.active_object.bim_boundary_properties
|
||||
if self.bprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
row.operator("bim.disable_editing_boundary", icon="CANCEL", text="")
|
||||
self.draw_relation_editor(boundary, "RelatingSpace", "relating_space")
|
||||
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
|
||||
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
|
||||
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text=f"{boundary_id}", icon="GHOST_ENABLED")
|
||||
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
|
||||
self.draw_relation_data(boundary, "RelatingSpace")
|
||||
self.draw_relation_data(boundary, "RelatedBuildingElement")
|
||||
self.draw_relation_data(boundary, "ParentBoundary")
|
||||
self.draw_relation_data(boundary, "CorrespondingBoundary")
|
||||
if hasattr(boundary, "InnerBoundaries"):
|
||||
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="InnerBoundaries")
|
||||
row.label(text=f"[{i}]")
|
||||
row.label(text=f"{inner_boundary.is_a()}/{inner_boundary.Name}")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.update_boundary_geometry")
|
||||
|
||||
def draw_relation_data(self, boundary, ifc_attribute: str):
|
||||
row = self.layout.row(align=True)
|
||||
if hasattr(boundary, ifc_attribute):
|
||||
row.label(text=ifc_attribute)
|
||||
entity = getattr(boundary, ifc_attribute)
|
||||
if entity:
|
||||
row.label(text=f"{entity.is_a()}/{entity.Name}")
|
||||
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
|
||||
op.global_id = entity.GlobalId
|
||||
else:
|
||||
row.label(text="")
|
||||
|
||||
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
|
||||
if hasattr(boundary, ifc_attribute):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.bprops, blender_property)
|
||||
|
||||
|
||||
class BIM_PT_SpaceBoundaries(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_idname = "BIM_PT_SpaceBoundaries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_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
|
||||
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
|
||||
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
|
||||
for ifc_class in valid_classes:
|
||||
if entity.is_a(ifc_class):
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.active_object.BIMObjectProperties
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_space_boundaries")
|
||||
if not Data.is_loaded:
|
||||
Data.load(self.ifc_file)
|
||||
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
|
||||
boundary_data = Data.boundaries[boundary_id]
|
||||
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
|
||||
row = self.layout.row()
|
||||
if building_element:
|
||||
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
|
||||
else:
|
||||
bld_el_description = None
|
||||
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
|
||||
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.boundary_id = boundary_id
|
||||
|
||||
@@ -16,17 +16,34 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import test.bim.bootstrap
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddBrick,
|
||||
operator.AddBrickFeed,
|
||||
operator.AssignBrickReference,
|
||||
operator.CloseBrickProject,
|
||||
operator.ConvertBrickProject,
|
||||
operator.ConvertIfcToBrick,
|
||||
operator.LoadBrickProject,
|
||||
operator.NewBrickFile,
|
||||
operator.RefreshBrickViewer,
|
||||
operator.RemoveBrick,
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
ui.BIM_PT_ifc_brickschema_references,
|
||||
ui.BIM_UL_bricks,
|
||||
)
|
||||
|
||||
|
||||
class TestExecuteIfcPatch(test.bim.bootstrap.NewFile):
|
||||
@test.bim.bootstrap.scenario
|
||||
def test_executing_ifcpatch(self):
|
||||
return """
|
||||
Given I set "scene.BIMPatchProperties.ifc_patch_recipes" to "OffsetObjectPlacements"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/basic-patched.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]"
|
||||
When I press "bim.execute_ifc_patch"
|
||||
Then the file "{cwd}/test/files/basic-patched.ifc" should contain "123454321"
|
||||
"""
|
||||
def register():
|
||||
bpy.types.Scene.BIMBrickProperties = bpy.props.PointerProperty(type=prop.BIMBrickProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMBrickProperties
|
||||
@@ -0,0 +1,180 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
try:
|
||||
from rdflib import URIRef, BNode
|
||||
except:
|
||||
# See #1860
|
||||
print("Warning: brickschema not available.")
|
||||
|
||||
|
||||
def refresh():
|
||||
BrickschemaData.is_loaded = False
|
||||
BrickschemaReferencesData.is_loaded = False
|
||||
|
||||
|
||||
class BrickschemaData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"is_loaded": cls.get_is_loaded(),
|
||||
"attributes": cls.attributes(),
|
||||
"namespaces": cls.namespaces(),
|
||||
"brick_equipment_classes": cls.brick_equipment_classes(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_is_loaded(cls):
|
||||
return BrickStore.graph is not None
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
try:
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
except:
|
||||
return []
|
||||
results = []
|
||||
uri = brick.uri
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
||||
SELECT DISTINCT ?name ?value ?sp ?sv WHERE {
|
||||
<{uri}> ?name ?value .
|
||||
OPTIONAL {
|
||||
{ ?name rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?name a brick:EntityProperty . }
|
||||
?value ?sp ?sv }
|
||||
}
|
||||
""".replace(
|
||||
"{uri}", uri
|
||||
)
|
||||
)
|
||||
|
||||
for row in query:
|
||||
name = row.get("name").toPython().split("#")[-1]
|
||||
value = row.get("value")
|
||||
results.append(
|
||||
{
|
||||
"name": name,
|
||||
"value": value.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(value, URIRef),
|
||||
"value_uri": value.toPython(),
|
||||
"is_globalid": name == "globalID",
|
||||
}
|
||||
)
|
||||
if isinstance(row.get("value"), BNode):
|
||||
for s, p, o in BrickStore.graph.triples((value, None, None)):
|
||||
results.append(
|
||||
{
|
||||
"name": name + ":" + p.toPython().split("#")[-1],
|
||||
"value": o.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(o, URIRef),
|
||||
"value_uri": o.toPython(),
|
||||
"is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def namespaces(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
results = []
|
||||
for alias, uri in BrickStore.graph.namespaces():
|
||||
results.append((uri, f"{alias}: {uri}", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def brick_equipment_classes(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
results = []
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
SELECT ?class WHERE {
|
||||
?class rdfs:subClassOf* brick:Equipment .
|
||||
}
|
||||
"""
|
||||
)
|
||||
for uri in sorted([x[0].toPython() for x in query]):
|
||||
results.append((uri, uri.split("#")[-1], ""))
|
||||
return results
|
||||
|
||||
|
||||
class BrickschemaReferencesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"is_loaded": cls.get_is_loaded(), "libraries": cls.libraries(), "references": cls.references()}
|
||||
|
||||
@classmethod
|
||||
def get_is_loaded(cls):
|
||||
return BrickStore.graph is not None
|
||||
|
||||
@classmethod
|
||||
def libraries(cls):
|
||||
results = []
|
||||
ifc = tool.Ifc.get()
|
||||
if not ifc:
|
||||
return results
|
||||
for library in ifc.by_type("IfcLibraryInformation"):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
elif ".ttl" in library.Location:
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
|
||||
if rel.is_a("IfcRelAssociatesLibrary"):
|
||||
reference = rel.RelatingLibrary
|
||||
if tool.Ifc.get_schema() == "IFC2X3" and "#" not in reference.ItemReference:
|
||||
continue
|
||||
if tool.Ifc.get_schema() != "IFC2X3" and "#" not in reference.Identification:
|
||||
continue
|
||||
results.append(
|
||||
{
|
||||
"id": reference.id(),
|
||||
"identification": reference.ItemReference
|
||||
if tool.Ifc.get_schema() == "IFC2X3"
|
||||
else reference.Identification,
|
||||
"name": reference.Name or "Unnamed",
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -0,0 +1,191 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
self._execute(context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_brick_project"
|
||||
bl_label = "Load Brickschema Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_brick_project(tool.Brick, filepath=self.filepath)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ViewBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_class"
|
||||
bl_label = "View Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
brick_class: bpy.props.StringProperty(name="Brick Class")
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
|
||||
|
||||
|
||||
class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_item"
|
||||
bl_label = "View Brick Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty(name="Brick Item")
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_item(tool.Brick, item=self.item)
|
||||
|
||||
|
||||
class RewindBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.rewind_brick_class"
|
||||
bl_label = "Rewind Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.rewind_brick_class(tool.Brick)
|
||||
|
||||
|
||||
class CloseBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.close_brick_project"
|
||||
bl_label = "Close Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.close_brick_project(tool.Brick)
|
||||
|
||||
|
||||
class ConvertBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_brick_project"
|
||||
bl_label = "Convert Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_brick_project(tool.Ifc, tool.Brick)
|
||||
|
||||
|
||||
class AssignBrickReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_brick_reference"
|
||||
bl_label = "Assign Brick Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
core.assign_brick_reference(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
element=tool.Ifc.get_entity(context.active_object),
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)),
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
|
||||
|
||||
class AddBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick"
|
||||
bl_label = "Add Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
library = None
|
||||
if props.libraries:
|
||||
library = tool.Ifc.get().by_id(int(props.libraries))
|
||||
core.add_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
|
||||
namespace=props.namespace,
|
||||
brick_class=props.brick_equipment_class,
|
||||
library=library,
|
||||
)
|
||||
|
||||
|
||||
class AddBrickFeed(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_feed"
|
||||
bl_label = "Add Brick Feed"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
source = tool.Ifc.get_entity([o for o in context.selected_objects if o != context.active_object][0])
|
||||
destination = tool.Ifc.get_entity(context.active_object)
|
||||
core.add_brick_feed(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
source=source,
|
||||
destination=destination,
|
||||
)
|
||||
|
||||
|
||||
class ConvertIfcToBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_ifc_to_brick"
|
||||
bl_label = "Convert IFC To Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
library = None
|
||||
if props.libraries:
|
||||
library = tool.Ifc.get().by_id(int(props.libraries))
|
||||
core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library)
|
||||
|
||||
|
||||
class NewBrickFile(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.new_brick_file"
|
||||
bl_label = "New Brick File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.new_brick_file(tool.Brick)
|
||||
|
||||
|
||||
class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.refresh_brick_viewer"
|
||||
bl_label = "Refresh Brick Viewer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick)
|
||||
|
||||
|
||||
class RemoveBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick"
|
||||
bl_label = "Remove Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
core.remove_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def update_active_brick_index(self, context):
|
||||
BrickschemaData.is_loaded = False
|
||||
|
||||
|
||||
def get_libraries(self, context):
|
||||
if not BrickschemaReferencesData.is_loaded:
|
||||
BrickschemaReferencesData.load()
|
||||
return BrickschemaReferencesData.data["libraries"]
|
||||
|
||||
|
||||
def get_namespaces(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
return BrickschemaData.data["namespaces"]
|
||||
|
||||
|
||||
def get_brick_equipment_classes(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
return BrickschemaData.data["brick_equipment_classes"]
|
||||
|
||||
|
||||
class Brick(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
label: StringProperty(name="Label")
|
||||
uri: StringProperty(name="URI")
|
||||
total_items: IntProperty(name="Total Items")
|
||||
|
||||
|
||||
class BIMBrickProperties(PropertyGroup):
|
||||
active_brick_class: StringProperty(name="Active Brick Class")
|
||||
brick_breadcrumbs: CollectionProperty(name="Brick Breadcrumbs", type=StrProperty)
|
||||
bricks: CollectionProperty(name="Bricks", type=Brick)
|
||||
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
|
||||
libraries: EnumProperty(name="Libraries", items=get_libraries)
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
|
||||
@@ -0,0 +1,132 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
|
||||
|
||||
class BIM_PT_brickschema(Panel):
|
||||
bl_label = "Brickschema Project"
|
||||
bl_idname = "BIM_PT_brickschema"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
if not BrickschemaData.data["is_loaded"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.new_brick_file", text="Create Project")
|
||||
row.operator("bim.load_brick_project", text="Load Project")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
row.label(text=self.props.active_brick_class)
|
||||
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "namespace", text="")
|
||||
row.prop(self.props, "brick_equipment_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
if attribute["is_uri"]:
|
||||
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.item = attribute["value_uri"]
|
||||
if attribute["is_globalid"]:
|
||||
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.global_id = attribute["value"]
|
||||
|
||||
|
||||
class BIM_PT_ifc_brickschema_references(Panel):
|
||||
bl_label = "IFC Brickschema References"
|
||||
bl_idname = "BIM_PT_ifc_brickschema_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not BrickschemaReferencesData.is_loaded:
|
||||
BrickschemaReferencesData.load()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
if not BrickschemaReferencesData.data["is_loaded"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Brickschema Project Loaded")
|
||||
return
|
||||
|
||||
if not BrickschemaReferencesData.data["libraries"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No IFC Libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.assign_brick_reference", icon="ADD")
|
||||
row.operator("bim.convert_ifc_to_brick", icon="IMPORT")
|
||||
|
||||
if not BrickschemaReferencesData.data["references"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="No References")
|
||||
|
||||
for reference in BrickschemaReferencesData.data["references"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=reference["identification"], icon="ASSET_MANAGER")
|
||||
row.label(text=reference["name"])
|
||||
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
|
||||
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
|
||||
|
||||
|
||||
class BIM_UL_bricks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
row.label(text=item.label if item.label else item.name)
|
||||
if item.total_items:
|
||||
row.label(text=str(item.total_items))
|
||||
@@ -0,0 +1,39 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import operator, workspace
|
||||
|
||||
classes = (
|
||||
operator.CadArcFrom2Points,
|
||||
operator.CadArcFrom3Points,
|
||||
operator.CadFillet,
|
||||
operator.CadMitre,
|
||||
operator.CadTrimExtend,
|
||||
workspace.CadHotkey,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.CadTool)
|
||||
@@ -0,0 +1,345 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import mathutils
|
||||
import bpy_extras
|
||||
import blenderbim.tool as tool
|
||||
|
||||
messages = {
|
||||
"SHARED_VERTEX": "Shared Vertex, no intersection possible",
|
||||
"PARALLEL_EDGES": "Edges Parallel, no intersection possible",
|
||||
"NON_PLANAR_EDGES": "Non Planar Edges, no clean intersection point",
|
||||
}
|
||||
|
||||
|
||||
class CadTrimExtend(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_trim_extend"
|
||||
bl_label = "CAD Trim / Extend"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def cancel_message(self, msg):
|
||||
print(msg)
|
||||
self.report({"WARNING"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def execute(self, context):
|
||||
# final attempt to enter unfragmented bm/mesh
|
||||
# ghastly, but what can I do? it works with these
|
||||
# fails without.
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = context.active_object
|
||||
me = obj.data
|
||||
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
if len(edges) == 2:
|
||||
message = tool.Cad.do_vtx_if_appropriate(bm, edges, mode="T")
|
||||
if isinstance(message, set):
|
||||
msg = messages.get(message.pop())
|
||||
return self.cancel_message(msg)
|
||||
bm = message
|
||||
else:
|
||||
return self.cancel_message("select two edges!")
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(me, loop_triangles=True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadMitre(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_mitre"
|
||||
bl_label = "CAD Mitre"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def cancel_message(self, msg):
|
||||
print(msg)
|
||||
self.report({"WARNING"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def execute(self, context):
|
||||
# final attempt to enter unfragmented bm/mesh
|
||||
# ghastly, but what can I do? it works with these
|
||||
# fails without.
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = context.active_object
|
||||
me = obj.data
|
||||
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
if len(edges) == 2:
|
||||
message = tool.Cad.do_vtx_if_appropriate(bm, edges, mode="V")
|
||||
if isinstance(message, set):
|
||||
msg = messages.get(message.pop())
|
||||
return self.cancel_message(msg)
|
||||
bm = message
|
||||
else:
|
||||
return self.cancel_message("select two edges!")
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(me, loop_triangles=True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadFillet(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_fillet"
|
||||
bl_label = "CAD Fillet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=0, default=1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for prop in self.__class__.__annotations__.keys():
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = bpy.context.active_object
|
||||
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
mesh = obj.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_edges = [e for e in bm.edges if e.select]
|
||||
if len(selected_edges) != 2:
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Assume the user has selected two edges sharing a vert, but merge verts
|
||||
# just in case the vertex is coincident but not shared.
|
||||
all_verts = list(set(selected_edges[0].verts) | set(selected_edges[1].verts))
|
||||
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
|
||||
|
||||
# Subdivide one of the edges to give us an extra vert to play with
|
||||
# without damaging any faces that already exist.
|
||||
cut = bmesh.ops.bisect_edges(bm, edges=[selected_edges[1]], cuts=1)
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
|
||||
is_wire = selected_edges[0].is_wire
|
||||
|
||||
# Recover the original intention of the selected edges after our bisect operation
|
||||
cut_edges = cut["geom_split"][1:]
|
||||
selected_edges = [e for e in bm.edges if e.select and e not in cut_edges]
|
||||
if set(selected_edges[0].verts) & set(cut_edges[0].verts):
|
||||
selected_edges.append(cut_edges[0])
|
||||
elif set(selected_edges[0].verts) & set(cut_edges[1].verts):
|
||||
selected_edges.append(cut_edges[1])
|
||||
|
||||
# Calculate the distance to slide each edge to make space for the fillet arc
|
||||
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
|
||||
v1 = selected_edges[0].other_vert(shared_vert)
|
||||
v2 = selected_edges[1].other_vert(shared_vert)
|
||||
dir1 = (v1.co - shared_vert.co).normalized()
|
||||
dir2 = (v2.co - shared_vert.co).normalized()
|
||||
edge_angle = dir1.angle(dir2)
|
||||
slide_distance = self.radius / math.tan(edge_angle / 2)
|
||||
|
||||
angle = math.pi - edge_angle
|
||||
fillet_v1 = shared_vert.co + (dir1 * slide_distance)
|
||||
fillet_v2 = shared_vert.co + (dir2 * slide_distance)
|
||||
|
||||
normal = mathutils.geometry.normal([v1.co, shared_vert.co, v2.co])
|
||||
|
||||
center = mathutils.geometry.intersect_line_line(
|
||||
fillet_v1, fillet_v1 + normal.cross(dir1), fillet_v2, fillet_v2 + normal.cross(dir2)
|
||||
)[0]
|
||||
|
||||
v = bm.verts.new(fillet_v1)
|
||||
steps = self.resolution * 4 if self.resolution else 1
|
||||
spin = bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=steps, angle=angle)
|
||||
|
||||
bmesh.ops.pointmerge(bm, verts=[shared_vert, v], merge_co=v.co)
|
||||
bmesh.ops.pointmerge(bm, verts=[v2, spin["geom_last"][0]], merge_co=spin["geom_last"][0].co)
|
||||
|
||||
# If faces are involved, then the chamfer edge protects existing faces from the newly created fillet.
|
||||
# If no faces are involved, we delete the chamfer edge.
|
||||
if is_wire:
|
||||
chamfer_edge = [e for e in bm.edges if shared_vert in e.verts and v2 in e.verts][0]
|
||||
bm.edges.remove(chamfer_edge)
|
||||
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadArcFrom2Points(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_arc_from_2_points"
|
||||
bl_label = "CAD Arc from 2 Points"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for prop in self.__class__.__annotations__.keys():
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
if bpy.context.mode != "EDIT_MESH":
|
||||
return {"CANCELLED"}
|
||||
obj = bpy.context.active_object
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
cursor = bpy.context.scene.cursor.location
|
||||
center = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, cursor)
|
||||
if not center:
|
||||
return {"CANCELLED"}
|
||||
mesh = obj.data
|
||||
mw = obj.matrix_world
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_verts = [v for v in bm.verts if v.select]
|
||||
if len(selected_verts) != 2:
|
||||
return {"CANCELLED"}
|
||||
v1 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[0].co)
|
||||
v2 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[1].co)
|
||||
l1 = v1 - center
|
||||
l2 = v2 - center
|
||||
angle = l1.angle_signed(l2)
|
||||
|
||||
if self.should_flip:
|
||||
if angle > 0:
|
||||
angle = -((math.pi * 2) - angle)
|
||||
else:
|
||||
angle = (math.pi * 2) + angle
|
||||
|
||||
v = selected_verts[0]
|
||||
bm.verts.remove(selected_verts[1])
|
||||
axis = region_3d.view_rotation @ mathutils.Vector((0, 0, 1))
|
||||
bmesh.ops.spin(
|
||||
bm,
|
||||
geom=[v],
|
||||
axis=mw.inverted().to_quaternion() @ axis,
|
||||
cent=mw.inverted() @ cursor,
|
||||
steps=self.resolution * 4,
|
||||
angle=-angle,
|
||||
)
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadArcFrom3Points(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_arc_from_3_points"
|
||||
bl_label = "CAD Arc from 3 Points"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
only_recalculate_center: bpy.props.BoolProperty(name="Only Recalculate Center", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for prop in self.__class__.__annotations__.keys():
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
if bpy.context.mode != "EDIT_MESH":
|
||||
return {"CANCELLED"}
|
||||
|
||||
obj = bpy.context.edit_object
|
||||
mesh = obj.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
|
||||
selected_verts = [v for v in bm.verts if v.select]
|
||||
if len(selected_verts) != 3:
|
||||
return {"CANCELLED"}
|
||||
pts = [v.co for v in selected_verts]
|
||||
center = tool.Cad.get_center_of_arc(pts, obj)
|
||||
if not center:
|
||||
return {"CANCELLED"}
|
||||
|
||||
bpy.context.scene.cursor.location = center
|
||||
if self.only_recalculate_center:
|
||||
return {"FINISHED"}
|
||||
|
||||
center = obj.matrix_world.inverted() @ center
|
||||
|
||||
def get_distance_to_other_points(vert):
|
||||
other_verts = [v for v in selected_verts if v != vert]
|
||||
total_vector = mathutils.Vector((0, 0, 0))
|
||||
for other_vert in other_verts:
|
||||
total_vector += other_vert.co - vert.co
|
||||
return total_vector.length
|
||||
|
||||
# For three selected points that represent a start, end, and point on an
|
||||
# arc, the start and end verts can be identified by being furthest away
|
||||
# from the other 2 points.
|
||||
arc_end_verts = sorted(selected_verts, key=get_distance_to_other_points)[-2:]
|
||||
normal = mathutils.geometry.normal(pts)
|
||||
|
||||
dir1 = (arc_end_verts[0].co - center).normalized()
|
||||
dir2 = (arc_end_verts[1].co - center).normalized()
|
||||
|
||||
# Let's get the matrix that represents the coordinate system of the arc.
|
||||
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
|
||||
z = normal
|
||||
x = (arc_end_verts[0].co - center).normalized()
|
||||
y = z.cross(x)
|
||||
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||
|
||||
dir1 = ((arc_matrix.inverted() @ arc_end_verts[0].co) - (arc_matrix.inverted() @ center)).normalized()
|
||||
dir2 = ((arc_matrix.inverted() @ arc_end_verts[1].co) - (arc_matrix.inverted() @ center)).normalized()
|
||||
angle = dir1.xy.angle_signed(dir2.xy)
|
||||
|
||||
v = arc_end_verts[0]
|
||||
bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=self.resolution * 4, angle=-angle)
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,93 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
|
||||
|
||||
class CadTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "EDIT_MESH"
|
||||
|
||||
bl_idname = "bim.cad_tool"
|
||||
bl_label = "CAD Tool"
|
||||
bl_description = "Gives you CAD authoring related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
|
||||
bl_widget = None
|
||||
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
|
||||
bl_keymap = (
|
||||
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.cad_fillet", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
# Enable Mesh Tools add-on to get this amazing tool
|
||||
("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
("bim.cad_arc_from_2_points", {"type": "C", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
("bim.cad_arc_from_3_points", {"type": "V", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Extend", icon="EVENT_E")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Mitre", icon="EVENT_T")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Fillet", icon="EVENT_F")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Offset", icon="EVENT_O")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="2-Point Arc", icon="EVENT_C")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="3-Point Arc", icon="EVENT_V")
|
||||
|
||||
|
||||
class CadHotkey(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_hotkey"
|
||||
bl_label = "CAD Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMModelProperties
|
||||
self.has_ifc_class = True
|
||||
try:
|
||||
self.has_ifc_class = bool(self.props.ifc_class)
|
||||
except:
|
||||
pass
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def hotkey_S_E(self):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.cad_mitre()
|
||||
@@ -34,9 +34,6 @@ classes = (
|
||||
operator.SmartClashGroup,
|
||||
operator.SelectSmartGroup,
|
||||
operator.LoadSmartGroupsForActiveClashSet,
|
||||
operator.SetBlenderClashSetA,
|
||||
operator.SetBlenderClashSetB,
|
||||
operator.ExecuteBlenderClash,
|
||||
prop.ClashSource,
|
||||
prop.ClashSet,
|
||||
prop.SmartClashGroup,
|
||||
|
||||
@@ -411,82 +411,3 @@ class SelectSmartGroup(bpy.types.Operator):
|
||||
obj.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BlenderClasher:
|
||||
def process_clash_set(self, context):
|
||||
import collision
|
||||
|
||||
a_cm = collision.CollisionManager()
|
||||
b_cm = collision.CollisionManager()
|
||||
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")
|
||||
return
|
||||
for contact in results[1]:
|
||||
if contact.raw.penetration_depth < 0.01:
|
||||
continue
|
||||
print("-----")
|
||||
print(contact.names)
|
||||
print(contact.raw.normal)
|
||||
print(contact.raw.pos)
|
||||
|
||||
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, 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, 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)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
del bm
|
||||
return mesh
|
||||
|
||||
|
||||
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):
|
||||
context.scene.BIMClashProperties.blender_clash_set_a.clear()
|
||||
for obj in context.selected_objects:
|
||||
new = context.scene.BIMClashProperties.blender_clash_set_a.add()
|
||||
new.name = obj.name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
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):
|
||||
context.scene.BIMClashProperties.blender_clash_set_b.clear()
|
||||
for obj in context.selected_objects:
|
||||
new = context.scene.BIMClashProperties.blender_clash_set_b.add()
|
||||
new.name = obj.name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExecuteBlenderClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_blender_clash"
|
||||
bl_label = "Execute Blender Clash"
|
||||
|
||||
def execute(self, context):
|
||||
blender_clasher = BlenderClasher()
|
||||
blender_clasher.process_clash_set(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -34,17 +35,6 @@ class BIM_PT_ifcclash(Panel):
|
||||
scene = context.scene
|
||||
props = scene.BIMClashProperties
|
||||
|
||||
layout.label(text="Blender Clash:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.set_blender_clash_set_a")
|
||||
row.operator("bim.set_blender_clash_set_b")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.execute_blender_clash")
|
||||
|
||||
layout.label(text="IFC Clash:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_clash_set")
|
||||
row.operator("bim.import_clash_sets", text="", icon="IMPORT")
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_classifications(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -86,6 +87,7 @@ class BIM_PT_classification_references(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_cobie(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_constraints(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -73,6 +74,7 @@ class BIM_PT_object_constraints(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -68,7 +68,7 @@ class EnableEditingContext(bpy.types.Operator, Operator):
|
||||
context: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_context(tool.ContextEditor, context=tool.Ifc.get().by_id(self.context))
|
||||
core.enable_editing_context(tool.Context, context=tool.Ifc.get().by_id(self.context))
|
||||
|
||||
|
||||
class DisableEditingContext(bpy.types.Operator, Operator):
|
||||
@@ -77,7 +77,7 @@ class DisableEditingContext(bpy.types.Operator, Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_context(tool.ContextEditor)
|
||||
core.disable_editing_context(tool.Context)
|
||||
|
||||
|
||||
class EditContext(bpy.types.Operator, Operator):
|
||||
@@ -86,4 +86,4 @@ class EditContext(bpy.types.Operator, Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_context(tool.Ifc, tool.ContextEditor)
|
||||
core.edit_context(tool.Ifc, tool.Context)
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_context(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -31,6 +31,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -26,6 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -22,10 +22,13 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.AddCsvAttribute,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.RemoveAllCsvAttributes,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportIfcCsv,
|
||||
operator.EyedropIfcCsv,
|
||||
operator.SelectCsvIfcFile,
|
||||
operator.ImportCsvAttributes,
|
||||
operator.ExportCsvAttributes,
|
||||
prop.CsvProperties,
|
||||
ui.BIM_PT_ifccsv,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,65 @@ class RemoveCsvAttribute(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_all_csv_attributes"
|
||||
bl_label = "Remove all CSV Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.CsvProperties.csv_attributes.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.import_csv_attributes"
|
||||
bl_label = "Import CSV Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = context.scene.CsvProperties.csv_attributes
|
||||
csv_attributes.clear()
|
||||
csv_json = json.load(open(self.filepath))
|
||||
i = 0
|
||||
for attribute in csv_json:
|
||||
csv_attributes.add()
|
||||
csv_attributes[i].name = attribute
|
||||
i += 1
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.export_csv_attributes"
|
||||
bl_label = "Export CSV Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".json"
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = []
|
||||
with open(self.filepath, "w") as outfile:
|
||||
for attribute in context.scene.CsvProperties.csv_attributes:
|
||||
csv_attributes.append(attribute.name)
|
||||
|
||||
json.dump(csv_attributes, outfile)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC to CSV"
|
||||
@@ -111,6 +170,8 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file)
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from bpy.props import (
|
||||
|
||||
|
||||
class CsvProperties(PropertyGroup):
|
||||
csv_ifc_file: StringProperty(default="", name="CSV IFC File")
|
||||
csv_ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_selector: StringProperty(default="", name="IFC Selector")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
|
||||
csv_delimiter: EnumProperty(
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_ifccsv(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -46,15 +47,29 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_selector")
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_csv_attribute")
|
||||
split = row.split(factor=0.7)
|
||||
c = split.column()
|
||||
c.operator("bim.add_csv_attribute")
|
||||
c = split.column()
|
||||
c.operator("bim.import_csv_attributes", icon="IMPORT", text="Load Template")
|
||||
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
if props.csv_attributes:
|
||||
row = layout.row()
|
||||
row.label()
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL", text="")
|
||||
row = layout.row()
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="Create Template")
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
@@ -62,6 +77,9 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
row.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.7)
|
||||
c = split.column()
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
c = split.column()
|
||||
c.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
|
||||
@@ -20,17 +20,21 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.PrintIfcFile,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.ValidateIfcFile,
|
||||
operator.ProfileImportIFC,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.InspectFromStepId,
|
||||
operator.InspectFromObject,
|
||||
operator.InspectFromStepId,
|
||||
operator.ParseExpress,
|
||||
operator.PrintIfcFile,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.RewindInspector,
|
||||
operator.SelectExpressFile,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.SelectHighestPolygonMeshes,
|
||||
operator.ValidateIfcFile,
|
||||
prop.BIMDebugProperties,
|
||||
ui.BIM_PT_debug,
|
||||
)
|
||||
|
||||
@@ -16,20 +16,25 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import time
|
||||
import logging
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.debug as core
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class PrintIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.print_ifc_file"
|
||||
bl_label = "Print IFC File"
|
||||
bl_description = "Prints the file contents in the system console.\nAccess it with Window > Toggle System Console"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -43,6 +48,7 @@ class PrintIfcFile(bpy.types.Operator):
|
||||
class PurgeIfcLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_ifc_links"
|
||||
bl_label = "Purge IFC Links"
|
||||
bl_description = "Purge all definitions and references from the file.\nWarning : Cannot be undone."
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.data.objects:
|
||||
@@ -96,7 +102,8 @@ class ProfileImportIFC(bpy.types.Operator):
|
||||
|
||||
class CreateAllShapes(bpy.types.Operator):
|
||||
bl_idname = "bim.create_all_shapes"
|
||||
bl_label = "Create All Shapes"
|
||||
bl_label = "Test All Shapes"
|
||||
bl_description = "Look for errors in all the shapes contained in the file"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -126,7 +133,7 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
print("Failures:")
|
||||
print(f"Failures: {len(failures)}")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
return {"FINISHED"}
|
||||
@@ -135,6 +142,7 @@ 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_description = "Recreate a mesh object from a STEP ID"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_include_curves: bpy.props.BoolProperty()
|
||||
|
||||
@@ -154,9 +162,12 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
if self.should_include_curves:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
if shape:
|
||||
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
else:
|
||||
mesh = None
|
||||
obj = bpy.data.objects.new("Debug", mesh)
|
||||
context.scene.collection.objects.link(obj)
|
||||
return {"FINISHED"}
|
||||
@@ -165,20 +176,37 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
class SelectHighPolygonMeshes(bpy.types.Operator):
|
||||
bl_idname = "bim.select_high_polygon_meshes"
|
||||
bl_label = "Select High Polygon Meshes"
|
||||
bl_description = "Select objects containing more polygons than the specified number"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
threshold: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
[
|
||||
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
|
||||
]
|
||||
for obj in context.view_layer.objects:
|
||||
if obj.type == "MESH" and len(obj.data.polygons) > self.threshold:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectHighestPolygonMeshes(bpy.types.Operator):
|
||||
bl_idname = "bim.select_highest_polygon_meshes"
|
||||
bl_label = "Select Highest Polygon Meshes"
|
||||
bl_description = "Select objects with a number of polygons superior to the specified percentile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
percentile: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objects = [obj for obj in context.view_layer.objects if obj.type == "MESH"]
|
||||
if objects:
|
||||
percentile = len(max(objects, key=lambda o: len(o.data.polygons)).data.polygons) * self.percentile / 100
|
||||
print(f"Selected all Meshes with more than {int(percentile)} polygons")
|
||||
[obj.select_set(True) for obj in objects if len(obj.data.polygons) > percentile]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RewindInspector(bpy.types.Operator):
|
||||
bl_idname = "bim.rewind_inspector"
|
||||
bl_label = "Rewind Inspector"
|
||||
bl_description = "Rewind the Inspector to the previously inspected element"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDebugProperties
|
||||
@@ -195,6 +223,7 @@ class RewindInspector(bpy.types.Operator):
|
||||
class InspectFromStepId(bpy.types.Operator):
|
||||
bl_idname = "bim.inspect_from_step_id"
|
||||
bl_label = "Inspect From STEP ID"
|
||||
bl_description = "Inspect the attributes and references by looking up the specified STEP ID"
|
||||
step_id: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
@@ -245,12 +274,21 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
class InspectFromObject(bpy.types.Operator):
|
||||
bl_idname = "bim.inspect_from_object"
|
||||
bl_label = "Inspect From Object"
|
||||
bl_description = "Inspect the Active Object's attributes and references"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
if bpy.app.version >= (3, 0, 0):
|
||||
cls.poll_message_set("No Active Object")
|
||||
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
if bpy.app.version >= (3, 0, 0):
|
||||
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
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)
|
||||
bpy.ops.bim.inspect_from_step_id(step_id=context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -262,3 +300,40 @@ class PrintObjectPlacement(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ParseExpress(bpy.types.Operator):
|
||||
bl_idname = "bim.parse_express"
|
||||
bl_label = "Parse Express"
|
||||
|
||||
def execute(self, context):
|
||||
core.parse_express(tool.Debug, context.scene.BIMDebugProperties.express_file)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectExpressFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_express_file"
|
||||
bl_label = "Select Express File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select an IFC EXPRESS definition"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]:
|
||||
context.scene.BIMDebugProperties.express_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class PurgeHdf5Cache(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_hdf5_cache"
|
||||
bl_label = "Purge HDF5 Cache"
|
||||
|
||||
def execute(self, context):
|
||||
core.purge_hdf5_cache(tool.Debug)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -33,8 +33,10 @@ from bpy.props import (
|
||||
class BIMDebugProperties(PropertyGroup):
|
||||
step_id: IntProperty(name="STEP ID")
|
||||
number_of_polygons: IntProperty(name="Number of Polygons", min=0)
|
||||
active_step_id: IntProperty(name="STEP ID")
|
||||
percentile_of_polygons: IntProperty(name="Percentile of Polygons", min=0, max=100, default=90, subtype="PERCENTAGE")
|
||||
active_step_id: IntProperty(name="STEP ID", default=1, soft_min=1)
|
||||
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
|
||||
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
|
||||
express_file: StringProperty(name="Express File")
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_debug(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -38,9 +39,17 @@ 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 = self.layout.row(align=True)
|
||||
row.prop(props, "express_file", text="")
|
||||
row.operator("bim.parse_express", icon="IMPORT", text="")
|
||||
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.print_ifc_file")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_hdf5_cache")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_ifc_links")
|
||||
|
||||
@@ -56,8 +65,13 @@ class BIM_PT_debug(Panel):
|
||||
row.prop(props, "step_id", text="")
|
||||
|
||||
row = layout.split(factor=0.7, align=True)
|
||||
row.operator("bim.select_high_polygon_meshes")
|
||||
row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons
|
||||
row.prop(props, "number_of_polygons", text="")
|
||||
row = layout.split(factor=0.7, align=True)
|
||||
row.operator(
|
||||
"bim.select_highest_polygon_meshes"
|
||||
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
|
||||
row.prop(props, "percentile_of_polygons", text="")
|
||||
|
||||
layout.label(text="Inspector:")
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a __init__.py file to load all of its classes. Every
|
||||
# operation, property, and interface needs to be registered with the Blender
|
||||
# system when the add-on loads. This is where it happens.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
# You'll need to provide a list of every one of your classes here. If you forget
|
||||
# to specify your class, it won't load and you won't be able to use that
|
||||
# operator, property, or interface.
|
||||
classes = (
|
||||
operator.DemonstrateHelloWorld,
|
||||
operator.DemonstrateRenameProject,
|
||||
prop.BIMDemoProperties,
|
||||
ui.BIM_PT_demo,
|
||||
)
|
||||
|
||||
|
||||
# When the add-on loads, this register function is called. This allows you to
|
||||
# perform additional tasks during startup. If you need to store custom
|
||||
# properties, this is where you tell Blender where they are going to be stored.
|
||||
# You might see more advanced registrations happening in other modules.
|
||||
def register():
|
||||
# Properties are usually stored on bpy.types.Scene when they are something
|
||||
# that affects everything in the project, or bpy.types.Object when they
|
||||
# affect a single BIM element.
|
||||
bpy.types.Scene.BIMDemoProperties = bpy.props.PointerProperty(type=prop.BIMDemoProperties)
|
||||
|
||||
|
||||
# When someone disables the add-on, we need to unload everything we loaded. This
|
||||
# does the reverse of the register function.
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMDemoProperties
|
||||
@@ -0,0 +1,78 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a data.py file to load data for its interface panels to
|
||||
# display. When a panel needs to show information that doesn't come from a
|
||||
# Blender property, it gets that data from one of these classes. This separation
|
||||
# of "fetching data" and "displaying data" is a common concept that makes UI
|
||||
# code much simpler, more efficient, and less error-prone to refresh data.
|
||||
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
# All data must have a refresh function. The refresh function simply sets all
|
||||
# data class to be not yet loaded. So the next time the interface is drawn to
|
||||
# the user, it will force the data to be reloaded.
|
||||
def refresh():
|
||||
# When you define your own data classes, just add to this list!
|
||||
DemoData.is_loaded = False
|
||||
|
||||
|
||||
# This is a sample data class. It correlates to a single interface panel. Panels
|
||||
# should not share data classes. This makes it easy to write your interface
|
||||
# without having your code mixed in with other parts of the interface. As a
|
||||
# convention, the class is named the same name as the panel.
|
||||
class DemoData:
|
||||
# All data classes must have two variables. One to store all the data it has
|
||||
# loaded and another to store the load state.
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
# Every data class must have a load function. This lets us load data in a
|
||||
# predictable manner.
|
||||
@classmethod
|
||||
def load(cls):
|
||||
# The load function always has two responsibilities: populate the data,
|
||||
# and set is_loaded to true.
|
||||
cls.data = {
|
||||
"has_project": cls.has_project(),
|
||||
"project_name": cls.project_name(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_project(cls):
|
||||
# Here, we check whether or not there is an active IFC project in our
|
||||
# Blender session.
|
||||
return bool(tool.Ifc.get())
|
||||
|
||||
@classmethod
|
||||
def project_name(cls):
|
||||
# Imagine how messy our UI code would be if all of this was mixed in
|
||||
# with our layout code. Here, it's isolated and testable, and the UX
|
||||
# becomes easier to maintain and change.
|
||||
ifc = tool.Ifc.get()
|
||||
if ifc:
|
||||
return ifc.by_type("IfcProject")[0].Name or "Unnamed"
|
||||
@@ -0,0 +1,79 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has an operator.py file to define all of the buttons that a user
|
||||
# can click on from the Blender interface. Blender calls these buttons
|
||||
# "Operators", since they correlate to a single user operation.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.demo as core
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
# Each button correlates to a class like the one below. In this case, we're
|
||||
# creating a new button that will execute a hello world feature.
|
||||
class DemonstrateHelloWorld(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# Every operator has a unique ID. If you enable Python tooltips and hover
|
||||
# over any button in the interface, you will see each button will run a
|
||||
# function that uses this ID. For example, hovering over this button will
|
||||
# show that the code it executes is "bpy.ops.bim.demonstrate_hello_world()"
|
||||
bl_idname = "bim.demonstrate_hello_world"
|
||||
|
||||
# In the interface, this button will have the text "Print Hello World".
|
||||
bl_label = "Print Hello World"
|
||||
|
||||
# This code means that the user can undo or redo after pressing the button.
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
# When hovering over the button, this helpful description will be shown.
|
||||
bl_description = "Prints the text 'Hello World'"
|
||||
|
||||
# When the button is pressed, this _execute() function will run.
|
||||
def _execute(self, context):
|
||||
# Every operator should do one thing only: execute a core function. In
|
||||
# order to execute a core function, the operator's responsibility is to
|
||||
# pass in all of the inputs the core needs to do its job.
|
||||
|
||||
# A core function simply tells tools what to do, so a core function will
|
||||
# always need at least one tool as an input.
|
||||
core.demonstrate_hello_world(tool.Demo)
|
||||
|
||||
|
||||
# This class, just like the one above, will correlate to another button in the
|
||||
# interface. It has a different ID and a different label.
|
||||
class DemonstrateRenameProject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.demonstrate_rename_project"
|
||||
bl_label = "Rename IFC Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Renames your IFC project name and prints the name"
|
||||
|
||||
def _execute(self, context):
|
||||
# This core function requires three inputs: two tools, and a name
|
||||
# string. In this case, the name is taken from some custom Blender
|
||||
# properties. Generally, the inputs to the core function will come from
|
||||
# properties (such as an input field) or data from the scene (like the
|
||||
# actively selected object).
|
||||
core.demonstrate_rename_project(tool.Ifc, tool.Demo, name=bpy.context.scene.BIMDemoProperties.name)
|
||||
@@ -0,0 +1,61 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a prop.py file to define Blender properties. Any time you
|
||||
# want an interface widget like an input field, dropdown, checkbox, or number
|
||||
# slider, you need a Blender property to store that widget's data. If you want
|
||||
# to store data that will affect the Blender interface, you also need a
|
||||
# property. Properties are stored in the .blend file, so when your user closes
|
||||
# their Blender session, and reopens it, things are how they left it.
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
|
||||
# Properties have many different data types. We won't use all of them in this
|
||||
# demo module, but this is a list for your reference.
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
# All properties must belong in a property group. Usually, you'd have a group
|
||||
# named after your module.
|
||||
class BIMDemoProperties(PropertyGroup):
|
||||
# This first property is a string. This means that in the interface, it will
|
||||
# represent a text input field. We can give it a name and a default value.
|
||||
# The name will be the label shown next to the input field in the interface.
|
||||
name: StringProperty(name="Name", default="New Project Name")
|
||||
# Not all properties need to be shown using their equivalent input widget.
|
||||
# In this case, we can store a message string, but we will never show it as
|
||||
# an input text field in the ui.py.
|
||||
message: StringProperty(name="Message")
|
||||
show_hints: BoolProperty(name="Show Hints", default=False)
|
||||
@@ -0,0 +1,131 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a ui.py file to define its interface. Interfaces describe how
|
||||
# panels, buttons, labels, and input fields are laid out.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.demo.data import DemoData
|
||||
|
||||
# Every panel in the interface correlates to one of these classes. If you enable
|
||||
# the "Developer Extras" option in Blender, then you can actually right click on
|
||||
# any panel in Blender, and click "Edit Source". This will bring you to a ui.py
|
||||
# file like this one where you can see this code. Pretty neat!
|
||||
class BIM_PT_demo(bpy.types.Panel):
|
||||
# Every panel has a title.
|
||||
bl_label = "BlenderBIM Demo"
|
||||
|
||||
# Every panel must have an ID. It must be unique. For example, you may want
|
||||
# to later reference the panel (such as if you want to create a nested
|
||||
# subpanel).
|
||||
bl_idname = "BIM_PT_demo"
|
||||
|
||||
# This tells the panel to appear in the properties section (in the bottom
|
||||
# right by default) of the Blender interface.
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
# This tells the panel to appear in the "scene" tab of the properties panel.
|
||||
bl_context = "scene"
|
||||
|
||||
# Every panel has a draw function. This draws all the layout of the panel,
|
||||
# including all of its labels, buttons, and so on. Note that if the panel is
|
||||
# hidden, this draw function will not be called.
|
||||
def draw(self, context):
|
||||
# Before drawing any content, the panel must load any dynamic variables
|
||||
# that it will show. Most panels have dynamic content, which display
|
||||
# changing data from your BIM model. You must load that data first. The
|
||||
# two lines are always the same, "if not loaded, then load".
|
||||
if not DemoData.is_loaded:
|
||||
# Each panel should have its own data class that it loads data from.
|
||||
# Note that we only load data if it hasn't already been loaded.
|
||||
# This is because interface panels are drawn continuously. This
|
||||
# draw() function will be called every time you scroll or move your
|
||||
# mouse over it. Loading data is slow, so we only refresh data when
|
||||
# we have to.
|
||||
DemoData.load()
|
||||
|
||||
# Interface panels often show properties. For convenience, define where
|
||||
# the properties are stored for the module.
|
||||
self.props = context.scene.BIMDemoProperties
|
||||
|
||||
# This defines a new "row" in our layout. When a new row is defined, the
|
||||
# things on that row, like buttons, labels, and input fields, show on a
|
||||
# new line.
|
||||
row = self.layout.row()
|
||||
# This is the simplest interface element - a label placed in our row.
|
||||
# If you enable the "Icon Viewer" add-on, you can view a list of
|
||||
# Blender's built-in icons to choose from in the Blender text editor's
|
||||
# side panel.
|
||||
row.label(text="This is a demo panel", icon="INFO")
|
||||
|
||||
# Our interface can contain simple logic. For example, if we don't have
|
||||
# an IFC project, show an error message and don't draw anything else.
|
||||
# The ui.py should always have very simple logic. Details like how to
|
||||
# determine whether we have a project is delegated to the data loader.
|
||||
if not DemoData.data["has_project"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Load or create an IFC project first", icon="ERROR")
|
||||
return
|
||||
|
||||
row = self.layout.row()
|
||||
# When you want to show a button in the interface, you have to specify
|
||||
# an operator. Operators are defined in operator.py. You reference an
|
||||
# operator using its unique ID.
|
||||
row.operator("bim.demonstrate_hello_world")
|
||||
|
||||
# Blender properties can be used to affect what your panel shows. Notice
|
||||
# how the logic is very simple. Only use simple boolean predicates and
|
||||
# loops in your layout. No complex logic should ever been seen in the
|
||||
# interface code.
|
||||
if self.props.message:
|
||||
row = self.layout.row()
|
||||
row.label(text=self.props.message)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Project Name")
|
||||
# Sometimes you need data that doesn't come from Blender properties,
|
||||
# like from your IFC model. In this case, just get some data from your
|
||||
# data loader. For those familiar with how templating languages work,
|
||||
# this is exactly the same.
|
||||
row.label(text=DemoData.data["project_name"])
|
||||
|
||||
row = self.layout.row()
|
||||
# We've seen how to show text labels and buttons using operators. What
|
||||
# about text input fields, number sliders, dropdowns, checkboxes, and
|
||||
# scrollable lists? The way Blender works is that you simply show a
|
||||
# Blender property in your interface. The data type of that property
|
||||
# determines which UI widget is shown. A text property will show a text
|
||||
# field. A number property will show a number slider. An enum property
|
||||
# will show a drop down. And so on. In this case, our name property is a
|
||||
# text data type, so expect to see a text input field show up here.
|
||||
row.prop(self.props, "name")
|
||||
row = self.layout.row()
|
||||
# Here's another button, referencing another operator.
|
||||
row.operator("bim.demonstrate_rename_project")
|
||||
|
||||
if self.props.show_hints:
|
||||
row = self.layout.row()
|
||||
row.label(text="Name cannot be blank!")
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_diff(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -20,35 +20,29 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadInformation,
|
||||
operator.LoadDocumentReferences,
|
||||
operator.DisableDocumentEditingUI,
|
||||
operator.EnableEditingDocument,
|
||||
operator.DisableEditingDocument,
|
||||
operator.AddInformation,
|
||||
operator.AddDocumentReference,
|
||||
operator.EditInformation,
|
||||
operator.EditDocumentReference,
|
||||
operator.RemoveDocument,
|
||||
operator.EnableAssigningDocument,
|
||||
operator.DisableAssigningDocument,
|
||||
operator.AddInformation,
|
||||
operator.AssignDocument,
|
||||
operator.DisableDocumentEditingUI,
|
||||
operator.DisableEditingDocument,
|
||||
operator.EditDocument,
|
||||
operator.EnableEditingDocument,
|
||||
operator.LoadDocument,
|
||||
operator.LoadParentDocument,
|
||||
operator.LoadProjectDocuments,
|
||||
operator.RemoveDocument,
|
||||
operator.UnassignDocument,
|
||||
prop.Document,
|
||||
prop.BIMDocumentProperties,
|
||||
prop.BIMObjectDocumentProperties,
|
||||
ui.BIM_PT_documents,
|
||||
ui.BIM_PT_object_documents,
|
||||
ui.BIM_UL_documents,
|
||||
ui.BIM_UL_object_documents,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
|
||||
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMDocumentProperties
|
||||
del bpy.types.Object.BIMObjectDocumentProperties
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
DocumentData.is_loaded = False
|
||||
ObjectDocumentData.is_loaded = False
|
||||
|
||||
|
||||
class DocumentData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_information": cls.total_information(),
|
||||
"parent_document": cls.parent_document(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_information(cls):
|
||||
return len(
|
||||
[
|
||||
rel
|
||||
for rel in tool.Ifc.get().by_type("IfcProject")[0].HasAssociations or []
|
||||
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation")
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def parent_document(cls):
|
||||
props = bpy.context.scene.BIMDocumentProperties
|
||||
if len(props.breadcrumbs):
|
||||
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return str(parent.DocumentId)
|
||||
return str(parent.Identification)
|
||||
return ""
|
||||
|
||||
|
||||
class ObjectDocumentData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"documents": cls.documents(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def documents(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return results
|
||||
for rel in getattr(element, "HasAssociations", []):
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
if not rel.RelatingDocument.is_a("IfcDocumentReference"):
|
||||
continue
|
||||
|
||||
name = rel.RelatingDocument.Name
|
||||
if not name and rel.RelatingDocument.ReferencedDocument:
|
||||
name = rel.RelatingDocument.ReferencedDocument.Name
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
identification = rel.RelatingDocument.ItemReference
|
||||
if not identification and rel.RelatingDocument.ReferencedDocument:
|
||||
identification = rel.RelatingDocument.ReferencedDocument.DocumentId
|
||||
else:
|
||||
identification = rel.RelatingDocument.Identification
|
||||
if not identification and rel.RelatingDocument.ReferencedDocument:
|
||||
identification = rel.RelatingDocument.ReferencedDocument.Identification
|
||||
|
||||
location = rel.RelatingDocument.Location
|
||||
if location is None and rel.RelatingDocument.ReferencedDocument:
|
||||
location = rel.RelatingDocument.ReferencedDocument.Location
|
||||
if location:
|
||||
if not "://" in location:
|
||||
if not os.path.isabs(location):
|
||||
location = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), location))
|
||||
location = "file://" + location
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.RelatingDocument.id(),
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"location": location,
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -20,303 +20,133 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.document as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.document.data import Data
|
||||
|
||||
|
||||
class LoadInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.load_information"
|
||||
bl_label = "Load Information"
|
||||
class LoadProjectDocuments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_project_documents"
|
||||
bl_label = "Load Project Documents"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMDocumentProperties
|
||||
props.documents.clear()
|
||||
for information_id, information in Data.information.items():
|
||||
new = props.documents.add()
|
||||
new.name = information["Name"] or "Unnamed"
|
||||
if self.file.schema == "IFC2X3":
|
||||
new.identification = information["DocumentId"] or "*"
|
||||
else:
|
||||
new.identification = information["Identification"] or "*"
|
||||
new.ifc_definition_id = information_id
|
||||
props.is_editing = "information"
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.load_project_documents(tool.Document)
|
||||
|
||||
|
||||
class LoadDocumentReferences(bpy.types.Operator):
|
||||
bl_idname = "bim.load_document_references"
|
||||
bl_label = "Load Document References"
|
||||
class LoadDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_document"
|
||||
bl_label = "Load Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
|
||||
|
||||
|
||||
class LoadParentDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_parent_document"
|
||||
bl_label = "Load Parent Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMDocumentProperties
|
||||
props.documents.clear()
|
||||
for reference_id, reference in Data.references.items():
|
||||
new = props.documents.add()
|
||||
new.name = reference["Name"] or "Unnamed"
|
||||
if self.file.schema == "IFC2X3":
|
||||
new.identification = reference["ItemReference"] or "*"
|
||||
else:
|
||||
new.identification = reference["Identification"] or "*"
|
||||
new.ifc_definition_id = reference_id
|
||||
props.is_editing = "reference"
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.load_parent_document(tool.Document)
|
||||
|
||||
|
||||
class DisableDocumentEditingUI(bpy.types.Operator):
|
||||
class DisableDocumentEditingUI(bpy.types.Operator, tool.Ifc.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 = ""
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_document_editing_ui(tool.Document)
|
||||
|
||||
|
||||
class EnableEditingDocument(bpy.types.Operator):
|
||||
class EnableEditingDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_document"
|
||||
bl_label = "Enable Editing Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
props.document_attributes.clear()
|
||||
|
||||
if props.is_editing == "information":
|
||||
data = Data.information[self.document]
|
||||
ifc_class = "IfcDocumentInformation"
|
||||
elif props.is_editing == "reference":
|
||||
data = Data.references[self.document]
|
||||
ifc_class = "IfcDocumentReference"
|
||||
|
||||
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":
|
||||
continue
|
||||
new = props.document_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()]
|
||||
props.active_document_id = self.document
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.enable_editing_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
|
||||
|
||||
|
||||
class DisableEditingDocument(bpy.types.Operator):
|
||||
class DisableEditingDocument(bpy.types.Operator, tool.Ifc.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
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_editing_document(tool.Document)
|
||||
|
||||
|
||||
class AddInformation(bpy.types.Operator):
|
||||
class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_information"
|
||||
bl_label = "Add Information"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
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())
|
||||
return {"FINISHED"}
|
||||
core.add_information(tool.Ifc, tool.Document)
|
||||
|
||||
|
||||
class AddDocumentReference(bpy.types.Operator):
|
||||
class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_document_reference"
|
||||
bl_label = "Add Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
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())
|
||||
return {"FINISHED"}
|
||||
core.add_reference(tool.Ifc, tool.Document)
|
||||
|
||||
|
||||
class EditInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_information"
|
||||
class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_document"
|
||||
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:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif attribute.enum_items:
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
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"}
|
||||
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
|
||||
|
||||
|
||||
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:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
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"}
|
||||
|
||||
|
||||
class RemoveDocument(bpy.types.Operator):
|
||||
class RemoveDocument(bpy.types.Operator, tool.Ifc.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()
|
||||
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()
|
||||
elif props.is_editing == "reference":
|
||||
bpy.ops.bim.load_document_references()
|
||||
return {"FINISHED"}
|
||||
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document))
|
||||
|
||||
|
||||
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):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectDocumentProperties
|
||||
if props.available_document_types == "IfcDocumentInformation":
|
||||
bpy.ops.bim.load_information()
|
||||
elif props.available_document_types == "IfcDocumentReference":
|
||||
bpy.ops.bim.load_document_references()
|
||||
props.is_adding = props.available_document_types
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
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):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectDocumentProperties
|
||||
props.is_adding = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignDocument(bpy.types.Operator):
|
||||
class AssignDocument(bpy.types.Operator, tool.Ifc.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):
|
||||
self.file = IfcStore.get_file()
|
||||
document = tool.Ifc.get().by_id(self.document)
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
obj_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not obj_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"document.assign_document",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj_id),
|
||||
"document": self.file.by_id(self.document),
|
||||
}
|
||||
)
|
||||
Data.load(self.file, obj_id)
|
||||
return {"FINISHED"}
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.assign_document(tool.Ifc, product=element, document=document)
|
||||
|
||||
|
||||
class UnassignDocument(bpy.types.Operator):
|
||||
class UnassignDocument(bpy.types.Operator, tool.Ifc.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):
|
||||
self.file = IfcStore.get_file()
|
||||
document = tool.Ifc.get().by_id(self.document)
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
obj_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not obj_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"document.unassign_document",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj_id),
|
||||
"document": self.file.by_id(self.document),
|
||||
}
|
||||
)
|
||||
Data.load(self.file, obj_id)
|
||||
return {"FINISHED"}
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.unassign_document(tool.Ifc, product=element, document=document)
|
||||
|
||||
@@ -34,6 +34,7 @@ from bpy.props import (
|
||||
class Document(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
is_information: BoolProperty(name="Is Information")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
@@ -41,12 +42,6 @@ class BIMDocumentProperties(PropertyGroup):
|
||||
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
|
||||
active_document_id: IntProperty(name="Active Document Id")
|
||||
documents: CollectionProperty(name="Documents", type=Document)
|
||||
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
|
||||
active_document_index: IntProperty(name="Active Document Index")
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
|
||||
|
||||
class BIMObjectDocumentProperties(PropertyGroup):
|
||||
is_adding: StringProperty(name="Is Adding")
|
||||
available_document_types: EnumProperty(
|
||||
items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types"
|
||||
)
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
from ifcopenshell.api.document.data import Data
|
||||
from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
|
||||
|
||||
|
||||
class BIM_PT_documents(Panel):
|
||||
@@ -29,45 +29,50 @@ class BIM_PT_documents(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if not DocumentData.is_loaded:
|
||||
DocumentData.load()
|
||||
|
||||
self.props = context.scene.BIMDocumentProperties
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "information":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE")
|
||||
if self.props.is_editing == "information":
|
||||
row.operator("bim.add_information", text="", icon="ADD")
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_information", text="", icon="IMPORT")
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "reference":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN")
|
||||
if self.props.is_editing == "reference":
|
||||
row.operator("bim.add_document_reference", text="", icon="ADD")
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_document_references", text="", icon="IMPORT")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
|
||||
)
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_project_documents", text="", icon="IMPORT")
|
||||
|
||||
if not self.props.is_editing:
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.breadcrumbs:
|
||||
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
|
||||
row.label(text=DocumentData.data["parent_document"])
|
||||
else:
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_information", text="", icon="ADD")
|
||||
if self.props.breadcrumbs:
|
||||
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
|
||||
|
||||
if self.props.active_document_id:
|
||||
self.draw_editable_ui(context)
|
||||
row.operator("bim.edit_document", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
|
||||
elif self.props.documents and self.props.active_document_index < len(self.props.documents):
|
||||
ifc_definition_id = self.props.documents[self.props.active_document_index].ifc_definition_id
|
||||
row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL").document = ifc_definition_id
|
||||
row.operator("bim.remove_document", text="", icon="X").document = ifc_definition_id
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
draw_attributes(self.props.document_attributes, self.layout)
|
||||
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
|
||||
|
||||
if self.props.active_document_id:
|
||||
draw_attributes(self.props.document_attributes, self.layout)
|
||||
|
||||
|
||||
class BIM_PT_object_documents(Panel):
|
||||
@@ -77,6 +82,7 @@ class BIM_PT_object_documents(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -87,88 +93,64 @@ class BIM_PT_object_documents(Panel):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
if not ObjectDocumentData.is_loaded:
|
||||
ObjectDocumentData.load()
|
||||
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMDocumentProperties
|
||||
self.props = obj.BIMObjectDocumentProperties
|
||||
self.props = context.scene.BIMDocumentProperties
|
||||
self.file = IfcStore.get_file()
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if self.oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
|
||||
self.draw_add_ui()
|
||||
|
||||
document_ids = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not document_ids:
|
||||
if not ObjectDocumentData.data["documents"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Documents", icon="FILE")
|
||||
|
||||
for document_id in document_ids:
|
||||
try:
|
||||
document = Data.information[document_id]
|
||||
document_type = "IfcDocumentInformation"
|
||||
icon = "FILE"
|
||||
except:
|
||||
document = Data.references[document_id]
|
||||
document_type = "IfcDocumentReference"
|
||||
icon = "FILE_HIDDEN"
|
||||
for document in ObjectDocumentData.data["documents"]:
|
||||
row = self.layout.row(align=True)
|
||||
if self.file.schema == "IFC2X3":
|
||||
if document_type == "IfcDocumentInformation":
|
||||
row.label(text=document.get("DocumentId") or "*", icon=icon)
|
||||
elif document_type == "IfcDocumentReference":
|
||||
row.label(text=document.get("ItemReference") or "*", icon=icon)
|
||||
else:
|
||||
row.label(text=document.get("Identification") or "*", icon=icon)
|
||||
row.label(text=document.get("Name") or "Unnamed")
|
||||
row.operator("bim.unassign_document", text="", icon="X").document = document_id
|
||||
row.label(text=document["identification"] or "*", icon="FILE")
|
||||
row.label(text=document["name"] or "Unnamed")
|
||||
if document["location"]:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
|
||||
row.operator("bim.unassign_document", text="", icon="X").document = document["id"]
|
||||
|
||||
def draw_add_ui(self):
|
||||
if self.props.is_adding:
|
||||
if not self.props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
|
||||
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
|
||||
row.operator("bim.disable_assigning_document", text="", icon="CANCEL")
|
||||
self.layout.template_list(
|
||||
"BIM_UL_object_documents",
|
||||
"",
|
||||
self.sprops,
|
||||
"documents",
|
||||
self.sprops,
|
||||
"active_document_index",
|
||||
)
|
||||
row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.breadcrumbs:
|
||||
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
|
||||
row.label(text=DocumentData.data["parent_document"])
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_document_types", text="")
|
||||
row.operator("bim.enable_assigning_document", text="", icon="ADD")
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.documents and self.props.active_document_index < len(self.props.documents):
|
||||
document = self.props.documents[self.props.active_document_index]
|
||||
if not document.is_information:
|
||||
row.operator("bim.assign_document", text="", icon="ADD").document = document.ifc_definition_id
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
|
||||
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
|
||||
|
||||
|
||||
class BIM_UL_documents(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.identification)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id:
|
||||
if context.scene.BIMDocumentProperties.is_editing == "information":
|
||||
row.operator("bim.edit_information", text="", icon="CHECKMARK")
|
||||
elif context.scene.BIMDocumentProperties.is_editing == "reference":
|
||||
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
|
||||
elif context.scene.BIMDocumentProperties.active_document_id:
|
||||
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL")
|
||||
|
||||
if item.is_information:
|
||||
op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.document = item.ifc_definition_id
|
||||
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
|
||||
row.label(text="", icon="FILE")
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
row.label(text="", icon="FILE_HIDDEN")
|
||||
|
||||
|
||||
class BIM_UL_object_documents(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.identification)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
|
||||
split1 = row.split(factor=0.1)
|
||||
split1.label(text=item.identification)
|
||||
split2 = split1.split(factor=0.9)
|
||||
split2.label(text=item.name)
|
||||
|
||||
@@ -20,40 +20,49 @@ import bpy
|
||||
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.ActivateView,
|
||||
operator.AddAnnotation,
|
||||
operator.AddDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.AddDrawingStyleAttribute,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RefreshDrawingList,
|
||||
operator.CleanWireframes,
|
||||
operator.CopyGrid,
|
||||
operator.AddDrawingToSheet,
|
||||
operator.AddSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddSectionsAnnotations,
|
||||
operator.AddSheet,
|
||||
operator.BuildSchedule,
|
||||
operator.CleanWireframes,
|
||||
operator.ContractSheet,
|
||||
operator.CopyGrid,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableEditingAssignedProduct,
|
||||
operator.DisableEditingDrawings,
|
||||
operator.DisableEditingSchedules,
|
||||
operator.DisableEditingSheets,
|
||||
operator.DisableEditingText,
|
||||
operator.EditAssignedProduct,
|
||||
operator.EditText,
|
||||
operator.EditVectorStyle,
|
||||
operator.EnableEditingAssignedProduct,
|
||||
operator.EnableEditingText,
|
||||
operator.ExpandSheet,
|
||||
operator.LoadDrawings,
|
||||
operator.LoadSchedules,
|
||||
operator.LoadSheets,
|
||||
operator.OpenSchedule,
|
||||
operator.OpenSheet,
|
||||
operator.OpenView,
|
||||
operator.RemoveDrawing,
|
||||
operator.RemoveDrawingFromSheet,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RemoveSchedule,
|
||||
operator.RemoveSheet,
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SelectDocIfcFile,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
@@ -62,14 +71,17 @@ classes = (
|
||||
prop.DocProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.BIMTextProperties,
|
||||
prop.BIMAssignedProductProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_product_assignments,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_UL_drawinglist,
|
||||
ui.BIM_UL_sheets,
|
||||
gizmos.UglyDotGizmo,
|
||||
gizmos.DotGizmo,
|
||||
gizmos.DimensionLabelGizmo,
|
||||
@@ -81,6 +93,8 @@ classes = (
|
||||
def register():
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
|
||||
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
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)
|
||||
@@ -89,6 +103,8 @@ def register():
|
||||
def unregister():
|
||||
del bpy.types.Scene.DocProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.Object.BIMAssignedProductProperties
|
||||
del bpy.types.Object.BIMTextProperties
|
||||
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)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import os
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
@@ -34,13 +35,13 @@ class Annotator:
|
||||
return float(sizes[str(size)])
|
||||
|
||||
@staticmethod
|
||||
def add_text(context, related_element=None):
|
||||
def add_text(related_element=None):
|
||||
curve = bpy.data.curves.new(type="FONT", name="Text")
|
||||
curve.body = "TEXT"
|
||||
obj = bpy.data.objects.new("Text", curve)
|
||||
obj.matrix_world = context.scene.camera.matrix_world
|
||||
obj.matrix_world = bpy.context.scene.camera.matrix_world
|
||||
if related_element is None:
|
||||
location, _, _, _ = Annotator.get_placeholder_coords(context)
|
||||
location, _, _, _ = Annotator.get_placeholder_coords(bpy.context)
|
||||
else:
|
||||
obj.data.BIMTextProperties.related_element = related_element
|
||||
location = related_element.location
|
||||
@@ -49,12 +50,12 @@ class Annotator:
|
||||
font = bpy.data.fonts.get("OpenGost TypeB TT")
|
||||
if not font:
|
||||
font = bpy.data.fonts.load(
|
||||
os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
os.path.join(bpy.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 = context.scene.camera.users_collection[0]
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
@@ -82,9 +83,9 @@ class Annotator:
|
||||
text_obj.data.size = font_size
|
||||
|
||||
@staticmethod
|
||||
def add_line_to_annotation(obj, context, co1=None, co2=None):
|
||||
def add_line_to_annotation(obj, co1=None, co2=None):
|
||||
if co1 is None:
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords(context)
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords()
|
||||
co1 = obj.matrix_world.inverted() @ co1
|
||||
co2 = obj.matrix_world.inverted() @ co2
|
||||
if isinstance(obj.data, bpy.types.Mesh):
|
||||
@@ -101,8 +102,8 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj, context):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
|
||||
def add_plane_to_annotation(obj):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
|
||||
co1 = obj.matrix_world.inverted() @ co1 # bot left
|
||||
co2 = obj.matrix_world.inverted() @ co2 # top left
|
||||
co3 = obj.matrix_world.inverted() @ co3 # bot right
|
||||
@@ -150,27 +151,54 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
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(name)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new(name, type="CURVE")
|
||||
def get_annotation_obj(drawing, object_type, data_type):
|
||||
camera = tool.Ifc.get_object(drawing)
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world[0][3] = co1.x
|
||||
matrix_world[1][3] = co1.y
|
||||
matrix_world[2][3] = co1.z
|
||||
collection = camera.users_collection[0]
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new(name, data)
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
if object_type != "ANGLE":
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.ObjectType == object_type:
|
||||
return obj
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new(object_type)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_placeholder_coords(context):
|
||||
camera = context.scene.camera
|
||||
def get_placeholder_coords(camera=None):
|
||||
if not camera:
|
||||
camera = bpy.context.scene.camera
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
if context.scene.render.resolution_x > context.scene.render.resolution_y:
|
||||
y = camera.data.ortho_scale * (context.scene.render.resolution_y / context.scene.render.resolution_x) / 4
|
||||
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
|
||||
y = (
|
||||
camera.data.ortho_scale
|
||||
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
|
||||
/ 4
|
||||
)
|
||||
else:
|
||||
y = camera.data.ortho_scale / 4
|
||||
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
ProductAssignmentsData.is_loaded = False
|
||||
TextData.is_loaded = False
|
||||
SheetsData.is_loaded = False
|
||||
SchedulesData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
|
||||
|
||||
class ProductAssignmentsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"relating_product": cls.relating_product()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def relating_product(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation"):
|
||||
return
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
name = rel.RelatingProduct.Name or "Unnamed"
|
||||
return f"{rel.RelatingProduct.is_a()}/{name}"
|
||||
|
||||
|
||||
class TextData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
|
||||
return []
|
||||
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
|
||||
return [
|
||||
{"name": "Literal", "value": text_literal.Literal},
|
||||
{"name": "BoxAlignment", "value": text_literal.BoxAlignment},
|
||||
]
|
||||
|
||||
|
||||
class SheetsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_sheets": cls.total_sheets()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_sheets(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
|
||||
|
||||
|
||||
class DrawingsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_drawings": cls.total_drawings(), "location_hint": cls.location_hint()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_drawings(cls):
|
||||
return len([e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"])
|
||||
|
||||
@classmethod
|
||||
def location_hint(cls):
|
||||
if bpy.context.scene.DocProperties.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
||||
results = [("0", "Origin", "")]
|
||||
results.extend(
|
||||
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
|
||||
)
|
||||
return results
|
||||
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
|
||||
|
||||
|
||||
class SchedulesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_schedules": cls.total_schedules()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_schedules(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
|
||||
@@ -45,6 +45,3 @@ def set_active_camera_resolution(scene):
|
||||
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)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import math
|
||||
import mathutils.geometry
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
|
||||
@@ -259,13 +260,11 @@ def format_distance(value, isArea=False, hide_units=True):
|
||||
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.active_drawing
|
||||
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
|
||||
except (KeyError, IndexError):
|
||||
raise RuntimeError("missing drawing collection")
|
||||
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
|
||||
return camera.users_collection[0], camera
|
||||
except:
|
||||
return None, None
|
||||
|
||||
|
||||
def get_project_collection(scene):
|
||||
|
||||
@@ -18,9 +18,14 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import enum
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
import enum
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -53,6 +58,36 @@ def purge():
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def update_target_view(self, context):
|
||||
DrawingsData.data["location_hint"] = DrawingsData.location_hint()
|
||||
|
||||
|
||||
def get_location_hint(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
return DrawingsData.data["location_hint"]
|
||||
|
||||
|
||||
def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
scale = self.custom_diagram_scale
|
||||
if "|" not in scale:
|
||||
return
|
||||
human_scale, scale = scale.split("|")
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
|
||||
)
|
||||
|
||||
|
||||
def get_diagram_scales(self, context):
|
||||
global diagram_scales_enum
|
||||
if (
|
||||
@@ -64,7 +99,8 @@ def get_diagram_scales(self, context):
|
||||
diagram_scales_enum = [
|
||||
("CUSTOM", "Custom", ""),
|
||||
("1'=1'-0\"|1/1", "1'=1'-0\"", ""),
|
||||
('6"=1\'-0"|1/6', '6"=1\'-0"', ""),
|
||||
('6"=1\'-0"|1/2', '6"=1\'-0"', ""),
|
||||
('3"=1\'-0"|1/4', '3"=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"', ""),
|
||||
@@ -113,31 +149,47 @@ def get_diagram_scales(self, context):
|
||||
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 update_drawing_name(self, context):
|
||||
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def refreshActiveDrawingIndex(self, context):
|
||||
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
|
||||
def update_schedule_name(self, context):
|
||||
schedule = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_schedule_name(tool.Ifc, tool.Drawing, schedule=schedule, name=self.name)
|
||||
|
||||
|
||||
def update_has_underlay(self, context):
|
||||
update_layer(self, context, "HasUnderlay", self.has_underlay)
|
||||
|
||||
|
||||
def update_has_linework(self, context):
|
||||
update_layer(self, context, "HasLinework", self.has_linework)
|
||||
|
||||
|
||||
def update_has_annotation(self, context):
|
||||
update_layer(self, context, "HasAnnotation", self.has_annotation)
|
||||
|
||||
|
||||
def update_layer(self, context, name, value):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
|
||||
|
||||
|
||||
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, ""))
|
||||
files = Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
|
||||
files = sorted([str(f.stem) for f in files])
|
||||
titleblocks_enum.extend([(f, f, "") for f in files])
|
||||
return titleblocks_enum
|
||||
|
||||
|
||||
@@ -150,6 +202,10 @@ def refreshTitleblocks(self, context):
|
||||
def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
tool.Drawing.update_text_value(obj)
|
||||
decoration.DecorationsHandler.install(context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
@@ -175,13 +231,15 @@ class Variable(PropertyGroup):
|
||||
|
||||
|
||||
class Drawing(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateDrawingName)
|
||||
camera: PointerProperty(name="Camera", type=bpy.types.Object)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_drawing_name)
|
||||
target_view: StringProperty(name="Target View")
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
file: StringProperty(name="File")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_schedule_name)
|
||||
identification: StringProperty(name="Identification")
|
||||
|
||||
|
||||
class Sheet(PropertyGroup):
|
||||
@@ -195,9 +253,12 @@ class Sheet(PropertyGroup):
|
||||
def get_name(self):
|
||||
return self.get("name")
|
||||
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
identification: StringProperty(name="Identification")
|
||||
name: StringProperty(name="Name", get=get_name, set=set_name)
|
||||
drawings: CollectionProperty(name="Drawings", type=Drawing)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
is_sheet: BoolProperty(name="Is Sheet", default=False)
|
||||
reference_type: StringProperty(name="Reference Type")
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
||||
|
||||
|
||||
class DrawingStyle(PropertyGroup):
|
||||
@@ -246,76 +307,78 @@ class RasterStyleProperty(enum.Enum):
|
||||
|
||||
|
||||
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)
|
||||
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
|
||||
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "Plan", ""),
|
||||
("ELEVATION_VIEW", "Elevation", ""),
|
||||
("SECTION_VIEW", "Section", ""),
|
||||
("REFLECTED_PLAN_VIEW", "RCP", ""),
|
||||
("MODEL_VIEW", "Model", ""),
|
||||
],
|
||||
name="Target View",
|
||||
default="PLAN_VIEW",
|
||||
update=update_target_view,
|
||||
)
|
||||
location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
|
||||
drawings: CollectionProperty(name="Drawings", type=Drawing)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
|
||||
active_drawing_id: IntProperty(name="Active Drawing Id")
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
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)
|
||||
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
|
||||
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
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
|
||||
@property
|
||||
def active_schedule(self):
|
||||
return self.schedules[self.active_schedule_index]
|
||||
|
||||
@property
|
||||
def active_drawing(self):
|
||||
return self.drawings[self.active_drawing_index]
|
||||
|
||||
@property
|
||||
def active_sheet(self):
|
||||
return self.sheets[self.active_sheet_index]
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
|
||||
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
|
||||
representation: StringProperty(name="Representation")
|
||||
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=get_diagram_scales, name="Drawing Scale")
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale")
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_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")
|
||||
|
||||
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
|
||||
# By checking this, you will know whether or not the camera IFC representation needs to be refreshed
|
||||
def update_representation(self, obj):
|
||||
representation = json.dumps(
|
||||
{
|
||||
"matrix": [list(x) for x in obj.matrix_world],
|
||||
"raster_x": self.raster_x,
|
||||
"raster_y": self.raster_y,
|
||||
"ortho_scale": obj.data.ortho_scale,
|
||||
"clip_end": obj.data.clip_end,
|
||||
}
|
||||
)
|
||||
if self.representation != representation:
|
||||
self.representation = representation
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
value: StringProperty(name="Value", default="TEXT")
|
||||
font_size: EnumProperty(
|
||||
items=[
|
||||
("1.8", "1.8 - Small", ""),
|
||||
@@ -324,17 +387,11 @@ class BIMTextProperties(PropertyGroup):
|
||||
("5.0", "5.0 - Header", ""),
|
||||
("7.0", "7.0 - Title", ""),
|
||||
],
|
||||
default="2.5",
|
||||
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)
|
||||
|
||||
class BIMAssignedProductProperties(PropertyGroup):
|
||||
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
||||
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import urllib.parse
|
||||
import pystache
|
||||
import ntpath
|
||||
import os
|
||||
import ntpath
|
||||
import pystache
|
||||
import urllib.parse
|
||||
import xml.etree.ElementTree as ET
|
||||
import blenderbim.tool as tool
|
||||
from shutil import copy
|
||||
from xml.dom import minidom
|
||||
|
||||
@@ -30,8 +31,7 @@ class SheetBuilder:
|
||||
self.data_dir = None
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, name, titleblock_name):
|
||||
sheet_path = os.path.join(self.data_dir, f"{name}.svg")
|
||||
def create(self, sheet_path, titleblock_name):
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
@@ -59,11 +59,16 @@ class SheetBuilder:
|
||||
with open(sheet_path, "w") as f:
|
||||
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
|
||||
|
||||
def add_drawing(self, view_name, sheet_name):
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
|
||||
view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg")
|
||||
def add_drawing(self, reference, drawing, sheet):
|
||||
filename = drawing.Name
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
sheet_dir = os.path.join(self.data_dir, "sheets")
|
||||
drawing_dir = os.path.join(self.data_dir, "diagrams")
|
||||
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
|
||||
drawing_path = os.path.join(drawing_dir, filename + ".svg")
|
||||
underlay_path = os.path.join(drawing_dir, filename + "-underlay.png")
|
||||
|
||||
if not os.path.isfile(view_path):
|
||||
if not os.path.isfile(sheet_path):
|
||||
raise FileNotFoundError
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
@@ -72,7 +77,7 @@ class SheetBuilder:
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
|
||||
view_tree = ET.parse(view_path)
|
||||
view_tree = ET.parse(drawing_path)
|
||||
view_root = view_tree.getroot()
|
||||
|
||||
# The view is placed into a group with a background image element.
|
||||
@@ -80,29 +85,50 @@ class SheetBuilder:
|
||||
# here to accommodate browsers which do not nest images.
|
||||
view = ET.SubElement(sheet_root, "g")
|
||||
view.attrib["data-type"] = "drawing"
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-drawing"] = drawing.GlobalId
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
background = ET.SubElement(view, "image")
|
||||
background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
background.attrib["width"] = str(view_width)
|
||||
background.attrib["height"] = str(view_height)
|
||||
if os.path.isfile(underlay_path):
|
||||
background = ET.SubElement(view, "image")
|
||||
background.attrib["data-type"] = "background"
|
||||
background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
background.attrib["width"] = str(view_width)
|
||||
background.attrib["height"] = str(view_height)
|
||||
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
if os.path.isfile(drawing_path):
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "foreground"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view)
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def add_schedule(self, schedule_name, sheet_name):
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
|
||||
view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg")
|
||||
def remove_drawing(self, reference, sheet):
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet)
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
|
||||
for g in sheet_root.findall('{http://www.w3.org/2000/svg}g'):
|
||||
if g.attrib.get("data-id") == str(reference.id()):
|
||||
sheet_root.remove(g)
|
||||
break
|
||||
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def add_schedule(self, reference, schedule, sheet):
|
||||
view_path = tool.Drawing.get_document_uri(schedule)
|
||||
schedule_name = os.path.splitext(os.path.basename(view_path))[0]
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet)
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
@@ -115,30 +141,35 @@ class SheetBuilder:
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
group = ET.SubElement(sheet_root, "g")
|
||||
group.attrib["data-type"] = "schedule"
|
||||
view = ET.SubElement(sheet_root, "g")
|
||||
view.attrib["data-type"] = "schedule"
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-schedule"] = str(schedule.id())
|
||||
|
||||
foreground = ET.SubElement(group, "image")
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "table"
|
||||
foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, group)
|
||||
self.add_view_title(30, view_height + 35, view)
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def add_view_title(self, x, y, parent):
|
||||
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
|
||||
title_root = title_tree.getroot()
|
||||
title = ET.SubElement(parent, "image")
|
||||
title.attrib["data-type"] = "view-title"
|
||||
title.attrib["xlink:href"] = "../templates/view-title.svg"
|
||||
title.attrib["x"] = str(x)
|
||||
title.attrib["y"] = str(y)
|
||||
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
|
||||
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
|
||||
|
||||
def build(self, sheet_name):
|
||||
def build(self, sheet):
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
|
||||
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
|
||||
@@ -149,68 +180,86 @@ class SheetBuilder:
|
||||
tree = ET.parse(sheet_path)
|
||||
root = tree.getroot()
|
||||
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"}))
|
||||
titleblock.remove(image)
|
||||
|
||||
self.group_number = 1
|
||||
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"]'))
|
||||
self.build_titleblock(root, sheet)
|
||||
self.build_drawings(root, sheet)
|
||||
self.build_schedules(root)
|
||||
|
||||
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):
|
||||
for view in drawings:
|
||||
def build_titleblock(self, root, sheet):
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, sheet.get_info()))
|
||||
titleblock.remove(image)
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
background = images[0]
|
||||
foreground = images[1]
|
||||
view_title = images[2]
|
||||
self.scale = "NTS"
|
||||
|
||||
# Add foreground
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
background = None
|
||||
foreground = None
|
||||
view_title = None
|
||||
|
||||
# Add background
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
for image in images:
|
||||
if image.attrib["data-type"] == "background":
|
||||
background = image
|
||||
elif image.attrib["data-type"] == "foreground":
|
||||
foreground = image
|
||||
elif image.attrib["data-type"] == "view-title":
|
||||
view_title = image
|
||||
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
if foreground is not None:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
|
||||
# Add view title
|
||||
foreground_path = self.get_href(foreground)
|
||||
view.append(
|
||||
self.parse_embedded_svg(
|
||||
view_title,
|
||||
{"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale},
|
||||
)
|
||||
)
|
||||
if background is not None:
|
||||
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))
|
||||
|
||||
if view_title is not None:
|
||||
foreground_path = self.get_href(foreground)
|
||||
data = reference.get_info()
|
||||
if not data["Name"]:
|
||||
data["Name"] = ntpath.basename(foreground_path)[0:-4]
|
||||
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
self.group_number += 1
|
||||
def build_schedules(self, root):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
|
||||
|
||||
def build_schedules(self, schedules):
|
||||
for group in schedules:
|
||||
images = group.findall("{http://www.w3.org/2000/svg}image")
|
||||
schedule = images[0]
|
||||
group_title = images[1]
|
||||
self.scale = "NTS"
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
|
||||
group.append(self.parse_embedded_svg(schedule, {}))
|
||||
|
||||
path = self.get_href(schedule)
|
||||
group.append(
|
||||
self.parse_embedded_svg(
|
||||
group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
|
||||
)
|
||||
)
|
||||
table = None
|
||||
view_title = None
|
||||
|
||||
for image in images:
|
||||
group.remove(image)
|
||||
if image.attrib["data-type"] == "table":
|
||||
table = image
|
||||
elif image.attrib["data-type"] == "view-title":
|
||||
view_title = image
|
||||
|
||||
self.group_number += 1
|
||||
if table is not None:
|
||||
view.append(self.parse_embedded_svg(table, {}))
|
||||
|
||||
if view_title is not None:
|
||||
path = self.get_href(table)
|
||||
data = reference.get_info()
|
||||
if not data["Name"]:
|
||||
data["Name"] = schedule.Name or "Unnamed"
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
def get_href(self, element):
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
|
||||
|
||||