mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 04:32:23 +00:00
Compare commits
610 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7107a91f6f | |||
| 438ed22f8f | |||
| a9dfc9a9fd | |||
| f8b09b3525 | |||
| f1ab02b311 | |||
| 25df2f6414 | |||
| 42719d3fe3 | |||
| 1f6d23c19c | |||
| 94db9ec522 | |||
| a47c57c22c | |||
| 8f4bc0ea00 | |||
| dd70b09fb1 | |||
| c930050da6 | |||
| 55a7a72e61 | |||
| ca2ae36d41 | |||
| 81b4e42ca2 | |||
| 55e61b55d8 | |||
| a99529cb1e | |||
| bf43efe509 | |||
| da7f5b015b | |||
| 3de9915389 | |||
| 418444a196 | |||
| 171251b27d | |||
| 9fef920a6f | |||
| 5bff837a61 | |||
| 05f6f4efab | |||
| e4f5b0e3d8 | |||
| 49b733f660 | |||
| 36ed618adf | |||
| 48042011e2 | |||
| 0e1939a871 | |||
| bea1e61739 | |||
| 72cd82f450 | |||
| befa059591 | |||
| 7229f7271c | |||
| 9c5970e757 | |||
| 2fe3f0a9df | |||
| 3929f02e94 | |||
| 1abf80c310 | |||
| 2c18c89807 | |||
| 1b81fa5aa1 | |||
| baa08b3221 | |||
| b9a9abd30d | |||
| 33536957d8 | |||
| d092fd45ba | |||
| 78c748df5d | |||
| 3d76de3041 | |||
| ed3e9f3347 | |||
| eb836436b9 | |||
| c34a9c52d0 | |||
| 88d96f7d57 | |||
| 69e447be1c | |||
| df09acfa31 | |||
| 6343f77c0d | |||
| 285fc2269e | |||
| 68a1da9190 | |||
| ceae7d4fdb | |||
| 371daa6c80 | |||
| 948993e066 | |||
| ec4f0e5ad6 | |||
| ef56c6ce7e | |||
| 3c02da416e | |||
| 6660bf7618 | |||
| 000e5ec29a | |||
| ef8f4df4ab | |||
| dff70f2608 | |||
| 0d7967ff62 | |||
| 4fe459dab6 | |||
| 0e5e1f7873 | |||
| 82d4829c83 | |||
| a24fab6d92 | |||
| 748055dcf1 | |||
| 3904162360 | |||
| 7ca24ede4c | |||
| 1e59b10479 | |||
| cda61bff5b | |||
| 09a9753ca7 | |||
| 1621449143 | |||
| d0ef7f69b1 | |||
| 3df95b8970 | |||
| ddd4b0e262 | |||
| 6b05b6e7fb | |||
| 9dd728ce7e | |||
| 90b40ff0dc | |||
| 3c27c52c9f | |||
| 05d4dc5666 | |||
| edaabf5bd2 | |||
| ad79c63a37 | |||
| 9461bf2129 | |||
| f2097c0c5f | |||
| d838e1808d | |||
| 5ed54d9e7c | |||
| 78aba1c7e4 | |||
| 3faaee80ec | |||
| a847b8f4e4 | |||
| 24170a8a16 | |||
| 5438331fe1 | |||
| 3c957e0cbe | |||
| 10672f3e1a | |||
| fa90e420eb | |||
| 347bb2df4b | |||
| 986ffe66f8 | |||
| c1bedec354 | |||
| 182b6c7b07 | |||
| 22edbddd84 | |||
| 50f6f44d26 | |||
| e93a697179 | |||
| f1a5805b1c | |||
| 126f530af1 | |||
| 5dfbbb8d96 | |||
| 6962e505f9 | |||
| 5c2523e752 | |||
| 635dff73b4 | |||
| c85e4e7ceb | |||
| 7ed4301c6c | |||
| 1193e7025a | |||
| cc1ee19115 | |||
| e975eac4a7 | |||
| 779fc35228 | |||
| 8e59282fe0 | |||
| 11c8d47909 | |||
| 2661999f3e | |||
| e4bfb05875 | |||
| fe14988d50 | |||
| d45bef5e70 | |||
| 82d03b98c6 | |||
| 9c1ecf3f2c | |||
| 8f6be60c6e | |||
| 81a1bd9206 | |||
| 9f7dffb29d | |||
| eee799bfc4 | |||
| 9665adb61d | |||
| e40019f3cb | |||
| b2f95bfa60 | |||
| be2c7a62fd | |||
| 38f5468f19 | |||
| 31dcbc0159 | |||
| bd81320c08 | |||
| 0707f37247 | |||
| 0993736cea | |||
| cc3e2204e2 | |||
| 8ac5f0318b | |||
| 270a61e73a | |||
| 1c057b1ce1 | |||
| e74e824417 | |||
| 73d1de5dcf | |||
| 4549be0d05 | |||
| e3f2f2175d | |||
| a604cf2b2a | |||
| d6b4c1f454 | |||
| d3258693b2 | |||
| 9eba3b6eaa | |||
| 7b75364feb | |||
| 4a4a0f09ef | |||
| 6d14c2d448 | |||
| 57edd47ed7 | |||
| aefcedf754 | |||
| 2c685c846f | |||
| 6536f7b56b | |||
| 9d3881b00e | |||
| baffbd6a32 | |||
| 9e4702c102 | |||
| b63f3f335c | |||
| 29b0bcb6b9 | |||
| 62fa0bb218 | |||
| 9e67565c71 | |||
| dee4cf591e | |||
| bc5d6ee113 | |||
| 5af3b537da | |||
| 4b159d400c | |||
| f3c012660a | |||
| b5b8b5df26 | |||
| 6d38d9b538 | |||
| ef11ae6540 | |||
| a3802c25c0 | |||
| 377bcfe6da | |||
| 63a5c0986f | |||
| 8320623210 | |||
| 90c4a2fd41 | |||
| 4ee66caaf9 | |||
| 23251de6f1 | |||
| 37287bac7e | |||
| 43a7340f5d | |||
| 2363b5e167 | |||
| 62bf8c2287 | |||
| a5a958d6e2 | |||
| 812d8508f4 | |||
| c40cc3f740 | |||
| 56040951f1 | |||
| 39b1a65dc5 | |||
| 438f35fe88 | |||
| 9240518066 | |||
| 27b71a9d07 | |||
| 0f1d675bef | |||
| 19345ff538 | |||
| 7b28bc9c68 | |||
| d67eae55df | |||
| 8e375dcf73 | |||
| a34d0cabfc | |||
| a668743d10 | |||
| b13cbbb244 | |||
| b6ae5fd344 | |||
| 1c85034e19 | |||
| b0d1823968 | |||
| f0e03c79df | |||
| 6109cdf41b | |||
| ec0873d527 | |||
| 63223172b4 | |||
| 9778253d21 | |||
| 0140b4ded1 | |||
| dca60b83a9 | |||
| 42e683375c | |||
| 66e07c2f69 | |||
| 2b067b9dec | |||
| c22d7d2f41 | |||
| ea3d489c24 | |||
| 97683a14ef | |||
| d79b912e0e | |||
| 7ec8f8b03e | |||
| 58199ef7c9 | |||
| d5c46514d3 | |||
| f400973557 | |||
| 148d6e7b1b | |||
| 74dec650a7 | |||
| 5f6e003e4e | |||
| d3df22b44b | |||
| 257208d55b | |||
| d8b9b09d14 | |||
| 6f9e14ceb0 | |||
| d854984dda | |||
| 2951721886 | |||
| 02801b32a6 | |||
| b243e04fd2 | |||
| 02902d9a41 | |||
| 5c33034455 | |||
| 7541293a3c | |||
| da7d4dac7a | |||
| 507ab8ec11 | |||
| e93ff7a6b1 | |||
| 098ae2b69f | |||
| a58a10e93d | |||
| 9be7d951c3 | |||
| 6cf482dc63 | |||
| aaa4683462 | |||
| 0b5535b6d4 | |||
| 45feaeaec4 | |||
| 90f92cd139 | |||
| 0285d97e3f | |||
| bf80e4b5e1 | |||
| d4e4717857 | |||
| fca90e9b0f | |||
| ffbb91216f | |||
| 09620e060f | |||
| 0ea38384cc | |||
| aae659c5fd | |||
| b7a481ec79 | |||
| 763eaf83da | |||
| 2843d1e13e | |||
| e897797955 | |||
| cc8f103465 | |||
| f1c3a90bc5 | |||
| c90c438324 | |||
| ae1ff3f6f7 | |||
| e2224386bf | |||
| 6ad51ebd18 | |||
| 0fb9f584cb | |||
| 9a34b94568 | |||
| 7bca3bcac3 | |||
| de27554043 | |||
| 306ee3dfa4 | |||
| 3593b6b5c2 | |||
| e06acab39b | |||
| 6dd4e0f53a | |||
| 2c2a37cacb | |||
| 7935bd22c2 | |||
| 6887210a1a | |||
| 33dc3a4b06 | |||
| ba4610155f | |||
| 28eeba49ba | |||
| 0a00e529c3 | |||
| c5024cf6be | |||
| 0f3b416c3f | |||
| addacd9ce5 | |||
| 5582bb2d4c | |||
| 8b27c0e755 | |||
| ca4efa6e50 | |||
| cfac835607 | |||
| 3aa72ae74a | |||
| 07579ee9a1 | |||
| fe4823681f | |||
| 6816ad5c0f | |||
| aace987149 | |||
| 339ab9eeab | |||
| eee844e204 | |||
| 46cfe70181 | |||
| 294935ceed | |||
| f2097b9036 | |||
| e48a3a06c0 | |||
| c07371cb76 | |||
| 8d450aa500 | |||
| 726c792fbe | |||
| 83dda3e73f | |||
| 5c78e29849 | |||
| e6d48cd37f | |||
| a3ecde3ad9 | |||
| 2bb64a6cae | |||
| 764c818d10 | |||
| 2d18185928 | |||
| 43468166d2 | |||
| fbfd6bc485 | |||
| 1578efe366 | |||
| 76948c5156 | |||
| 562ed5908d | |||
| 9caac14014 | |||
| 695f75f3a7 | |||
| 7d7c04ddc0 | |||
| 863bf8ebbe | |||
| 0fcc28f50c | |||
| 04e9a08f3d | |||
| 837c401116 | |||
| b087d5125f | |||
| a6bff09c78 | |||
| 4716c0b7b7 | |||
| 1d040a170e | |||
| 951026a95c | |||
| c6b82bfa56 | |||
| 01b6d90432 | |||
| 504a116ebf | |||
| be785a878c | |||
| 5c2b7404d2 | |||
| 84e9e2396a | |||
| 7687d5e26e | |||
| 25ce3b039e | |||
| 9c4c42e27e | |||
| 26dd850ac1 | |||
| 0380c286a5 | |||
| 10aca77de4 | |||
| b6f2fdef77 | |||
| 9cc1f5f0e3 | |||
| adcf22f281 | |||
| ad0c49ac37 | |||
| 73d77b8382 | |||
| 3e45f39ea2 | |||
| a2bb015c79 | |||
| b231f43fc7 | |||
| 383038fdb4 | |||
| fbd8ea1edb | |||
| 26e292dd34 | |||
| c520e16031 | |||
| 101972fbad | |||
| f23ebc4031 | |||
| 44431db092 | |||
| d1b6c1ffe4 | |||
| 4d98151b62 | |||
| 5e8e18e3b5 | |||
| 808ff548ec | |||
| bc9b482373 | |||
| 4506eb0df9 | |||
| 921d412fc7 | |||
| 6089d3af78 | |||
| b8168ca606 | |||
| 61a7325bc0 | |||
| c483116b32 | |||
| 55a623dc97 | |||
| ad420108b0 | |||
| 9944c7d2de | |||
| 1a8f2be4fd | |||
| 13f64fdfe2 | |||
| 38a9e29102 | |||
| 3c23a57138 | |||
| 7baed872c6 | |||
| 9a7a284561 | |||
| e9c24a2656 | |||
| 5c73ef0a6d | |||
| 6c29c53a72 | |||
| dadcbe61ea | |||
| 93502a05ea | |||
| 5ebde4c79a | |||
| cf21ae22f1 | |||
| cc500ae833 | |||
| 256c0baa14 | |||
| 66decc604e | |||
| 356bc18078 | |||
| 1344cbc192 | |||
| e00ffb07ad | |||
| dba43590a5 | |||
| a0613cfee6 | |||
| c3de72b71a | |||
| 39fc60a482 | |||
| 0d7e71a269 | |||
| 78a2f65ec3 | |||
| 802bd74c52 | |||
| 33cf40debb | |||
| ff63fd9f6f | |||
| 5b207e6f54 | |||
| 695dc6ec5a | |||
| d3bcb8c6b9 | |||
| 6226aea3c3 | |||
| a30ad9b886 | |||
| 2b1e17681f | |||
| 8f25a31f19 | |||
| 1c7e6cf1b9 | |||
| fc7459c07f | |||
| 8f6932de64 | |||
| b708100c03 | |||
| d4e2d3d2dd | |||
| 13309cb8c4 | |||
| 5a4bc0909b | |||
| 39540e5794 | |||
| 0a0f977b5c | |||
| 13a75e7ad7 | |||
| 99574d6255 | |||
| 968cceb725 | |||
| 002985a446 | |||
| 5adc29b0b3 | |||
| 52ea4ff44d | |||
| 4e31a87c20 | |||
| 5d85390715 | |||
| 3e13eb8942 | |||
| 262c6d1fcb | |||
| 12b5a88861 | |||
| 2ccc9d886e | |||
| a78769c8ff | |||
| 1a142ad5eb | |||
| 2bc22e150c | |||
| aad5d280fd | |||
| 7ab07ed149 | |||
| 3690998dbc | |||
| 1b83fcc1c7 | |||
| 5cf25c45f9 | |||
| efa734c06e | |||
| e242244ec0 | |||
| 6f9da163fb | |||
| a3fd169bb6 | |||
| 899056f929 | |||
| 132fef1b76 | |||
| 422f772c40 | |||
| 66f5081ada | |||
| 839e35f1da | |||
| 40d9713326 | |||
| 17b04ad697 | |||
| 2f99dd8e98 | |||
| 49f825ba82 | |||
| 3f18ad0bc5 | |||
| 991c42e816 | |||
| 20d6eae50a | |||
| 9740582a84 | |||
| a8046a6d69 | |||
| d6655a5c14 | |||
| 0a3a2db4ae | |||
| 499eb5ea14 | |||
| b8ca287883 | |||
| 52f3392ece | |||
| d53641ce5e | |||
| 7a3ad6c3a1 | |||
| 0b2007011e | |||
| eae6983ca5 | |||
| 0ec337748f | |||
| 2699278ba3 | |||
| 0406166d8a | |||
| 2128eda87b | |||
| 59e11aa804 | |||
| 21f4fb169f | |||
| 30fc52ebd9 | |||
| 64bc7af4ef | |||
| b3f1fc667a | |||
| fcbb28f9d8 | |||
| 240a31db3a | |||
| c1bde09585 | |||
| 5dd2b5e555 | |||
| 37dcb92220 | |||
| 6c43c60d04 | |||
| b8d191ac1c | |||
| 57173c7740 | |||
| 2d3295ec54 | |||
| 941ede117c | |||
| 4210b8333f | |||
| 1723d1e5a9 | |||
| 3ee06ee7a0 | |||
| d3e8da58dc | |||
| 29e000b939 | |||
| 6686a72d1f | |||
| 7dd20f69d7 | |||
| 0044ef5588 | |||
| ffa5686b22 | |||
| ab8d6bea99 | |||
| e7bb89f582 | |||
| c80b0464e5 | |||
| 7e3547b309 | |||
| 45c7432ff7 | |||
| c98c20d403 | |||
| d26a2580ea | |||
| f84c4ab2e5 | |||
| de6676a516 | |||
| bfbb7bec94 | |||
| ac00aaae4d | |||
| 422222a7cb | |||
| e1c7b37d62 | |||
| dec978743b | |||
| 10fae033c8 | |||
| b23180d287 | |||
| 99ccd81b3f | |||
| e2d3f3a351 | |||
| 5e6a444aa2 | |||
| 72cf5cfc59 | |||
| 75cb68d2f1 | |||
| 7f40a9d723 | |||
| dcc9d0e9cd | |||
| 7228329f5e | |||
| d8786717b6 | |||
| b73bd3506f | |||
| bdb2791b35 | |||
| 549ea11945 | |||
| aed01b5703 | |||
| 13eb8562e0 | |||
| c022a12835 | |||
| 04eb47ff65 | |||
| 10ecd16f0c | |||
| 75852bfa75 | |||
| 750c720c9b | |||
| 4697681fcb | |||
| 9398363cbf | |||
| ae45e6b960 | |||
| 88c002703e | |||
| 51c78a89bd | |||
| e36a958c0d | |||
| a8126a6f90 | |||
| 9b857cdefa | |||
| 90e6400115 | |||
| 2dcfcf9708 | |||
| f4971da42e | |||
| 008f953b6c | |||
| 5481237480 | |||
| 4f7564e5ae | |||
| d214cbaece | |||
| d0c5f1e69f | |||
| 4ba3240fb4 | |||
| 6d8e5e4c83 | |||
| cd1402570b | |||
| 6c0e528dd2 | |||
| 297a513d21 | |||
| 9f0a4ba064 | |||
| 8813ed66e0 | |||
| fafcd5dfca | |||
| 155a8b79da | |||
| f523870b57 | |||
| 2ed24434c8 | |||
| 6878b2c072 | |||
| 0ed8dc970d | |||
| bc85f09109 | |||
| 793b28e43b | |||
| ed4abacd1e | |||
| 8bf55ab46c | |||
| 7edabce657 | |||
| 56187e4da3 | |||
| 31a12faef1 | |||
| eb94c16465 | |||
| e8437a9c54 | |||
| 840039ef1e | |||
| 84a749605e | |||
| c1283d2ab0 | |||
| 07b5c4918b | |||
| 703f0a6b70 | |||
| 5e98dee46f | |||
| b2eddf55d2 | |||
| db7be640d4 | |||
| 04fa597844 | |||
| 694dbeb96c | |||
| 7afd358c3c | |||
| b0a5a570a9 | |||
| c45b84c850 | |||
| d575a43aee | |||
| 0b151ea559 | |||
| 926970b256 | |||
| d58af0bfa5 | |||
| 429da7c6f3 | |||
| 60a7433573 | |||
| 08a5d444d4 | |||
| 1982069b69 | |||
| 7c06c65df3 | |||
| 6263766202 | |||
| 66fbbeff54 | |||
| 3c5ad1328f | |||
| 3345d7e088 | |||
| 2bb25cb874 | |||
| 444a728e7a | |||
| 70bea0c865 | |||
| 6eea1e9035 | |||
| 4deb1836ad | |||
| 4c8007dcfe | |||
| 000116bb3d | |||
| a2b6f21c7a | |||
| a374ee39d3 | |||
| 0f5c82dffd | |||
| 268fed761e | |||
| fe01d252db | |||
| dc36d74337 | |||
| 7a0da7bd8e | |||
| 530d5a4e63 | |||
| 2d847d1704 | |||
| 631d8fcfb1 | |||
| a8888efb88 | |||
| a51ecea9b1 | |||
| 4d49517111 | |||
| d5d1b3567b | |||
| 7827ce3448 | |||
| a05059db01 | |||
| b78490ca56 | |||
| b46ccc9b78 | |||
| 4f1e21dfb0 |
@@ -0,0 +1,57 @@
|
||||
name: ci-bcf-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-bcf-pypi.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2 # https://github.com/actions/checkout
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%y%m%d')"
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/bcf &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/bcf/dist
|
||||
@@ -1,37 +0,0 @@
|
||||
name: ci-bcf
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||
contains(github.event.head_commit.message, '[bcf release]')
|
||||
steps:
|
||||
- run: echo ok go
|
||||
upload:
|
||||
needs: activate
|
||||
name: Upload BCF package to Pypi
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Build package
|
||||
run: |
|
||||
cd src/bcf
|
||||
pip install build
|
||||
python -m build
|
||||
- name: Test
|
||||
run: |
|
||||
cd src/bcf
|
||||
make test
|
||||
- name: Publish package
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
packages_dir: src/bcf/dist
|
||||
@@ -88,6 +88,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
sudo /usr/bin/python -m pip install python-dateutil
|
||||
sudo /usr/bin/python -m pip install mathutils
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -114,6 +114,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
sudo /usr/bin/python -m pip install python-dateutil
|
||||
sudo /usr/bin/python -m pip install mathutils
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
+4
-1
@@ -96,5 +96,8 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
# apple
|
||||
.DS_Store
|
||||
|
||||
# clangd cache
|
||||
.cache
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
|
||||
@@ -55,7 +55,6 @@ Those marked with an asterisk are part of IfcOpenShell.
|
||||
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later |
|
||||
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later |
|
||||
| ifccobie | Extract IFC data for COBie handover requirements | LGPL-3.0-or-later |
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* |
|
||||
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later |
|
||||
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later |
|
||||
|
||||
+641
-585
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
################################################################################
|
||||
# #
|
||||
# This file is part of IfcOpenShell. #
|
||||
# #
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the Lesser GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3.0 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# IfcOpenShell 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 #
|
||||
# Lesser GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the Lesser GNU General Public License #
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
|
||||
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
|
||||
endif()
|
||||
|
||||
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
|
||||
string(REGEX REPLACE "\n" ";" files "${files}")
|
||||
|
||||
foreach(file ${files})
|
||||
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
|
||||
|
||||
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
|
||||
exec_program(
|
||||
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
|
||||
OUTPUT_VARIABLE rm_out
|
||||
RETURN_VALUE rm_retval
|
||||
)
|
||||
|
||||
if(NOT "${rm_retval}" STREQUAL 0)
|
||||
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
|
||||
endif()
|
||||
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
|
||||
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -4,6 +4,7 @@ set MY_PY_VER=%PY_VER:.=%
|
||||
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
|
||||
cmake -G "Ninja" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
|
||||
-D CMAKE_BUILD_TYPE:STRING=Release ^
|
||||
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
|
||||
@@ -13,6 +13,7 @@ if [ `uname` == Darwin ]; then
|
||||
fi
|
||||
|
||||
cmake -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
${CMAKE_PLATFORM_FLAGS[@]} \
|
||||
|
||||
+3
-3
@@ -36,10 +36,10 @@ requirements:
|
||||
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt ==7.7.0
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- mpfr
|
||||
- gmp # [unix]
|
||||
|
||||
+14
-1
@@ -1,3 +1,9 @@
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
SED:=sed -i
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED:=sed -i '' -e
|
||||
endif
|
||||
|
||||
.PHONY: ci
|
||||
ci:
|
||||
tox
|
||||
@@ -12,6 +18,13 @@ models:
|
||||
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
rm -rf dist
|
||||
$(SED) "s/999999/$(VERSION)/" pyproject.toml
|
||||
python -m build
|
||||
$(SED) "s/$(VERSION)/999999/" pyproject.toml
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
+3
-82
@@ -1,84 +1,5 @@
|
||||
# bcf
|
||||
|
||||
A simple Python implementation of BCF.
|
||||
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
|
||||
is available via `bcfapi.py`.
|
||||
|
||||
It tries to support BCF-XML version 2.1 and 3.0, and BCF-API 3.0.
|
||||
|
||||
## bcfxml
|
||||
|
||||
The `bcfxml.load` function lets you read a BCF-XML file.
|
||||
It takes care of using the right version based on the "bcf.version" file contained in the BCF package.
|
||||
|
||||
The BCF files are extracted and parsed on-demand, and edits are stored in memory until you call the `save` method.
|
||||
|
||||
```python
|
||||
from bcf.bcfxml import load
|
||||
|
||||
# Load a project
|
||||
with load("/path/to/file.bcf") as bcfxml:
|
||||
project = bcfxml.project
|
||||
print(project.name)
|
||||
|
||||
# To edit a project, just modify the object directly
|
||||
bcfxml.project.name = "New name"
|
||||
|
||||
# Get a dictionary of topics
|
||||
topics = bcfxml.topics
|
||||
|
||||
for topic_guid, topic_handler in bcfxml.topics.items():
|
||||
topic = topic_handler.topic
|
||||
print("Topic guid is", topic.guid)
|
||||
print("Topic title is", topic.title)
|
||||
|
||||
# Fetch extra data about a topic
|
||||
header = topic_handler.header
|
||||
comments = topic_handler.comments
|
||||
viewpoints = topic_handler.viewpoints
|
||||
|
||||
for comment in comments:
|
||||
print(comment.guid)
|
||||
print(comment.comment)
|
||||
print(comment.author)
|
||||
|
||||
# Get a particular topic
|
||||
topic = bcfxml.get_topic(guid)
|
||||
|
||||
# Modify a topic
|
||||
topic.title = "New title"
|
||||
|
||||
bcfxml.save()
|
||||
```
|
||||
|
||||
## bcfapi
|
||||
|
||||
The `bcfapi` module lets you interact with the BCF-API standard.
|
||||
|
||||
```python
|
||||
from bcf.v3.bcfapi import FoundationClient, BcfClient
|
||||
|
||||
foundation_client = FoundationClient("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "OPENCDE_BASEURL")
|
||||
auth_methods = foundation_client.get_auth_methods()
|
||||
|
||||
# Our library currently only implements the authorization_code flow
|
||||
if "authorization_code" in auth_methods:
|
||||
foundation_client.login()
|
||||
|
||||
bcf_client = BcfClient(foundation_client)
|
||||
|
||||
versions = foundation_client.get_versions()
|
||||
for version in versions:
|
||||
if "3.0" in versions:
|
||||
if version["api_id"] == "bcf" and version["version_id"] == "3.0":
|
||||
bcf_client.set_version(version)
|
||||
|
||||
data = bcf_client.get_projects()
|
||||
print(data)
|
||||
project_id = data[0]["project_id"]
|
||||
print(project_id)
|
||||
data = bcf_client.get_project(project_id)
|
||||
print(data)
|
||||
data = bcf_client.get_extensions(project_id)
|
||||
print(data)
|
||||
```
|
||||
A simple Python implementation of the BCF standard. Manipulation of BCF-XML is
|
||||
available via `bcfxml.py` and manipulation of BCF-API is available via
|
||||
`bcfapi.py`.
|
||||
|
||||
@@ -17,7 +17,7 @@ dependencies = [
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
version = "0.0.1"
|
||||
version = "0.0.999999"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
@@ -76,7 +76,7 @@ deps =
|
||||
black
|
||||
isort
|
||||
pylint
|
||||
commands =
|
||||
commands =
|
||||
black {posargs:.}
|
||||
isort {posargs:.}
|
||||
pylint {posargs:.} --output-format=colorized
|
||||
@@ -138,4 +138,4 @@ max-attributes = 10
|
||||
|
||||
[tool.pylint.format]
|
||||
expected-line-ending-format = "LF"
|
||||
max-line-length = 120
|
||||
max-line-length = 120
|
||||
+26
-36
@@ -38,22 +38,25 @@ endif
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
||||
PYVERSION:=py310
|
||||
PYPI_IMP:=cp
|
||||
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
PYPI_VERSION:=3.9
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYLIBDIR:=python3.10
|
||||
PYNUMBER:=310
|
||||
PYPI_VERSION:=3.10
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
@@ -62,23 +65,23 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
PYPI_PLATFORM:=--platform macosx_10_9_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
@@ -87,23 +90,23 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
PYPI_PLATFORM:=--platform macosx_11_0_arm64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
@@ -112,18 +115,19 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
PYPI_PLATFORM:=--platform win_amd64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py39h39b25cb_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py39hb6c915b_0.conda
|
||||
@@ -140,7 +144,8 @@ LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe15
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
@@ -164,7 +169,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fc50bdd-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f0e03c7-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -194,8 +199,10 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCTester functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifctester/ifctester dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCOBie functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccobie/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCFM functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcfm/ifcfm dist/blenderbim/libs/site/packages/
|
||||
# Provides bSDD functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/bsdd/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCDiff functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcdiff/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCSV functionality
|
||||
@@ -376,6 +383,10 @@ endif
|
||||
mkdir dist/working
|
||||
cd dist/working && $(PYTHON) -m venv env
|
||||
cd dist/working && mkdir site-packages
|
||||
# Required by IfcCSV
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pandas $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install openpyxl --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install odfpy --target=./site-packages
|
||||
# Required by IFCCityJSON
|
||||
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
@@ -426,8 +437,7 @@ ifeq ($(PLATFORM), win)
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl except on Windows
|
||||
ifneq ($(PLATFORM), win)
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(QHULL_URL)
|
||||
cd dist/working && tar -xf qhull*
|
||||
@@ -440,8 +450,10 @@ endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
@@ -476,7 +488,7 @@ ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
# Uh, do nothing, apparently? No DLLs are shipped.
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
@@ -582,28 +594,6 @@ endif
|
||||
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for XLSX support
|
||||
# TODO: see if we can replace this with openpyxl which does both read/write
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz
|
||||
cd dist/working && tar -xzvf XlsxWriter*
|
||||
cd dist/working/XlsxWriter-1.2.9/ && cp -r xlsxwriter ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for ODS support
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/97/73/8ade73f6749177003f7ce3304f524774adda96e6aaab30ea79fd8fda7934/odfpy-1.4.1.tar.gz
|
||||
cd dist/working && tar -xzvf odfpy*
|
||||
cd dist/working/odfpy-1.4.1/ && cp -r odf ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by odfpy
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf defusedxml*
|
||||
cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by augin
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
|
||||
|
||||
@@ -30,6 +30,7 @@ modules = {
|
||||
"project": None,
|
||||
"search": None,
|
||||
"bcf": None,
|
||||
"bsdd": None,
|
||||
"root": None,
|
||||
"unit": None,
|
||||
"model": None,
|
||||
@@ -44,7 +45,7 @@ modules = {
|
||||
"void": None,
|
||||
"aggregate": None,
|
||||
"geometry": None,
|
||||
"cobie": None,
|
||||
"fm": None,
|
||||
"resource": None,
|
||||
"cost": None,
|
||||
"sequence": None,
|
||||
@@ -77,6 +78,7 @@ modules = {
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
"ifcgit": None,
|
||||
"covering": 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,
|
||||
@@ -134,22 +136,33 @@ classes = [
|
||||
ui.BIM_PT_tab_object_metadata,
|
||||
ui.BIM_PT_tab_misc,
|
||||
# Geometry and materials
|
||||
ui.BIM_PT_tab_placement,
|
||||
ui.BIM_PT_tab_representations,
|
||||
ui.BIM_PT_tab_geometric_relationships,
|
||||
ui.BIM_PT_tab_parametric_geometry,
|
||||
ui.BIM_PT_tab_materials,
|
||||
ui.BIM_PT_tab_styles,
|
||||
# Drawings and documents
|
||||
ui.BIM_PT_tab_sheets,
|
||||
ui.BIM_PT_tab_drawings,
|
||||
ui.BIM_PT_tab_schedules,
|
||||
ui.BIM_PT_tab_references,
|
||||
# Services and systems
|
||||
ui.BIM_PT_tab_services,
|
||||
ui.BIM_PT_tab_services_object,
|
||||
# Structural analysis
|
||||
ui.BIM_PT_tab_structural,
|
||||
# Construction scheduling
|
||||
ui.BIM_PT_tab_status,
|
||||
ui.BIM_PT_tab_resources,
|
||||
ui.BIM_PT_tab_cost,
|
||||
ui.BIM_PT_tab_sequence,
|
||||
# Facility management
|
||||
ui.BIM_PT_tab_handover,
|
||||
ui.BIM_PT_tab_operations,
|
||||
# Quality and coordination
|
||||
ui.BIM_PT_tab_quality_control,
|
||||
ui.BIM_PT_tab_clash_detection,
|
||||
ui.BIM_PT_tab_collaboration,
|
||||
ui.BIM_PT_tab_sandbox,
|
||||
# TODO: move this somewhere else and clean it up
|
||||
|
||||
@@ -44,17 +44,29 @@
|
||||
<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="SurveyArea">
|
||||
<g id="dot">
|
||||
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea-CONTROLPOINT">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
|
||||
<g id="setout-tag">
|
||||
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
|
||||
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
|
||||
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
|
||||
<text x="3" y="-8" class="regular" text-anchor="start" dominant-baseline="auto" data-type="text-template"></text>
|
||||
<text x="3" y="-4" class="regular" text-anchor="start" dominant-baseline="auto" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="setout-point">
|
||||
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
|
||||
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
|
||||
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
|
||||
</g>
|
||||
<g id="control-point">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
|
||||
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea-TRAVERSEPOINT">
|
||||
<g id="traverse-point">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
|
||||
</g>
|
||||
<g id="SurveyArea-SPOTELEVATION">
|
||||
<g id="spot-elevation">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -51,5 +51,5 @@ var json_data = `{{{json_data}}}`;
|
||||
<script>
|
||||
var data = `{{{data}}}`;
|
||||
setupPage(data);
|
||||
create_gantt_chart(json_data)
|
||||
</script>
|
||||
create_gantt_chart(atob(json_data))
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
|
||||
FILE_NAME('/dev/null','2023-07-30T21:07:33+10:00',(),(),'IfcOpenShell v0.7.0-8f41ae0c1','IfcOpenShell v0.7.0-8f41ae0c1','Nobody');
|
||||
FILE_NAME('/dev/null','2023-09-07T13:40:06+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROJECT('0TszJmTqLCyxcP10DtK_MP',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('0Y_CJXSsbCgBlSGFw0mbLa',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('3IxQaOPH5Fx90CGmGm1gtQ',$,$,$,#1,(#2));
|
||||
#1=IFCPROJECT('1arVsMni92iPUaaryGTUz2',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('1FfDG5b21DuBDOO4eUE9rF',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('3tfDIC3Tf9wP8EUOoFeycK',$,$,$,#1,(#2));
|
||||
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#7=IFCUNITASSIGNMENT((#6,#4,#5));
|
||||
#7=IFCUNITASSIGNMENT((#5,#6,#4));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCDIRECTION((0.,0.,1.));
|
||||
#10=IFCDIRECTION((1.,0.,0.));
|
||||
@@ -25,82 +25,82 @@ DATA;
|
||||
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#20=IFCMATERIAL('Unknown',$,$);
|
||||
#21=IFCWALLTYPE('3U0gAUglf9WBlcfpB15EK0',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#21=IFCWALLTYPE('2o8NqQQbHA5BeErrgwfr7d',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#22=IFCMATERIALLAYERSET((#24),$,$);
|
||||
#23=IFCRELASSOCIATESMATERIAL('3J36H22Gz1ovVzdiKgApaM',$,$,$,(#21),#22);
|
||||
#23=IFCRELASSOCIATESMATERIAL('1Q$5DZKr98n82Eitz2dWey',$,$,$,(#21),#22);
|
||||
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
|
||||
#25=IFCRELDECLARES('28ApgMJzH10h$Ng$fmCpYf',$,$,$,#2,(#63,#21,#54,#87,#153,#34,#67,#210,#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96));
|
||||
#26=IFCWALLTYPE('1wGfdUdC10TuASJo4jnO99',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#25=IFCRELDECLARES('1KXKUEn8z0g8lvyEsCTWD3',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#153,#34,#67,#210));
|
||||
#26=IFCWALLTYPE('3KlIOv_P9A79tkt8D_jq_m',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#27=IFCMATERIALLAYERSET((#29),$,$);
|
||||
#28=IFCRELASSOCIATESMATERIAL('0NRoxSQPzCgQHfsu5ru91z',$,$,$,(#26),#27);
|
||||
#28=IFCRELASSOCIATESMATERIAL('32B9Wx8Z1FxhIN7m77MiHG',$,$,$,(#26),#27);
|
||||
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
|
||||
#30=IFCWALLTYPE('1lt5MuGLLDfwmZd0tNruF7',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#30=IFCWALLTYPE('2DqrSeel1AGw6h3b$ujafZ',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#31=IFCMATERIALLAYERSET((#33),$,$);
|
||||
#32=IFCRELASSOCIATESMATERIAL('21rGVxPODC$xmWYFaDy1T6',$,$,$,(#30),#31);
|
||||
#32=IFCRELASSOCIATESMATERIAL('2FeYUpt3D4tAvwgGmwXWZn',$,$,$,(#30),#31);
|
||||
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#34=IFCWALLTYPE('0NZm1D8h12xAxyW7fL5nc1',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#34=IFCWALLTYPE('2jNdEOFIP1eQb3WZePlFGY',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#35=IFCMATERIALLAYERSET((#37),$,$);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2yvmN42crCJevtirp5cMeV',$,$,$,(#34),#35);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2bFeM6xevAwucuwMlauX77',$,$,$,(#34),#35);
|
||||
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#38=IFCCOVERINGTYPE('1eb4wZ_5n6xRGrs3rlKMiM',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#38=IFCCOVERINGTYPE('3310gCgH59w9tu$DfmiCEN',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#39=IFCMATERIALLAYERSET((#41),$,$);
|
||||
#40=IFCRELASSOCIATESMATERIAL('3HKIKf06T6xRqM6Lp69$OM',$,$,$,(#38),#39);
|
||||
#40=IFCRELASSOCIATESMATERIAL('3pTNmWd_jBoOWyHc8Yf4qI',$,$,$,(#38),#39);
|
||||
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
|
||||
#42=IFCCOVERINGTYPE('2E89aYmtrEfxhX_jsNqviN',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#42=IFCCOVERINGTYPE('2sksWFuPzCgeNk1ofXS6PZ',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#43=IFCMATERIALLAYERSET((#45),$,$);
|
||||
#44=IFCRELASSOCIATESMATERIAL('2115lEhhP4Z9G1Yhd_DqSC',$,$,$,(#42),#43);
|
||||
#44=IFCRELASSOCIATESMATERIAL('36rl5NddT1ux6ejkQAkT2z',$,$,$,(#42),#43);
|
||||
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
|
||||
#46=IFCPROPERTYSET('0vtU63YmjAgg8DeHlTgNx7',$,'EPset_Parametric',$,(#47));
|
||||
#46=IFCPROPERTYSET('2VZJ9xfdn1NeOY6uEmnnYW',$,'EPset_Parametric',$,(#47));
|
||||
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
|
||||
#48=IFCCOVERINGTYPE('0XiSBI57L9Uf8f2NaQ_ksi',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#48=IFCCOVERINGTYPE('1uU6rxt95AC91lZ0jTRG6G',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#49=IFCMATERIALLAYERSET((#51),$,$);
|
||||
#50=IFCRELASSOCIATESMATERIAL('3U3JdGAmbABA4mB$Fgpq1l',$,$,$,(#48),#49);
|
||||
#50=IFCRELASSOCIATESMATERIAL('2lFv4xQYr5bwtiSsMTVCgz',$,$,$,(#48),#49);
|
||||
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
|
||||
#52=IFCPROPERTYSET('17nhQ5I4b9ZvhDu4gRJLKO',$,'EPset_Parametric',$,(#53));
|
||||
#52=IFCPROPERTYSET('0HnrRhcd16Yfbgtci4sqCG',$,'EPset_Parametric',$,(#53));
|
||||
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
|
||||
#54=IFCRAMPTYPE('1BTHjVDP19tgb3MDCSkWCJ',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#54=IFCRAMPTYPE('3nonXCVn58jhVV08N8c97B',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#55=IFCMATERIALLAYERSET((#57),$,$);
|
||||
#56=IFCRELASSOCIATESMATERIAL('3HN21bHtL16vvHA2GnnxH1',$,$,$,(#54),#55);
|
||||
#56=IFCRELASSOCIATESMATERIAL('2NdIrmbUP6bh4x$p_RY31C',$,$,$,(#54),#55);
|
||||
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
|
||||
#59=IFCPILETYPE('04_2WZSqH8TgT7ET6DCCpd',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#59=IFCPILETYPE('2uuf5kq5TDDfO9jGN7XIHh',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#60=IFCMATERIALPROFILESET($,$,(#62),$);
|
||||
#61=IFCRELASSOCIATESMATERIAL('1qTZZRXsPF9hmYwT$tgFgp',$,$,$,(#59),#60);
|
||||
#61=IFCRELASSOCIATESMATERIAL('3hi821Ffj8YR9yFjp226LU',$,$,$,(#59),#60);
|
||||
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
|
||||
#63=IFCSLABTYPE('3_OV9$Sd52KRhIW2TWIxbW',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#63=IFCSLABTYPE('2janmz3nH4P9IHaOVvGY_B',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#64=IFCMATERIALLAYERSET((#66),$,$);
|
||||
#65=IFCRELASSOCIATESMATERIAL('1agyf45$X1NhR127pUL0Iw',$,$,$,(#63),#64);
|
||||
#65=IFCRELASSOCIATESMATERIAL('0og5Zg4ib67g6XWgOgqBWy',$,$,$,(#63),#64);
|
||||
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#67=IFCSLABTYPE('0LvmpIGf9AVPcG$Kt06TEk',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#67=IFCSLABTYPE('26UKpEaTb9fh4qrqi3Ymzj',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#68=IFCMATERIALLAYERSET((#70),$,$);
|
||||
#69=IFCRELASSOCIATESMATERIAL('0EKQPSL656secN86SgeutH',$,$,$,(#67),#68);
|
||||
#69=IFCRELASSOCIATESMATERIAL('1BeBXPqen9TQEE42SxzhjF',$,$,$,(#67),#68);
|
||||
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
|
||||
#72=IFCCOLUMNTYPE('1SI8m9m813tAXfSnM1fCbm',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#72=IFCCOLUMNTYPE('1KnWzBBoLCNfJ3_J79LT7d',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#73=IFCMATERIALPROFILESET($,$,(#75),$);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1EhwSTRtPBQgBt2x6dhuT$',$,$,$,(#72),#73);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1W8OWQjEL05gH5hEpve6Lh',$,$,$,(#72),#73);
|
||||
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
|
||||
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
|
||||
#77=IFCCOLUMNTYPE('25GZwy2zLFcgi57r5S8iY3',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#77=IFCCOLUMNTYPE('33jPFERfL9bfTeWfTL5kZ7',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#78=IFCMATERIALPROFILESET($,$,(#80),$);
|
||||
#79=IFCRELASSOCIATESMATERIAL('2VURAOhz93ZQqT9YJUA1DO',$,$,$,(#77),#78);
|
||||
#79=IFCRELASSOCIATESMATERIAL('3czOrQw2v9BfS0PeQCrzWq',$,$,$,(#77),#78);
|
||||
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
|
||||
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
|
||||
#82=IFCCOLUMNTYPE('1Wg$I84s9BTh$iDuZnmSkH',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#82=IFCCOLUMNTYPE('21YcNdKNj2$95U55yNx1Nw',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#83=IFCMATERIALPROFILESET($,$,(#85),$);
|
||||
#84=IFCRELASSOCIATESMATERIAL('3fIIwEe7DAle7x_zISvMUD',$,$,$,(#82),#83);
|
||||
#84=IFCRELASSOCIATESMATERIAL('3n6Cru3xr6jvDptU9u7QEF',$,$,$,(#82),#83);
|
||||
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
|
||||
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
|
||||
#87=IFCBEAMTYPE('2yg2Irs7L26gFsD_AvvFUu',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#87=IFCBEAMTYPE('2CsGpD$6nDCxWv9jXTtvq9',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#88=IFCMATERIALPROFILESET($,$,(#90),$);
|
||||
#89=IFCRELASSOCIATESMATERIAL('1PU_gbkifF9hYpi9DvXTAU',$,$,$,(#87),#88);
|
||||
#89=IFCRELASSOCIATESMATERIAL('2LJExc74j9rgBSBx51HDPK',$,$,$,(#87),#88);
|
||||
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
|
||||
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
|
||||
#92=IFCBEAMTYPE('2Nf2L5qpz8K8KhUNFBg_fT',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#92=IFCBEAMTYPE('32pHN2b0P0awGw$U8PpneO',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#93=IFCMATERIALPROFILESET($,$,(#95),$);
|
||||
#94=IFCRELASSOCIATESMATERIAL('1SgarGNCzEQQpfs29NMq0y',$,$,$,(#92),#93);
|
||||
#94=IFCRELASSOCIATESMATERIAL('2wg8R1Drb2xhQ2Y_pRSh3c',$,$,$,(#92),#93);
|
||||
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
|
||||
#96=IFCWINDOWTYPE('2zRUFP8Ef81xDOR2iJx1zK',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#96=IFCWINDOWTYPE('1Gg9HfZEr69u8SdWfDs2J3',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
|
||||
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
|
||||
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
|
||||
@@ -157,7 +157,7 @@ DATA;
|
||||
#150=IFCDIRECTION((0.,0.,1.));
|
||||
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
|
||||
#152=IFCREPRESENTATIONMAP(#151,#147);
|
||||
#153=IFCDOORTYPE('0oekOIA$z8lxWiQs9_tFWU',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#153=IFCDOORTYPE('0NBUmPKyT9WecsIeYJrEqg',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
|
||||
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
|
||||
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
|
||||
@@ -214,7 +214,7 @@ DATA;
|
||||
#207=IFCDIRECTION((0.,0.,1.));
|
||||
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
|
||||
#209=IFCREPRESENTATIONMAP(#208,#204);
|
||||
#210=IFCFURNITURETYPE('0I2mc$m3T60hLXZYg9Y1wu',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#210=IFCFURNITURETYPE('3Kyc6IyarAUw3_8fkNtXIg',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
|
||||
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
|
||||
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
|
||||
@@ -957,131 +957,161 @@ DATA;
|
||||
#950=IFCDIRECTION((0.,0.,1.));
|
||||
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
|
||||
#952=IFCREPRESENTATIONMAP(#951,#947);
|
||||
#953=IFCTYPEPRODUCT('2be705eyD4DPBr6j4K7ZEx',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('0uEfutIxDDIxMIBUJOZpSM',$,'EPset_Annotation',$,(#955));
|
||||
#955=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
|
||||
#956=IFCTYPEPRODUCT('1M7mm9wUHFoxdM0MdAMyqM',$,'FINE',$,'IfcAnnotation/LINEWORK',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('3AZ8m1Bdn1gfbvagzKr2c3',$,'EPset_Annotation',$,(#958));
|
||||
#958=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
|
||||
#959=IFCTYPEPRODUCT('3LdrNIFOvAkBzrNnqWAZfA',$,'THIN',$,'IfcAnnotation/LINEWORK',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('0$9iwgSID7xBul4Tb9r2du',$,'EPset_Annotation',$,(#961));
|
||||
#961=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
|
||||
#962=IFCTYPEPRODUCT('3fFNO0t1TB$wYRaIkKwUCH',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('33Fm$9zgb1cgsCOy859RlR',$,'EPset_Annotation',$,(#964));
|
||||
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
|
||||
#965=IFCTYPEPRODUCT('1CpMBhemj5VQC43bU9J7zG',$,'THICK',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('1OLDsFSDz2TPz5ifkQvWo8',$,'EPset_Annotation',$,(#967));
|
||||
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
|
||||
#968=IFCTYPEPRODUCT('0NITtya3b16hoA3sh17SL4',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('1_kfqWVDn0puI29Ah6eitX',$,'EPset_Annotation',$,(#970));
|
||||
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
|
||||
#971=IFCTYPEPRODUCT('0rRN9EsvL5AQ3PorxrZSKM',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#972),(#991),$);
|
||||
#972=IFCPROPERTYSET('3lRZ$d2VH8cxZYh$mm45iq',$,'EPset_Annotation',$,(#973));
|
||||
#973=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
|
||||
#974=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#975=IFCDIRECTION((0.,0.,1.));
|
||||
#976=IFCDIRECTION((1.,0.,0.));
|
||||
#977=IFCAXIS2PLACEMENT3D(#974,#975,#976);
|
||||
#978=IFCPLANAREXTENT(1000.,1000.);
|
||||
#979=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#977,.RIGHT.,#978,'center');
|
||||
#980=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#981=IFCDIRECTION((0.,0.,1.));
|
||||
#982=IFCDIRECTION((1.,0.,0.));
|
||||
#983=IFCAXIS2PLACEMENT3D(#980,#981,#982);
|
||||
#984=IFCPLANAREXTENT(1000.,1000.);
|
||||
#985=IFCTEXTLITERALWITHEXTENT('{{Name}}',#983,.RIGHT.,#984,'center');
|
||||
#986=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#979,#985));
|
||||
#987=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#988=IFCDIRECTION((1.,0.,0.));
|
||||
#989=IFCDIRECTION((0.,0.,1.));
|
||||
#990=IFCAXIS2PLACEMENT3D(#987,#989,#988);
|
||||
#991=IFCREPRESENTATIONMAP(#990,#986);
|
||||
#992=IFCTYPEPRODUCT('0bFZUWHjbA2e03hRHNinTt',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#993),(#1006),$);
|
||||
#993=IFCPROPERTYSET('3TJOWv5NDBXBNCshYFjsFV',$,'EPset_Annotation',$,(#994));
|
||||
#994=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
|
||||
#995=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#996=IFCDIRECTION((0.,0.,1.));
|
||||
#953=IFCTYPEPRODUCT('0TBMBnD_b66QLUWKD9HLsc',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('2dQzX_K4r1Xet6dz9zBlgs',$,'EPset_Annotation',$,(#955));
|
||||
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
|
||||
#956=IFCTYPEPRODUCT('0vo_7PU3H9ygTnrIipqPRI',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('3APQOw$FP1ivxp08UxHsl6',$,'EPset_Annotation',$,(#958));
|
||||
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
|
||||
#959=IFCTYPEPRODUCT('32V27G3$T1OO3TbXg6948F',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('0cTSYXMc9B$PTXVDrhBYYW',$,'EPset_Annotation',$,(#961));
|
||||
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
|
||||
#962=IFCTYPEPRODUCT('2PXIC7Bg914gJhRh2XeGnN',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('0RiYsOxp529gXnCkw44wF8',$,'EPset_Annotation',$,(#964));
|
||||
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
|
||||
#965=IFCTYPEPRODUCT('1Ou1kA3Vb4HhuNBvM0uGe0',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('2gzly9D0L1qPK2MExeXgRO',$,'EPset_Annotation',$,(#967));
|
||||
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
|
||||
#968=IFCTYPEPRODUCT('3lx$KQPRbEZwbQ7Xdfm5gw',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('0vsVpqs6zArvA2bfBtvQew',$,'EPset_Annotation',$,(#970));
|
||||
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
|
||||
#971=IFCTYPEPRODUCT('00j$y97p903w2HOb35lAQ2',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
|
||||
#972=IFCPROPERTYSET('240rEBDOn8PAvfnm_43s55',$,'EPset_Annotation',$,(#973));
|
||||
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
|
||||
#974=IFCTYPEPRODUCT('2tVdFGorj6dfrL4uA1kyW8',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
|
||||
#975=IFCPROPERTYSET('2IEGHncr1D$R8fKA9zCEJP',$,'EPset_Annotation',$,(#976));
|
||||
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
|
||||
#977=IFCTYPEPRODUCT('1T5C$$ONTBB8A9a7vv0Gn6',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
|
||||
#978=IFCPROPERTYSET('1gRomAS1L2WguA51ZI0E40',$,'EPset_Annotation',$,(#979));
|
||||
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
|
||||
#980=IFCTYPEPRODUCT('2LmKYrAEr2K8EwJyCAsyc4',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
|
||||
#981=IFCPROPERTYSET('0nF9du8qDAF9aldqjCUsbX',$,'EPset_Annotation',$,(#982));
|
||||
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-tag'),$);
|
||||
#983=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#984=IFCDIRECTION((0.,0.,1.));
|
||||
#985=IFCDIRECTION((1.,0.,0.));
|
||||
#986=IFCAXIS2PLACEMENT3D(#983,#984,#985);
|
||||
#987=IFCPLANAREXTENT(1000.,1000.);
|
||||
#988=IFCTEXTLITERALWITHEXTENT('E ``round({{easting}}, 0.001)``',#986,.RIGHT.,#987,'center');
|
||||
#989=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#990=IFCDIRECTION((0.,0.,1.));
|
||||
#991=IFCDIRECTION((1.,0.,0.));
|
||||
#992=IFCAXIS2PLACEMENT3D(#989,#990,#991);
|
||||
#993=IFCPLANAREXTENT(1000.,1000.);
|
||||
#994=IFCTEXTLITERALWITHEXTENT('N ``round({{northing}}, 0.001)``',#992,.RIGHT.,#993,'center');
|
||||
#995=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#988,#994));
|
||||
#996=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#997=IFCDIRECTION((1.,0.,0.));
|
||||
#998=IFCAXIS2PLACEMENT3D(#995,#996,#997);
|
||||
#999=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1000=IFCTEXTLITERALWITHEXTENT('{{Name}}',#998,.RIGHT.,#999,'center');
|
||||
#1001=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1000));
|
||||
#1002=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1003=IFCDIRECTION((1.,0.,0.));
|
||||
#1004=IFCDIRECTION((0.,0.,1.));
|
||||
#1005=IFCAXIS2PLACEMENT3D(#1002,#1004,#1003);
|
||||
#1006=IFCREPRESENTATIONMAP(#1005,#1001);
|
||||
#1007=IFCTYPEPRODUCT('2DlmDdasX05un_uMP4O23U',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1008),(#1033),$);
|
||||
#1008=IFCPROPERTYSET('1$OAHURHr3GON7ZzAJnv72',$,'EPset_Annotation',$,(#1009));
|
||||
#1009=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
|
||||
#998=IFCDIRECTION((0.,0.,1.));
|
||||
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
|
||||
#1000=IFCREPRESENTATIONMAP(#999,#995);
|
||||
#1001=IFCTYPEPRODUCT('1cdhVmqEPF6e13_TFOdlQw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
|
||||
#1002=IFCPROPERTYSET('1MUPGQolnDEgp0NxcMN56E',$,'EPset_Annotation',$,(#1003));
|
||||
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
|
||||
#1004=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1005=IFCDIRECTION((0.,0.,1.));
|
||||
#1006=IFCDIRECTION((1.,0.,0.));
|
||||
#1007=IFCAXIS2PLACEMENT3D(#1004,#1005,#1006);
|
||||
#1008=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1009=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1007,.RIGHT.,#1008,'center');
|
||||
#1010=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1011=IFCDIRECTION((0.,0.,1.));
|
||||
#1012=IFCDIRECTION((1.,0.,0.));
|
||||
#1013=IFCAXIS2PLACEMENT3D(#1010,#1011,#1012);
|
||||
#1014=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1015=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1013,.RIGHT.,#1014,'center');
|
||||
#1016=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1017=IFCDIRECTION((0.,0.,1.));
|
||||
#1016=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1009,#1015));
|
||||
#1017=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1018=IFCDIRECTION((1.,0.,0.));
|
||||
#1019=IFCAXIS2PLACEMENT3D(#1016,#1017,#1018);
|
||||
#1020=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1021=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1019,.RIGHT.,#1020,'center');
|
||||
#1022=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1023=IFCDIRECTION((0.,0.,1.));
|
||||
#1024=IFCDIRECTION((1.,0.,0.));
|
||||
#1025=IFCAXIS2PLACEMENT3D(#1022,#1023,#1024);
|
||||
#1026=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1027=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1025,.RIGHT.,#1026,'center');
|
||||
#1028=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1015,#1021,#1027));
|
||||
#1029=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1030=IFCDIRECTION((1.,0.,0.));
|
||||
#1031=IFCDIRECTION((0.,0.,1.));
|
||||
#1032=IFCAXIS2PLACEMENT3D(#1029,#1031,#1030);
|
||||
#1033=IFCREPRESENTATIONMAP(#1032,#1028);
|
||||
#1034=IFCTYPEPRODUCT('2vNx3CIlrFs8bLFlmGvJF3',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1035),(#1048),$);
|
||||
#1035=IFCPROPERTYSET('1l$T4lUqnBb93zcgfbSPvx',$,'EPset_Annotation',$,(#1036));
|
||||
#1036=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
|
||||
#1037=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1038=IFCDIRECTION((0.,0.,1.));
|
||||
#1039=IFCDIRECTION((1.,0.,0.));
|
||||
#1040=IFCAXIS2PLACEMENT3D(#1037,#1038,#1039);
|
||||
#1041=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1042=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1040,.RIGHT.,#1041,'center');
|
||||
#1043=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1042));
|
||||
#1044=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1045=IFCDIRECTION((1.,0.,0.));
|
||||
#1046=IFCDIRECTION((0.,0.,1.));
|
||||
#1047=IFCAXIS2PLACEMENT3D(#1044,#1046,#1045);
|
||||
#1048=IFCREPRESENTATIONMAP(#1047,#1043);
|
||||
#1049=IFCTYPEPRODUCT('3hqzFtbSb56RjhAZNaB4Vh',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1050),(#1063),$);
|
||||
#1050=IFCPROPERTYSET('3U7kaKt_nBUvD$U2ryif_H',$,'EPset_Annotation',$,(#1051));
|
||||
#1051=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1019=IFCDIRECTION((0.,0.,1.));
|
||||
#1020=IFCAXIS2PLACEMENT3D(#1017,#1019,#1018);
|
||||
#1021=IFCREPRESENTATIONMAP(#1020,#1016);
|
||||
#1022=IFCTYPEPRODUCT('0OuDi3gRH2CxW9mrtE0vXw',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
|
||||
#1023=IFCPROPERTYSET('2X7dAo_1P5ruRnlKA4kIl6',$,'EPset_Annotation',$,(#1024));
|
||||
#1024=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
|
||||
#1025=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1026=IFCDIRECTION((0.,0.,1.));
|
||||
#1027=IFCDIRECTION((1.,0.,0.));
|
||||
#1028=IFCAXIS2PLACEMENT3D(#1025,#1026,#1027);
|
||||
#1029=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1030=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1028,.RIGHT.,#1029,'center');
|
||||
#1031=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1030));
|
||||
#1032=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1033=IFCDIRECTION((1.,0.,0.));
|
||||
#1034=IFCDIRECTION((0.,0.,1.));
|
||||
#1035=IFCAXIS2PLACEMENT3D(#1032,#1034,#1033);
|
||||
#1036=IFCREPRESENTATIONMAP(#1035,#1031);
|
||||
#1037=IFCTYPEPRODUCT('3WEV_9wQn6AQTESgjW36PH',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
|
||||
#1038=IFCPROPERTYSET('02FAX8dIzAlunWD5ak$6sU',$,'EPset_Annotation',$,(#1039));
|
||||
#1039=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
|
||||
#1040=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1041=IFCDIRECTION((0.,0.,1.));
|
||||
#1042=IFCDIRECTION((1.,0.,0.));
|
||||
#1043=IFCAXIS2PLACEMENT3D(#1040,#1041,#1042);
|
||||
#1044=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1045=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1043,.RIGHT.,#1044,'center');
|
||||
#1046=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1047=IFCDIRECTION((0.,0.,1.));
|
||||
#1048=IFCDIRECTION((1.,0.,0.));
|
||||
#1049=IFCAXIS2PLACEMENT3D(#1046,#1047,#1048);
|
||||
#1050=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1051=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1049,.RIGHT.,#1050,'center');
|
||||
#1052=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1053=IFCDIRECTION((0.,0.,1.));
|
||||
#1054=IFCDIRECTION((1.,0.,0.));
|
||||
#1055=IFCAXIS2PLACEMENT3D(#1052,#1053,#1054);
|
||||
#1056=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1057=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1055,.RIGHT.,#1056,'center');
|
||||
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1057));
|
||||
#1057=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}}, 0.01)``',#1055,.RIGHT.,#1056,'center');
|
||||
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1045,#1051,#1057));
|
||||
#1059=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1060=IFCDIRECTION((1.,0.,0.));
|
||||
#1061=IFCDIRECTION((0.,0.,1.));
|
||||
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
|
||||
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
|
||||
#1064=IFCTYPEPRODUCT('2SnSxJPiD9sh$NDx92OCSS',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('3Lu86QVuv5nfjkuPLvdQSH',$,'EPset_Annotation',$,(#1066));
|
||||
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1064=IFCTYPEPRODUCT('1evqvQLLL1zxdsIWqEn0lK',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('2KPJlGyer0UBmphfR7952k',$,'EPset_Annotation',$,(#1066));
|
||||
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
|
||||
#1067=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1068=IFCDIRECTION((0.,0.,1.));
|
||||
#1069=IFCDIRECTION((1.,0.,0.));
|
||||
#1070=IFCAXIS2PLACEMENT3D(#1067,#1068,#1069);
|
||||
#1071=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1072=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1070,.RIGHT.,#1071,'center');
|
||||
#1072=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1070,.RIGHT.,#1071,'center');
|
||||
#1073=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1072));
|
||||
#1074=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1075=IFCDIRECTION((1.,0.,0.));
|
||||
#1076=IFCDIRECTION((0.,0.,1.));
|
||||
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
|
||||
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
|
||||
#1079=IFCTYPEPRODUCT('3Nem6d4xX87O3deyWDi3AW',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
|
||||
#1080=IFCPROPERTYSET('15bspgnA9CEuFox6xFjoTL',$,'EPset_Annotation',$,(#1081));
|
||||
#1081=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1082=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1083=IFCDIRECTION((0.,0.,1.));
|
||||
#1084=IFCDIRECTION((1.,0.,0.));
|
||||
#1085=IFCAXIS2PLACEMENT3D(#1082,#1083,#1084);
|
||||
#1086=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1087=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1085,.RIGHT.,#1086,'center');
|
||||
#1088=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1087));
|
||||
#1089=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1090=IFCDIRECTION((1.,0.,0.));
|
||||
#1091=IFCDIRECTION((0.,0.,1.));
|
||||
#1092=IFCAXIS2PLACEMENT3D(#1089,#1091,#1090);
|
||||
#1093=IFCREPRESENTATIONMAP(#1092,#1088);
|
||||
#1094=IFCTYPEPRODUCT('0klFX9AjnEnPBkdIURv8XD',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
|
||||
#1095=IFCPROPERTYSET('1d53tifbv2rwcoFFJosiHf',$,'EPset_Annotation',$,(#1096));
|
||||
#1096=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1097=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1098=IFCDIRECTION((0.,0.,1.));
|
||||
#1099=IFCDIRECTION((1.,0.,0.));
|
||||
#1100=IFCAXIS2PLACEMENT3D(#1097,#1098,#1099);
|
||||
#1101=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1102=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1100,.RIGHT.,#1101,'center');
|
||||
#1103=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1102));
|
||||
#1104=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1105=IFCDIRECTION((1.,0.,0.));
|
||||
#1106=IFCDIRECTION((0.,0.,1.));
|
||||
#1107=IFCAXIS2PLACEMENT3D(#1104,#1106,#1105);
|
||||
#1108=IFCREPRESENTATIONMAP(#1107,#1103);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -5,27 +5,27 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
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,#16,#17,#18,#19,#20,#21));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.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','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Status.ifc','2020-01-01T00:00:00',(),(),'EPset_Status','EPset_Status',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3JoyTt$cz4fhYuQvJeTMhs',$,'EPset_Status','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProduct',(#2,#4));
|
||||
#2= IFCSIMPLEPROPERTYTEMPLATE('2HBOdOfz5E3hZ7IBLCq$$D',$,'Status','Status of the element, predominately used in renovation or retrofitting projects. The status can be assigned to as "New" - element designed as new addition, "Existing" - element exists and remains, "Demolish" - element existed but is to be demolished, "Temporary" - element will exists only temporary (like a temporary support structure).',.P_ENUMERATEDVALUE.,'IfcLabel',$,#3,$,$,$,.READWRITE.);
|
||||
#3= IFCPROPERTYENUMERATION('EPEnum_ElementStatus',(IFCLABEL('NEW'),IFCLABEL('EXISTING'),IFCLABEL('DEMOLISH'),IFCLABEL('TEMPORARY'),IFCLABEL('OTHER'),IFCLABEL('NOTKNOWN'),IFCLABEL('UNSET')),$);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3ttXxqmlz62BlxjzEPpqvH',$,'UserDefinedStatus','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,23 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2023-09-28T23:00:50',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('1TLQmgUKf0I8Zi07bm9Jqn',$,'ePSet_ProjectedCRS','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#2,#3,#4,#5,#6,#7));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2cH53PtVT3v88uf_uGFblG',$,'Name','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('3JsReA6vXDqR7aNC$haRYW',$,'Description','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('0u0uEsT5HFtuX5GvozO9ba',$,'GeodeticDatum','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('1JgEX9Fsf0$ggxYltdkqiY',$,'VerticalDatum','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('3ahTZjYrD0MAOo5IH6QuGH',$,'MapProjection','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('3hqJR2ghD0GAti7q4sXKj7',$,'MapZone','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCPROPERTYSETTEMPLATE('1jGHTycOj0vA8xHfRl4TNs',$,'ePSet_MapConversion','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#9,#10,#11,#12,#13,#15));
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('0nxsULXo17JRrAvNteYtNR',$,'Eastings','',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('26RfaGJs5CqemN0Xin12XL',$,'Northings','',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('0ziCn3nZv3m850VEUwWH42',$,'OrthogonalHeight','',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('3d4BOt4In8WR119ms1SaAe',$,'XAxisAbscissa','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('1Q4Voqj2j84RQRI8l67m28',$,'XAxisOrdinate','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('0O1Hc0$XnDNOG_jPBmbW5r',$,'Scale','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -82,7 +82,7 @@ class IfcExporter:
|
||||
)
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
self.get_application_name(), self.get_application_version()
|
||||
self.get_application_name(), tool.Blender.get_blenderbim_version()
|
||||
)
|
||||
|
||||
def sync_all_objects(self):
|
||||
@@ -93,6 +93,8 @@ class IfcExporter:
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
continue
|
||||
if obj.library:
|
||||
continue
|
||||
tool.Collector.sync(obj)
|
||||
result = self.sync_object_placement(obj)
|
||||
if result:
|
||||
@@ -162,7 +164,7 @@ class IfcExporter:
|
||||
return "BlenderBIM"
|
||||
|
||||
def get_application_version(self):
|
||||
return ".".join(
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
@@ -172,6 +174,9 @@ class IfcExporter:
|
||||
][0]
|
||||
]
|
||||
)
|
||||
if blenderbim.bim.last_commit_hash != "8888888":
|
||||
version += f"-{blenderbim.bim.last_commit_hash[:7]}"
|
||||
return version
|
||||
|
||||
|
||||
class IfcExportSettings:
|
||||
|
||||
@@ -27,6 +27,7 @@ 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 blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
|
||||
from mathutils import Vector
|
||||
from math import cos, degrees
|
||||
|
||||
@@ -35,24 +36,6 @@ cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
global_subscription_owner = object()
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
objects = bpy.context.selected_objects
|
||||
if bpy.context.active_object:
|
||||
objects += [bpy.context.active_object]
|
||||
for obj in objects:
|
||||
if (
|
||||
obj.mode != "EDIT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
|
||||
or not obj.BIMObjectProperties.ifc_definition_id
|
||||
):
|
||||
continue
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
tool.Ifc.edit(obj)
|
||||
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
try:
|
||||
obj.name
|
||||
@@ -73,17 +56,27 @@ def name_callback(obj, data):
|
||||
refresh_ui_data()
|
||||
return
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if "/" in obj.name:
|
||||
object_name = obj.name
|
||||
element_name = obj.name.split("/", 1)[1]
|
||||
else:
|
||||
element_name = obj.name
|
||||
object_name = element.is_a() + f"/{element_name}"
|
||||
obj.name = object_name # NOTE: doesn't trigger infinite recursion
|
||||
|
||||
if element.is_a("IfcGridAxis"):
|
||||
element.AxisTag = obj.name.split("/")[1]
|
||||
element.AxisTag = object_name.split("/")[1]
|
||||
refresh_ui_data()
|
||||
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
element.Name = element_name
|
||||
if obj.BIMObjectProperties.collection:
|
||||
obj.BIMObjectProperties.collection.name = obj.name
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
obj.BIMObjectProperties.collection.name = object_name
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
@@ -106,7 +99,7 @@ def update_bim_tool_props():
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
|
||||
if not current_tool or current_tool.idname != "bim.bim_tool":
|
||||
if not current_tool or current_tool.idname not in LIST_OF_TOOLS:
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -114,6 +107,16 @@ def update_bim_tool_props():
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return
|
||||
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
is_bim_tool = current_tool.idname == "bim.bim_tool"
|
||||
if is_bim_tool:
|
||||
props.ifc_class = element_type.is_a()
|
||||
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
|
||||
props.relating_type_id = str(element_type.id())
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
if not extrusion:
|
||||
return
|
||||
@@ -124,7 +127,6 @@ def update_bim_tool_props():
|
||||
return x_angle
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
@@ -175,30 +177,20 @@ def refresh_ui_data():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
|
||||
refresh_ui_data()
|
||||
for name, value in modules.items():
|
||||
try:
|
||||
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# TODO: deprecate prop purge functions and refactor into data classes.
|
||||
try:
|
||||
getattr(value, "prop").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene):
|
||||
IfcStore.purge()
|
||||
purge_module_data()
|
||||
refresh_ui_data()
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
IfcStore.get_schema()
|
||||
@@ -210,7 +202,7 @@ def undo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
purge_module_data()
|
||||
refresh_ui_data()
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
@@ -219,7 +211,7 @@ def redo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
purge_module_data()
|
||||
refresh_ui_data()
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
|
||||
@@ -273,6 +273,81 @@ def convert_property_group_from_si(property_group, skip_props=()):
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
def draw_filter(layout, props, data, module):
|
||||
if not data.is_loaded:
|
||||
data.load()
|
||||
|
||||
sprops = bpy.context.scene.BIMSearchProperties
|
||||
|
||||
if tool.Ifc.get():
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{len(data.data['saved_searches'])} Saved Searches")
|
||||
|
||||
if data.data["saved_searches"]:
|
||||
row.operator("bim.load_search", text="", icon="IMPORT").module = module
|
||||
row.operator("bim.save_search", text="", icon="EXPORT").module = module
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
|
||||
row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module
|
||||
|
||||
for i, filter_group in enumerate(props.filter_groups):
|
||||
box = layout.box()
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(sprops, "facet", text="")
|
||||
op = row.operator("bim.add_filter", text="Add Filter", icon="ADD")
|
||||
op.type = sprops.facet
|
||||
op.index = i
|
||||
op.module = module
|
||||
|
||||
for j, ifc_filter in enumerate(filter_group.filters):
|
||||
if ifc_filter.type == "entity":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_3D")
|
||||
elif ifc_filter.type == "attribute":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="COPY_ID")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "type":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_VOLUME")
|
||||
elif ifc_filter.type == "material":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="MATERIAL")
|
||||
elif ifc_filter.type == "property":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "pset", text="", icon="PROPERTIES")
|
||||
row.prop(ifc_filter, "name", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "classification":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="OUTLINER")
|
||||
elif ifc_filter.type == "location":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="PACKAGE")
|
||||
elif ifc_filter.type == "query":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="GRIP")
|
||||
op = row.operator("bim.select_filter_elements", text="", icon="EYEDROPPER")
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
op.module = module
|
||||
op = row.operator("bim.remove_filter", text="", icon="X")
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
op.module = module
|
||||
|
||||
row = box.row()
|
||||
op = row.operator("bim.remove_filter_group", icon="X")
|
||||
op.index = i
|
||||
op.module = module
|
||||
|
||||
|
||||
# TODO this should move into ifcopenshell.util
|
||||
|
||||
|
||||
|
||||
@@ -220,8 +220,9 @@ class IfcStore:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
blenderbim.bim.handler.subscribe_to(
|
||||
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
|
||||
)
|
||||
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
|
||||
@@ -247,8 +248,9 @@ class IfcStore:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
blenderbim.bim.handler.subscribe_to(
|
||||
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
|
||||
)
|
||||
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@@ -321,7 +323,7 @@ class IfcStore:
|
||||
IfcStore.begin_transaction(operator)
|
||||
if tool.Ifc.get():
|
||||
tool.Ifc.get().begin_transaction()
|
||||
if BrickStore.graph:
|
||||
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
|
||||
BrickStore.begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
|
||||
@@ -339,7 +341,7 @@ class IfcStore:
|
||||
IfcStore.add_transaction_operation(
|
||||
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
|
||||
)
|
||||
if BrickStore.graph:
|
||||
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
|
||||
BrickStore.end_transaction()
|
||||
IfcStore.end_transaction(operator)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -20,10 +20,9 @@ import os
|
||||
import re
|
||||
import bpy
|
||||
import time
|
||||
import json
|
||||
import bmesh
|
||||
import shutil
|
||||
import logging
|
||||
import threading
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
@@ -31,7 +30,6 @@ import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from itertools import chain, accumulate
|
||||
@@ -39,16 +37,6 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
|
||||
|
||||
class FileCopy(threading.Thread):
|
||||
def __init__(self, file_path, destination):
|
||||
threading.Thread.__init__(self)
|
||||
self.file_path = file_path
|
||||
self.destination = destination
|
||||
|
||||
def run(self):
|
||||
shutil.copy(self.file_path, self.destination)
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
def __init__(self, ifc_import_settings, ifc_importer):
|
||||
self.mesh = None
|
||||
@@ -108,7 +96,9 @@ class MaterialCreator:
|
||||
has_parsed = True
|
||||
elif hasattr(element, "RepresentationMaps"):
|
||||
for representation_map in element.RepresentationMaps:
|
||||
if self.parse_representation(representation_map.MappedRepresentation):
|
||||
if not representation_map.MappedRepresentation:
|
||||
has_parsed = True # Accommodate invalid IFC data from Revit
|
||||
elif self.parse_representation(representation_map.MappedRepresentation):
|
||||
has_parsed = True
|
||||
return has_parsed
|
||||
|
||||
@@ -158,12 +148,15 @@ class MaterialCreator:
|
||||
faces_remap = None
|
||||
texture_map = None
|
||||
if coordinates.is_a("IfcIndexedPolygonalTextureMap"):
|
||||
faces_remap = [[coordinates_remap[i-1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
|
||||
for tex_coord_index in coordinates.TexCoordIndices]
|
||||
faces_remap = [
|
||||
[coordinates_remap[i - 1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
|
||||
for tex_coord_index in coordinates.TexCoordIndices
|
||||
]
|
||||
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices]
|
||||
elif coordinates.is_a("IfcIndexedTriangleTextureMap"):
|
||||
faces_remap = [[coordinates_remap[i-1] for i in triangle_face]
|
||||
for triangle_face in coordinates.MappedTo.CoordIndex]
|
||||
faces_remap = [
|
||||
[coordinates_remap[i - 1] for i in triangle_face] for triangle_face in coordinates.MappedTo.CoordIndex
|
||||
]
|
||||
texture_map = coordinates.TexCoordIndex
|
||||
|
||||
# apply uv to each face
|
||||
@@ -178,7 +171,7 @@ class MaterialCreator:
|
||||
)
|
||||
# apply uv to each loop
|
||||
for loop, i in zip(bface.loops, texCoordIndex):
|
||||
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i-1]
|
||||
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i - 1]
|
||||
|
||||
# Finish up, write the bmesh back to the mesh
|
||||
bm.to_mesh(self.mesh)
|
||||
@@ -223,20 +216,8 @@ class IfcImporter:
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.diff = None
|
||||
self.file = None
|
||||
self.settings = ifcopenshell.geom.settings()
|
||||
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_curve = ifcopenshell.geom.settings()
|
||||
self.settings_curve.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings_curve.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings_curve.set(self.settings_curve.STRICT_TOLERANCE, True)
|
||||
self.settings_curve.set(self.settings_curve.INCLUDE_CURVES, True)
|
||||
self.settings_native = ifcopenshell.geom.settings()
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
|
||||
self.context_settings = []
|
||||
self.contexts = []
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
@@ -275,6 +256,8 @@ class IfcImporter:
|
||||
self.profile_code("Calculate unit scale")
|
||||
self.calculate_model_offset()
|
||||
self.profile_code("Calculate model offset")
|
||||
self.predict_dense_mesh()
|
||||
self.profile_code("Predict dense mesh")
|
||||
self.set_units()
|
||||
self.profile_code("Set units")
|
||||
self.create_project()
|
||||
@@ -318,6 +301,9 @@ class IfcImporter:
|
||||
self.profile_code("Merging by colour")
|
||||
self.set_default_context()
|
||||
self.profile_code("Setting default context")
|
||||
if self.ifc_import_settings.should_setup_viewport_camera:
|
||||
self.setup_viewport_camera()
|
||||
self.setup_arrays()
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
@@ -340,32 +326,83 @@ class IfcImporter:
|
||||
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
|
||||
|
||||
def process_context_filter(self):
|
||||
# Facetation is to accommodate broken Revit files
|
||||
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
|
||||
self.body_contexts = [
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
|
||||
if c.ContextIdentifier in ["Body", "Facetation"]
|
||||
]
|
||||
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
|
||||
self.body_contexts.extend(
|
||||
[
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
|
||||
if c.ContextType == "Model"
|
||||
]
|
||||
)
|
||||
if self.body_contexts:
|
||||
self.settings.set_context_ids(self.body_contexts)
|
||||
# Annotation ContextType is to accommodate broken Revit files
|
||||
# See https://github.com/Autodesk/revit-ifc/issues/187
|
||||
self.plan_contexts = [
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext")
|
||||
if c.ContextType in ["Plan", "Annotation"] or c.ContextIdentifier == "Annotation"
|
||||
type_priority = ["Model", "Plan", "Annotation"]
|
||||
identifier_priority = [
|
||||
"Body",
|
||||
"Body-FallBack",
|
||||
"Facetation",
|
||||
"FootPrint",
|
||||
"Profile",
|
||||
"Surface",
|
||||
"Reference",
|
||||
"Axis",
|
||||
"Clearance",
|
||||
"Box",
|
||||
"Lighting",
|
||||
"Annotation",
|
||||
"CoG",
|
||||
]
|
||||
if self.plan_contexts:
|
||||
self.settings_2d.set_context_ids(self.plan_contexts)
|
||||
target_view_priority = [
|
||||
"MODEL_VIEW",
|
||||
"PLAN_VIEW",
|
||||
"REFLECTED_PLAN_VIEW",
|
||||
"ELEVATION_VIEW",
|
||||
"SECTION_VIEW",
|
||||
"GRAPH_VIEW",
|
||||
"SKETCH_VIEW",
|
||||
"USERDEFINED",
|
||||
"NOTDEFINED",
|
||||
]
|
||||
|
||||
def sort_context(context):
|
||||
priority = []
|
||||
if context.ContextType in type_priority:
|
||||
priority.append(len(type_priority) - type_priority.index(context.ContextType))
|
||||
else:
|
||||
priority.append(0)
|
||||
return tuple(priority)
|
||||
|
||||
def sort_subcontext(context):
|
||||
priority = []
|
||||
|
||||
if context.ContextType in type_priority:
|
||||
priority.append(len(type_priority) - type_priority.index(context.ContextType))
|
||||
else:
|
||||
priority.append(0)
|
||||
|
||||
if context.ContextIdentifier in identifier_priority:
|
||||
priority.append(len(identifier_priority) - identifier_priority.index(context.ContextIdentifier))
|
||||
else:
|
||||
priority.append(0)
|
||||
|
||||
if context.TargetView in target_view_priority:
|
||||
priority.append(len(target_view_priority) - target_view_priority.index(context.TargetView))
|
||||
else:
|
||||
priority.append(0)
|
||||
|
||||
priority.append(context.TargetScale or 0) # Big then small
|
||||
|
||||
return tuple(priority)
|
||||
|
||||
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
|
||||
self.contexts = sorted(
|
||||
self.file.by_type("IfcGeometricRepresentationSubContext"), key=sort_subcontext, reverse=True
|
||||
) + sorted(
|
||||
self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False),
|
||||
key=sort_context,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
for context in self.contexts:
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
settings.set(settings.STRICT_TOLERANCE, True)
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
settings.set_context_ids([context.id()])
|
||||
self.context_settings.append(settings)
|
||||
|
||||
def process_element_filter(self):
|
||||
offset = self.ifc_import_settings.element_offset
|
||||
@@ -436,13 +473,51 @@ class IfcImporter:
|
||||
or getattr(element, "HasOpenings", None)
|
||||
):
|
||||
return
|
||||
representations = self.get_transformed_body_representations(element.Representation.Representations)
|
||||
|
||||
representation = None
|
||||
representation_priority = None
|
||||
context = None
|
||||
|
||||
for rep in element.Representation.Representations:
|
||||
if rep.ContextOfItems in self.contexts:
|
||||
rep_priority = self.contexts.index(rep.ContextOfItems)
|
||||
if representation is None or rep_priority < representation_priority:
|
||||
representation = rep
|
||||
representation_priority = rep_priority
|
||||
context = rep.ContextOfItems
|
||||
|
||||
if not representation:
|
||||
return
|
||||
|
||||
matrix = np.eye(4)
|
||||
representation_id = None
|
||||
|
||||
rep = representation
|
||||
while True:
|
||||
if rep.Items and rep.Items[0].is_a("IfcMappedItem"):
|
||||
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(rep.Items[0])
|
||||
if not np.allclose(rep_matrix, np.eye(4)):
|
||||
matrix = rep_matrix @ matrix
|
||||
if representation_id is None:
|
||||
representation_id = rep.id()
|
||||
rep = rep.Items[0].MappingSource.MappedRepresentation
|
||||
else:
|
||||
if representation_id is None:
|
||||
representation_id = rep.id()
|
||||
break
|
||||
resolved_representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
|
||||
matrix[0][3] *= self.unit_scale
|
||||
matrix[1][3] *= self.unit_scale
|
||||
matrix[2][3] *= self.unit_scale
|
||||
|
||||
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
|
||||
if self.is_native_swept_disk_solid(element, representations):
|
||||
if self.is_native_swept_disk_solid(element, resolved_representation):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
"matrix": matrix,
|
||||
"context": context,
|
||||
"geometry_id": representation_id,
|
||||
"representation": resolved_representation,
|
||||
"type": "IfcSweptDiskSolid",
|
||||
}
|
||||
return True
|
||||
@@ -451,51 +526,52 @@ class IfcImporter:
|
||||
return False # Performance improvements only occur on edge cases currently
|
||||
|
||||
# FacetedBreps (without voids) are meshes. See #841.
|
||||
if self.is_native_faceted_brep(representations):
|
||||
if self.is_native_faceted_brep(resolved_representation):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
"matrix": matrix,
|
||||
"context": context,
|
||||
"geometry_id": representation_id,
|
||||
"representation": resolved_representation,
|
||||
"type": "IfcFacetedBrep",
|
||||
}
|
||||
return True
|
||||
|
||||
if self.is_native_face_based_surface_model(representations):
|
||||
if self.is_native_face_based_surface_model(resolved_representation):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
"matrix": matrix,
|
||||
"context": context,
|
||||
"geometry_id": representation_id,
|
||||
"representation": resolved_representation,
|
||||
"type": "IfcFaceBasedSurfaceModel",
|
||||
}
|
||||
return True
|
||||
|
||||
def is_native_swept_disk_solid(self, element, representations):
|
||||
for representation in representations:
|
||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
def is_native_swept_disk_solid(self, element, representation):
|
||||
items = representation.Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_native_faceted_brep(self, representations):
|
||||
for representation in representations:
|
||||
for i in representation["raw"].Items:
|
||||
if i.is_a() != "IfcFacetedBrep":
|
||||
return False
|
||||
def is_native_faceted_brep(self, representation):
|
||||
for i in representation.Items:
|
||||
if i.is_a() != "IfcFacetedBrep":
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_native_face_based_surface_model(self, representations):
|
||||
for representation in representations:
|
||||
for i in representation["raw"].Items:
|
||||
if i.is_a() != "IfcFaceBasedSurfaceModel":
|
||||
return False
|
||||
def is_native_face_based_surface_model(self, representation):
|
||||
for i in representation.Items:
|
||||
if i.is_a() != "IfcFaceBasedSurfaceModel":
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_products_from_shape_representation(self, element):
|
||||
@@ -507,6 +583,26 @@ class IfcImporter:
|
||||
products.extend(self.get_products_from_shape_representation(inverse_element))
|
||||
return products
|
||||
|
||||
def predict_dense_mesh(self):
|
||||
threshold = 10000 # Just from experience.
|
||||
|
||||
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
return
|
||||
|
||||
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
|
||||
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
|
||||
def calculate_model_offset(self):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
@@ -573,7 +669,9 @@ class IfcImporter:
|
||||
return
|
||||
if not self.does_element_likely_have_geometry_far_away(element):
|
||||
continue
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, element)
|
||||
shape = self.create_generic_shape(element)
|
||||
if not shape:
|
||||
continue
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
@@ -645,7 +743,7 @@ class IfcImporter:
|
||||
self.ifc_import_settings.logger.error("An invalid grid was found %s", grid)
|
||||
continue
|
||||
if grid.Representation:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
|
||||
shape = self.create_generic_shape(grid)
|
||||
grid_obj = self.create_product(grid, shape)
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
|
||||
grid_obj.lock_location = (True, True, True)
|
||||
@@ -664,7 +762,7 @@ class IfcImporter:
|
||||
|
||||
def create_grid_axes(self, axes, grid_collection, grid_obj):
|
||||
for axis in axes:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
|
||||
shape = self.create_generic_shape(axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
|
||||
@@ -684,27 +782,21 @@ class IfcImporter:
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
mesh = None
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
if representation:
|
||||
for context in self.contexts:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, context)
|
||||
if not representation:
|
||||
continue
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
shape = self.create_generic_shape(representation)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
else:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
break
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
@@ -728,20 +820,16 @@ class IfcImporter:
|
||||
checkpoint = time.time()
|
||||
self.incrementally_merge_objects()
|
||||
native_data = self.native_data[element.GlobalId]
|
||||
representation = native_data["representation"]
|
||||
if not representation:
|
||||
continue
|
||||
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
|
||||
mesh_name = f"{context_id}/{representation.id()}"
|
||||
mesh_name = f"{native_data['context'].id()}/{native_data['geometry_id']}"
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
if native_data["type"] == "IfcSweptDiskSolid":
|
||||
mesh = self.create_native_swept_disk_solid(element, mesh_name)
|
||||
mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data)
|
||||
elif native_data["type"] == "IfcFacetedBrep":
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
|
||||
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
||||
tool.Ifc.link(representation, mesh)
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
|
||||
tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh)
|
||||
mesh.name = mesh_name
|
||||
self.meshes[mesh_name] = mesh
|
||||
self.create_product(element, mesh=mesh)
|
||||
@@ -753,21 +841,25 @@ class IfcImporter:
|
||||
def create_elements(self):
|
||||
self.create_generic_elements(self.elements)
|
||||
|
||||
def create_generic_shape(self, element):
|
||||
for settings in self.context_settings:
|
||||
try:
|
||||
result = ifcopenshell.geom.create_shape(settings, element)
|
||||
if result:
|
||||
return result
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
# Based on my experience in viewing BIM models, representations are prioritised as follows:
|
||||
# 1. 3D Body, 2. 2D Body, 3. 2D Plans / annotations, 4. Point clouds, 5. No representation
|
||||
# If an element has a representation that doesn't follow 1, 2, 3, or 4, it will not show by default.
|
||||
# The user can load them later if they want to view them.
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
elements -= products
|
||||
for settings in self.context_settings:
|
||||
if not elements:
|
||||
break
|
||||
products = self.create_products(elements, settings=settings)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
|
||||
@@ -825,9 +917,6 @@ class IfcImporter:
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(self, products, settings=None):
|
||||
if settings is None:
|
||||
settings = self.settings
|
||||
|
||||
results = set()
|
||||
if not products:
|
||||
return results
|
||||
@@ -864,16 +953,8 @@ class IfcImporter:
|
||||
shape = iterator.get()
|
||||
if shape:
|
||||
product = self.file.by_id(shape.id)
|
||||
if self.body_contexts:
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
else:
|
||||
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.id):
|
||||
# We only load a single context, and we prioritise the Body context. See #1290.
|
||||
pass
|
||||
else:
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
if not iterator.next():
|
||||
break
|
||||
print("Done creating geometry")
|
||||
@@ -901,10 +982,10 @@ class IfcImporter:
|
||||
self.structural_collection.children.link(self.structural_connection_collection)
|
||||
self.project["blender"].children.link(self.structural_collection)
|
||||
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_2d)
|
||||
self.create_generic_elements(set(self.file.by_type("IfcStructuralCurveMember")))
|
||||
self.create_generic_elements(set(self.file.by_type("IfcStructuralCurveConnection")))
|
||||
self.create_generic_elements(set(self.file.by_type("IfcStructuralSurfaceMember")))
|
||||
self.create_generic_elements(set(self.file.by_type("IfcStructuralSurfaceConnection")))
|
||||
self.create_structural_point_connections()
|
||||
|
||||
def create_structural_point_connections(self):
|
||||
@@ -973,10 +1054,14 @@ class IfcImporter:
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcCartesianPointList"):
|
||||
if item.is_a("IfcCartesianPointList3D"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPointList2D"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)).to_3d() * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPoint"):
|
||||
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
|
||||
|
||||
@@ -1049,7 +1134,7 @@ class IfcImporter:
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
styles.extend(style.Styles)
|
||||
|
||||
def create_native_faceted_brep(self, element, mesh_name):
|
||||
def create_native_faceted_brep(self, element, mesh_name, native_data):
|
||||
# TODO: georeferencing?
|
||||
# co [x y z x y z x y z ...]
|
||||
# vertex_index [i i i i i ...]
|
||||
@@ -1066,10 +1151,11 @@ class IfcImporter:
|
||||
"material_ids": [],
|
||||
}
|
||||
|
||||
for representation in element.Representation.Representations:
|
||||
if representation.ContextOfItems.id() not in self.body_contexts:
|
||||
continue
|
||||
self.convert_representation(representation)
|
||||
for item in native_data["representation"].Items:
|
||||
if item.is_a() == "IfcFacetedBrep":
|
||||
self.convert_representation_item_faceted_brep(item)
|
||||
elif item.is_a() == "IfcFaceBasedSurfaceModel":
|
||||
self.convert_representation_item_face_based_surface_model(item)
|
||||
|
||||
mesh = bpy.data.meshes.new("Native")
|
||||
|
||||
@@ -1086,19 +1172,33 @@ class IfcImporter:
|
||||
)
|
||||
verts = [None] * len(self.mesh_data["co"])
|
||||
for i in range(0, len(self.mesh_data["co"]), 3):
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
self.mesh_data["co"][i] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||
offset_point[0] * self.unit_scale,
|
||||
offset_point[1] * self.unit_scale,
|
||||
offset_point[2] * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector(
|
||||
(
|
||||
*ifcopenshell.util.geolocation.enh2xyz(
|
||||
self.mesh_data["co"][i] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||
offset_point[0] * self.unit_scale,
|
||||
offset_point[1] * self.unit_scale,
|
||||
offset_point[2] * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
),
|
||||
1,
|
||||
)
|
||||
)
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
else:
|
||||
verts = [c * self.unit_scale for c in self.mesh_data["co"]]
|
||||
verts = [None] * len(self.mesh_data["co"])
|
||||
for i in range(0, len(self.mesh_data["co"]), 3):
|
||||
verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector(
|
||||
(
|
||||
self.mesh_data["co"][i] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||
1,
|
||||
)
|
||||
)
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
mesh.vertices.add(self.mesh_data["total_verts"])
|
||||
@@ -1108,25 +1208,13 @@ class IfcImporter:
|
||||
mesh.polygons.add(self.mesh_data["total_polygons"])
|
||||
mesh.polygons.foreach_set("loop_start", self.mesh_data["loop_start"])
|
||||
mesh.polygons.foreach_set("loop_total", self.mesh_data["loop_total"])
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * self.mesh_data["total_polygons"])
|
||||
mesh.update()
|
||||
|
||||
mesh["ios_materials"] = self.mesh_data["materials"]
|
||||
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
|
||||
return mesh
|
||||
|
||||
def convert_representation(self, representation):
|
||||
for item in representation.Items:
|
||||
self.convert_representation_item(item)
|
||||
|
||||
def convert_representation_item(self, item):
|
||||
if item.is_a("IfcMappedItem"):
|
||||
# mapping_target = matrix
|
||||
self.convert_representation(item.MappingSource.MappedRepresentation)
|
||||
elif item.is_a() == "IfcFacetedBrep":
|
||||
self.convert_representation_item_faceted_brep(item)
|
||||
elif item.is_a() == "IfcFaceBasedSurfaceModel":
|
||||
self.convert_representation_item_face_based_surface_model(item)
|
||||
|
||||
def convert_representation_item_face_based_surface_model(self, item):
|
||||
mesh = item.get_info_2(recursive=True)
|
||||
for face_set in mesh["FbsmFaces"]:
|
||||
@@ -1221,63 +1309,34 @@ class IfcImporter:
|
||||
self.mesh_data["loop_start"].extend(loop_start)
|
||||
# list(di1.keys())
|
||||
|
||||
def create_native_swept_disk_solid(self, element, mesh_name):
|
||||
def create_native_swept_disk_solid(self, element, mesh_name, native_data):
|
||||
# TODO: georeferencing?
|
||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
||||
curve.dimensions = "3D"
|
||||
curve.resolution_u = 2
|
||||
polyline = curve.splines.new("POLY")
|
||||
|
||||
for representation in self.native_data[element.GlobalId]["representations"]:
|
||||
for item in representation["raw"].Items:
|
||||
# TODO: support inner radius, start param, and end param
|
||||
geometry = ifcopenshell.geom.create_shape(self.settings_native, item.Directrix)
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
v2 = None
|
||||
for edge in edges:
|
||||
v1 = vertices[edge[0]]
|
||||
if v1 != v2:
|
||||
polyline = curve.splines.new("POLY")
|
||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
|
||||
for item in native_data["representation"].Items:
|
||||
# TODO: support inner radius, start param, and end param
|
||||
geometry = self.create_generic_shape(item.Directrix)
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
v2 = None
|
||||
for edge in edges:
|
||||
v1 = vertices[edge[0]]
|
||||
if v1 != v2:
|
||||
polyline = curve.splines.new("POLY")
|
||||
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v1)
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v2)
|
||||
|
||||
curve.bevel_depth = self.unit_scale * item.Radius
|
||||
curve.use_fill_caps = True
|
||||
return curve
|
||||
|
||||
def create_native_annotation(self, element, mesh_name):
|
||||
# TODO: georeferencing?
|
||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
||||
curve.dimensions = "3D"
|
||||
curve.resolution_u = 2
|
||||
polyline = curve.splines.new("POLY")
|
||||
|
||||
for representation in self.native_data[element.GlobalId]["representations"]:
|
||||
for item in representation["raw"].Items:
|
||||
# TODO: support inner radius, start param, and end param
|
||||
geometry = ifcopenshell.geom.create_shape(self.settings_native, item.Directrix)
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
v2 = None
|
||||
for edge in edges:
|
||||
v1 = vertices[edge[0]]
|
||||
if v1 != v2:
|
||||
polyline = curve.splines.new("POLY")
|
||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
|
||||
|
||||
curve.bevel_depth = self.unit_scale * item.Radius
|
||||
return curve
|
||||
|
||||
def merge_by_class(self):
|
||||
merge_set = {}
|
||||
id_set = {}
|
||||
@@ -1679,41 +1738,6 @@ class IfcImporter:
|
||||
result[2][3] *= self.unit_scale
|
||||
return result
|
||||
|
||||
def get_body_representation(self, representations):
|
||||
for representation in representations:
|
||||
if (
|
||||
representation.RepresentationIdentifier == "Body"
|
||||
and representation.RepresentationType == "MappedRepresentation"
|
||||
):
|
||||
if len(representation.Items) > 1:
|
||||
return representation
|
||||
return self.get_body_representation([representation.Items[0].MappingSource.MappedRepresentation])
|
||||
elif representation.RepresentationIdentifier == "Body":
|
||||
return representation
|
||||
|
||||
def get_transformed_body_representations(self, representations, matrix=None):
|
||||
if matrix is None:
|
||||
matrix = mathutils.Matrix()
|
||||
results = []
|
||||
for representation in representations:
|
||||
if (
|
||||
representation.RepresentationIdentifier == "Body"
|
||||
and representation.RepresentationType == "MappedRepresentation"
|
||||
):
|
||||
for item in representation.Items:
|
||||
# TODO: Confirm if this transformation is right
|
||||
transform = self.get_axis2placement(item.MappingSource.MappingOrigin)
|
||||
if item.MappingTarget:
|
||||
transform = transform @ self.get_cartesiantransformationoperator(item.MappingTarget)
|
||||
results.extend(
|
||||
self.get_transformed_body_representations(
|
||||
[item.MappingSource.MappedRepresentation], transform @ matrix
|
||||
)
|
||||
)
|
||||
elif representation.RepresentationIdentifier == "Body":
|
||||
results.append({"raw": representation, "matrix": self.scale_matrix(matrix)})
|
||||
return results
|
||||
|
||||
def scale_matrix(self, matrix):
|
||||
matrix[0][3] *= self.unit_scale
|
||||
matrix[1][3] *= self.unit_scale
|
||||
@@ -1902,6 +1926,22 @@ class IfcImporter:
|
||||
obj.matrix_world = matrix_world
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
def setup_viewport_camera(self):
|
||||
context_override = tool.Blender.get_viewport_context()
|
||||
with bpy.context.temp_override(**context_override):
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.view3d.view_selected()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
def setup_arrays(self):
|
||||
for element in self.file.by_type("IfcElement"):
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
if not pset_data or not pset_data.get("Data", None): # skip array children
|
||||
continue
|
||||
for i in range(len(json.loads(pset_data["Data"]))):
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
def __init__(self):
|
||||
@@ -1924,6 +1964,7 @@ class IfcImportSettings:
|
||||
self.element_limit = 30000
|
||||
self.has_filter = None
|
||||
self.should_filter_spatial_elements = True
|
||||
self.should_setup_viewport_camera = True
|
||||
self.elements = set()
|
||||
self.collection_mode = "DECOMPOSITION"
|
||||
|
||||
|
||||
@@ -202,6 +202,7 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj in context.selectable_objects:
|
||||
aggregate_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = aggregate_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -35,13 +35,16 @@ classes = (
|
||||
operator.ViewBrickItem,
|
||||
operator.SerializeBrick,
|
||||
operator.AddBrickNamespace,
|
||||
operator.SetBrickListRoot,
|
||||
operator.RemoveBrickRelation,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
ui.BIM_PT_ifc_brickschema_references,
|
||||
ui.BIM_PT_brickschema_project_info,
|
||||
ui.BIM_PT_brickschema_namespaces,
|
||||
ui.BIM_PT_brickschema_create_entity,
|
||||
ui.BIM_PT_brickschema_viewport,
|
||||
ui.BIM_UL_bricks,
|
||||
ui.BIM_PT_ifc_brickschema_references,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class BrickschemaData:
|
||||
|
||||
@classmethod
|
||||
def get_is_loaded(cls):
|
||||
return BrickStore.graph is not None
|
||||
return BrickStore.graph is not None # `if BrickStore.graph` by itself takes ages.
|
||||
|
||||
@classmethod
|
||||
def active_relations(cls):
|
||||
@@ -67,45 +67,63 @@ class BrickschemaData:
|
||||
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 ?predicate ?object ?sp ?sv WHERE {
|
||||
<{uri}> ?predicate ?object .
|
||||
OPTIONAL {
|
||||
{ ?predicate rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?predicate a brick:EntityProperty . }
|
||||
?object ?sp ?sv }
|
||||
SELECT DISTINCT ?predicate ?object ?label ?sp ?sv WHERE {
|
||||
<{uri}> ?predicate ?object .
|
||||
OPTIONAL {
|
||||
?object rdfs:label ?label .
|
||||
}
|
||||
OPTIONAL {
|
||||
{ ?predicate rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?predicate a brick:EntityProperty . }
|
||||
?object ?sp ?sv .
|
||||
}
|
||||
}
|
||||
GROUP BY ?object
|
||||
""".replace(
|
||||
"{uri}", uri
|
||||
)
|
||||
)
|
||||
for row in query:
|
||||
predicate = row.get("predicate")
|
||||
predicate_name = predicate.toPython().split("#")[-1]
|
||||
object = row.get("object")
|
||||
object_name = object.toPython().split("#")[-1]
|
||||
predicate_uri = row.get("predicate")
|
||||
predicate_name = predicate_uri.toPython().split("#")[-1]
|
||||
object_uri = row.get("object")
|
||||
object_name = row.get("label")
|
||||
if not object_name:
|
||||
if isinstance(object_uri, BNode):
|
||||
object_name = "[]"
|
||||
else:
|
||||
try:
|
||||
object_name = object_uri.toPython().split("#")[-1]
|
||||
except:
|
||||
object_name = str(object_uri)
|
||||
results.append(
|
||||
{
|
||||
"predicate": predicate,
|
||||
"predicate_uri": predicate_uri,
|
||||
"predicate_name": predicate_name,
|
||||
"object": object,
|
||||
"object_uri": object_uri,
|
||||
"object_name": object_name,
|
||||
"is_uri": isinstance(object, URIRef),
|
||||
"object_uri": object.toPython(),
|
||||
"is_globalid": predicate == "globalID",
|
||||
"is_uri": isinstance(object_uri, URIRef),
|
||||
"is_globalid": predicate_name == "ifcGlobalID",
|
||||
}
|
||||
)
|
||||
# if isinstance(row.get("object"), BNode):
|
||||
# for s, p, o in BrickStore.graph.triples((object, None, None)):
|
||||
# results.append(
|
||||
# {
|
||||
# "predicate": predicate + ":" + p.toPython().split("#")[-1],
|
||||
# "object": o.toPython().split("#")[-1],
|
||||
# "is_uri": isinstance(o, URIRef),
|
||||
# "object_uri": o.toPython(),
|
||||
# "is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
# }
|
||||
# )
|
||||
if isinstance(object_uri, BNode):
|
||||
for subject2, predicate2, object2 in BrickStore.graph.triples((object_uri, None, None)):
|
||||
predicate2_name = predicate2.toPython().split("#")[-1]
|
||||
try:
|
||||
object2_name = object2.toPython().split("#")[-1]
|
||||
except:
|
||||
object2_name = str(object2)
|
||||
results.append(
|
||||
{
|
||||
"predicate_uri": None,
|
||||
"predicate_name": predicate_name + ":" + predicate2_name,
|
||||
"object_uri": object2,
|
||||
"object_name": object2_name,
|
||||
"is_uri": isinstance(object2, URIRef),
|
||||
"is_globalid": predicate2_name == "ifcGlobalID",
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@@ -131,7 +149,7 @@ class BrickschemaReferencesData:
|
||||
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:
|
||||
elif library.Location and ".ttl" in library.Location:
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
return results
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class LoadBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_brick_project"
|
||||
bl_label = "Load Brickschema Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load in a Brick project from a file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
|
||||
@@ -56,6 +57,7 @@ class ViewBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_class"
|
||||
bl_label = "View Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Inspect the subclasses of this class"
|
||||
brick_class: bpy.props.StringProperty(name="Brick Class")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
@@ -67,17 +69,22 @@ class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_item"
|
||||
bl_label = "View Brick Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Inspect this entity in the viewer"
|
||||
item: bpy.props.StringProperty(name="Brick Item")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
|
||||
try:
|
||||
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
|
||||
except:
|
||||
self.report({'ERROR'}, f'Could not find {self.item}')
|
||||
|
||||
|
||||
class RewindBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.rewind_brick_class"
|
||||
bl_label = "Rewind Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Go back to the previous list view"
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -88,6 +95,7 @@ class CloseBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.close_brick_project"
|
||||
bl_label = "Close Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Close the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
core.close_brick_project(tool.Brick)
|
||||
@@ -97,6 +105,7 @@ class ConvertBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_brick_project"
|
||||
bl_label = "Convert Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create an Ifc library for this Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_brick_project(tool.Ifc, tool.Brick)
|
||||
@@ -106,9 +115,18 @@ class AssignBrickReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_brick_reference"
|
||||
bl_label = "Assign Brick Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Assign the selected Ifc entity to the selected Brick entity"
|
||||
|
||||
def _execute(self, context):
|
||||
if not context.active_object:
|
||||
self.report({'ERROR'}, f'No Ifc selected')
|
||||
return
|
||||
props = context.scene.BIMBrickProperties
|
||||
try:
|
||||
props.bricks[props.active_brick_index]
|
||||
except:
|
||||
self.report({'ERROR'}, f'No Brick selected')
|
||||
return
|
||||
core.assign_brick_reference(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
@@ -122,19 +140,17 @@ class AddBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick"
|
||||
bl_label = "Add Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create the Brick entity"
|
||||
|
||||
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_entity_class,
|
||||
library=library,
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
label=props.new_brick_label,
|
||||
)
|
||||
|
||||
@@ -143,6 +159,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_relation"
|
||||
bl_label = "Add Brick Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create the Brick relationship"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -152,7 +169,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
|
||||
elif props.split_screen_toggled:
|
||||
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
|
||||
else:
|
||||
object = props.new_brick_relation_namespace + props.new_brick_relation_object
|
||||
object = props.namespace + props.new_brick_relation_object
|
||||
core.add_brick_relation(
|
||||
tool.Brick,
|
||||
brick_uri=brick.uri,
|
||||
@@ -165,6 +182,7 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_ifc_to_brick"
|
||||
bl_label = "Convert IFC To Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert Ifc entities and relations to Brick entities and relations"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -178,6 +196,7 @@ class NewBrickFile(bpy.types.Operator):
|
||||
bl_idname = "bim.new_brick_file"
|
||||
bl_label = "New Brick File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create a Brick project from scratch"
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
@@ -210,16 +229,17 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.refresh_brick_viewer"
|
||||
bl_label = "Refresh Brick Viewer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
bl_description = "Refresh the list view"
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick, split_screen=self.split_screen)
|
||||
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"}
|
||||
bl_description = "Delete this entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -262,32 +282,20 @@ class SerializeBrick(bpy.types.Operator):
|
||||
class AddBrickNamespace(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_namespace"
|
||||
bl_label = "Add Brick Namespace"
|
||||
bl_description = "Bind a new namespace to the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
alias = props.new_brick_namespace_alias
|
||||
uri = props.new_brick_namespace_uri
|
||||
core.add_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
|
||||
|
||||
class SetBrickListRoot(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.set_brick_list_root"
|
||||
bl_label = "Set Brick View Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
if self.split_screen:
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
else:
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=self.split_screen)
|
||||
core.add_brick_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
|
||||
|
||||
class RemoveBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick_relation"
|
||||
bl_label = "Remove Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Delete this relationship"
|
||||
predicate: bpy.props.StringProperty(name="Relation")
|
||||
object: bpy.props.StringProperty(name="Object")
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.tool.brick as tool
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
def update_active_brick_index(self, context):
|
||||
@@ -43,12 +45,12 @@ def get_libraries(self, context):
|
||||
|
||||
|
||||
def get_namespaces(self, context):
|
||||
return BrickStore.namespaces
|
||||
return [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
|
||||
|
||||
|
||||
def get_brick_entity_classes(self, context):
|
||||
entity = self.brick_entity_create_type
|
||||
return BrickStore.entity_classes[entity]
|
||||
return [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
|
||||
|
||||
|
||||
def get_brick_roots(self, context):
|
||||
@@ -56,13 +58,22 @@ def get_brick_roots(self, context):
|
||||
|
||||
|
||||
def get_brick_relations(self, context):
|
||||
def is_label(relation):
|
||||
return relation["predicate_name"] == "label"
|
||||
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
|
||||
new_relations = BrickStore.relationships.copy()
|
||||
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return new_relations
|
||||
return BrickStore.relationships
|
||||
relations = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
if relation["predicate_name"] == "label":
|
||||
return relations
|
||||
relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return relations
|
||||
|
||||
|
||||
|
||||
def update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
|
||||
|
||||
def split_screen_update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
|
||||
|
||||
|
||||
class Brick(PropertyGroup):
|
||||
@@ -79,10 +90,9 @@ class BIMBrickProperties(PropertyGroup):
|
||||
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
|
||||
libraries: EnumProperty(name="Libraries", items=get_libraries)
|
||||
set_list_root_toggled: BoolProperty(name="Set List Root Toggled", default=False)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
|
||||
# namespace manager
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
|
||||
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
|
||||
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
|
||||
# create brick entity
|
||||
@@ -93,13 +103,12 @@ class BIMBrickProperties(PropertyGroup):
|
||||
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
|
||||
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
|
||||
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
|
||||
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
|
||||
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
|
||||
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
|
||||
add_brick_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
|
||||
# create relations split screen
|
||||
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
|
||||
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
|
||||
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
|
||||
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
|
||||
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
|
||||
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots)
|
||||
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
|
||||
@@ -34,17 +34,36 @@ class BIM_PT_brickschema(Panel):
|
||||
def draw(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
|
||||
class BIM_PT_brickschema_project_info(Panel):
|
||||
bl_label = "Project Info"
|
||||
bl_idname = "BIM_PT_brickschema_project_info"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
def draw(self, context):
|
||||
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 BrickStore.path:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=BrickStore.path, icon="FILEBROWSER")
|
||||
else:
|
||||
row.label(text="No file", icon="FILEBROWSER")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if BrickStore.last_saved:
|
||||
row.label(text=BrickStore.last_saved, icon="TIME")
|
||||
else:
|
||||
row.label(text="Not saved", icon="TIME")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
|
||||
@@ -53,134 +72,173 @@ class BIM_PT_brickschema(Panel):
|
||||
op.should_save_as = True
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
|
||||
|
||||
if self.props.brick_settings_toggled:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text="Active Namespace:")
|
||||
|
||||
row = box.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
class BIM_PT_brickschema_namespaces(Panel):
|
||||
bl_label = "Namespaces"
|
||||
bl_idname = "BIM_PT_brickschema_namespaces"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text="Bind New Namespace:")
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return BrickStore.graph != None
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
|
||||
col = row.column()
|
||||
col.alignment = "CENTER"
|
||||
col.scale_x = 1.1
|
||||
col.label(text=":")
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Entity:")
|
||||
row.label(text="Active Namespace:")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_entity_create_type", text="")
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_label", text="")
|
||||
prop_with_search(row, self.props, "brick_entity_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
# row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
row.label(text="Bind New Namespace:")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
col.alignment = "CENTER"
|
||||
col.scale_x = 1.1
|
||||
col.label(text=":")
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
|
||||
|
||||
class BIM_PT_brickschema_create_entity(Panel):
|
||||
bl_label = "Create Entity"
|
||||
bl_idname = "BIM_PT_brickschema_create_entity"
|
||||
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 BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
# TO DO: hide this if selected entity already has a reference, or something similar
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_entity_create_type", text="Class")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "brick_entity_class", text="Type")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
active = tool.Ifc.get_entity(context.active_object)
|
||||
if active and context.selected_objects:
|
||||
row.label(text="Label:")
|
||||
row.label(text=active.Name if active.Name else "Unnamed")
|
||||
else:
|
||||
row.prop(data=self.props, property="new_brick_label", text="Label")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_brick", text="Create Entity")
|
||||
|
||||
|
||||
class BIM_PT_brickschema_viewport(Panel):
|
||||
bl_label = "Viewport"
|
||||
bl_idname = "BIM_PT_brickschema_viewport"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.column().alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
|
||||
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
|
||||
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
|
||||
grid = self.layout.grid_flow(even_columns=True)
|
||||
grid1 = grid.column(align=True)
|
||||
row = grid1.row(align=True)
|
||||
grid_left = grid.column(align=True)
|
||||
row = grid_left.row(align=True)
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = False
|
||||
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
|
||||
row.label(text=self.props.active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid1.row(align=True)
|
||||
op = row.operator("bim.set_brick_list_root", text="Set View")
|
||||
op.split_screen = False
|
||||
row = grid_left.row(align=True)
|
||||
row.prop(data=self.props, property="brick_list_root", text="")
|
||||
|
||||
row = grid1.row()
|
||||
row = grid_left.row()
|
||||
BIM_UL_bricks.split_screen = False
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
if self.props.split_screen_toggled:
|
||||
grid2 = grid.column(align=True)
|
||||
row = grid2.row(align=True)
|
||||
grid_right = grid.column(align=True)
|
||||
row = grid_right.row(align=True)
|
||||
if len(self.props.split_screen_brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = True
|
||||
row.label(text=self.props.split_screen_active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid2.row(align=True)
|
||||
op = row.operator("bim.set_brick_list_root", text="Set View")
|
||||
op.split_screen = True
|
||||
row = grid_right.row(align=True)
|
||||
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
|
||||
|
||||
row = grid2.row()
|
||||
row = grid_right.row()
|
||||
BIM_UL_bricks.split_screen = True
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
|
||||
|
||||
if BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
row.column().alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
|
||||
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Relation:")
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
if self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
|
||||
if split_screen_selection.total_items:
|
||||
try:
|
||||
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
|
||||
except:
|
||||
split_screen_selection = None
|
||||
if not split_screen_selection or split_screen_selection.total_items:
|
||||
row.label(text="No selection", icon="INFO")
|
||||
else:
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
|
||||
|
||||
elif self.props.brick_create_relations_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
|
||||
if self.props.brick_create_relations_toggled and self.props.add_brick_relation_failed:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Failed to find this entity!", icon="ERROR")
|
||||
|
||||
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
split = self.layout.split(factor=0.85, align=True)
|
||||
row = split.row(align=True)
|
||||
row.label(text=relation["predicate_name"])
|
||||
row.separator()
|
||||
row.label(text=relation["object_name"])
|
||||
if self.props.brick_edit_relations_toggled and relation["predicate_name"] != "type":
|
||||
row = split.row(align=True)
|
||||
row.column().alignment = "RIGHT"
|
||||
if self.props.brick_edit_relations_toggled and relation["predicate_uri"] and relation["predicate_name"] != "type":
|
||||
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
|
||||
op.predicate = relation["predicate"]
|
||||
op.object = relation["object"]
|
||||
if relation["is_uri"] and relation["predicate_name"] != "type":
|
||||
op.predicate = relation["predicate_uri"]
|
||||
op.object = relation["object_uri"]
|
||||
if relation["is_uri"] and relation["object_uri"].toPython().split("#")[-1] != self.props.active_brick_class:
|
||||
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.item = relation["object_uri"]
|
||||
if relation["is_globalid"]:
|
||||
@@ -188,6 +246,23 @@ class BIM_PT_brickschema(Panel):
|
||||
op.global_id = relation["object_name"]
|
||||
|
||||
|
||||
class BIM_UL_bricks(UIList):
|
||||
split_screen = False
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
split = layout.split(factor=0.85, align=True)
|
||||
row = split.row()
|
||||
label = item.label if item.label else item.name
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
op.split_screen = self.split_screen
|
||||
row.label(text=label)
|
||||
if item.total_items:
|
||||
row = split.row()
|
||||
row.label(text=str(item.total_items))
|
||||
|
||||
class BIM_PT_ifc_brickschema_references(Panel):
|
||||
bl_label = "Brickschema References"
|
||||
bl_idname = "BIM_PT_ifc_brickschema_references"
|
||||
@@ -211,15 +286,24 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
row.label(text="No Brickschema Project Loaded")
|
||||
return
|
||||
|
||||
if not BrickschemaReferencesData.data["libraries"]:
|
||||
if not BrickschemaReferencesData.data["libraries"] and BrickStore.path:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No IFC Libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
return
|
||||
|
||||
elif not BrickschemaReferencesData.data["libraries"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No IFC Libraries: save the Brick project to create a new library", icon="ERROR")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Libraries must have location pointing to a \".ttl\" file", icon="INFO")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
if BrickStore.path:
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.assign_brick_reference", icon="ADD")
|
||||
@@ -234,19 +318,4 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
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):
|
||||
split_screen = False
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
label = item.label if item.label else item.name
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
op.split_screen = self.split_screen
|
||||
label = label + " (" + str(item.total_items) + ")"
|
||||
row.label(text=label)
|
||||
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
|
||||
@@ -0,0 +1,42 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.GetBSDDClassificationProperties,
|
||||
operator.LoadBSDDDomains,
|
||||
operator.SearchBSDDClassifications,
|
||||
operator.SetActiveBSDDDomain,
|
||||
prop.BSDDDomain,
|
||||
prop.BSDDClassification,
|
||||
prop.BSDDPset,
|
||||
prop.BIMBSDDProperties,
|
||||
ui.BIM_UL_bsdd_domains,
|
||||
ui.BIM_UL_bsdd_classifications,
|
||||
ui.BIM_PT_bsdd,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMBSDDProperties = bpy.props.PointerProperty(type=prop.BIMBSDDProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMBSDDProperties
|
||||
@@ -0,0 +1,132 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import bsdd
|
||||
import json
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class LoadBSDDDomains(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bsdd_domains"
|
||||
bl_label = "Load bSDD Domains"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMBSDDProperties
|
||||
props.domains.clear()
|
||||
client = bsdd.Client()
|
||||
for domain in sorted(client.Domain(), key=lambda x: x["name"]):
|
||||
new = props.domains.add()
|
||||
new.name = domain["name"]
|
||||
new.namespace_uri = domain["namespaceUri"]
|
||||
new.default_language_code = domain["defaultLanguageCode"]
|
||||
new.organization_name_owner = domain["organizationNameOwner"]
|
||||
new.status = domain["status"]
|
||||
new.version = domain["version"]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetActiveBSDDDomain(bpy.types.Operator):
|
||||
bl_idname = "bim.set_active_bsdd_domain"
|
||||
bl_label = "Load bSDD Domains"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
uri: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMBSDDProperties
|
||||
props.active_domain = self.name
|
||||
props.active_uri = self.uri
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SearchBSDDClassifications(bpy.types.Operator):
|
||||
bl_idname = "bim.search_bsdd_classifications"
|
||||
bl_label = "Search bSDD Classifications"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMBSDDProperties
|
||||
props.classifications.clear()
|
||||
client = bsdd.Client()
|
||||
related_ifc_entities = []
|
||||
if len(props.keyword) < 3:
|
||||
return {"FINISHED"}
|
||||
if props.should_filter_ifc_class and context.active_object:
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if element:
|
||||
related_ifc_entities = [element.is_a()]
|
||||
results = client.ClassificationSearchOpen(props.keyword, DomainNamespaceUris=[props.active_uri], RelatedIfcEntities=related_ifc_entities)
|
||||
for result in sorted(results["classifications"], key=lambda x: x["referenceCode"]):
|
||||
new = props.classifications.add()
|
||||
new.name = result["name"]
|
||||
new.reference_code = result["referenceCode"]
|
||||
new.description = result.get("description", "")
|
||||
new.namespace_uri = result["namespaceUri"]
|
||||
new.domain_name = result["domainName"]
|
||||
new.domain_namespace_uri = result["domainNamespaceUri"]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetBSDDClassificationProperties(bpy.types.Operator):
|
||||
bl_idname = "bim.get_bsdd_classification_properties"
|
||||
bl_label = "Search bSDD Classifications"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.scene.BIMBSDDProperties
|
||||
bprops.classification_psets.clear()
|
||||
bsdd_classification = bprops.classifications[bprops.active_classification_index]
|
||||
client = bsdd.Client()
|
||||
data = client.Classification(bsdd_classification.namespace_uri)
|
||||
|
||||
properties = data.get("classificationProperties", None)
|
||||
if not properties:
|
||||
return {"FINISHED"}
|
||||
|
||||
psets = {}
|
||||
|
||||
for prop in properties:
|
||||
if prop.get("propertyDomainName") != "IFC":
|
||||
continue
|
||||
pset = prop.get("propertySet", None)
|
||||
if not pset:
|
||||
continue
|
||||
psets.setdefault(pset, {})
|
||||
|
||||
predefined_value = prop.get("predefinedValue")
|
||||
if predefined_value:
|
||||
possible_values = [predefined_value]
|
||||
else:
|
||||
possible_values = prop.get("possibleValues", []) or []
|
||||
possible_values = [v["value"] for v in possible_values]
|
||||
|
||||
psets[pset][prop["name"]] = possible_values
|
||||
|
||||
for pset_name, pset in psets.items():
|
||||
new = bprops.classification_psets.add()
|
||||
new.name = pset_name
|
||||
for name, values in pset.items():
|
||||
new2 = new.properties.add()
|
||||
new2.name = name
|
||||
new2.enum_items = json.dumps(values)
|
||||
new2.data_type = "enum"
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,66 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class BSDDDomain(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
namespace_uri: StringProperty(name="URI")
|
||||
default_language_code: StringProperty(name="Language")
|
||||
organization_name_owner: StringProperty(name="Organization")
|
||||
status: StringProperty(name="Status")
|
||||
version: StringProperty(name="Version")
|
||||
|
||||
|
||||
class BSDDClassification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
reference_code: StringProperty(name="Reference Code")
|
||||
description: StringProperty(name="Description")
|
||||
namespace_uri: StringProperty(name="Namespace URI")
|
||||
domain_name: StringProperty(name="Domain Name")
|
||||
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
|
||||
|
||||
|
||||
class BSDDPset(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||
|
||||
|
||||
class BIMBSDDProperties(PropertyGroup):
|
||||
active_domain: StringProperty(name="Active Domain")
|
||||
active_uri: StringProperty(name="Active URI")
|
||||
domains: CollectionProperty(name="Domains", type=BSDDDomain)
|
||||
active_domain_index: IntProperty(name="Active Domain Index")
|
||||
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
|
||||
active_classification_index: IntProperty(name="Active Classification Index")
|
||||
keyword: StringProperty(name="Keyword")
|
||||
should_filter_ifc_class: BoolProperty(name="Filter Active IFC Class", default=True)
|
||||
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
|
||||
@@ -0,0 +1,71 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_bsdd(Panel):
|
||||
bl_label = "buildingSMART Data Dictionary"
|
||||
bl_idname = "BIM_PT_bsdd"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMBSDDProperties
|
||||
if props.active_domain:
|
||||
row = self.layout.row()
|
||||
row.label(text="Active: " + props.active_domain, icon="URL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active bSDD Domain", icon="ERROR")
|
||||
|
||||
if len(props.domains):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_bsdd_domains",
|
||||
"",
|
||||
props,
|
||||
"domains",
|
||||
props,
|
||||
"active_domain_index",
|
||||
)
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_bsdd_domains")
|
||||
|
||||
|
||||
class BIM_UL_bsdd_domains(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{item.name} ({item.organization_name_owner})")
|
||||
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.name = item.name
|
||||
op.uri = item.namespace_uri
|
||||
|
||||
|
||||
class BIM_UL_bsdd_classifications(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.reference_code)
|
||||
row.label(text=item.name)
|
||||
@@ -336,8 +336,15 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
else:
|
||||
element_file = self.file
|
||||
|
||||
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.GlobalId in global_ids:
|
||||
try:
|
||||
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
continue
|
||||
|
||||
global_id = getattr(element, "GlobalId", None)
|
||||
if not global_id:
|
||||
continue
|
||||
if global_id in global_ids:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
bl_parent_id = "BIM_PT_tab_clash_detection"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -102,9 +102,10 @@ class BIM_PT_clash_manager(Panel):
|
||||
bl_idname = "BIM_PT_clash_manager"
|
||||
bl_label = "Clash Manager"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_clash_detection"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -21,7 +21,9 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddClassification,
|
||||
operator.AddClassificationFromBSDD,
|
||||
operator.AddClassificationReference,
|
||||
operator.AddClassificationReferenceFromBSDD,
|
||||
operator.ChangeClassificationLevel,
|
||||
operator.DisableEditingClassification,
|
||||
operator.DisableEditingClassificationReference,
|
||||
|
||||
@@ -54,6 +54,25 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
|
||||
class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_classification_from_bsdd"
|
||||
bl_label = "Add Classification From bSDD"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBSDDProperties
|
||||
domain = [d for d in props.domains if d.name == props.active_domain][0]
|
||||
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||
if element.Name == props.active_domain or element.Location == domain.namespace_uri:
|
||||
return
|
||||
classification = ifcopenshell.api.run(
|
||||
"classification.add_classification", tool.Ifc.get(), classification=props.active_domain
|
||||
)
|
||||
classification.Source = domain.organization_name_owner
|
||||
classification.Location = domain.namespace_uri
|
||||
classification.Edition = domain.version
|
||||
|
||||
|
||||
class EnableEditingClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_classification"
|
||||
bl_label = "Enable Editing Classification"
|
||||
@@ -254,6 +273,70 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
|
||||
class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_classification_reference_from_bsdd"
|
||||
bl_label = "Add Classification Reference From bSDD"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
if self.obj_type == "Object":
|
||||
if context.selected_objects:
|
||||
objects = [o.name for o in context.selected_objects]
|
||||
else:
|
||||
objects = [context.active_object.name]
|
||||
else:
|
||||
objects = [self.obj]
|
||||
props = context.scene.BIMClassificationProperties
|
||||
bprops = context.scene.BIMBSDDProperties
|
||||
|
||||
bsdd_classification = bprops.classifications[bprops.active_classification_index]
|
||||
|
||||
classification = None
|
||||
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||
if (
|
||||
element.Name == bsdd_classification.domain_name
|
||||
or element.Location == bsdd_classification.domain_namespace_uri
|
||||
):
|
||||
classification = element
|
||||
break
|
||||
|
||||
if not classification:
|
||||
classification = ifcopenshell.api.run(
|
||||
"classification.add_classification", tool.Ifc.get(), classification=bsdd_classification.domain_name
|
||||
)
|
||||
classification.Location = bsdd_classification.domain_namespace_uri
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
if not ifc_definition_id:
|
||||
continue
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
tool.Ifc.get(),
|
||||
product=element,
|
||||
classification=classification,
|
||||
identification=bsdd_classification.reference_code,
|
||||
name=bsdd_classification.name,
|
||||
)
|
||||
reference.Location = bsdd_classification.namespace_uri
|
||||
|
||||
for classification_pset in bprops.classification_psets:
|
||||
pset = ifcopenshell.util.element.get_pset(element, classification_pset.name)
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=element, name=classification_pset.name
|
||||
)
|
||||
properties = {}
|
||||
for prop in classification_pset.properties:
|
||||
properties[prop.name] = prop.get_value()
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=properties)
|
||||
|
||||
|
||||
class ChangeClassificationLevel(bpy.types.Operator):
|
||||
bl_idname = "bim.change_classification_level"
|
||||
bl_label = "Change Classification Level"
|
||||
|
||||
@@ -48,6 +48,15 @@ class ClassificationReference(PropertyGroup):
|
||||
|
||||
|
||||
class BIMClassificationProperties(PropertyGroup):
|
||||
classification_source: EnumProperty(
|
||||
items=[
|
||||
("FILE", "IFC File", ""),
|
||||
("BSDD", "buildingSMART Data Dictionary", ""),
|
||||
("MANUAL", "Manual Entry", ""),
|
||||
],
|
||||
name="Classification Source",
|
||||
default="FILE",
|
||||
)
|
||||
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
|
||||
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
|
||||
active_classification_id: IntProperty(name="Active Classification Id")
|
||||
|
||||
@@ -48,6 +48,42 @@ class BIM_PT_classifications(Panel):
|
||||
|
||||
self.props = context.scene.BIMClassificationProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Source", icon="OUTLINER")
|
||||
row.prop(self.props, "classification_source", text="")
|
||||
|
||||
if self.props.classification_source == "FILE":
|
||||
self.draw_add_file_ui(context)
|
||||
elif self.props.classification_source == "BSDD":
|
||||
self.draw_add_bsdd_ui(context)
|
||||
elif self.props.classification_source == "MANUAL":
|
||||
self.draw_add_manual_ui(context)
|
||||
|
||||
for classification in ClassificationsData.data["classifications"]:
|
||||
if self.props.active_classification_id == classification["id"]:
|
||||
self.draw_editable_ui()
|
||||
else:
|
||||
self.draw_ui(classification)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
row = self.layout.row()
|
||||
row.label(text="TODO", icon="ERROR")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
self.bprops = context.scene.BIMBSDDProperties
|
||||
|
||||
if not self.bprops.active_domain:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active bSDD Domain", icon="ERROR")
|
||||
return
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_classification_from_bsdd", icon="ADD")
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
if ClassificationsData.data["has_classification_file"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_classifications", text="")
|
||||
@@ -58,12 +94,6 @@ class BIM_PT_classifications(Panel):
|
||||
row.label(text="No Active Classification Library")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
|
||||
for classification in ClassificationsData.data["classifications"]:
|
||||
if self.props.active_classification_id == classification["id"]:
|
||||
self.draw_editable_ui()
|
||||
else:
|
||||
self.draw_ui(classification)
|
||||
|
||||
def draw_editable_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
|
||||
@@ -84,6 +114,7 @@ class ReferenceUI:
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMClassificationProperties
|
||||
self.bprops = context.scene.BIMBSDDProperties
|
||||
self.props = obj.BIMClassificationReferenceProperties
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
@@ -100,14 +131,76 @@ class ReferenceUI:
|
||||
self.draw_reference_ui(reference)
|
||||
|
||||
def draw_add_ui(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Source", icon="OUTLINER")
|
||||
row.prop(self.sprops, "classification_source", text="")
|
||||
|
||||
if self.sprops.classification_source == "FILE":
|
||||
self.draw_add_file_ui(context)
|
||||
elif self.sprops.classification_source == "BSDD":
|
||||
self.draw_add_bsdd_ui(context)
|
||||
elif self.sprops.classification_source == "MANUAL":
|
||||
self.draw_add_manual_ui(context)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
row = self.layout.row()
|
||||
row.label(text="TODO", icon="ERROR")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
if not self.bprops.active_domain:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active bSDD Domain", icon="ERROR")
|
||||
return
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.bprops, "keyword", text="")
|
||||
row.operator("bim.search_bsdd_classifications", text="", icon="VIEWZOOM")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(self.bprops, "should_filter_ifc_class")
|
||||
|
||||
if len(self.bprops.classifications):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_bsdd_classifications",
|
||||
"",
|
||||
self.bprops,
|
||||
"classifications",
|
||||
self.bprops,
|
||||
"active_classification_index",
|
||||
)
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Search Results")
|
||||
|
||||
if self.bprops.active_classification_index < len(self.bprops.classifications):
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator(
|
||||
"bim.add_classification_reference_from_bsdd", text="Add Classification Reference", icon="ADD"
|
||||
)
|
||||
op.obj = self.obj
|
||||
op.obj_type = self.obj_type
|
||||
row.operator("bim.get_bsdd_classification_properties", text="", icon="COPY_ID")
|
||||
|
||||
if len(self.bprops.classification_psets):
|
||||
for pset in self.bprops.classification_psets:
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.label(text=pset.name, icon="COPY_ID")
|
||||
blenderbim.bim.helper.draw_attributes(pset.properties, box)
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
if not self.data.data["active_classification_library"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Active Classification Library")
|
||||
row.label(text="No Active Classification Library", icon="ERROR")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"Active Classification Library: {self.data.data['active_classification_library']}")
|
||||
#row.prop(self.sprops, "available_classifications", text="")
|
||||
# row.prop(self.sprops, "available_classifications", text="")
|
||||
if not self.sprops.available_library_references:
|
||||
op = row.operator("bim.change_classification_level", text="", icon="GREASEPENCIL")
|
||||
op.parent_id = int(self.sprops.available_classifications)
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import os
|
||||
import logging
|
||||
import ifcopenshell
|
||||
import json
|
||||
import webbrowser
|
||||
import tempfile
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class SelectCobieIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cobie_ifc_file"
|
||||
bl_label = "Select COBie IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.COBieProperties.cobie_ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectCobieJsonFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cobie_json_file"
|
||||
bl_label = "Select COBie JSON File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.COBieProperties.cobie_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExecuteIfcCobie(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_cobie"
|
||||
bl_label = "Execute IFCCOBie"
|
||||
file_format: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.COBieProperties
|
||||
return props.should_load_from_memory or props.cobie_ifc_file
|
||||
|
||||
def execute(self, context):
|
||||
from cobie import IfcCobieParser
|
||||
|
||||
props = context.scene.COBieProperties
|
||||
|
||||
if props.should_load_from_memory:
|
||||
output_dir = tempfile.gettempdir()
|
||||
else:
|
||||
output_dir = os.path.dirname(props.cobie_ifc_file)
|
||||
|
||||
output = os.path.join(output_dir, "output")
|
||||
logger = logging.getLogger("IFCtoCOBie")
|
||||
fh = logging.FileHandler(os.path.join(output_dir, "cobie.log"))
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fh.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s : %(message)s"))
|
||||
logger = logging.getLogger("IFCtoCOBie")
|
||||
logger.addHandler(fh)
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
if props.cobie_json_file:
|
||||
with open(props.cobie_json_file, "r") as f:
|
||||
custom_data = json.load(f)
|
||||
else:
|
||||
custom_data = {}
|
||||
parser = IfcCobieParser(logger, selector)
|
||||
|
||||
ifc_file = IfcStore.get_file()
|
||||
|
||||
if not (ifc_file and props.should_load_from_memory):
|
||||
ifc_file = props.cobie_ifc_file
|
||||
|
||||
parser.parse(
|
||||
ifc_file,
|
||||
props.cobie_types,
|
||||
props.cobie_components,
|
||||
custom_data,
|
||||
)
|
||||
if self.file_format == "xlsx":
|
||||
from cobie import CobieXlsWriter
|
||||
|
||||
writer = CobieXlsWriter(parser, output)
|
||||
writer.write()
|
||||
webbrowser.open("file://" + output + "." + self.file_format)
|
||||
elif self.file_format == "ods":
|
||||
from cobie import CobieOdsWriter
|
||||
|
||||
writer = CobieOdsWriter(parser, output)
|
||||
writer.write()
|
||||
webbrowser.open("file://" + output + "." + self.file_format)
|
||||
else:
|
||||
from cobie import CobieCsvWriter
|
||||
|
||||
writer = CobieCsvWriter(parser, output_dir)
|
||||
writer.write()
|
||||
webbrowser.open("file://" + output_dir)
|
||||
webbrowser.open("file://" + output_dir + "/cobie.log")
|
||||
return {"FINISHED"}
|
||||
@@ -20,64 +20,65 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCostColumn,
|
||||
operator.AddCostItem,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.AddCostSchedule,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.EditCostSchedule,
|
||||
operator.AddCostValue,
|
||||
operator.AddCurrency,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.AssignCostItemQuantity,
|
||||
operator.AssignCostItemType,
|
||||
operator.AssignCostValue,
|
||||
operator.CalculateCostItemResourceValue,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.ClearCostItemAssignments,
|
||||
operator.ContractCostItem,
|
||||
operator.ContractCostItemRate,
|
||||
operator.ContractCostItems,
|
||||
operator.CopyCostItem,
|
||||
operator.CopyCostItemValues,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.EditCostItem,
|
||||
operator.EditCostItemQuantity,
|
||||
operator.EditCostItemValue,
|
||||
operator.EditCostItemValueFormula,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EditCostSchedule,
|
||||
operator.EnableEditingCostItem,
|
||||
operator.ExportCostSchedules,
|
||||
operator.ExpandCostItems,
|
||||
operator.EnableEditingCostItemQuantities,
|
||||
operator.EnableEditingCostItemQuantity,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EnableEditingCostItemValue,
|
||||
operator.EnableEditingCostItemValueFormula,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.AddCostColumn,
|
||||
operator.RemoveCostColumn,
|
||||
operator.AddCostItem,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.ExpandCostItem,
|
||||
operator.ContractCostItem,
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ExpandCostItems,
|
||||
operator.ExportCostSchedules,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
operator.LoadProductCostItems,
|
||||
operator.LoadScheduleOfRates,
|
||||
operator.RemoveCostColumn,
|
||||
operator.RemoveCostItem,
|
||||
operator.AssignCostItemType,
|
||||
operator.UnassignCostItemType,
|
||||
operator.AssignCostItemQuantity,
|
||||
operator.UnassignCostItemQuantity,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.RemoveCostItemQuantity,
|
||||
operator.AddCostValue,
|
||||
operator.RemoveCostItemValue,
|
||||
operator.CopyCostItemValues,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.ReorderCostItem,
|
||||
operator.SelectCostItemProducts,
|
||||
operator.SelectCostScheduleProducts,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
operator.AssignCostValue,
|
||||
operator.LoadScheduleOfRates,
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ContractCostItemRate,
|
||||
operator.CalculateCostItemResourceValue,
|
||||
operator.ClearCostItemAssignments,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.LoadProductCostItems,
|
||||
operator.ReorderCostItem,
|
||||
operator.SelectUnassignedProducts,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.CopyCostItem,
|
||||
operator.AddCurrency,
|
||||
operator.UnassignCostItemQuantity,
|
||||
operator.UnassignCostItemType,
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
|
||||
@@ -117,6 +117,7 @@ class CostSchedulesData:
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
has_unit_basis = False
|
||||
is_sum = False
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
@@ -130,6 +131,11 @@ class CostSchedulesData:
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
has_unit_basis = True
|
||||
else:
|
||||
data["UnitBasisValueComponent"] = 1
|
||||
data["UnitBasisUnitSymbol"] = "U"
|
||||
if cost_value.Category == "*":
|
||||
is_sum = True
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
@@ -137,7 +143,8 @@ class CostSchedulesData:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"]
|
||||
data["TotalAppliedValue"] = None
|
||||
if is_sum:
|
||||
data["TotalAppliedValue"] = None
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item, data):
|
||||
@@ -154,7 +161,7 @@ class CostSchedulesData:
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
else:
|
||||
data["UnitSymbol"] = None
|
||||
data["UnitSymbol"] = "U"
|
||||
|
||||
# same_unit_nested_cost_item = set()
|
||||
# data["DerivedTotalCostQuantity"] = None
|
||||
|
||||
@@ -33,9 +33,26 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Add Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add a new cost schedule"
|
||||
name: bpy.props.StringProperty()
|
||||
object_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_cost_schedule(tool.Ifc, predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types)
|
||||
core.add_cost_schedule(
|
||||
tool.Ifc,
|
||||
name=self.name,
|
||||
predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types,
|
||||
object_type=self.object_type,
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "name", text="Name")
|
||||
layout.prop(context.scene.BIMCostProperties, "cost_schedule_predefined_types", text="Type")
|
||||
if context.scene.BIMCostProperties.cost_schedule_predefined_types == "USERDEFINED":
|
||||
layout.prop(self, "object_type", text="Object type")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -156,6 +173,17 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
|
||||
|
||||
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.contract_cost_items"
|
||||
bl_label = "Contract Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Collapse cost item tree"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.contract_cost_items(tool.Cost)
|
||||
|
||||
|
||||
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_cost_item"
|
||||
bl_label = "Remove Cost Item"
|
||||
@@ -635,7 +663,9 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
|
||||
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
|
||||
r = core.export_cost_schedules(
|
||||
tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule
|
||||
)
|
||||
if isinstance(r, str):
|
||||
self.report({"ERROR"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -21,7 +21,6 @@ import blenderbim.bim.module.cost.prop as CostProp
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
@@ -30,30 +29,32 @@ class BIM_PT_cost_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_cost"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not CostSchedulesData.is_loaded:
|
||||
CostSchedulesData.load()
|
||||
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
if not self.props.active_cost_schedule_id:
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules found.", icon="COMMUNITY")
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
|
||||
row.label(text="No Cost Schedules found.", icon="TEXT")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
row2 = self.layout.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.operator("bim.export_cost_schedules", text="Export all", icon="EXPORT")
|
||||
|
||||
def draw_cost_schedule_ui(self, cost_schedule):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -138,8 +139,8 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
|
||||
row.operator("bim.expand_all_tasks", text="Expand All")
|
||||
row.operator("bim.contract_all_tasks", text="Contract All")
|
||||
row.operator("bim.expand_cost_items", text="Expand All")
|
||||
row.operator("bim.contract_cost_items", text="Contract All")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
|
||||
@@ -619,18 +620,22 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||
layout.label(text=text)
|
||||
|
||||
def draw_quantity_column(self, layout, cost_item):
|
||||
if CostSchedulesData.data["is_editing_rates"]:
|
||||
self.draw_uom_column(layout, cost_item)
|
||||
else:
|
||||
self.draw_total_quantity_column(layout, cost_item)
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"])
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_value_column(self, layout, cost_item):
|
||||
if cost_item["TotalAppliedValue"]:
|
||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||
@@ -642,8 +647,11 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||
layout.label(text=text)
|
||||
|
||||
def draw_order_operator(self, row, ifc_definition_id, cost_item):
|
||||
if cost_item["NestingIndex"] is not None:
|
||||
@@ -656,20 +664,6 @@ class BIM_UL_cost_items_trait:
|
||||
op.cost_item = ifc_definition_id
|
||||
op.new_index = cost_item["NestingIndex"] - 1
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
|
||||
# else:
|
||||
# if cost_item["TotalCostQuantity"] == 0:
|
||||
# layout.label(text="-")
|
||||
# else:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
|
||||
|
||||
|
||||
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
|
||||
def __init__(self):
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import prop, workspace
|
||||
|
||||
classes = (
|
||||
prop.BIMCoveringProperties,
|
||||
workspace.Hotkey,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.CoveringTool, after={"bim.structural_tool"}, separator=False, group=False)
|
||||
bpy.types.Scene.BIMCoveringProperties = bpy.props.PointerProperty(type=prop.BIMCoveringProperties)
|
||||
# bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
|
||||
# bpy.types.Scene.BIMSpatialManagerProperties = bpy.props.PointerProperty(type=prop.BIMSpatialManagerProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.CoveringTool)
|
||||
del bpy.types.Scene.BIMCoveringProperties
|
||||
# del bpy.types.Object.BIMObjectSpatialProperties
|
||||
# del bpy.types.Scene.BIMSpatialManagerProperties
|
||||
Binary file not shown.
+9
-6
@@ -17,6 +17,8 @@
|
||||
# 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,
|
||||
@@ -28,11 +30,12 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class COBieProperties(PropertyGroup):
|
||||
cobie_ifc_file: StringProperty(default="", name="COBie IFC File")
|
||||
cobie_types: StringProperty(default=".COBieType", name="COBie Types")
|
||||
cobie_components: StringProperty(default=".COBie", name="COBie Components")
|
||||
cobie_json_file: StringProperty(default="", name="COBie JSON File")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
class BIMCoveringProperties(PropertyGroup):
|
||||
pass
|
||||
# depth: bpy.props.FloatProperty(name="Depth", default=0.1, subtype="DISTANCE", description="Flooring depth")
|
||||
depth: bpy.props.FloatProperty(name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height")
|
||||
@@ -0,0 +1,242 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
# declaring it here to avoid circular import problems
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
class CoveringTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.covering_tool"
|
||||
bl_label = "Covering Tool"
|
||||
bl_description = "Create and edit coverings"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.covering")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCoveringType"
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.covering_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.covering_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
def add_layout_hotkey(layout, text, hotkey, description):
|
||||
args = ["covering", layout, text, hotkey, description]
|
||||
tool.Blender.add_layout_hotkey_operator(*args)
|
||||
|
||||
class CoveringToolUI:
|
||||
@classmethod
|
||||
def draw(cls, context, layout, ifc_element_type = None):
|
||||
cls.layout = layout
|
||||
cls.props = context.scene.BIMModelProperties
|
||||
cls.covering_props = context.scene.BIMCoveringProperties
|
||||
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
if not tool.Ifc.get():
|
||||
row.label(text="No IFC Project", icon="ERROR")
|
||||
return
|
||||
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load(ifc_element_type)
|
||||
elif AuthoringData.data["ifc_element_type"] != ifc_element_type:
|
||||
AuthoringData.load(ifc_element_type)
|
||||
|
||||
if context.region.type == "TOOL_HEADER":
|
||||
cls.draw_header_interface()
|
||||
elif context.region.type in ("UI", "WINDOW"):
|
||||
cls.draw_basic_bim_tool_interface()
|
||||
|
||||
cls.draw_default_interface()
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def draw_header_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_default_interface(cls):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl3", text="RL")
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
|
||||
relating_type_id = int(cls.props.relating_type_id)
|
||||
type_material_usage = ifcopenshell.util.element.get_material(tool.Ifc.get().by_id(relating_type_id)).is_a()
|
||||
|
||||
if (AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
type_material_usage == "IfcMaterialLayerSet" and
|
||||
not bpy.context.selected_objects):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
# PLEASE KEEP COMMENTS AS A REMINDER
|
||||
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
|
||||
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
|
||||
if tool.Ifc.get_entity(collection_obj):
|
||||
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
|
||||
else:
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
elif (AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
type_material_usage == "IfcMaterialLayerSet" and
|
||||
element and
|
||||
bpy.context.selected_objects and
|
||||
element.is_a("IfcWall")):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
|
||||
|
||||
elif (element and
|
||||
bpy.context.selected_objects and
|
||||
element.is_a("IfcCovering") and
|
||||
# AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
AuthoringData.data["active_material_usage"] == "LAYER3"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
op = row.operator("bim.regen_selected_flooring_object")
|
||||
|
||||
|
||||
|
||||
# elif AuthoringData.data["predefined_type"] == "CEILING":
|
||||
# row = cls.layout.row(align=True)
|
||||
# row.prop(data=cls.props, property="ceiling_height", text="ceiling height")
|
||||
# if element and bpy.context.selected_objects and element.is_a("IfcWall"):
|
||||
# op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
|
||||
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
|
||||
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
|
||||
# else:
|
||||
# op = row.operator("bim.add_instance_ceiling_from_cursor")
|
||||
# op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
# op.from_invoke = True
|
||||
# if cls.props.relating_type_id.isnumeric():
|
||||
# op.relating_type_id = int(cls.props.relating_type_id)
|
||||
else:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls):
|
||||
# shared by both sidebar and header
|
||||
row = cls.layout.row(align=True)
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
else:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
row = cls.layout.row()
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
|
||||
|
||||
@classmethod
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
if cls.props.ifc_class:
|
||||
box = cls.layout.box()
|
||||
if AuthoringData.data["type_thumbnail"]:
|
||||
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
|
||||
else:
|
||||
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
|
||||
op.ifc_class = cls.props.ifc_class
|
||||
|
||||
|
||||
class Hotkey(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.covering_hotkey"
|
||||
bl_label = "Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
description: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return operator.description or ""
|
||||
|
||||
def _execute(self, context):
|
||||
# self.props = context.scene.BIMCoveringProperties
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
|
||||
# self.props = context.scene.BIMSpatialProperties
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
def hotkey_S_A(self):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
|
||||
if AuthoringData.data["predefined_type"] == "FLOORING":
|
||||
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
|
||||
bpy.ops.bim.add_instance_flooring_coverings_from_walls()
|
||||
elif tool.Ifc.get_entity(collection_obj):
|
||||
bpy.ops.bim.add_instance_flooring_covering_from_cursor()
|
||||
else:
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
else:
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
|
||||
if AuthoringData.data["predefined_type"] == "FLOORING":
|
||||
if element and bpy.context.selected_objects and element.is_a("IfcCovering"):
|
||||
bpy.ops.bim.regen_selected_flooring_object()
|
||||
@@ -21,14 +21,15 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCsvAttribute,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.RemoveAllCsvAttributes,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportIfcCsv,
|
||||
operator.EyedropIfcCsv,
|
||||
operator.SelectCsvIfcFile,
|
||||
operator.ImportCsvAttributes,
|
||||
operator.ExportCsvAttributes,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportCsvAttributes,
|
||||
operator.ImportIfcCsv,
|
||||
operator.RemoveAllCsvAttributes,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.ReorderCsvAttribute,
|
||||
operator.SelectCsvIfcFile,
|
||||
prop.CsvAttribute,
|
||||
prop.CsvProperties,
|
||||
ui.BIM_PT_ifccsv,
|
||||
)
|
||||
|
||||
@@ -16,16 +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 bpy
|
||||
import ifccsv
|
||||
import ifcopenshell
|
||||
import os
|
||||
import logging
|
||||
import bpy
|
||||
import json
|
||||
import webbrowser
|
||||
import ifccsv
|
||||
import logging
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.handler import purge_module_data
|
||||
from blenderbim.bim.handler import refresh_ui_data
|
||||
|
||||
|
||||
class AddCsvAttribute(bpy.types.Operator):
|
||||
@@ -61,28 +61,46 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ReorderCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.reorder_csv_attribute"
|
||||
bl_label = "Reorder CSV Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
old_index: bpy.props.IntProperty()
|
||||
new_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
old = context.scene.CsvProperties.csv_attributes[self.old_index]
|
||||
new = context.scene.CsvProperties.csv_attributes[self.new_index]
|
||||
props = ["name", "header", "sort", "group", "varies_value", "summary"]
|
||||
for prop in props:
|
||||
value = getattr(new, prop)
|
||||
setattr(new, prop, getattr(old, prop))
|
||||
setattr(old, prop, value)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.import_csv_attributes"
|
||||
bl_label = "Import CSV Template"
|
||||
bl_label = "Load CSV Settings"
|
||||
bl_description = "Import a json template for CSV export"
|
||||
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_props = context.scene.CsvProperties
|
||||
csv_json = json.load(open(self.filepath))
|
||||
|
||||
expression = csv_json.get("expression", "")
|
||||
if expression:
|
||||
csv_props.ifc_selector = expression
|
||||
|
||||
attributes = csv_json.get("attributes", [])
|
||||
if attributes:
|
||||
csv_props.csv_attributes.clear()
|
||||
for attribute in attributes:
|
||||
csv_props.csv_attributes.add().name = attribute
|
||||
props = context.scene.CsvProperties
|
||||
data = json.load(open(self.filepath))
|
||||
tool.Search.import_filter_query(data["query"], props.filter_groups)
|
||||
|
||||
props.csv_attributes.clear()
|
||||
for attribute in data["attributes"]:
|
||||
new = props.csv_attributes.add()
|
||||
new.name = attribute["name"]
|
||||
new.header = attribute["header"]
|
||||
new.sort = attribute["sort"]
|
||||
new.group = attribute["group"]
|
||||
new.summary = attribute["summary"]
|
||||
new.formatting = attribute["formatting"]
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -92,7 +110,7 @@ class ImportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.export_csv_attributes"
|
||||
bl_label = "Export CSV Template"
|
||||
bl_label = "Save CSV Settings"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save a json template for CSV export"
|
||||
filename_ext = ".json"
|
||||
@@ -100,22 +118,25 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_props = context.scene.CsvProperties
|
||||
props = context.scene.CsvProperties
|
||||
|
||||
csv_template = {}
|
||||
expression = csv_props.ifc_selector
|
||||
if expression:
|
||||
csv_template["expression"] = expression
|
||||
|
||||
csv_attributes = []
|
||||
for attribute in csv_props.csv_attributes:
|
||||
attribute_name = attribute.name
|
||||
csv_attributes.append(attribute_name)
|
||||
if csv_attributes:
|
||||
csv_template["attributes"] = csv_attributes
|
||||
data = {
|
||||
"query": tool.Search.export_filter_query(props.filter_groups),
|
||||
"attributes": [
|
||||
{
|
||||
"name": a.name,
|
||||
"header": a.header,
|
||||
"sort": a.sort,
|
||||
"group": a.group,
|
||||
"summary": a.summary,
|
||||
"formatting": a.formatting,
|
||||
}
|
||||
for a in props.csv_attributes
|
||||
],
|
||||
}
|
||||
|
||||
with open(self.filepath, "w") as outfile:
|
||||
json.dump(csv_template, outfile)
|
||||
json.dump(data, outfile)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -129,9 +150,8 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC"
|
||||
#filename_ext = ".csv"
|
||||
filename_ext = ".csv"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.CsvProperties
|
||||
@@ -149,19 +169,56 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
ifc_file = IfcStore.get_file()
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
results = ifcopenshell.util.selector.filter_elements(
|
||||
ifc_file, tool.Search.export_filter_query(props.filter_groups)
|
||||
)
|
||||
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
headers = [a.header for a in props.csv_attributes]
|
||||
|
||||
sort = []
|
||||
groups = []
|
||||
summaries = []
|
||||
formatting = []
|
||||
for attribute in props.csv_attributes:
|
||||
if attribute.sort != "NONE":
|
||||
sort.append({"name": attribute.name, "order": attribute.sort})
|
||||
if attribute.group != "NONE":
|
||||
groups.append({"name": attribute.name, "type": attribute.group, "varies_value": attribute.varies_value})
|
||||
if attribute.summary != "NONE":
|
||||
summaries.append({"name": attribute.name, "type": attribute.summary})
|
||||
|
||||
if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting:
|
||||
formatting.append({"name": attribute.name, "format": attribute.formatting})
|
||||
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
|
||||
ifc_csv.export(
|
||||
ifc_file,
|
||||
results,
|
||||
attributes,
|
||||
headers=headers,
|
||||
output=self.filepath,
|
||||
format=props.format,
|
||||
should_preserve_existing=props.should_preserve_existing,
|
||||
delimiter=sep,
|
||||
include_global_id=props.include_global_id,
|
||||
null=props.null_value,
|
||||
empty=props.empty_value,
|
||||
bool_true=props.true_value,
|
||||
bool_false=props.false_value,
|
||||
sort=sort,
|
||||
groups=groups,
|
||||
summaries=summaries,
|
||||
formatting=formatting,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.import_ifccsv"
|
||||
bl_label = "Import CSV to IFC"
|
||||
filename_ext = ".csv"
|
||||
bl_label = "Import to IFC"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv;*.ods;*.xlsx", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -180,25 +237,20 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
ifc_csv.Import(
|
||||
ifc_file,
|
||||
self.filepath,
|
||||
attributes=attributes,
|
||||
delimiter=sep,
|
||||
null=props.null_value,
|
||||
empty=props.empty_value,
|
||||
bool_true=props.true_value,
|
||||
bool_false=props.false_value,
|
||||
)
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EyedropIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.eyedrop_ifccsv"
|
||||
bl_label = "Query Selected Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
global_ids = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in context.selected_objects:
|
||||
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
|
||||
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
|
||||
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
|
||||
refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.search.prop import BIMFilterGroup
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -31,10 +32,46 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
class CsvAttribute(PropertyGroup):
|
||||
name: StringProperty(name="Query", default="class")
|
||||
header: StringProperty(name="Header Value", default="IFC Class")
|
||||
sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")])
|
||||
group: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "Don't group any rows"),
|
||||
("GROUP", "Group", "All rows where this value is identical will be merged"),
|
||||
("CONCAT", "Concatenation", "Concatenate values if values vary within a group"),
|
||||
("VARIES", "Varies", "Show a custom value if values vary within a group"),
|
||||
("SUM", "Sum", "Sums the total value of rows in a group"),
|
||||
("AVERAGE", "Average", "Averages the total value of rows in a group"),
|
||||
("MIN", "Min", "Gets the minimum value of rows in a group"),
|
||||
("MAX", "Max", "Gets the maximum value of rows in a group"),
|
||||
]
|
||||
)
|
||||
varies_value: StringProperty(default="Varies", name="Varies Value")
|
||||
summary: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "Don't provide a summary row"),
|
||||
("SUM", "Sum", "Sums the total value of all rows"),
|
||||
("AVERAGE", "Average", "Averages the total value of all rows"),
|
||||
("MIN", "Min", "Gets the minimum value of all rows"),
|
||||
("MAX", "Max", "Gets the maximum value of all rows"),
|
||||
]
|
||||
)
|
||||
formatting: StringProperty(default="{{value}}", name="Formatting")
|
||||
|
||||
|
||||
class CsvProperties(PropertyGroup):
|
||||
csv_ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_selector: StringProperty(default="", name="IFC Selector")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute)
|
||||
should_preserve_existing: BoolProperty(default=False, name="Preserve Existing")
|
||||
include_global_id: BoolProperty(default=True, name="Include GlobalId")
|
||||
null_value: StringProperty(default="N/A", name="Null Value")
|
||||
empty_value: StringProperty(default="-", name="Empty String Value")
|
||||
true_value: StringProperty(default="YES", name="True Value")
|
||||
false_value: StringProperty(default="NO", name="False Value")
|
||||
csv_delimiter: EnumProperty(
|
||||
items=[
|
||||
(";", ";", ""),
|
||||
@@ -55,4 +92,9 @@ class CsvProperties(PropertyGroup):
|
||||
default="csv",
|
||||
)
|
||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
||||
should_show_sort: BoolProperty(default=False, name="Show Sorting")
|
||||
should_show_group: BoolProperty(default=False, name="Show Grouping")
|
||||
should_show_summary: BoolProperty(default=False, name="Show Summary")
|
||||
should_show_formatting: BoolProperty(default=False, name="Show Formatting")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
# 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 blenderbim.bim.module.search.data import SearchData
|
||||
|
||||
|
||||
class BIM_PT_ifccsv(Panel):
|
||||
bl_label = "CSV Import/Export"
|
||||
bl_label = "Spreadsheet Import/Export"
|
||||
bl_idname = "BIM_PT_ifccsv"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -36,45 +38,84 @@ class BIM_PT_ifccsv(Panel):
|
||||
props = scene.CsvProperties
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = layout.row()
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "should_load_from_memory")
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_ifc_file")
|
||||
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_selector")
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
layout.separator()
|
||||
if props.should_show_settings:
|
||||
layout.use_property_split = True
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "format")
|
||||
|
||||
if props.format == "csv":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_preserve_existing")
|
||||
row = layout.row()
|
||||
row.prop(props, "include_global_id")
|
||||
row = layout.row()
|
||||
row.prop(props, "null_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "empty_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "true_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "false_value")
|
||||
layout.use_property_split = False
|
||||
|
||||
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_csv_attribute", icon="ADD")
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT")
|
||||
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
|
||||
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
|
||||
row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="")
|
||||
row.prop(props, "should_show_formatting", icon="CON_TRANSLIKE", text="")
|
||||
|
||||
total = len(props.csv_attributes)
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.prop(attribute, "header", text="")
|
||||
if props.should_show_sort:
|
||||
row.prop(attribute, "sort", text="")
|
||||
if props.should_show_group:
|
||||
row.prop(attribute, "group", text="")
|
||||
if attribute.group == "VARIES":
|
||||
row.prop(attribute, "varies_value", text="")
|
||||
if props.should_show_summary:
|
||||
row.prop(attribute, "summary", text="")
|
||||
if props.should_show_formatting:
|
||||
row.prop(attribute, "formatting", text="")
|
||||
if total > 1:
|
||||
if index != 0:
|
||||
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_UP", text="")
|
||||
op.old_index = index
|
||||
op.new_index = index - 1
|
||||
if index + 1 != total:
|
||||
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_DOWN", text="")
|
||||
op.old_index = index
|
||||
op.new_index = index + 1
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "format")
|
||||
|
||||
if props.format == 'csv':
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.5)
|
||||
c = split.column()
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
|
||||
c = split.column()
|
||||
c.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
row.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
|
||||
row.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
|
||||
@@ -20,23 +20,26 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ConvertToBlender,
|
||||
operator.CopyDebugInformation,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.InspectFromObject,
|
||||
operator.InspectFromStepId,
|
||||
operator.OverrideDisplayType,
|
||||
operator.ParseExpress,
|
||||
operator.PrintIfcFile,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.ConvertToBlender,
|
||||
operator.RewindInspector,
|
||||
operator.SelectExpressFile,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.SelectHighestPolygonMeshes,
|
||||
operator.ValidateIfcFile,
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
prop.BIMDebugProperties,
|
||||
ui.BIM_PT_debug,
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ class PurgeIfcLinks(bpy.types.Operator):
|
||||
context.scene.BIMProperties.ifc_file = ""
|
||||
context.scene.BIMDebugProperties.attributes.clear()
|
||||
IfcStore.purge()
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class ConvertToBlender(bpy.types.Operator):
|
||||
m.BIMMaterialProperties.ifc_style_id = False
|
||||
bpy.context.scene.BIMProperties.ifc_file = ""
|
||||
IfcStore.purge()
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -154,6 +154,8 @@ class ValidateIfcFile(bpy.types.Operator):
|
||||
logger = logging.getLogger("validate")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ifcopenshell.validate.validate(IfcStore.get_file(), logger, express_rules=True)
|
||||
|
||||
self.report({"INFO"}, "Check validation results in the system console.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -441,3 +443,55 @@ class PurgeHdf5Cache(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
core.purge_hdf5_cache(tool.Debug)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideDisplayType(bpy.types.Operator):
|
||||
bl_idname = "bim.override_display_type"
|
||||
bl_label = "Override Display Type"
|
||||
display: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
obj.display_type = self.display
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PrintUnusedElementStats(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.print_unused_elements_stats"
|
||||
bl_label = "Purge Unused Elements Stats"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = (
|
||||
"Print all unused elements in current IFC project in system console, not limited to the selected class"
|
||||
)
|
||||
|
||||
ignore_contexts: bpy.props.BoolProperty(name="Ignore Contexts", default=True)
|
||||
ignore_relationships: bpy.props.BoolProperty(name="Ignore Relationships", default=True)
|
||||
ignore_types: bpy.props.BoolProperty(name="Ignore Types", default=True)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDebugProperties
|
||||
# ignore some classes that could have zero 0 inverse references by their nature
|
||||
ignore_classes = []
|
||||
if self.ignore_contexts:
|
||||
ignore_classes += ["IfcRepresentationContext"]
|
||||
if self.ignore_relationships:
|
||||
ignore_classes += ["IfcRelationship"]
|
||||
if self.ignore_types:
|
||||
ignore_classes += ["IfcTypeProduct"]
|
||||
|
||||
unused_elements = tool.Debug.print_unused_elements_stats(props.ifc_class_purge, ignore_classes)
|
||||
self.report({"INFO"}, f"{unused_elements} unused elements found, check the system console for the details.")
|
||||
|
||||
|
||||
class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.purge_unused_elements_by_class"
|
||||
bl_label = "Purge Unused Elements By Class"
|
||||
bl_description = (
|
||||
"Will find all elements of class that have no inverse refernces and will remove them, use very carefully."
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDebugProperties
|
||||
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
|
||||
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
|
||||
|
||||
@@ -40,3 +40,14 @@ class BIMDebugProperties(PropertyGroup):
|
||||
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
|
||||
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
|
||||
express_file: StringProperty(name="Express File")
|
||||
display_type: EnumProperty(
|
||||
items=[
|
||||
("BOUNDS", "Bounds", ""),
|
||||
("WIRE", "Wire", ""),
|
||||
("SOLID", "Solid", ""),
|
||||
("TEXTURED", "Textured", ""),
|
||||
],
|
||||
name="Display Type",
|
||||
default="BOUNDS",
|
||||
)
|
||||
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
|
||||
|
||||
@@ -83,6 +83,10 @@ class BIM_PT_debug(Panel):
|
||||
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
|
||||
row.prop(props, "percentile_of_polygons", text="")
|
||||
|
||||
row = layout.split(factor=0.5, align=True)
|
||||
row.prop(props, "display_type", text="")
|
||||
row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type
|
||||
|
||||
if context.active_object and context.active_object.data:
|
||||
mprops = context.active_object.data.BIMMeshProperties
|
||||
row = layout.row()
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifccsv
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class SelectDiffJsonFile(bpy.types.Operator):
|
||||
@@ -51,8 +52,27 @@ class VisualiseDiff(bpy.types.Operator):
|
||||
diff = json.load(file)
|
||||
for obj in context.visible_objects:
|
||||
obj.color = (1.0, 1.0, 1.0, 1.0)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
|
||||
ifc_file = ""
|
||||
for scene in obj.users_scene:
|
||||
if scene.BIMProperties.ifc_file:
|
||||
ifc_file = scene.BIMProperties.ifc_file
|
||||
if scene.library:
|
||||
break
|
||||
|
||||
if ifc_file:
|
||||
if ifc_file not in IfcStore.session_files:
|
||||
IfcStore.session_files[ifc_file] = ifcopenshell.open(ifc_file)
|
||||
element_file = IfcStore.session_files[ifc_file]
|
||||
else:
|
||||
element_file = ifc_file
|
||||
|
||||
try:
|
||||
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
continue
|
||||
global_id = getattr(element, "GlobalId", None)
|
||||
if not global_id:
|
||||
|
||||
@@ -92,10 +92,10 @@ classes = (
|
||||
prop.BIMTextProperties,
|
||||
prop.BIMAssignedProductProperties,
|
||||
prop.BIMAnnotationProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_references,
|
||||
ui.BIM_PT_product_assignments,
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
# 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 os
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.element
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class Annotator:
|
||||
@@ -90,6 +92,15 @@ class Annotator:
|
||||
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_vertex_to_annotation(obj):
|
||||
verts_world_space = Annotator.get_placeholder_coords()
|
||||
vert_local = obj.matrix_world.inverted() @ verts_world_space[0]
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
bm.verts.new(vert_local)
|
||||
tool.Blender.apply_bmesh(obj.data, bm, obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj, remove_face=False):
|
||||
# default order = bot left, top left, bot right, top right
|
||||
@@ -111,7 +122,7 @@ class Annotator:
|
||||
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 = tool.Drawing.get_camera_matrix(camera)
|
||||
matrix_world.translation = co1
|
||||
collection = camera.BIMObjectProperties.collection
|
||||
|
||||
@@ -142,6 +153,8 @@ class Annotator:
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
elif data_type == "empty":
|
||||
data = None
|
||||
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
@@ -154,6 +167,13 @@ class Annotator:
|
||||
camera = bpy.context.scene.camera
|
||||
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
|
||||
if (
|
||||
ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(camera), "EPset_Drawing", "TargetView")
|
||||
== "REFLECTED_PLAN_VIEW"
|
||||
):
|
||||
z_offset *= -1
|
||||
|
||||
y = camera.data.ortho_scale / 4
|
||||
|
||||
res_x = bpy.context.scene.render.resolution_x
|
||||
@@ -166,6 +186,7 @@ class Annotator:
|
||||
|
||||
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
center.z = 0
|
||||
center = camera.matrix_world @ center
|
||||
|
||||
return (
|
||||
center + z_offset,
|
||||
|
||||
@@ -123,6 +123,8 @@ class DrawingsData:
|
||||
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
|
||||
)
|
||||
return results
|
||||
elif bpy.context.scene.DocProperties.target_view in ["MODEL_VIEW"]:
|
||||
return [(h.upper(), h, "") for h in ["Orthographic", "Perspective"]]
|
||||
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -52,23 +52,23 @@ class profile_consequential:
|
||||
cls.test_name = test_name
|
||||
|
||||
@classmethod
|
||||
def log(cls):
|
||||
def log(cls, args=[]):
|
||||
if cls.start_time is not None:
|
||||
cls.lines.append(f"{cls.test_name}\t{timer() - cls.start_time:.10f}")
|
||||
args = "" if not args else "\t" + "\t".join(args)
|
||||
cls.lines.append(f"{cls.test_name}\t{timer() - cls.start_time:.10f}{args}")
|
||||
|
||||
@classmethod
|
||||
def stop(cls):
|
||||
cls.log()
|
||||
cls.start_time = None
|
||||
print("\n".join(cls.lines))
|
||||
lines = "\n".join(cls.lines)
|
||||
print(lines)
|
||||
import pyperclip
|
||||
|
||||
pyperclip.copy(lines)
|
||||
cls.lines = []
|
||||
|
||||
|
||||
def ccw(A, B, C):
|
||||
"""whether a-b-c located in counter-clockwise order in 2d space"""
|
||||
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
|
||||
|
||||
|
||||
def worldspace_to_winspace(verts, context):
|
||||
"""Convert world space verts to window space"""
|
||||
region = context.region
|
||||
@@ -248,8 +248,13 @@ class BaseDecorator:
|
||||
if check_mode and obj.data.is_editmode:
|
||||
return self.get_editmesh_geom(obj)
|
||||
|
||||
vertices = [obj.matrix_world @ v.co for v in obj.data.vertices]
|
||||
indices = [e.vertices for e in obj.data.edges]
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
vertices = [obj.matrix_world @ v.co for v in bm.verts]
|
||||
# In object mode, it's nicer to not show "internal edges". Most will be dissolved anyway.
|
||||
indices = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) != 2]
|
||||
bm.free()
|
||||
|
||||
return vertices, indices
|
||||
|
||||
def get_editmesh_geom(self, obj):
|
||||
@@ -520,7 +525,10 @@ class BaseDecorator:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
text_dir = (camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(camera)
|
||||
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, text_world_position)
|
||||
props = obj.BIMTextProperties
|
||||
@@ -742,7 +750,7 @@ class AngleDecorator(BaseDecorator):
|
||||
except ZeroDivisionError:
|
||||
continue
|
||||
circle_angle = acos(cos_a)
|
||||
counter_clockwise = ccw(v2, v1, v0)
|
||||
counter_clockwise = tool.Cad.is_counter_clockwise_order(v2, v1, v0)
|
||||
angle_circle = get_angle_circle(circle_start, circle_angle, counter_clockwise)
|
||||
add_verts_sequence([v1 + v for v in angle_circle], start_i_arcs, **out_kwargs_arcs)
|
||||
|
||||
@@ -774,7 +782,7 @@ class AngleDecorator(BaseDecorator):
|
||||
region3d = context.region_data
|
||||
|
||||
viewportDrawingScale = self.get_viewport_drawing_scale(context)
|
||||
ANGLE_LABEL_OFFSET = 25 * viewportDrawingScale
|
||||
ANGLE_LABEL_OFFSET = 20 * viewportDrawingScale
|
||||
last_segment_i = len(indices) - 1
|
||||
|
||||
for edge_i, edge_vertices in enumerate(indices):
|
||||
@@ -792,24 +800,24 @@ class AngleDecorator(BaseDecorator):
|
||||
edge1_ws = v2 - v1
|
||||
try:
|
||||
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
|
||||
angle_rad = acos(cos_a)
|
||||
angle = angle_rad / pi * 180
|
||||
except ZeroDivisionError:
|
||||
continue
|
||||
circle_angle_rad = acos(cos_a)
|
||||
circle_angle = circle_angle_rad / pi * 180
|
||||
angle = 0
|
||||
|
||||
# calculate angle position
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
p2 = location_3d_to_region_2d(region, region3d, v2)
|
||||
p0, p1, p2 = [location_3d_to_region_2d(region, region3d, p) for p in vertices[i0 : i1 + 2]]
|
||||
edge0 = p0 - p1
|
||||
edge1 = p2 - p1
|
||||
base_edge = edge0 if ccw(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-circle_angle_rad / 2, 2) @ base_edge).normalized() * ANGLE_LABEL_OFFSET
|
||||
radius = min(edge0.length_squared, edge1.length_squared) ** 0.5
|
||||
# TODO: can be helpful for svg positioning
|
||||
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
|
||||
label_position = p1 + text_offset
|
||||
|
||||
text = f"{int(circle_angle)}d"
|
||||
text = f"{int(angle)}deg"
|
||||
label_dir = Vector((1, 0))
|
||||
self.draw_label(context, text, label_position, label_dir)
|
||||
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
|
||||
|
||||
|
||||
class DiameterDecorator(DimensionDecorator):
|
||||
@@ -1923,7 +1931,7 @@ class DecorationsHandler:
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
# NOTE: we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR
|
||||
# NOTE: we USE POST_PIXEL here so that we can use both 3D_POLYLINE_UNIFORM_COLOR
|
||||
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
|
||||
|
||||
@@ -1941,6 +1949,7 @@ class DecorationsHandler:
|
||||
self.decorators[object_type] = self.decorators["FALL"]
|
||||
|
||||
def get_objects_and_decorators(self, collection):
|
||||
# TODO: do it in data instead of the handler for performance?
|
||||
results = []
|
||||
|
||||
for obj in collection.all_objects:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
import blenderbim.tool as tool
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@@ -37,10 +38,28 @@ def depsgraph_update_pre_handler(scene):
|
||||
def set_active_camera_resolution(scene):
|
||||
if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings:
|
||||
return
|
||||
props = scene.camera.data.BIMCameraProperties
|
||||
ortho_scale = max((props.width, props.height))
|
||||
aspect_ratio = props.width / props.height
|
||||
if (
|
||||
scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x
|
||||
or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y
|
||||
(scene.camera.data.ortho_scale != ortho_scale)
|
||||
or (scene.render.resolution_x / scene.render.resolution_y != aspect_ratio)
|
||||
):
|
||||
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
|
||||
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
|
||||
scene.camera.data.ortho_scale = ortho_scale
|
||||
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(scene.camera)
|
||||
scale_ratio = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
|
||||
|
||||
if props.width > props.height:
|
||||
aspect_ratio = props.height / props.width
|
||||
raster_x = ortho_scale * scale_ratio * props.dpi / 0.0254
|
||||
raster_y = ortho_scale * aspect_ratio * scale_ratio * props.dpi / 0.0254
|
||||
else:
|
||||
aspect_ratio = props.width / props.height
|
||||
raster_x = ortho_scale * aspect_ratio * scale_ratio * props.dpi / 0.0254
|
||||
raster_y = ortho_scale * scale_ratio * props.dpi / 0.0254
|
||||
|
||||
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x = int(raster_x)
|
||||
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y = int(raster_y)
|
||||
|
||||
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
|
||||
|
||||
@@ -182,8 +182,12 @@ def format_distance(
|
||||
|
||||
# Check values and compose string
|
||||
if inches == 12:
|
||||
feet += 1
|
||||
inches = 0
|
||||
if unit_length == "INCHES":
|
||||
inches = 12
|
||||
if unit_length == "FEET":
|
||||
feet += 1
|
||||
inches = 0
|
||||
|
||||
|
||||
if not isArea:
|
||||
add_inches = bool(inches) or not suppress_zero_inches
|
||||
|
||||
@@ -26,7 +26,6 @@ import shutil
|
||||
import hashlib
|
||||
import shapely
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
@@ -70,15 +69,6 @@ class profile:
|
||||
print(self.task, timer() - self.start)
|
||||
|
||||
|
||||
def open_with_user_command(user_command, path):
|
||||
if user_command:
|
||||
commands = eval(user_command)
|
||||
for command in commands:
|
||||
subprocess.Popen(command)
|
||||
else:
|
||||
webbrowser.open("file://" + path)
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -269,7 +259,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
with profile("Combine SVG layers"):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
|
||||
@@ -485,7 +475,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc)
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
tree = ifcopenshell.geom.tree()
|
||||
@@ -555,7 +545,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces:
|
||||
# shapely variant
|
||||
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
nm = group.attrib["{http://www.ifcopenshell.org/ns}name"]
|
||||
m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"]))
|
||||
m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"]))
|
||||
@@ -566,10 +556,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
m44[1][3] = m3[1][2]
|
||||
m44 = np.linalg.inv(m44)
|
||||
|
||||
projections = group.findall('.//{http://www.w3.org/2000/svg}g[@class="projection"]') or []
|
||||
elements_with_faces = set()
|
||||
for element in drawing_elements.copy():
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj and obj.type == "MESH" and len(obj.data.polygons):
|
||||
elements_with_faces.add(element.GlobalId)
|
||||
|
||||
projections = root.xpath(
|
||||
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
|
||||
)
|
||||
|
||||
boundary_lines = []
|
||||
for projection in projections:
|
||||
boundary_lines = []
|
||||
global_id = projection.attrib["{http://www.ifcopenshell.org/ns}guid"]
|
||||
if global_id not in elements_with_faces:
|
||||
continue
|
||||
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
|
||||
# Rounding is necessary to ensure coincident points are coincident
|
||||
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
@@ -578,50 +579,55 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
|
||||
start, end = tool.Drawing.extend_line(start, end, 0.5)
|
||||
boundary_lines.append(shapely.LineString([start, end]))
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
for polygon in closed_polygons.geoms:
|
||||
# Less than 1mm2 is not worth styling on sheet
|
||||
if polygon.area < 1:
|
||||
continue
|
||||
centroid = polygon.centroid
|
||||
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
|
||||
if internal_point:
|
||||
internal_point = [internal_point.x, internal_point.y]
|
||||
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
|
||||
m44, m4, internal_point, -100.0
|
||||
)
|
||||
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
|
||||
if not inside_elements:
|
||||
elements = [
|
||||
e
|
||||
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||
if not e.instance.is_a("IfcAnnotation")
|
||||
and tool.Cad.is_point_on_edge(
|
||||
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
|
||||
)
|
||||
]
|
||||
if elements:
|
||||
path = etree.Element("path")
|
||||
d = (
|
||||
"M"
|
||||
+ " L".join(
|
||||
[",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]
|
||||
)
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
for polygon in closed_polygons.geoms:
|
||||
# Less than 1mm2 is not worth styling on sheet
|
||||
if polygon.area < 1:
|
||||
continue
|
||||
centroid = polygon.centroid
|
||||
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
|
||||
if internal_point:
|
||||
internal_point = [internal_point.x, internal_point.y]
|
||||
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
|
||||
m44, m4, internal_point, -100.0
|
||||
)
|
||||
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
|
||||
if not inside_elements:
|
||||
elements = [
|
||||
e
|
||||
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||
if not e.instance.is_a("IfcAnnotation")
|
||||
and tool.Cad.is_point_on_edge(
|
||||
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
|
||||
)
|
||||
]
|
||||
if elements:
|
||||
path = etree.Element("path")
|
||||
d = (
|
||||
"M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
for interior in polygon.interiors:
|
||||
d += (
|
||||
" M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
for interior in polygon.interiors:
|
||||
d += (
|
||||
" M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
path.attrib["d"] = d
|
||||
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
||||
classes.append("surface")
|
||||
path.set("class", " ".join(list(classes)))
|
||||
group.insert(0, path)
|
||||
path.attrib["d"] = d
|
||||
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
||||
classes.append(f"intpoint-{internal_point}")
|
||||
classes.append(f"ab-{a}, {b}")
|
||||
for i, ray_result in enumerate(elements):
|
||||
classes.append(f"el{i}-{ray_result.instance.id()}")
|
||||
classes.append(f"el{i}-pos-{list(ray_result.position)}")
|
||||
classes.append(f"el{i}-dst-{ray_result.distance}")
|
||||
classes.append("surface")
|
||||
path.set("class", " ".join(list(classes)))
|
||||
group.insert(0, path)
|
||||
|
||||
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
|
||||
results = etree.tostring(root).decode("utf8")
|
||||
@@ -636,7 +642,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
dom1 = parseString(svg_data_1)
|
||||
svg1 = dom1.childNodes[0]
|
||||
groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"]
|
||||
groups1 = [g for g in yield_groups(svg1) if "projection" in g.getAttribute("class")]
|
||||
|
||||
ls_groups = ifcopenshell.ifcopenshell_wrapper.svg_to_line_segments(results, "projection")
|
||||
|
||||
@@ -829,7 +835,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def setup_serialiser(self, ifc):
|
||||
def setup_serialiser(self, ifc, target_view):
|
||||
self.svg_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
@@ -840,7 +846,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setPolygonal(True)
|
||||
self.serialiser.setUseHlrPoly(True)
|
||||
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
|
||||
self.serialiser.setProfileThreshold(1000)
|
||||
self.serialiser.setProfileThreshold(10000)
|
||||
self.serialiser.setUseNamespace(True)
|
||||
self.serialiser.setAlwaysProject(True)
|
||||
self.serialiser.setAutoElevation(False)
|
||||
@@ -853,6 +859,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
self.serialiser.setUsePrefiltering(True) # See #3359
|
||||
self.serialiser.setUnifyInputs(True)
|
||||
self.serialiser.setSegmentProjection(True)
|
||||
if target_view == "REFLECTED_PLAN_VIEW":
|
||||
self.serialiser.setMirrorY(True)
|
||||
# tree = ifcopenshell.geom.tree()
|
||||
# This instructs the tree to explode BReps into faces and return
|
||||
# the style of the face when running tree.select_ray()
|
||||
@@ -887,6 +897,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
)
|
||||
return classes
|
||||
|
||||
def is_manifold(self, obj):
|
||||
result = self.is_manifold_cache.get(obj.data.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
for edge in bm.edges:
|
||||
if not edge.is_manifold:
|
||||
bm.free()
|
||||
self.is_manifold_cache[obj.data.name] = False
|
||||
return False
|
||||
self.is_manifold_cache[obj.data.name] = True
|
||||
return True
|
||||
|
||||
def merge_linework_and_add_metadata(self, root):
|
||||
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
|
||||
if join_criteria:
|
||||
@@ -895,16 +920,27 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Drawing convention states that same objects classes with the same material are merged when cut.
|
||||
join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"']
|
||||
|
||||
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
joined_paths = {}
|
||||
self.is_manifold_cache = {}
|
||||
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("cut")
|
||||
el.set("class", " ".join(classes))
|
||||
if "projection" in el.get("class", "").split():
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("projection")
|
||||
el.set("class", " ".join(classes))
|
||||
continue
|
||||
else:
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("cut")
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not self.is_manifold(obj):
|
||||
continue
|
||||
|
||||
# An element group will contain a bunch of paths representing the
|
||||
# cut of that element. However IfcOpenShell may not correctly
|
||||
@@ -1022,9 +1058,12 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# IfcConvert puts the projection afterwards which is not correct since
|
||||
# projection should be drawn underneath the cut.
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']")
|
||||
projection.getparent().remove(projection)
|
||||
group.insert(0, projection)
|
||||
projections = root.xpath(
|
||||
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
|
||||
)
|
||||
for projection in projections:
|
||||
projection.getparent().remove(projection)
|
||||
group.insert(0, projection)
|
||||
|
||||
def generate_annotation(self, context):
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):
|
||||
@@ -1035,7 +1074,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
|
||||
filtered_drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
elements = [e for e in elements if e in filtered_drawing_elements]
|
||||
filtered_drawing_annotations = {e for e in filtered_drawing_elements if e.is_a("IfcAnnotation")}
|
||||
elements = {e for e in elements if e in filtered_drawing_elements}
|
||||
elements = list(elements | filtered_drawing_annotations)
|
||||
|
||||
annotations = sorted(
|
||||
elements, key=lambda a: (tool.Drawing.get_annotation_z_index(a), 1 if a.ObjectType == "TEXT" else 0)
|
||||
@@ -1236,11 +1277,15 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
|
||||
# These variables will be made available to the evaluated commands
|
||||
svg = references["SHEET"]
|
||||
basename = os.path.basename(svg)
|
||||
path = os.path.dirname(svg)
|
||||
pdf = os.path.splitext(svg)[0] + ".pdf"
|
||||
eps = os.path.splitext(svg)[0] + ".eps"
|
||||
dxf = os.path.splitext(svg)[0] + ".dxf"
|
||||
replacements = {
|
||||
"svg": svg,
|
||||
"basename": os.path.basename(svg),
|
||||
"path": os.path.dirname(svg),
|
||||
"pdf": pdf,
|
||||
"eps": os.path.splitext(svg)[0] + ".eps",
|
||||
"dxf": os.path.splitext(svg)[0] + ".dxf",
|
||||
}
|
||||
|
||||
has_sheet_reference = False
|
||||
for reference in tool.Drawing.get_document_references(sheet):
|
||||
@@ -1273,22 +1318,23 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
|
||||
if svg2pdf_command:
|
||||
# With great power comes great responsibility. Example:
|
||||
# [['inkscape', svg, '-o', pdf]]
|
||||
commands = eval(svg2pdf_command)
|
||||
# [["inkscape", "svg", "-o", "pdf"]]
|
||||
commands = json.loads(svg2pdf_command)
|
||||
for command in commands:
|
||||
subprocess.run(command)
|
||||
subprocess.run([replacements.get(c, c) for c in command])
|
||||
|
||||
if svg2dxf_command:
|
||||
# With great power comes great responsibility. Example:
|
||||
# [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]
|
||||
commands = eval(svg2dxf_command)
|
||||
# [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]
|
||||
commands = json.loads(svg2dxf_command)
|
||||
for command in commands:
|
||||
subprocess.run(command)
|
||||
command[0] = shutil.which(command[0]) or command[0]
|
||||
subprocess.run([replacements.get(c, c) for c in command])
|
||||
|
||||
if svg2pdf_command:
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
|
||||
else:
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
|
||||
|
||||
|
||||
class SelectAllDrawings(bpy.types.Operator):
|
||||
@@ -1355,7 +1401,9 @@ class OpenDrawing(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
for drawing_uri in drawing_uris:
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri)
|
||||
tool.Drawing.open_with_user_command(
|
||||
context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1391,6 +1439,7 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
if view3d_context:
|
||||
bpy.ops.object.hide_view_clear(view3d_context)
|
||||
bpy.ops.bim.activate_status_filters()
|
||||
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@@ -1512,11 +1561,17 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
|
||||
]
|
||||
else:
|
||||
if not self.drawing:
|
||||
self.report({"ERROR"}, "No drawing selected")
|
||||
return {"CANCELLED"}
|
||||
drawings = [tool.Ifc.get().by_id(self.drawing)]
|
||||
|
||||
removed_drawings = [drawing.id() for drawing in drawings]
|
||||
|
||||
for drawing in drawings:
|
||||
sheet_references = tool.Drawing.get_sheet_references(drawing)
|
||||
for reference in sheet_references:
|
||||
bpy.ops.bim.remove_drawing_from_sheet(reference=reference.id())
|
||||
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
@@ -1862,7 +1917,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", document=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -312,7 +312,6 @@ class DocProperties(PropertyGroup):
|
||||
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)
|
||||
is_editing_references: BoolProperty(name="Is Editing References", default=False)
|
||||
@@ -375,6 +374,9 @@ class BIMCameraProperties(PropertyGroup):
|
||||
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
dpi: IntProperty(name="DPI", default=75)
|
||||
width: FloatProperty(name="Width", default=50, subtype="DISTANCE")
|
||||
height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
|
||||
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
|
||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||
|
||||
@@ -502,6 +504,8 @@ ANNOTATION_TYPES_DATA = {
|
||||
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
|
||||
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
|
||||
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
|
||||
"SYMBOL": ("Symbol", "", "KEYFRAME", "empty"),
|
||||
"MULTI_SYMBOL": ("Multi-Symbol", "", "OUTLINER_DATA_POINTCLOUD", "mesh"),
|
||||
"LINEWORK": ("Line", "", "SNAP_MIDPOINT", "mesh"),
|
||||
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
|
||||
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
|
||||
@@ -538,7 +542,7 @@ class BIMAnnotationProperties(PropertyGroup):
|
||||
name="Create Representation For Type",
|
||||
default=False,
|
||||
description='Whether "Add type" should define a representation for the type \n'
|
||||
"or allow occurences to have their own",
|
||||
"or allow occurrences to have their own",
|
||||
)
|
||||
is_adding_type: bpy.props.BoolProperty(default=False)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
|
||||
@@ -118,6 +118,25 @@ class Scheduler:
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
# sometimes there are no column styles (e.g. in ODS from IfcCSV)
|
||||
# and we just use some constant number for column widths
|
||||
if not column_widths:
|
||||
row_columns = []
|
||||
for tr in table.getElementsByType(TableRow):
|
||||
row_cols = 0
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
column_span = td.getAttribute("numbercolumnsspanned")
|
||||
column_span = int(column_span) if column_span else 1
|
||||
|
||||
col_repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
row_cols += column_span * col_repeat
|
||||
row_columns.append(row_cols)
|
||||
|
||||
n_columns = max(row_columns)
|
||||
column_widths = [25] * n_columns # some constant width value 👀
|
||||
column_styles = [None] * n_columns
|
||||
|
||||
# collect rows height
|
||||
row_heights = []
|
||||
# TODO: never used yet because unsure about priority for row styles
|
||||
@@ -351,7 +370,7 @@ class Scheduler:
|
||||
wrap_text: if True, text will be wrapped to fit in cell
|
||||
cell_width: width of cell, used for wrapping text
|
||||
"""
|
||||
text_lines = [str(p).upper() for p in p_tags]
|
||||
text_lines = [str(p) for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": font_size,
|
||||
@@ -383,12 +402,27 @@ class Scheduler:
|
||||
else:
|
||||
wrapped_lines = text_lines
|
||||
|
||||
for line_number, text_line in enumerate(wrapped_lines[::-1]):
|
||||
if box_alignment.startswith("top"):
|
||||
dy_dir = 1
|
||||
line_number = 0
|
||||
elif box_alignment.startswith("bot"):
|
||||
dy_dir = -1
|
||||
line_number = 0
|
||||
else: # middle row
|
||||
# the idea is that the middle row should always stay in the center
|
||||
# e.g. dy offset for 3 lines is: 1, 0, -1
|
||||
# for 2 lines: 0.5, -0.5
|
||||
dy_dir = -1
|
||||
line_number = (len(wrapped_lines) - 1) / 2
|
||||
|
||||
for text_line in wrapped_lines[::dy_dir]:
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
tspan = self.svg.tspan(text_line, insert=(x, y), **text_params)
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"-{line_number}em"})
|
||||
tspan.update({"dy": f"{line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
line_number += dy_dir
|
||||
|
||||
self.svg.add(text_tag)
|
||||
|
||||
def convert_to_mm(self, value):
|
||||
|
||||
@@ -174,6 +174,8 @@ class SheetBuilder:
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
if not os.path.exists(layout_path):
|
||||
return
|
||||
layout_tree = ET.parse(layout_path)
|
||||
layout_root = layout_tree.getroot()
|
||||
|
||||
@@ -343,8 +345,12 @@ class SheetBuilder:
|
||||
def build_drawings(self, root, sheet):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
drawing_id = int(view.attrib["data-id"])
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
try:
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
except:
|
||||
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
|
||||
continue
|
||||
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
|
||||
@@ -385,8 +391,12 @@ class SheetBuilder:
|
||||
|
||||
def build_schedules(self, root, sheet):
|
||||
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"]))
|
||||
try:
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
|
||||
except:
|
||||
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
|
||||
continue
|
||||
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
|
||||
@@ -414,7 +424,7 @@ class SheetBuilder:
|
||||
view.remove(image)
|
||||
|
||||
def get_href(self, element):
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace('\\','/')
|
||||
|
||||
def parse_embedded_svg(self, image, data):
|
||||
group = ET.Element("g")
|
||||
|
||||
@@ -36,7 +36,7 @@ import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
from math import pi, ceil, atan, degrees
|
||||
from math import pi, ceil, atan, degrees, acos
|
||||
from mathutils import geometry, Vector
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
@@ -326,11 +326,12 @@ class SvgWriter:
|
||||
# We have to decide whether this should come from Blender or from IFC.
|
||||
# For the moment, for convenience of experimenting with ideas, it comes
|
||||
# from Blender. In the future, it should probably come from IFC.
|
||||
if not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
|
||||
classes = self.get_attribute_classes(obj)
|
||||
if len(obj.data.vertices) and not len(obj.data.edges):
|
||||
if obj.data is None:
|
||||
return self.draw_empty_annotation(obj, classes)
|
||||
elif not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
elif len(obj.data.vertices) and not len(obj.data.edges):
|
||||
return self.draw_point_annotation(obj, classes)
|
||||
elif len(obj.data.polygons) == 0:
|
||||
return self.draw_edge_annotation(obj, classes)
|
||||
@@ -774,6 +775,18 @@ class SvgWriter:
|
||||
"text-anchor": text_anchor,
|
||||
}
|
||||
|
||||
def add_fill_bg(self, element, copy=True):
|
||||
if copy:
|
||||
element = element.copy()
|
||||
if hasattr(element, "xml"):
|
||||
attrib = element.xml.attrib
|
||||
elif isinstance(element, ET.Element):
|
||||
attrib = element.attrib
|
||||
else: # assuming it's svgwrite.base.BaseElement
|
||||
attrib = element.attribs
|
||||
attrib["filter"] = "url(#fill-background)"
|
||||
return element
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -791,20 +804,21 @@ class SvgWriter:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
classes_str = " ".join(classes)
|
||||
fill_bg = "fill-bg" in classes
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
if not symbol:
|
||||
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
|
||||
else:
|
||||
if symbol:
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
|
||||
text_transform = symbol_transform
|
||||
|
||||
symbol_svg = self.find_xml_symbol_by_id(symbol)
|
||||
if symbol_svg:
|
||||
@@ -815,10 +829,20 @@ class SvgWriter:
|
||||
if template_text_fields:
|
||||
symbol_xml.attrib["transform"] = symbol_transform
|
||||
symbol_xml.attrib.pop("id")
|
||||
# note: zip makes sure that we iterate over the shortest list
|
||||
# NOTE: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
field.text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
field.text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element
|
||||
)
|
||||
field.attrib["class"] = classes_str
|
||||
|
||||
if fill_bg:
|
||||
symbol_copied = symbol_svg.copy()
|
||||
for text_tag in symbol_copied.xml.findall("text"):
|
||||
self.add_fill_bg(text_tag, copy=False)
|
||||
# NOTE: in case we'll later need to add fill-bg for the entire symbol:
|
||||
# self.add_fill_bg(symbol_svg, copy=False)
|
||||
self.svg.add(symbol_copied)
|
||||
self.svg.add(symbol_svg)
|
||||
return None
|
||||
|
||||
@@ -827,20 +851,40 @@ class SvgWriter:
|
||||
|
||||
line_number = 0
|
||||
for text_literal in text_literals:
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
angle,
|
||||
text_literal.BoxAlignment,
|
||||
classes_str,
|
||||
fill_bg="fill-bg" in classes,
|
||||
fill_bg=fill_bg,
|
||||
line_number_start=line_number,
|
||||
)
|
||||
for tag in text_tags:
|
||||
self.svg.add(tag)
|
||||
line_number += len(tag.elements)
|
||||
|
||||
def draw_empty_annotation(self, obj, classes):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
point = self.project_point_onto_camera(obj.matrix_world.translation)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
return
|
||||
|
||||
point = Vector(((x_offset + point.x), (y_offset - point.y)))
|
||||
symbol_position_svg = point * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
|
||||
def draw_point_annotation(self, obj, classes):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -849,12 +893,14 @@ class SvgWriter:
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if point_type:
|
||||
svg_id += f"-{point_type}"
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
return
|
||||
|
||||
for symbol_position in projected_points:
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
@@ -936,54 +982,54 @@ class SvgWriter:
|
||||
|
||||
def draw_angle_annotations(self, obj):
|
||||
points = obj.data.splines[0].points
|
||||
region = bpy.context.region
|
||||
area = tool.Blender.get_viewport_context()["area"]
|
||||
region_3d = area.spaces.active.region_3d
|
||||
# [1, 2, 3, 4, 5] -> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
|
||||
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
|
||||
|
||||
for points_chunk in points_chunked:
|
||||
points_2d = [view3d_utils.location_3d_to_region_2d(region, region_3d, p.co.xyz) for p in points_chunk]
|
||||
points_chunk = [obj.matrix_world @ p.co.xyz for p in points_chunk]
|
||||
self.draw_svg_3point_arc(obj, points_chunk)
|
||||
|
||||
edge0 = points_2d[0] - points_2d[1]
|
||||
edge1 = points_2d[2] - points_2d[1]
|
||||
angle_radius = min(edge0.length, edge1.length)
|
||||
dir0 = edge0.normalized()
|
||||
dir1 = edge1.normalized()
|
||||
dir2 = ((dir0 + dir1) / 2).normalized()
|
||||
|
||||
# calculate p3 which is the center of the arc
|
||||
# to use draw_svg_3point_arc()
|
||||
p3 = points_2d[1] + dir2 * angle_radius
|
||||
|
||||
# make all edges the same radius
|
||||
p0 = points_2d[1] + dir0 * angle_radius
|
||||
p2 = points_2d[1] + dir1 * angle_radius
|
||||
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
|
||||
# points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
new_verts = [bm.verts.new(p) for p in points_chunk]
|
||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in ((0, 1), (1, 2))]
|
||||
self.draw_svg_3point_arc(obj, bm)
|
||||
|
||||
def draw_svg_3point_arc(self, obj, bm):
|
||||
def draw_svg_3point_arc(self, obj, angle_points):
|
||||
"""`angle_points` are expected to be already in world space"""
|
||||
# This implementation uses an SVG arc, which means that it can only draw
|
||||
# arcs that are orthogonal to the view (e.g. not arcs in 3D).
|
||||
# Gosh this is bad code :(
|
||||
|
||||
points = [v.co for v in bm.verts][:3]
|
||||
center = tool.Cad.get_center_of_arc(points, obj)
|
||||
classes = self.get_attribute_classes(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
bm.verts.ensure_lookup_table()
|
||||
arc_end_verts = [v for v in bm.verts if len(v.link_edges) == 1]
|
||||
arc_end_pts = [matrix_world @ v.co for v in arc_end_verts]
|
||||
def position_on_svg(p):
|
||||
p = self.project_point_onto_camera(p)
|
||||
offset = Vector([self.raw_width / 2, self.raw_height / 2])
|
||||
return (offset + p.xy * Vector((1, -1))) * self.svg_scale
|
||||
|
||||
# Probably need this when rewriting to use an SVG polyline instead of an arc
|
||||
def get_angle_value():
|
||||
"""points should be in world space"""
|
||||
# calculate arc angle, need to make sure we do it in world space
|
||||
v0, v1, v2 = angle_points
|
||||
edge0_ws = v0 - v1
|
||||
edge1_ws = v2 - v1
|
||||
try:
|
||||
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
|
||||
angle_rad = acos(cos_a)
|
||||
angle = angle_rad / pi * 180
|
||||
except ZeroDivisionError:
|
||||
angle = 0
|
||||
return angle
|
||||
|
||||
angle = get_angle_value()
|
||||
angle_points = [position_on_svg(p) for p in angle_points]
|
||||
|
||||
# creating arc and making sure radius is consistent across the arc
|
||||
edge0 = angle_points[0] - angle_points[1]
|
||||
edge1 = angle_points[2] - angle_points[1]
|
||||
angle_radius = min(edge0.length, edge1.length)
|
||||
dir0 = edge0.normalized()
|
||||
dir1 = edge1.normalized()
|
||||
arc_mid_dir = ((dir0 + dir1) / 2).normalized()
|
||||
arc_points = [angle_points[1] + direction * angle_radius for direction in [dir0, arc_mid_dir, dir1]]
|
||||
|
||||
arc_end_pts = [arc_points[0], arc_points[2]]
|
||||
arc_mid_point = arc_points[1]
|
||||
|
||||
# The commented code below can be useful when we start using SVG polyline instead of an arc
|
||||
# arc_path = [arc_end_verts[0]]
|
||||
# while True:
|
||||
# last_point = arc_end_verts[0]
|
||||
@@ -996,79 +1042,44 @@ class SvgWriter:
|
||||
# if not found_another_point:
|
||||
# break
|
||||
|
||||
distance_between_end_verts = (arc_end_verts[0].co - arc_end_verts[1].co).length
|
||||
arc_mid_vert = arc_end_verts[0].link_edges[0].other_vert(arc_end_verts[0])
|
||||
is_reflex = 0 if (arc_mid_vert.co - arc_end_verts[1].co).length < distance_between_end_verts else 1
|
||||
|
||||
bm.free()
|
||||
|
||||
# Calculate the angle
|
||||
# This is the true normal in 3D, whereas the camera projection we use is the drawing direction.
|
||||
# This assumes (because we use SVG arcs) that the radius is always orthogonal to our view.
|
||||
# When rewriting to use polylines, we should use this normal instead.
|
||||
# center = tool.Cad.get_center_of_arc([p.to_3d() for p in arc_points], None).to_2d()
|
||||
# normal = mathutils.geometry.normal([arc_end_pts[0], arc_end_pts[1], center])
|
||||
normal = Vector(self.camera_projection)
|
||||
dir1 = (arc_end_pts[0] - center).normalized()
|
||||
dir2 = (arc_end_pts[1] - center).normalized()
|
||||
# normal = Vector(self.camera_projection)
|
||||
# dir1 = (arc_end_pts[0] - center).normalized()
|
||||
# arc_mid_dir = (arc_end_pts[1] - 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_pts[0] - center).normalized()
|
||||
y = z.cross(x)
|
||||
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||
# z = normal
|
||||
# x = (arc_end_pts[0] - center).normalized()
|
||||
# y = z.cross(x)
|
||||
# arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||
|
||||
dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
angle = -dir1.xy.angle_signed(dir2.xy)
|
||||
|
||||
# if is_reflex:
|
||||
# angle = angle % (math.pi * 2)
|
||||
|
||||
# Center of gravity of all vertices, used to help position the text
|
||||
cog = Vector((0, 0, 0))
|
||||
for point in points:
|
||||
cog += point
|
||||
cog = matrix_world @ (cog / len(points))
|
||||
|
||||
radius = ((matrix_world @ points[0]) - center).length
|
||||
|
||||
arc_midpoint = center + ((cog - center).normalized() * radius)
|
||||
|
||||
text_position = self.project_point_onto_camera(arc_midpoint)
|
||||
text_position = Vector(
|
||||
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
|
||||
)
|
||||
|
||||
center_projected = self.project_point_onto_camera(center)
|
||||
center_position = Vector(
|
||||
((x_offset + center_projected.x) * self.svg_scale, (y_offset - center_projected.y) * self.svg_scale)
|
||||
)
|
||||
text_offset = (text_position - center_position).xy.normalized() * 5
|
||||
text_position += text_offset
|
||||
# dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
# dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
# angle = -dir1.xy.angle_signed(dir2.xy)
|
||||
|
||||
# calculating text parameters and adding text
|
||||
text_position = arc_mid_point + arc_mid_dir * 5
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
angle_text = abs(round(math.degrees(angle), 3))
|
||||
if is_reflex:
|
||||
angle_text = 360 - angle_text
|
||||
self.svg.add(self.svg.text(f"{angle_text}deg", insert=tuple(text_position), class_="ANGLE", **text_style))
|
||||
angle_text = f"{int(angle)}deg"
|
||||
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
|
||||
|
||||
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
||||
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
|
||||
p1 = Vector(
|
||||
((x_offset + arc_proj_end_pts[0].x) * self.svg_scale, (y_offset - arc_proj_end_pts[0].y) * self.svg_scale)
|
||||
)
|
||||
p2 = Vector(
|
||||
((x_offset + arc_proj_end_pts[1].x) * self.svg_scale, (y_offset - arc_proj_end_pts[1].y) * self.svg_scale)
|
||||
)
|
||||
r = radius * self.svg_scale
|
||||
# reflex = 1 if angle > math.pi else 0
|
||||
reflex = is_reflex
|
||||
if reflex:
|
||||
sense = 0 if angle > 0 else 1
|
||||
else:
|
||||
sense = 1 if angle > 0 else 0
|
||||
p1, p2 = arc_end_pts
|
||||
r = angle_radius
|
||||
# reflex: 0 => arc < 180 degrees
|
||||
# 1 => arc > 180 degrees
|
||||
reflex = int(angle > 180)
|
||||
# sense: 0 => moving at negative angles
|
||||
# 1 => moving at positive angles
|
||||
sense = int(tool.Cad.is_counter_clockwise_order(*arc_points))
|
||||
d = f"M {p1.x} {p1.y} A {r} {r} 0 {reflex} {sense} {p2.x} {p2.y}"
|
||||
classes = self.get_attribute_classes(obj)
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def draw_radius_annotations(self, obj):
|
||||
@@ -1344,18 +1355,10 @@ class SvgWriter:
|
||||
multiline_to_bottom=True,
|
||||
fill_bg=False,
|
||||
line_number_start=0,
|
||||
_draw_fill_bg=False,
|
||||
):
|
||||
"""returns list of created text tags"""
|
||||
text_tags = []
|
||||
if fill_bg:
|
||||
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
|
||||
del method_kwargs["self"]
|
||||
del method_kwargs["text_tags"]
|
||||
text_tags += self.create_text_tag(**method_kwargs)
|
||||
|
||||
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
|
||||
|
||||
if not multiline:
|
||||
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
|
||||
@@ -1392,6 +1395,11 @@ class SvgWriter:
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
|
||||
if fill_bg:
|
||||
fill_bg_tags = [self.add_fill_bg(text_tag) for text_tag in text_tags]
|
||||
text_tags = fill_bg_tags + text_tags
|
||||
|
||||
return text_tags
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
|
||||
@@ -31,28 +31,28 @@ from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
bl_label = "Drawing Generation"
|
||||
bl_label = "Active Drawing"
|
||||
bl_idname = "BIM_PT_camera"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_drawings"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
if "/" not in context.active_object.name:
|
||||
layout.label(text="This is not a BIM camera.")
|
||||
if not (context.scene.camera and hasattr(context.scene.camera.data, "BIMCameraProperties")):
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active Drawing", icon="ERROR")
|
||||
return
|
||||
|
||||
layout.use_property_split = True
|
||||
dprops = context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
if "/" not in context.scene.camera.name:
|
||||
self.layout.label(text="This is not a BIM camera.")
|
||||
return
|
||||
|
||||
col = layout.column(align=True)
|
||||
self.layout.use_property_split = True
|
||||
dprops = context.scene.DocProperties
|
||||
props = context.scene.camera.data.BIMCameraProperties
|
||||
|
||||
col = self.layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
|
||||
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
|
||||
@@ -63,32 +63,36 @@ class BIM_PT_camera(Panel):
|
||||
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = layout.row()
|
||||
row = self.layout.row()
|
||||
row.prop(props, "calculate_shapely_surfaces")
|
||||
row = layout.row()
|
||||
row = self.layout.row()
|
||||
row.prop(props, "calculate_svgfill_surfaces")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "width")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "height")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_x")
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_y")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.camera.data, "clip_end", text="Depth")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "diagram_scale")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "diagram_scale", text="Scale")
|
||||
row.prop(props, "is_nts", text="", icon="MOD_EDGESPLIT")
|
||||
|
||||
if props.diagram_scale == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "custom_scale_numerator", text="Custom Scale")
|
||||
row.prop(props, "custom_scale_denominator", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
if props.has_underlay:
|
||||
row = self.layout.row()
|
||||
row.prop(props, "dpi")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
|
||||
op = row.operator("bim.open_drawing", icon="URL", text="")
|
||||
op.view = context.active_object.name.split("/")[1]
|
||||
op.view = context.scene.camera.name.split("/")[1]
|
||||
|
||||
|
||||
class BIM_PT_drawing_underlay(Panel):
|
||||
@@ -97,12 +101,12 @@ class BIM_PT_drawing_underlay(Panel):
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_camera"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
return context.scene.camera and hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -164,10 +168,8 @@ class BIM_PT_drawings(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
bl_parent_id = "BIM_PT_tab_drawings"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
@@ -245,10 +247,8 @@ class BIM_PT_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
bl_parent_id = "BIM_PT_tab_schedules"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not DocumentsData.is_loaded:
|
||||
@@ -297,10 +297,8 @@ class BIM_PT_references(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
bl_parent_id = "BIM_PT_tab_references"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not DocumentsData.is_loaded:
|
||||
@@ -314,7 +312,7 @@ class BIM_PT_references(Panel):
|
||||
|
||||
if not self.props.is_editing_references:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
|
||||
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN")
|
||||
row.operator("bim.load_references", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
@@ -341,10 +339,8 @@ class BIM_PT_sheets(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
bl_parent_id = "BIM_PT_tab_sheets"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not SheetsData.is_loaded:
|
||||
|
||||
+9
-8
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -20,17 +20,18 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.SelectCobieIfcFile,
|
||||
operator.SelectCobieJsonFile,
|
||||
operator.ExecuteIfcCobie,
|
||||
prop.COBieProperties,
|
||||
ui.BIM_PT_cobie,
|
||||
operator.ExecuteIfcFM,
|
||||
operator.ExecuteIfcFMFederate,
|
||||
operator.SelectFMIfcFile,
|
||||
operator.SelectFMSpreadsheetFiles,
|
||||
prop.BIMFMProperties,
|
||||
ui.BIM_PT_fm,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.COBieProperties = bpy.props.PointerProperty(type=prop.COBieProperties)
|
||||
bpy.types.Scene.BIMFMProperties = bpy.props.PointerProperty(type=prop.BIMFMProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.COBieProperties
|
||||
del bpy.types.Scene.BIMFMProperties
|
||||
@@ -0,0 +1,157 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import ifcfm
|
||||
import logging
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class SelectFMIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_fm_ifc_file"
|
||||
bl_label = "Select FM IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
props.ifc_files.clear()
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
for f in self.files:
|
||||
new = props.ifc_files.add()
|
||||
new.name = os.path.join(dirname, f.name)
|
||||
props.ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExecuteIfcFM(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifcfm"
|
||||
bl_label = "Execute IfcFM"
|
||||
file_format: bpy.props.StringProperty()
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv;*.ods;*.xlsx", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
return props.should_load_from_memory or props.ifc_file
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.BIMFMProperties
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
filepaths = []
|
||||
if not (ifc_file and props.should_load_from_memory):
|
||||
if len(props.ifc_files):
|
||||
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
|
||||
filepaths = [f.name for f in props.ifc_files]
|
||||
else:
|
||||
ifc_files = [ifcopenshell.open(props.ifc_file)]
|
||||
else:
|
||||
ifc_files = [ifc_file]
|
||||
|
||||
for i, ifc_file in enumerate(ifc_files):
|
||||
if filepaths:
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
prefix, _ = os.path.splitext(os.path.basename(filepaths[i]))
|
||||
basename = os.path.basename(self.filepath)
|
||||
filepath = os.path.join(dirname, f"{prefix}-{basename}")
|
||||
else:
|
||||
filepath = self.filepath
|
||||
parser = ifcfm.Parser(preset=props.engine)
|
||||
parser.parse(ifc_file)
|
||||
writer = ifcfm.Writer(parser)
|
||||
writer.write()
|
||||
if props.format == "csv":
|
||||
writer.write_csv("tmp/")
|
||||
elif props.format == "ods":
|
||||
writer.write_ods(filepath)
|
||||
elif props.format == "xlsx":
|
||||
writer.write_xlsx(filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectFMSpreadsheetFiles(bpy.types.Operator):
|
||||
bl_idname = "bim.select_fm_spreadsheet_files"
|
||||
bl_label = "Select FM Spreadsheet Files"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_glob: bpy.props.StringProperty(default="*.ods;*.xlsx", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
props.spreadsheet_files.clear()
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
for f in self.files:
|
||||
new = props.spreadsheet_files.add()
|
||||
new.name = os.path.join(dirname, f.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExecuteIfcFMFederate(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifcfm_federate"
|
||||
bl_label = "Execute IfcFM"
|
||||
file_format: bpy.props.StringProperty()
|
||||
filter_glob: bpy.props.StringProperty(default="*.ods;*.xlsx", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
return props.spreadsheet_files
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.BIMFMProperties
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
parser = ifcfm.Parser(preset=props.engine)
|
||||
parser.federate([f.name for f in props.spreadsheet_files])
|
||||
writer = ifcfm.Writer(parser)
|
||||
writer.write()
|
||||
if props.format == "ods":
|
||||
writer.write_ods(self.filepath)
|
||||
elif props.format == "xlsx":
|
||||
writer.write_xlsx(self.filepath)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,57 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class BIMFMProperties(PropertyGroup):
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
|
||||
spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty)
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
engine: EnumProperty(
|
||||
items=[
|
||||
("aohbsem", "AOH-BSEM", ""),
|
||||
("basic", "Basic", ""),
|
||||
("cobie24", "COBie 2.4", ""),
|
||||
("cobie3", "COBie 3", ""),
|
||||
],
|
||||
name="Engine",
|
||||
default="cobie24",
|
||||
)
|
||||
format: EnumProperty(
|
||||
items=[
|
||||
("csv", "csv", ""),
|
||||
("xlsx", "xlsx", ""),
|
||||
("ods", "ods", ""),
|
||||
],
|
||||
name="Format",
|
||||
default="ods",
|
||||
)
|
||||
+18
-17
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -21,9 +21,9 @@ from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_cobie(Panel):
|
||||
bl_label = "COBie"
|
||||
bl_idname = "BIM_PT_cobie"
|
||||
class BIM_PT_fm(Panel):
|
||||
bl_label = "Facility Management"
|
||||
bl_idname = "BIM_PT_fm"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
@@ -34,7 +34,7 @@ class BIM_PT_cobie(Panel):
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
props = scene.COBieProperties
|
||||
props = scene.BIMFMProperties
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = layout.row()
|
||||
@@ -42,22 +42,23 @@ class BIM_PT_cobie(Panel):
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "cobie_ifc_file")
|
||||
row.operator("bim.select_cobie_ifc_file", icon="FILE_FOLDER", text="")
|
||||
if len(props.ifc_files) > 1:
|
||||
row.label(text=f"{len(props.ifc_files)} Files Selected")
|
||||
else:
|
||||
row.prop(props, "ifc_file")
|
||||
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "cobie_types")
|
||||
row.prop(props, "engine")
|
||||
row = layout.row()
|
||||
row.prop(props, "cobie_components")
|
||||
row.prop(props, "format")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.execute_ifcfm", text="Convert To Spreadsheet")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "cobie_json_file")
|
||||
row.operator("bim.select_cobie_json_file", icon="FILE_FOLDER", text="")
|
||||
row.label(text=f"{len(props.spreadsheet_files)} Spreadsheets Selected")
|
||||
row.operator("bim.select_fm_spreadsheet_files", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.execute_ifc_cobie", text="CSV")
|
||||
op.file_format = "csv"
|
||||
op = row.operator("bim.execute_ifc_cobie", text="ODS")
|
||||
op.file_format = "ods"
|
||||
op = row.operator("bim.execute_ifc_cobie", text="XLSX")
|
||||
op.file_format = "xlsx"
|
||||
op = row.operator("bim.execute_ifcfm_federate", text="Merge Spreadsheets")
|
||||
@@ -22,34 +22,46 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.AddRepresentation,
|
||||
operator.CopyRepresentation,
|
||||
operator.DisableEditingRepresentationItems,
|
||||
operator.EditObjectPlacement,
|
||||
operator.EnableEditingRepresentationItems,
|
||||
operator.FlipObject,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveAggregate,
|
||||
operator.OverrideDuplicateMoveAggregateMacro,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
operator.OverrideDuplicateMoveLinkedMacro,
|
||||
operator.OverrideDuplicateMoveMacro,
|
||||
operator.OverrideDuplicateMoveAggregate,
|
||||
operator.OverrideDuplicateMoveAggregateMacro,
|
||||
operator.RefreshAggregate,
|
||||
operator.OverrideJoin,
|
||||
operator.OverrideMeshSeparate,
|
||||
operator.OverrideModeSetEdit,
|
||||
operator.OverrideModeSetObject,
|
||||
operator.OverrideOriginSet,
|
||||
operator.OverrideOutlinerDelete,
|
||||
operator.OverridePasteBuffer,
|
||||
operator.PurgeUnusedRepresentations,
|
||||
operator.RefreshAggregate,
|
||||
operator.RemoveConnection,
|
||||
operator.RemoveRepresentation,
|
||||
operator.SelectConnection,
|
||||
operator.SwitchRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
prop.RepresentationItem,
|
||||
prop.BIMObjectGeometryProperties,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_derived_placements,
|
||||
ui.BIM_PT_placement,
|
||||
ui.BIM_PT_representations,
|
||||
ui.BIM_PT_representation_items,
|
||||
ui.BIM_PT_connections,
|
||||
ui.BIM_PT_mesh,
|
||||
ui.BIM_PT_derived_coordinates,
|
||||
ui.BIM_PT_workarounds,
|
||||
ui.BIM_MT_object_set_origin,
|
||||
ui.BIM_MT_separate,
|
||||
ui.BIM_UL_representation_items,
|
||||
)
|
||||
|
||||
|
||||
@@ -66,10 +78,10 @@ def register():
|
||||
|
||||
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
|
||||
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
|
||||
bpy.types.VIEW3D_MT_edit_mesh.append(ui.edit_mesh_menu)
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
@@ -110,9 +122,9 @@ def register():
|
||||
|
||||
def unregister():
|
||||
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_edit_mesh.remove(ui.edit_mesh_menu)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
del bpy.types.Object.BIMGeometryProperties
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -23,8 +23,10 @@ from mathutils import Vector
|
||||
|
||||
|
||||
def refresh():
|
||||
DerivedPlacementsData.is_loaded = False
|
||||
PlacementData.is_loaded = False
|
||||
DerivedCoordinatesData.is_loaded = False
|
||||
RepresentationsData.is_loaded = False
|
||||
RepresentationItemsData.is_loaded = False
|
||||
ConnectionsData.is_loaded = False
|
||||
|
||||
|
||||
@@ -91,13 +93,57 @@ class RepresentationsData:
|
||||
return results
|
||||
|
||||
|
||||
class RepresentationItemsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_items": cls.total_items(),
|
||||
"active_surface_style": cls.active_surface_style(),
|
||||
"active_layer": cls.active_layer(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_items(cls):
|
||||
active_representation_id = None
|
||||
result = 0
|
||||
if bpy.context.active_object.data and hasattr(bpy.context.active_object.data, "BIMMeshProperties"):
|
||||
active_representation_id = bpy.context.active_object.data.BIMMeshProperties.ifc_definition_id
|
||||
element = tool.Ifc.get().by_id(active_representation_id)
|
||||
if not element.is_a("IfcShapeRepresentation"):
|
||||
return 0
|
||||
queue = list(element.Items)
|
||||
while queue:
|
||||
item = queue.pop()
|
||||
if item.is_a("IfcMappedItem"):
|
||||
queue.extend(item.MappingSource.MappedRepresentation.Items)
|
||||
else:
|
||||
result += 1
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def active_surface_style(cls):
|
||||
props = bpy.context.active_object.BIMGeometryProperties
|
||||
if props.active_item_index < len(props.items):
|
||||
return props.items[props.active_item_index].surface_style
|
||||
|
||||
@classmethod
|
||||
def active_layer(cls):
|
||||
props = bpy.context.active_object.BIMGeometryProperties
|
||||
if props.active_item_index < len(props.items):
|
||||
return props.items[props.active_item_index].layer
|
||||
|
||||
|
||||
class ConnectionsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"connections": cls.connections()}
|
||||
cls.data = {"connections": cls.connections(), "is_connection_realization": cls.is_connection_realization()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -114,7 +160,13 @@ class ConnectionsData:
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
realizing_elements = []
|
||||
realizing_elements_connection_type = ""
|
||||
if rel.is_a("IfcRelConnectsWithRealizingElements"):
|
||||
realizing_elements.extend(rel.RealizingElements)
|
||||
realizing_elements_connection_type = rel.ConnectionType
|
||||
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
else:
|
||||
related_element_connection_type = ""
|
||||
@@ -125,6 +177,8 @@ class ConnectionsData:
|
||||
"is_relating": True,
|
||||
"Name": related_element.Name or "Unnamed",
|
||||
"ConnectionType": related_element_connection_type,
|
||||
"realizing_elements": realizing_elements,
|
||||
"realizing_elements_connection_type": realizing_elements_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -134,7 +188,13 @@ class ConnectionsData:
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
realizing_elements = []
|
||||
realizing_elements_connection_type = ""
|
||||
if rel.is_a("IfcRelConnectsWithRealizingElements"):
|
||||
realizing_elements.extend(rel.RealizingElements)
|
||||
realizing_elements_connection_type = rel.ConnectionType
|
||||
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
else:
|
||||
relating_element_connection_type = ""
|
||||
@@ -145,13 +205,32 @@ class ConnectionsData:
|
||||
"is_relating": False,
|
||||
"Name": relating_element.Name or "Unnamed",
|
||||
"ConnectionType": relating_element_connection_type,
|
||||
"realizing_elements": realizing_elements,
|
||||
"realizing_elements_connection_type": realizing_elements_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def is_connection_realization(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
connections = element.IsConnectionRealization
|
||||
if not connections:
|
||||
return
|
||||
|
||||
class DerivedPlacementsData:
|
||||
results = []
|
||||
for rel in connections:
|
||||
data = {
|
||||
"realizing_elements_connection_type": rel.ConnectionType,
|
||||
"connected_from": rel.RelatingElement,
|
||||
"connected_to": rel.RelatedElement,
|
||||
}
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
|
||||
class DerivedCoordinatesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@@ -230,6 +309,25 @@ class DerivedPlacementsData:
|
||||
for i, storey in enumerate(storeys):
|
||||
if storey[0] != element:
|
||||
continue
|
||||
if i >= len(storeys):
|
||||
if i >= len(storeys) - 1:
|
||||
return "N/A"
|
||||
return "{0:.3f}".format(round(storeys[i + 1][1] - storey[1], 3))
|
||||
|
||||
|
||||
class PlacementData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"has_placement": cls.has_placement(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_placement(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element and hasattr(element, "ObjectPlacement"):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -57,14 +57,79 @@ class EditObjectPlacement(bpy.types.Operator, Operator):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
|
||||
class OverrideMeshSeparate(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.override_mesh_separate"
|
||||
bl_label = "IFC Mesh Separate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
|
||||
# You cannot separate meshes if the representation is mapped.
|
||||
relating_type = tool.Root.get_element_type(tool.Ifc.get_entity(obj))
|
||||
if relating_type and tool.Root.does_type_have_representations(relating_type):
|
||||
# We toggle edit mode to ensure that once representations are
|
||||
# unmapped, our Blender mesh only has a single user.
|
||||
tool.Blender.toggle_edit_mode(context)
|
||||
bpy.ops.bim.unassign_type(related_object=obj.name)
|
||||
tool.Blender.toggle_edit_mode(context)
|
||||
|
||||
selected_objects = context.selected_objects
|
||||
bpy.ops.mesh.separate(type=self.type)
|
||||
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
|
||||
new_objs = [obj]
|
||||
for new_obj in context.selected_objects:
|
||||
if new_obj == obj:
|
||||
continue
|
||||
# This is not very efficient, it needlessly copies the representations first.
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
new_objs.append(new_obj)
|
||||
for new_obj in new_objs:
|
||||
bpy.ops.bim.update_representation(obj=new_obj.name)
|
||||
|
||||
|
||||
class OverrideOriginSet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.override_origin_set"
|
||||
bl_label = "IFC Origin Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
origin_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if tool.Ifc.is_moved(obj):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if not representation:
|
||||
continue
|
||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
if not tool.Geometry.is_meshlike(representation):
|
||||
continue
|
||||
bpy.ops.object.origin_set(type=self.origin_type)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
|
||||
class AddRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
representation_conversion_method: bpy.props.EnumProperty(
|
||||
items=[
|
||||
("OUTLINE", "Trace Outline", ""),
|
||||
("BOX", "Bounding Box", ""),
|
||||
("OUTLINE", "Trace Outline", "Traces outline by local XY axes, for Profile - by local XZ axes."),
|
||||
(
|
||||
"BOX",
|
||||
"Bounding Box",
|
||||
"Creates a bounding box representation.\n"
|
||||
"For Plan context - 2D bounding box by local XY axes,\n"
|
||||
"for Profile - 2D bounding box by local XZ axes.\n"
|
||||
"For other contexts - bounding box is 3d.",
|
||||
),
|
||||
("PROJECT", "Full Representation", ""),
|
||||
],
|
||||
name="Representation Conversion Method",
|
||||
@@ -185,6 +250,16 @@ class RemoveRepresentation(bpy.types.Operator, Operator):
|
||||
)
|
||||
|
||||
|
||||
class PurgeUnusedRepresentations(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.purge_unused_representations"
|
||||
bl_label = "Purge Unused Representations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
purged_representations = core.purge_unused_representations(tool.Ifc, tool.Geometry)
|
||||
self.report({"INFO"}, f"{purged_representations} representations were purged.")
|
||||
|
||||
|
||||
class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.update_representation"
|
||||
bl_label = "Update Representation"
|
||||
@@ -214,6 +289,10 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
|
||||
|
||||
if getattr(product, "HasOpenings", False) and obj.data.BIMMeshProperties.has_openings_applied:
|
||||
# Meshlike things with openings can only be updated without openings applied.
|
||||
return
|
||||
|
||||
if not product.is_a("IfcGridAxis"):
|
||||
tool.Geometry.clear_cache(product)
|
||||
|
||||
@@ -231,8 +310,15 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
return
|
||||
if self.ifc_representation_class == "IfcTessellatedFaceSet":
|
||||
# We are explicitly casting to a tessellation, so remove all parametric materials.
|
||||
element_type = ifcopenshell.util.element.get_type(product)
|
||||
if element_type: # Some invalid IFCs use material sets without a type.
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
|
||||
else:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
return
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
@@ -285,7 +371,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
"style.assign_representation_styles",
|
||||
self.file,
|
||||
shape_representation=new_representation,
|
||||
styles=tool.Geometry.get_styles(obj),
|
||||
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
|
||||
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
@@ -353,6 +439,8 @@ class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
|
||||
parameters = context.active_object.data.BIMMeshProperties.ifc_parameters
|
||||
self.report({"INFO"}, f"{len(parameters)} parameters found.")
|
||||
|
||||
|
||||
class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
@@ -377,7 +465,7 @@ class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
if not element:
|
||||
continue
|
||||
bm.to_mesh(obj.data)
|
||||
old_rep = self.get_representation_by_context(element, geometric_context)
|
||||
old_rep = tool.Geometry.get_representation_by_context(element, geometric_context)
|
||||
if old_rep:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation", tool.Ifc.get(), product=element, representation=old_rep
|
||||
@@ -394,16 +482,6 @@ class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
profile_set_usage=None,
|
||||
)
|
||||
|
||||
def get_representation_by_context(self, element, context):
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
for r in element.Representation.Representations:
|
||||
if r.ContextOfItems == context:
|
||||
return r
|
||||
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
|
||||
for r in element.RepresentationMaps:
|
||||
if r.MappedRepresentation.ContextOfItems == context:
|
||||
return r.MappedRepresentation
|
||||
|
||||
|
||||
class OverrideDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_object_delete"
|
||||
@@ -452,6 +530,8 @@ class OverrideDelete(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
if self.is_batch:
|
||||
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
|
||||
|
||||
self.process_arrays(context)
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
@@ -461,6 +541,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
if self.is_batch:
|
||||
old_file = tool.Ifc.get()
|
||||
old_file.end_transaction()
|
||||
@@ -481,6 +562,30 @@ class OverrideDelete(bpy.types.Operator):
|
||||
data["old_file"].redo()
|
||||
tool.Ifc.set(data["new_file"])
|
||||
|
||||
def process_arrays(self, context):
|
||||
selected_objects = set(context.selected_objects)
|
||||
array_parents = set()
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
if not pset:
|
||||
continue
|
||||
array_parents.add(tool.Ifc.get().by_guid(pset["Parent"]))
|
||||
|
||||
for array_parent in array_parents:
|
||||
array_parent_obj = tool.Ifc.get_object(array_parent)
|
||||
data = [(i, data) for i, data in enumerate(tool.Blender.Modifier.Array.get_modifiers_data(array_parent))]
|
||||
# NOTE: there is a way to remove arrays more precisely but it's more complex
|
||||
for i, modifier_data in reversed(data):
|
||||
children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data))
|
||||
if children.issubset(selected_objects):
|
||||
with context.temp_override(active_object=array_parent_obj):
|
||||
bpy.ops.bim.remove_array(item=i)
|
||||
else:
|
||||
break # allows to remove only n last layers of an array
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
@@ -491,7 +596,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_ids) > 0
|
||||
return len(getattr(context, "selected_ids", [])) > 0
|
||||
|
||||
def execute(self, context):
|
||||
# In this override, we don't check self.hierarchy. This effectively
|
||||
@@ -606,6 +711,13 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
|
||||
|
||||
def _execute(self, context):
|
||||
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context)
|
||||
|
||||
@staticmethod
|
||||
def execute_duplicate_operator(self, context, linked=False):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -616,7 +728,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
new_active_obj = None
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
if linked and obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
new_active_obj = new_obj
|
||||
@@ -628,39 +740,111 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
@staticmethod
|
||||
def execute_ifc_duplicate_operator(self, context, linked=False):
|
||||
objects_to_duplicate = set(context.selected_objects)
|
||||
|
||||
# handle arrays
|
||||
arrays_to_duplicate, array_children = OverrideDuplicateMove.process_arrays(self, context)
|
||||
objects_to_duplicate -= array_children
|
||||
for child in array_children:
|
||||
child.select_set(False)
|
||||
|
||||
self.new_active_obj = None
|
||||
# Track decompositions so they can be recreated after the operation
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate)
|
||||
old_to_new = {}
|
||||
for obj in context.selected_objects:
|
||||
|
||||
for obj in objects_to_duplicate:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
|
||||
|
||||
linked_non_ifc_object = linked and not element
|
||||
|
||||
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
temp_data = None
|
||||
|
||||
if obj.data and not linked_non_ifc_object:
|
||||
# assure root.copy_class won't replace the previous mesh globally
|
||||
temp_data = obj.data.copy()
|
||||
new_obj.data = temp_data
|
||||
|
||||
if obj == context.active_object:
|
||||
self.new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
|
||||
if linked_non_ifc_object:
|
||||
continue
|
||||
|
||||
# clear object's collection so it will be able to have it's own
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
# Copy the actual class
|
||||
# copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
|
||||
# clean up the orphaned mesh with ifc id of the original object to avoid confusion
|
||||
if new and temp_data:
|
||||
tool.Blender.remove_data_block(temp_data)
|
||||
|
||||
if new:
|
||||
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new]
|
||||
# TODO: handle array data for other cases of duplication
|
||||
array_data = arrays_to_duplicate.get(obj, None)
|
||||
tool.Model.handle_array_on_copied_element(new, array_data)
|
||||
if array_data:
|
||||
for child in tool.Blender.Modifier.Array.get_all_children_objects(new):
|
||||
child.select_set(True)
|
||||
|
||||
# TODO: add new array children to recreate their decomposition too
|
||||
old_to_new[element] = [new]
|
||||
if new.is_a("IfcRelSpaceBoundary"):
|
||||
tool.Boundary.decorate_boundary(new_obj)
|
||||
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@staticmethod
|
||||
def process_arrays(self, context):
|
||||
selected_objects = set(context.selected_objects)
|
||||
array_parents = set()
|
||||
arrays_to_create = dict()
|
||||
array_children = set() # will be ignored during the duplication
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
if not pset:
|
||||
continue
|
||||
array_parents.add(tool.Ifc.get().by_guid(pset["Parent"]))
|
||||
|
||||
for array_parent in array_parents:
|
||||
array_parent_obj = tool.Ifc.get_object(array_parent)
|
||||
if array_parent_obj not in selected_objects:
|
||||
continue
|
||||
|
||||
array_data = []
|
||||
for modifier_data in tool.Blender.Modifier.Array.get_modifiers_data(array_parent):
|
||||
children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data))
|
||||
if children.issubset(selected_objects):
|
||||
modifier_data["children"] = []
|
||||
array_data.append(modifier_data)
|
||||
array_children.update(children)
|
||||
else:
|
||||
break # allows to duplicate only n first layers of an array
|
||||
|
||||
if array_data:
|
||||
arrays_to_create[array_parent_obj] = array_data
|
||||
|
||||
return arrays_to_create, array_children
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
@@ -679,53 +863,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
if self.new_active_obj:
|
||||
context.view_layer.objects.active = self.new_active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
new_active_obj = None
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.copy()
|
||||
if obj == context.active_object:
|
||||
new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
if new_active_obj:
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
return {"FINISHED"}
|
||||
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=True)
|
||||
|
||||
def _execute(self, context):
|
||||
self.new_active_obj = None
|
||||
# Track decompositions so they can be recreated after the operation
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
old_to_new = {}
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
self.new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
# Copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
if new:
|
||||
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = new
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
return {"FINISHED"}
|
||||
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
|
||||
|
||||
class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro):
|
||||
@@ -745,28 +886,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
if self.new_active_obj:
|
||||
context.view_layer.objects.active = self.new_active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
new_active_obj = None
|
||||
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
if new_active_obj:
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
return {"FINISHED"}
|
||||
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
|
||||
|
||||
def _execute(self, context):
|
||||
self.new_active_obj = None
|
||||
@@ -964,12 +1084,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
|
||||
)
|
||||
|
||||
if new_entity:
|
||||
# Checks if the object belongs to an Ifc Array
|
||||
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
|
||||
|
||||
tool.Model.handle_array_on_copied_element(new_entity)
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
@@ -1004,6 +1119,17 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
|
||||
|
||||
recreate_data_structure(new_root_entity)
|
||||
|
||||
# Remove connections with old objects
|
||||
for new in old_to_new.values():
|
||||
for connection in new[0].ConnectedTo:
|
||||
entity = connection.RelatedElement
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
for connection in new[0].ConnectedFrom:
|
||||
entity = connection.RelatingElement
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
|
||||
old_objs = []
|
||||
for old, new in old_to_new.items():
|
||||
old_objs.append(tool.Ifc.get_object(old))
|
||||
@@ -1012,7 +1138,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1067,9 +1193,64 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
else:
|
||||
parts = ifcopenshell.util.element.get_parts(instance_entity)
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
if not pset:
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [part.GlobalId],
|
||||
}
|
||||
data = [data_children]
|
||||
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(entity),
|
||||
related_obj=tool.Ifc.get_object(new_part),
|
||||
)
|
||||
duplicate_children(new_part)
|
||||
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
|
||||
else:
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
else:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [],
|
||||
}
|
||||
data = [data_children]
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
@@ -1091,47 +1272,58 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
|
||||
# This is needed to make sure the new object gets unlink from
|
||||
# the old object assembly collection
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
|
||||
# Copy the actual class
|
||||
new_entity = blenderbim.core.root.copy_class(
|
||||
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
|
||||
)
|
||||
|
||||
if new_entity:
|
||||
# Checks if the object belongs to an Ifc Array
|
||||
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
|
||||
tool.Model.handle_array_on_copied_element(new_entity)
|
||||
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=obj,
|
||||
related_obj=tool.Ifc.get_object(new_entity),
|
||||
)
|
||||
if not new_entity.is_a("IfcElementAssembly"):
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=obj,
|
||||
related_obj=tool.Ifc.get_object(new_entity),
|
||||
)
|
||||
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
|
||||
|
||||
return new_entity
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
self.report({"INFO"}, "Only 1 object need to be selected.")
|
||||
return {"FINISHED"}
|
||||
|
||||
selected_root_obj = context.selected_objects[0]
|
||||
selected_root_entity = tool.Ifc.get_entity(selected_root_obj)
|
||||
|
||||
if not selected_root_entity.is_a("IfcElementAssembly"):
|
||||
if selected_root_entity.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
elif selected_root_entity.Decomposes:
|
||||
if selected_root_entity.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
|
||||
selected_root_entity = selected_root_entity.Decomposes[0].RelatingObject
|
||||
selected_root_obj = tool.Ifc.get_object(selected_root_entity)
|
||||
else:
|
||||
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
|
||||
return {"FINISHED"}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
|
||||
if not pset:
|
||||
self.report({"INFO"}, "Object is not part of an assembly aggregate.")
|
||||
return {"FINISHED"}
|
||||
|
||||
pset_data = json.loads(pset["Data"])[0]
|
||||
instance_of = pset_data["instance_of"][0]
|
||||
original_root_entity = tool.Ifc.get().by_guid(instance_of)
|
||||
if original_root_entity == selected_root_entity:
|
||||
self.report({"INFO"}, "Cannot refresh original assembly. Select an assembly instance.")
|
||||
return {"FINISHED"}
|
||||
|
||||
parents = remove_objects(selected_root_entity)
|
||||
@@ -1144,13 +1336,24 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
for parent in parents:
|
||||
duplicate_children(parent)
|
||||
|
||||
# Remove connections with old objects
|
||||
for new in old_to_new.values():
|
||||
for connection in new[0].ConnectedTo:
|
||||
entity = connection.RelatedElement
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
for connection in new[0].ConnectedFrom:
|
||||
entity = connection.RelatingElement
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
|
||||
old_objs = []
|
||||
for old, new in old_to_new.items():
|
||||
old_objs.append(tool.Ifc.get_object(old))
|
||||
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
|
||||
matrix_diff = new_obj.matrix_world @ original_matrix
|
||||
matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world
|
||||
new_matrix = selected_matrix @ matrix_diff
|
||||
|
||||
new_obj.matrix_world = new_matrix
|
||||
@@ -1159,7 +1362,7 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1317,8 +1520,13 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
continue
|
||||
|
||||
is_profile = True
|
||||
representation_class = tool.Geometry.get_ifc_representation_class(element, representation)
|
||||
if usage_type == "PROFILE":
|
||||
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
|
||||
elif representation_class and "IfcCircleProfileDef" in representation_class:
|
||||
self.report({"INFO"}, "Can't edit Circle Profile Extrusion")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
elif (
|
||||
tool.Geometry.is_profile_based(obj.data)
|
||||
or usage_type == "LAYER3"
|
||||
@@ -1347,6 +1555,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
continue
|
||||
|
||||
if tool.Geometry.is_meshlike(representation):
|
||||
tool.Ifc.edit(obj)
|
||||
if getattr(element, "HasOpenings", None):
|
||||
# Mesh elements with openings must disable openings
|
||||
# so that you can edit the original topology.
|
||||
@@ -1365,8 +1574,9 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
else:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
|
||||
if len(selected_objs) > 1 and (
|
||||
not context.selected_objects or len(context.selected_objects) != len(selected_objs)
|
||||
):
|
||||
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
|
||||
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
|
||||
|
||||
@@ -1408,9 +1618,11 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
self.reload_representation(obj)
|
||||
else:
|
||||
self.reload_representation(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
for obj in self.unchanged_objs_with_openings:
|
||||
self.reload_representation(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def reload_representation(self, obj):
|
||||
@@ -1476,6 +1688,9 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
elif tool.Model.get_usage_type(element) == "PROFILE":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
# if in the process of editing arbitrary profile
|
||||
elif context.scene.BIMProfileProperties.active_arbitrary_profile_id:
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
return self.execute(context)
|
||||
@@ -1490,7 +1705,76 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
self.edited_objs.append(obj)
|
||||
elif getattr(element, "HasOpenings", None):
|
||||
self.unchanged_objs_with_openings.append(obj)
|
||||
else:
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
if self.edited_objs:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class FlipObject(bpy.types.Operator):
|
||||
bl_idname = "bim.flip_object"
|
||||
bl_label = "Flip Object"
|
||||
bl_description = "Flip object's local axes, keep the position"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
flip_local_axes: bpy.props.EnumProperty(
|
||||
name="Flip Local Axes", items=(("XY", "XY", ""), ("YZ", "YZ", ""), ("XZ", "XZ", "")), default="XY"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
tool.Geometry.flip_object(obj, self.flip_local_axes)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_representation_items"
|
||||
bl_label = "Enable Editing Representation Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing = True
|
||||
|
||||
props.items.clear()
|
||||
|
||||
if bpy.context.active_object.data and hasattr(bpy.context.active_object.data, "BIMMeshProperties"):
|
||||
active_representation_id = bpy.context.active_object.data.BIMMeshProperties.ifc_definition_id
|
||||
element = tool.Ifc.get().by_id(active_representation_id)
|
||||
if not element.is_a("IfcShapeRepresentation"):
|
||||
return
|
||||
queue = list(element.Items)
|
||||
while queue:
|
||||
item = queue.pop()
|
||||
if item.is_a("IfcMappedItem"):
|
||||
queue.extend(item.MappingSource.MappedRepresentation.Items)
|
||||
else:
|
||||
new = props.items.add()
|
||||
new.name = item.is_a()
|
||||
new.ifc_definition_id = item.id()
|
||||
|
||||
styles = []
|
||||
for inverse in tool.Ifc.get().get_inverse(item):
|
||||
if inverse.is_a("IfcStyledItem"):
|
||||
styles = inverse.Styles
|
||||
if styles and styles[0].is_a("IfcPresentationStyleAssignment"):
|
||||
styles = styles[0].Styles
|
||||
for style in styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
new.surface_style = style.Name or "Unnamed"
|
||||
elif inverse.is_a("IfcPresentationLayerAssignment"):
|
||||
new.layer = inverse.Name or "Unnamed"
|
||||
|
||||
|
||||
class DisableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_representation_items"
|
||||
bl_label = "Disable Editing Representation Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
obj.BIMGeometryProperties.is_editing = False
|
||||
|
||||
@@ -38,8 +38,18 @@ def get_contexts(self, context):
|
||||
return RepresentationsData.data["contexts"]
|
||||
|
||||
|
||||
class RepresentationItem(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
surface_style: StringProperty(name="Surface Style")
|
||||
layer: StringProperty(name="Layer")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMObjectGeometryProperties(PropertyGroup):
|
||||
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
items: CollectionProperty(name="Representation Items", type=RepresentationItem)
|
||||
active_item_index: IntProperty(name="Active Representation Item Index")
|
||||
|
||||
|
||||
class BIMGeometryProperties(PropertyGroup):
|
||||
|
||||
@@ -18,10 +18,16 @@
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from bpy.types import Panel, Menu, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData, ConnectionsData, DerivedPlacementsData
|
||||
from blenderbim.bim.module.geometry.data import (
|
||||
RepresentationsData,
|
||||
RepresentationItemsData,
|
||||
ConnectionsData,
|
||||
PlacementData,
|
||||
DerivedCoordinatesData,
|
||||
)
|
||||
|
||||
|
||||
def object_menu(self, context):
|
||||
@@ -29,6 +35,44 @@ def object_menu(self, context):
|
||||
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
|
||||
self.layout.menu("BIM_MT_object_set_origin", icon="PLUGIN")
|
||||
|
||||
|
||||
def edit_mesh_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.menu("BIM_MT_separate", icon="PLUGIN")
|
||||
|
||||
|
||||
class BIM_MT_separate(Menu):
|
||||
bl_idname = "BIM_MT_separate"
|
||||
bl_label = "IFC Separate"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC Selection").type = "SELECTED"
|
||||
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Material").type = "MATERIAL"
|
||||
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Loose Parts").type = "LOOSE"
|
||||
|
||||
|
||||
class BIM_MT_object_set_origin(Menu):
|
||||
bl_idname = "BIM_MT_object_set_origin"
|
||||
bl_label = "IFC Set Origin"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Geometry to Origin"
|
||||
).origin_type = "GEOMETRY_ORIGIN"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Geometry"
|
||||
).origin_type = "ORIGIN_GEOMETRY"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to 3D Cursor"
|
||||
).origin_type = "ORIGIN_CURSOR"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Surface)"
|
||||
).origin_type = "ORIGIN_CENTER_OF_MASS"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Volume)"
|
||||
).origin_type = "ORIGIN_CENTER_OF_VOLUME"
|
||||
|
||||
|
||||
def outliner_menu(self, context):
|
||||
@@ -86,6 +130,53 @@ class BIM_PT_representations(Panel):
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
|
||||
|
||||
class BIM_PT_representation_items(Panel):
|
||||
bl_label = "Representation Items"
|
||||
bl_idname = "BIM_PT_representation_items"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_tab_representations"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not RepresentationItemsData.is_loaded:
|
||||
RepresentationItemsData.load()
|
||||
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{RepresentationItemsData.data['total_items']} Items Found")
|
||||
|
||||
if props.is_editing:
|
||||
row.operator("bim.disable_editing_representation_items", icon="CANCEL", text="")
|
||||
else:
|
||||
row.operator("bim.enable_editing_representation_items", icon="IMPORT", text="")
|
||||
return
|
||||
|
||||
self.layout.template_list("BIM_UL_representation_items", "", props, "items", props, "active_item_index")
|
||||
|
||||
row = self.layout.row()
|
||||
if RepresentationItemsData.data["active_surface_style"]:
|
||||
row.label(text=RepresentationItemsData.data["active_surface_style"], icon="MATERIAL")
|
||||
else:
|
||||
row.label(text="No Surface Style", icon="MESH_UVSPHERE")
|
||||
|
||||
row = self.layout.row()
|
||||
if RepresentationItemsData.data["active_layer"]:
|
||||
row.label(text=RepresentationItemsData.data["active_layer"], icon="STICKY_UVS_LOC")
|
||||
else:
|
||||
row.label(text="No Presentation Layer", icon="STICKY_UVS_LOC")
|
||||
|
||||
|
||||
class BIM_PT_connections(Panel):
|
||||
bl_label = "Connections"
|
||||
bl_idname = "BIM_PT_connections"
|
||||
@@ -111,7 +202,7 @@ class BIM_PT_connections(Panel):
|
||||
layout = self.layout
|
||||
props = context.active_object.BIMObjectProperties
|
||||
|
||||
if not ConnectionsData.data["connections"]:
|
||||
if not ConnectionsData.data["connections"] and not ConnectionsData.data["is_connection_realization"]:
|
||||
layout.label(text="No connections found")
|
||||
|
||||
for connection in ConnectionsData.data["connections"]:
|
||||
@@ -123,6 +214,44 @@ class BIM_PT_connections(Panel):
|
||||
op = row.operator("bim.remove_connection", icon="X", text="")
|
||||
op.connection = connection["id"]
|
||||
|
||||
if connection["realizing_elements"]:
|
||||
row = self.layout.row(align=True)
|
||||
connection_type = connection["realizing_elements_connection_type"]
|
||||
connection_type = f" ({connection_type})" if connection_type else ""
|
||||
row.label(text=f"Realizing elements{connection_type}:")
|
||||
|
||||
for element in connection["realizing_elements"]:
|
||||
row = self.layout.row(align=True)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = element.id()
|
||||
row.label(text=obj.name)
|
||||
|
||||
# display connections where element is connection realization
|
||||
connections = ConnectionsData.data["is_connection_realization"]
|
||||
if not connections:
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Element is connections realization:")
|
||||
for connection in connections:
|
||||
# NOTE: not displayed yet
|
||||
connection_type = connection["realizing_elements_connection_type"]
|
||||
connection_type = f" ({connection_type})" if connection_type else ""
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
connected_from = connection["connected_from"]
|
||||
obj = tool.Ifc.get_object(connected_from)
|
||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_from.id()
|
||||
row.label(text=obj.name)
|
||||
|
||||
row.label(text="", icon="FORWARD")
|
||||
|
||||
connected_to = connection["connected_to"]
|
||||
obj = tool.Ifc.get_object(connected_to)
|
||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_to.id()
|
||||
row.label(text=obj.name)
|
||||
|
||||
|
||||
class BIM_PT_mesh(Panel):
|
||||
bl_label = "Representation Utilities"
|
||||
@@ -151,6 +280,9 @@ class BIM_PT_mesh(Panel):
|
||||
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_representation", text="Manually Save Representation")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.copy_representation", text="Copy Mesh From Active To Selected")
|
||||
|
||||
@@ -174,50 +306,99 @@ class BIM_PT_mesh(Panel):
|
||||
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
|
||||
|
||||
|
||||
def BIM_PT_transform(self, context):
|
||||
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Blender Offset")
|
||||
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.edit_object_placement")
|
||||
if context.active_object and context.active_object.data:
|
||||
mprops = context.active_object.data.BIMMeshProperties
|
||||
row = layout.row()
|
||||
row.operator("bim.get_representation_ifc_parameters")
|
||||
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
|
||||
row = layout.row(align=True)
|
||||
row.prop(ifc_parameter, "name", text="")
|
||||
row.prop(ifc_parameter, "value", text="")
|
||||
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
|
||||
|
||||
|
||||
class BIM_PT_derived_placements(Panel):
|
||||
bl_label = "Derived Placements"
|
||||
bl_idname = "BIM_PT_derived_placements"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
class BIM_PT_placement(Panel):
|
||||
bl_label = "Placement"
|
||||
bl_idname = "BIM_PT_placement"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_context = "scene"
|
||||
bl_order = 1
|
||||
bl_parent_id = "OBJECT_PT_transform"
|
||||
bl_parent_id = "BIM_PT_tab_placement"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
def draw(self, context):
|
||||
if not DerivedPlacementsData.is_loaded:
|
||||
DerivedPlacementsData.load()
|
||||
if not PlacementData.is_loaded:
|
||||
PlacementData.load()
|
||||
|
||||
if PlacementData.data["has_placement"]:
|
||||
row = self.layout.row()
|
||||
row.prop(context.active_object, "location", text="Location")
|
||||
row = self.layout.row()
|
||||
row.prop(context.active_object, "rotation_euler", text="Rotation")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Object Placement Found")
|
||||
|
||||
if context.active_object.BIMObjectProperties.blender_offset_type != "NONE":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
|
||||
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
|
||||
|
||||
|
||||
class BIM_PT_derived_coordinates(Panel):
|
||||
bl_label = "Derived Coordinates"
|
||||
bl_idname = "BIM_PT_derived_coordinates"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_order = 3
|
||||
bl_parent_id = "BIM_PT_tab_placement"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
def draw(self, context):
|
||||
if not DerivedCoordinatesData.is_loaded:
|
||||
DerivedCoordinatesData.load()
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.edit_object_placement", icon="EXPORT")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="XYZ Dimensions")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.enabled = False
|
||||
row.prop(context.active_object, "dimensions", text="X", index=0, slider=True)
|
||||
row.prop(context.active_object, "dimensions", text="Y", index=1, slider=True)
|
||||
row.prop(context.active_object, "dimensions", text="Z", index=2, slider=True)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Min Global Z")
|
||||
row.label(text=DerivedPlacementsData.data["min_global_z"])
|
||||
row.label(text=DerivedCoordinatesData.data["min_global_z"])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Max Global Z")
|
||||
row.label(text=DerivedPlacementsData.data["max_global_z"])
|
||||
row.label(text=DerivedCoordinatesData.data["max_global_z"])
|
||||
|
||||
if DerivedPlacementsData.data["has_collection"]:
|
||||
if DerivedCoordinatesData.data["has_collection"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Min Decomposed Z")
|
||||
row.label(text=DerivedPlacementsData.data["min_decomposed_z"])
|
||||
row.label(text=DerivedCoordinatesData.data["min_decomposed_z"])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Max Decomposed Z")
|
||||
row.label(text=DerivedPlacementsData.data["max_decomposed_z"])
|
||||
row.label(text=DerivedCoordinatesData.data["max_decomposed_z"])
|
||||
|
||||
if DerivedPlacementsData.data["is_storey"]:
|
||||
if DerivedCoordinatesData.data["is_storey"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Storey Height")
|
||||
row.label(text=DerivedPlacementsData.data["storey_height"])
|
||||
row.label(text=DerivedCoordinatesData.data["storey_height"])
|
||||
|
||||
|
||||
class BIM_PT_workarounds(Panel):
|
||||
@@ -246,3 +427,13 @@ class BIM_PT_workarounds(Panel):
|
||||
row.prop(props, "should_force_triangulation")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "should_use_presentation_style_assignment")
|
||||
|
||||
|
||||
class BIM_UL_representation_items(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name, icon=icon)
|
||||
if item.layer:
|
||||
row.label(text="", icon="STICKY_UVS_LOC")
|
||||
|
||||
@@ -34,6 +34,7 @@ classes = (
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
operator.ImportPlot,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_utilities,
|
||||
|
||||
@@ -56,7 +56,12 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def map_conversion(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not map_conversion:
|
||||
return {}
|
||||
del map_conversion["id"]
|
||||
return map_conversion
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
@@ -72,8 +77,8 @@ class GeoreferenceData:
|
||||
def map_derived_angle(cls):
|
||||
if (
|
||||
cls.data["map_conversion"]
|
||||
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
|
||||
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
|
||||
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None
|
||||
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None
|
||||
):
|
||||
return str(
|
||||
round(
|
||||
@@ -88,7 +93,12 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def projected_crs(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
projected_crs = ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS")
|
||||
if not projected_crs:
|
||||
return {}
|
||||
del projected_crs["id"]
|
||||
return projected_crs
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
|
||||
@@ -76,7 +76,7 @@ class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_grid_north"
|
||||
bl_label = "Set IFC Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set IFC grid north"
|
||||
bl_description = "Set IFC grid north based on current Blender North Offset"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_ifc_grid_north(tool.Georeference)
|
||||
@@ -86,7 +86,7 @@ class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_blender_grid_north"
|
||||
bl_label = "Set Blender Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set Blender grid north"
|
||||
bl_description = "Set Blender North Offset based on current IFC grid north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_blender_grid_north(tool.Georeference)
|
||||
@@ -110,7 +110,7 @@ class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Move curson location to the specified coordinates"
|
||||
bl_description = "Move cursor location to the specified coordinates"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -125,7 +125,7 @@ class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_true_north"
|
||||
bl_label = "Set IFC True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set IFC True north"
|
||||
bl_description = "Set IFC true north based on current Blender North Offset"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_ifc_true_north(tool.Georeference)
|
||||
@@ -135,7 +135,7 @@ class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_blender_true_north"
|
||||
bl_label = "Set Blender True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set Blender true north"
|
||||
bl_description = "Set Blender North Offset based on current IFC true north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_blender_true_north(tool.Georeference)
|
||||
@@ -187,3 +187,20 @@ class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_angle_to_coord(tool.Georeference, type=self.type)
|
||||
|
||||
|
||||
class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.import_plot"
|
||||
bl_label = "Import Plot"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Import plot"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
core.import_plot(tool.Georeference, filepath=self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
@@ -78,10 +78,14 @@ class BIM_PT_gis(Panel):
|
||||
def draw_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.label(text="IFC2X3 Fallback In Use", icon="ERROR")
|
||||
|
||||
if not GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Not Georeferenced")
|
||||
if tool.Ifc.get_schema != "IFC2X3":
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
|
||||
if props.has_blender_offset:
|
||||
@@ -110,8 +114,9 @@ class BIM_PT_gis(Panel):
|
||||
if GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
|
||||
for key, value in GeoreferenceData.data["projected_crs"].items():
|
||||
if not value:
|
||||
@@ -184,4 +189,4 @@ class BIM_PT_gis_utilities(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
|
||||
@@ -63,7 +63,7 @@ class EnableEditingLayer(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMLayerProperties
|
||||
props.layer_attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
|
||||
props.active_layer_id = self.layer
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -173,6 +173,8 @@ class ObjectMaterialData:
|
||||
items = cls.material.MaterialProfiles
|
||||
elif cls.material.is_a("IfcMaterialConstituentSet"):
|
||||
items = cls.material.MaterialConstituents
|
||||
elif cls.material.is_a("IfcMaterialList"):
|
||||
items = cls.material.Materials
|
||||
|
||||
icon = "LAYER_ACTIVE"
|
||||
if "Layer" in cls.material.is_a():
|
||||
@@ -191,7 +193,9 @@ class ObjectMaterialData:
|
||||
data["name"] = "No Profile"
|
||||
if item.is_a("IfcMaterialLayer"):
|
||||
data["name"] += f" ({item.LayerThickness})"
|
||||
if not item.is_a("IfcMaterialList"):
|
||||
if item.is_a("IfcMaterial"):
|
||||
data["material"] = item.Name or "Unnamed"
|
||||
else:
|
||||
data["material"] = item.Material.Name or "Unnamed"
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
@@ -174,7 +174,7 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
core.assign_material(tool.Ifc, tool.Material, material_type= self.material_type , objects=objects )
|
||||
core.assign_material(tool.Ifc, tool.Material, material_type=self.material_type, objects=objects)
|
||||
|
||||
|
||||
class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -185,7 +185,7 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
core.unassign_material(tool.Ifc, tool.Material, objects=objects )
|
||||
core.unassign_material(tool.Ifc, tool.Material, objects=objects)
|
||||
|
||||
|
||||
class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -350,10 +350,8 @@ class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"material.add_list_item",
|
||||
self.file,
|
||||
**{
|
||||
"material_list": self.file.by_id(self.list_item_set),
|
||||
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
|
||||
},
|
||||
material_list=self.file.by_id(self.list_item_set),
|
||||
material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat
|
||||
class BIM_PT_materials(Panel):
|
||||
bl_label = "Materials"
|
||||
bl_idname = "BIM_PT_materials"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
bl_parent_id = "BIM_PT_tab_materials"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -45,11 +45,10 @@ class BIM_PT_materials(Panel):
|
||||
self.props = context.scene.BIMMaterialProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Materials Found".format(MaterialsData.data["total_materials"]), icon="MATERIAL")
|
||||
row.label(text="{} Materials".format(MaterialsData.data["total_materials"]), icon="NODE_MATERIAL")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "material_type", text="")
|
||||
row.operator("bim.load_materials", text="", icon="IMPORT")
|
||||
return
|
||||
@@ -58,6 +57,8 @@ class BIM_PT_materials(Panel):
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.material_type == "IfcMaterial":
|
||||
if not self.props.active_material_id:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
@@ -66,13 +67,10 @@ class BIM_PT_materials(Panel):
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
|
||||
self.draw_editable_material_attributes_ui()
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
else:
|
||||
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
@@ -285,9 +283,11 @@ class BIM_PT_object_material(Panel):
|
||||
setattr(op, f"{ObjectMaterialData.data['set_item_name']}_index", index)
|
||||
|
||||
def draw_read_only_set_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Name")
|
||||
row.label(text=ObjectMaterialData.data["set"]["name"])
|
||||
if ObjectMaterialData.data["material_class"] != "IfcMaterialList":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Name")
|
||||
row.label(text=ObjectMaterialData.data["set"]["name"])
|
||||
|
||||
if ObjectMaterialData.data["set"]["description"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Description")
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.misc as core
|
||||
import blenderbim.core.geometry as core_geometry
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector, Matrix, Euler
|
||||
|
||||
@@ -140,7 +141,51 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
|
||||
return context.selected_objects and tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
core.split_along_edge(tool.Misc, cutter=context.active_object, objs=context.selected_objects)
|
||||
cutter = context.active_object
|
||||
objs = [o for o in context.selected_objects if o != cutter]
|
||||
|
||||
# Splitting only works on meshes
|
||||
for obj in objs:
|
||||
# You cannot split meshes if the representation is mapped.
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
relating_type = tool.Root.get_element_type(element)
|
||||
if relating_type and tool.Root.does_type_have_representations(relating_type):
|
||||
bpy.ops.bim.unassign_type(related_object=obj.name)
|
||||
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
core_geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
|
||||
if not tool.Geometry.is_meshlike(representation):
|
||||
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
|
||||
|
||||
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
|
||||
for obj in new_objs:
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
for obj in objs:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
core_geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
|
||||
class GetConnectedSystemElements(bpy.types.Operator, Operator):
|
||||
@@ -205,46 +250,50 @@ class DrawSystemArrows(bpy.types.Operator, Operator):
|
||||
return context.selected_objects and tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
|
||||
curve.data.dimensions = "3D"
|
||||
context.scene.collection.objects.link(curve)
|
||||
sinks = []
|
||||
sources = []
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
sources = []
|
||||
sinks = []
|
||||
for rel in getattr(element, "HasPorts", []) or []:
|
||||
if rel.RelatingPort.FlowDirection == "SOURCE":
|
||||
sources.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
elif rel.RelatingPort.FlowDirection == "SINK":
|
||||
sinks.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
sources_current = []
|
||||
sinks_current = []
|
||||
|
||||
for port in tool.System.get_ports(element):
|
||||
local_placement = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
|
||||
m = self.get_absolute_matrix(local_placement)
|
||||
if port.FlowDirection == "SOURCE":
|
||||
sources_current.append(m)
|
||||
elif port.FlowDirection == "SINK":
|
||||
sinks_current.append(m)
|
||||
else:
|
||||
sources.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
sinks.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
for sink in sinks:
|
||||
for source in sources:
|
||||
sources_current.append(m)
|
||||
sinks_current.append(m)
|
||||
|
||||
if sinks_current or sources_current:
|
||||
sinks.append(sinks_current)
|
||||
sources.append(sources_current)
|
||||
|
||||
if not sinks:
|
||||
self.report({"INFO"}, "No sinks/sources found for selected objects.")
|
||||
return {"FINISHED"}
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
|
||||
curve.data.dimensions = "3D"
|
||||
curve.show_in_front = True
|
||||
context.scene.collection.objects.link(curve)
|
||||
|
||||
for i in range(len(sinks)):
|
||||
for sink in sinks[i]:
|
||||
for source in sources[i]:
|
||||
polyline = curve.data.splines.new("POLY")
|
||||
polyline.points.add(1)
|
||||
polyline.points[0].co = (Matrix(sink).translation * unit_scale).to_4d()
|
||||
polyline.points[1].co = (Matrix(source).translation * unit_scale).to_4d()
|
||||
tool.Blender.select_and_activate_single_object(context, curve)
|
||||
|
||||
def get_absolute_matrix(self, matrix):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
|
||||
@@ -50,8 +50,9 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row()
|
||||
row.operator("bim.patch_non_parametric_mep_segment")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
|
||||
row.operator("bim.edit_sketch_extrusion_profile", text="", icon="FILE_REFRESH")
|
||||
row.operator("bim.disable_editing_sketch_extrusion_profile", text="", icon="CANCEL")
|
||||
row = layout.row()
|
||||
row.operator("bim.import_plot", text="Import Plot Coordinates", icon="FILE_FOLDER")
|
||||
|
||||
@@ -28,6 +28,7 @@ from . import (
|
||||
roof,
|
||||
slab,
|
||||
space,
|
||||
covering,
|
||||
stair,
|
||||
window,
|
||||
opening,
|
||||
@@ -78,6 +79,7 @@ classes = (
|
||||
opening.AddPotentialHalfSpaceSolid,
|
||||
opening.AddPotentialOpening,
|
||||
opening.EditOpenings,
|
||||
opening.CloneOpening,
|
||||
opening.FlipFill,
|
||||
opening.HideBooleans,
|
||||
opening.HideOpenings,
|
||||
@@ -105,6 +107,9 @@ classes = (
|
||||
slab.SetArcIndex,
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
covering.AddInstanceFlooringCoveringsFromWalls,
|
||||
covering.AddInstanceFlooringCoveringFromCursor,
|
||||
covering.RegenSelectedFlooringObject,
|
||||
space.ToggleSpaceVisibility,
|
||||
mep.FitFlowSegments,
|
||||
mep.RegenerateDistributionElement,
|
||||
@@ -178,6 +183,7 @@ classes = (
|
||||
roof.SetGableRoofEdgeAngle,
|
||||
mep.MEPAddObstruction,
|
||||
mep.MEPAddTransition,
|
||||
mep.MEPAddBend,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
@@ -169,7 +169,6 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item]["count"] = 1
|
||||
|
||||
if (self.keep_objs) & (self.item < (len(data) - 1)):
|
||||
self.report(
|
||||
@@ -188,6 +187,8 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Blender.Modifier.Array.bake_children_transform(element, self.item)
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, False)
|
||||
|
||||
if not self.keep_objs:
|
||||
data[self.item]["count"] = 1
|
||||
tool.Model.regenerate_array(parent, data, self.keep_objs)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
@@ -206,15 +207,29 @@ class SelectArrayParent(bpy.types.Operator):
|
||||
bl_idname = "bim.select_array_parent"
|
||||
bl_label = "Select Array Parent"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.StringProperty(description="Parent Element GUID")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
cls.poll_message_set("No active object selected")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
element = tool.Ifc.get().by_guid(self.parent)
|
||||
except:
|
||||
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
|
||||
object = context.active_object
|
||||
element = tool.Ifc.get_entity(object)
|
||||
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
if not array_pset:
|
||||
self.report({"ERROR"}, f"Object is not part of an array.")
|
||||
return {"CANCELLED"}
|
||||
obj = tool.Ifc.get_object(element)
|
||||
|
||||
try:
|
||||
parent_element = tool.Ifc.get().by_guid(array_pset["Parent"])
|
||||
except:
|
||||
self.report({"ERROR"}, f"Couldn't find array parent by guid '{array_pset['Parent']}'")
|
||||
return {"CANCELLED"}
|
||||
|
||||
obj = tool.Ifc.get_object(parent_element)
|
||||
if obj:
|
||||
tool.Blender.select_and_activate_single_object(context, active_object=obj)
|
||||
return {"FINISHED"}
|
||||
@@ -224,13 +239,26 @@ class SelectAllArrayObjects(bpy.types.Operator):
|
||||
bl_idname = "bim.select_all_array_objects"
|
||||
bl_label = "Select All Array Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.StringProperty(description="Parent Element GUID")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
cls.poll_message_set("No active object selected")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
object = context.active_object
|
||||
element = tool.Ifc.get_entity(object)
|
||||
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
if not array_pset:
|
||||
self.report({"ERROR"}, f"Object is not part of an array.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
parent_element = tool.Ifc.get().by_guid(self.parent)
|
||||
parent_element = tool.Ifc.get().by_guid(array_pset["Parent"])
|
||||
except RuntimeError:
|
||||
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
|
||||
self.report({"ERROR"}, f"Couldn't find array parent by guid '{array_pset['Parent']}'")
|
||||
return {"CANCELLED"}
|
||||
|
||||
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.covering as core
|
||||
|
||||
class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_instance_flooring_covering_from_cursor"
|
||||
bl_label = "Add Flooring From Cursor"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Add a typed instance flooring covering from cursor position. Move the cursor position into the desired position, select the right space collection and run the operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
return tool.Ifc.get_entity(collection_obj)
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
if not collection_obj:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
spatial_element = tool.Ifc.get_entity(collection_obj)
|
||||
if not spatial_element:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
core.add_instance_flooring_covering_from_cursor(tool.Ifc, tool.Spatial, tool.Model, tool.Type, tool.Geometry)
|
||||
|
||||
class RegenSelectedFlooringObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.regen_selected_flooring_object"
|
||||
bl_label = "Regen Flooring"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Regen selected flooring object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
return element.is_a("IfcCovering")
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if not element.is_a("IfcCovering"):
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
core.regen_selected_flooring_object(tool.Ifc, tool.Spatial, tool.Model, tool.Type, tool.Geometry)
|
||||
|
||||
|
||||
class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_instance_flooring_coverings_from_walls"
|
||||
bl_label = "Add Flooring From Walls"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add instance flooring coverings from selected walls. The active object must be a wall and layered vertically"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element:
|
||||
if element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
# walls (i.e. prismatic) in the active object storey.
|
||||
# In order to run, the active object must be a wall and
|
||||
# there must be selected walls
|
||||
|
||||
active_obj = bpy.context.active_object
|
||||
if not active_obj:
|
||||
self.report({"ERROR"}, "No active object. Please select a wall")
|
||||
return
|
||||
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element and not element.is_a("IfcWall"):
|
||||
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
|
||||
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if not container:
|
||||
self.report({"ERROR"}, "The wall is not contained.")
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||
return
|
||||
|
||||
core.add_instance_flooring_coverings_from_walls(tool.Ifc, tool.Spatial, tool.Collector, tool.Geometry)
|
||||
|
||||
@@ -53,6 +53,7 @@ class AuthoringData:
|
||||
cls.data["ifc_element_type"] = cls.ifc_element_type
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
|
||||
cls.data["predefined_type"] = cls.predefined_type()
|
||||
cls.data["type_class"] = cls.type_class()
|
||||
|
||||
# only after .type_class()
|
||||
@@ -247,6 +248,15 @@ class AuthoringData:
|
||||
return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results]
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def predefined_type(cls):
|
||||
relating_type_id = cls.props.relating_type_id
|
||||
if not relating_type_id:
|
||||
return
|
||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
predefined_type = relating_type.PredefinedType
|
||||
return predefined_type
|
||||
|
||||
@classmethod
|
||||
def selected_material_usages(cls):
|
||||
selected_usages = {}
|
||||
|
||||
@@ -68,6 +68,8 @@ def update_door_modifier_representation(context, obj):
|
||||
},
|
||||
}
|
||||
|
||||
previously_active_context = tool.Geometry.get_active_representation_context(obj)
|
||||
|
||||
# ELEVATION_VIEW representation
|
||||
profile = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW")
|
||||
if profile:
|
||||
@@ -78,6 +80,7 @@ def update_door_modifier_representation(context, obj):
|
||||
tool.Model.replace_object_ifc_representation(profile, obj, elevation_representation)
|
||||
|
||||
# MODEL_VIEW representation
|
||||
# (Model/Body defined only BEFORE Plan/Body to prevent #2744)
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
representation_data["context"] = body
|
||||
model_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
|
||||
@@ -113,14 +116,34 @@ def update_door_modifier_representation(context, obj):
|
||||
)
|
||||
tool.Model.replace_object_ifc_representation(plan_annotation, obj, plan_representation)
|
||||
|
||||
if plan_body or plan_annotation:
|
||||
# adding switch representation at the end instead of changing order of representations
|
||||
# to prevent #2744
|
||||
core.switch_representation(
|
||||
# adding switch representation at the end instead of changing order of representations
|
||||
# to prevent #2744
|
||||
if tool.Geometry.get_active_representation_context(obj) != previously_active_context:
|
||||
previously_active_representation = ifcopenshell.util.representation.get_representation(
|
||||
element,
|
||||
previously_active_context.ContextType,
|
||||
previously_active_context.ContextIdentifier,
|
||||
previously_active_context.TargetView,
|
||||
)
|
||||
|
||||
if not previously_active_representation:
|
||||
# we assume there is no representation because it was
|
||||
# Plan/Annotation/PLAN_VIEW
|
||||
previously_active_context = ifcopenshell.util.representation.get_context(
|
||||
ifc_file, "Plan", "Body", "PLAN_VIEW"
|
||||
)
|
||||
previously_active_representation = ifcopenshell.util.representation.get_representation(
|
||||
element,
|
||||
previously_active_context.ContextType,
|
||||
previously_active_context.ContextIdentifier,
|
||||
previously_active_context.TargetView,
|
||||
)
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=model_representation,
|
||||
representation=previously_active_representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
@@ -130,11 +153,11 @@ def update_door_modifier_representation(context, obj):
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.OperationType = props.door_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width / si_conversion
|
||||
occurence.OverallHeight = props.overall_height / si_conversion
|
||||
# occurrences attributes
|
||||
occurrences = tool.Ifc.get_all_element_occurrences(element)
|
||||
for occurrence in occurrences:
|
||||
occurrence.OverallWidth = props.overall_width / si_conversion
|
||||
occurrence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
|
||||
|
||||
@@ -29,16 +29,18 @@ import ifcopenshell.util.system
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import mathutils.geometry
|
||||
import numpy as np
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, degrees, radians
|
||||
from math import pi, degrees, radians, sin, cos, asin, tan
|
||||
from copy import copy
|
||||
from mathutils import Vector, Matrix
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
from blenderbim.bim.module.model.profile import DumbProfileJoiner
|
||||
from blenderbim.tool.cad import VTX_PRECISION
|
||||
|
||||
V = lambda *x: Vector([float(i) for i in x])
|
||||
|
||||
@@ -46,7 +48,8 @@ V = lambda *x: Vector([float(i) for i in x])
|
||||
class RegenerateDistributionElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.regenerate_distribution_element"
|
||||
bl_description = (
|
||||
"Regenerates the positions and segment lengths of a distribution element and all connected elements."
|
||||
"Regenerates the positions and segment lengths of a distribution element and all connected elements.\n"
|
||||
"Will try to adjust as less elements as possible, never rotate them. Segments will also try to change their length to fit"
|
||||
)
|
||||
bl_label = "Regenerate Distribution Element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -73,29 +76,68 @@ class RegenerateDistributionElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
[e for e in ifcopenshell.util.system.get_connected_from(element) if e not in processed_elements]
|
||||
)
|
||||
|
||||
if len(connected) == 1:
|
||||
extend_branch(list(connected)[0], branch, element)
|
||||
else:
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
|
||||
return branch
|
||||
|
||||
queue = extend_branch(current_element, [])[0]["children"]
|
||||
extended_branch = extend_branch(current_element, [])
|
||||
queue = extended_branch[0]["children"]
|
||||
|
||||
# import pprint
|
||||
# pprint.pprint(queue)
|
||||
|
||||
def get_connected_ports_between(element1, element2):
|
||||
ports1 = tool.System.get_ports(element1)
|
||||
ports2 = tool.System.get_ports(element2)
|
||||
|
||||
for p in ports1:
|
||||
connected_port = tool.System.get_connected_port(p)
|
||||
# in IFC2X3 there is no PredefinedType
|
||||
if getattr(p, "PredefinedType", None) == "WIRELESS":
|
||||
continue
|
||||
if connected_port in ports2:
|
||||
return p, connected_port
|
||||
|
||||
return None, None
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
def process_branch(branch):
|
||||
for branch_element in branch:
|
||||
element = branch_element["element"]
|
||||
print("processing", element)
|
||||
predecessor = branch_element["predecessor"]
|
||||
if False: # If the element does not need to be transformed, return early.
|
||||
return
|
||||
|
||||
# Perform the extend, translate, rotate, etc the element as necessary based on the predecessor.
|
||||
# For segments, prioritise extensions instead of translations.
|
||||
# For everything else, only translate. No rotation.
|
||||
# For everything besides segments, only translate. No rotation.
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj_pred = tool.Ifc.get_object(predecessor)
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
if tool.Ifc.is_moved(obj_pred):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj_pred)
|
||||
|
||||
port, port_pred = get_connected_ports_between(element, predecessor)
|
||||
port_matrix_pred = tool.Model.get_element_matrix(port_pred)
|
||||
|
||||
# Only segments can be extended
|
||||
# extension for them takes priority over translation
|
||||
if element.is_a("IfcFlowSegment"):
|
||||
DumbProfileJoiner().join_E(obj, port_matrix_pred.translation * si_conversion)
|
||||
context.view_layer.update() # update since extrusion might involve changing object's location
|
||||
|
||||
port_martix = tool.Model.get_element_matrix(port)
|
||||
port_location = port_martix.translation
|
||||
port_location_pred = port_matrix_pred.translation
|
||||
if not tool.Cad.are_vectors_equal(port_location, port_location_pred):
|
||||
obj.location += (port_location_pred - port_location) * si_conversion
|
||||
context.view_layer.update() # otherwise tool.Ifc.is_moved won't get triggered
|
||||
else:
|
||||
# If the element does not need to be transformed, return early.
|
||||
return
|
||||
|
||||
for child_branch in branch_element["children"]:
|
||||
process_branch(child_branch)
|
||||
|
||||
@@ -161,6 +203,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
is_on_axis2 = tool.Cad.is_point_on_edge(intersect2, axis2)
|
||||
if not is_on_axis1 and not is_on_axis2:
|
||||
fitting_type = "BEND"
|
||||
bpy.ops.bim.mep_add_bend()
|
||||
elif is_on_axis1 and is_on_axis2:
|
||||
fitting_type = "CROSS"
|
||||
else:
|
||||
@@ -168,6 +211,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif total_profiles == 2:
|
||||
if is_parallel:
|
||||
fitting_type = "TRANSITION"
|
||||
bpy.ops.bim.mep_add_transition()
|
||||
|
||||
elif total_selected_objs == 3:
|
||||
if total_profiles > 1:
|
||||
@@ -229,6 +273,10 @@ class MEPGenerator:
|
||||
if port_position == "end_port":
|
||||
tool.Model.edit_element_placement(port, end_port_matrix)
|
||||
|
||||
continue
|
||||
|
||||
# NOTE: currently this functionality is moved to bim.regenerate_distribution_element
|
||||
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
if not connected_port:
|
||||
continue
|
||||
@@ -289,7 +337,7 @@ class MEPGenerator:
|
||||
class_name = "".join(split_camel_case(element.is_a())[:-1] + [mep_class_type])
|
||||
return class_name
|
||||
|
||||
def get_compatible_fitting_type(self, segment_or_segments, port_or_ports, predefined_type):
|
||||
def get_compatible_fitting_type(self, segment_or_segments, port_or_ports, predefined_type, bbim_data=None):
|
||||
"""
|
||||
returns a dict of compatible fitting_type and start_port_match flag to correctly place the fitting.
|
||||
|
||||
@@ -302,7 +350,12 @@ class MEPGenerator:
|
||||
|
||||
There lies the problem that it won't be
|
||||
able to identify the fittings that were not yet connected to any segments yet.
|
||||
|
||||
|
||||
`bbim_data` is used to find compatible fitting build with BBIM parametrically (BBIM_Fitting pset).
|
||||
All data in `bbim_data` supposed to be in project units.
|
||||
"""
|
||||
|
||||
if not isinstance(segment_or_segments, collections.abc.Iterable):
|
||||
segments = [segment_or_segments]
|
||||
ports = [port_or_ports]
|
||||
@@ -310,14 +363,67 @@ class MEPGenerator:
|
||||
segments = segment_or_segments
|
||||
ports = port_or_ports
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
precision = VTX_PRECISION / si_conversion
|
||||
angle_precision = degrees(precision)
|
||||
start_port_match = True
|
||||
|
||||
segments_data = []
|
||||
for segment, port in zip(segments, ports, strict=True):
|
||||
segment_type = ifcopenshell.util.element.get_type(segment)
|
||||
# if segment doesn't have type we cannot check compatibility by available occurences
|
||||
# if segment doesn't have type we cannot check compatibility by available occurrences
|
||||
if segment_type is None:
|
||||
return
|
||||
segments_data.append((segment_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
def compatible_with_bbim_data(fitting_type):
|
||||
nonlocal start_port_match
|
||||
start_port_match = True
|
||||
if not bbim_data:
|
||||
return True
|
||||
fitting_type_obj = tool.Ifc.get_object(fitting_type)
|
||||
fitting_bbim_data = tool.Model.get_modeling_bbim_pset_data(fitting_type_obj, "BBIM_Fitting")
|
||||
if not fitting_bbim_data:
|
||||
return False
|
||||
|
||||
fitting_bbim_data = fitting_bbim_data["data_dict"]
|
||||
|
||||
def compare_value(key, second_key=None):
|
||||
second_key = second_key or key
|
||||
requested_value = bbim_data[key]
|
||||
fitting_value = fitting_bbim_data[second_key]
|
||||
|
||||
if isinstance(requested_value, float):
|
||||
compare_precision = angle_precision if key == "angle" else precision
|
||||
compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision)
|
||||
elif isinstance(fitting_value, list):
|
||||
compare = tool.Cad.are_vectors_equal(requested_value, Vector(fitting_value), precision)
|
||||
return compare
|
||||
|
||||
ignore_keys = []
|
||||
if predefined_type == "BEND":
|
||||
ignore_keys.extend(("start_length", "end_length"))
|
||||
# for bends there is a special case when lengths might not match
|
||||
# but fitting is still compatible if we flip it
|
||||
# since bend connects segments of the same type
|
||||
default_lengths_match = compare_value("start_length") and compare_value("end_length")
|
||||
if not default_lengths_match:
|
||||
switched_lengths_match = compare_value("start_length", "end_length") and compare_value(
|
||||
"end_length", "start_length"
|
||||
)
|
||||
if switched_lengths_match:
|
||||
start_port_match = False
|
||||
else:
|
||||
return False
|
||||
|
||||
for key in bbim_data:
|
||||
if key in ignore_keys:
|
||||
continue
|
||||
if not compare_value(key):
|
||||
return False
|
||||
return True
|
||||
|
||||
def are_connected_elements_compatible(segments_data, fitting_data):
|
||||
# prevent arguments mutation, not using deepcopy because of the errors with ifc elements
|
||||
segments_data = [copy(i) for i in segments_data]
|
||||
@@ -344,7 +450,7 @@ class MEPGenerator:
|
||||
|
||||
# NOTE: I have a feeling that there are cases where order
|
||||
# in which we're checking the segments is important
|
||||
# but I couldn't pin it down exact cases
|
||||
# but I couldn't pin it down to exact cases
|
||||
for test_segment_data in fitting_data[:]:
|
||||
for base_segment_data in segments_data:
|
||||
if not are_segments_compatible(test_segment_data, base_segment_data):
|
||||
@@ -355,42 +461,62 @@ class MEPGenerator:
|
||||
return len(segments_data) == 0
|
||||
|
||||
def pack_return_data(fitting_type, ports, segments_data):
|
||||
packed_data = {"fitting_type": fitting_type}
|
||||
|
||||
if predefined_type == "OBSTRUCTION":
|
||||
return packed_data
|
||||
|
||||
for port in ports:
|
||||
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||
if tool.Cad.is_x(port_local_position.length, 0.0):
|
||||
start_port = port
|
||||
break
|
||||
|
||||
connected_port = tool.System.get_connected_port(start_port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
return {"fitting_type": fitting_type, "start_port_match": element_type == segments_data[0][0]}
|
||||
packed_data["start_port_match"] = element_type == segments_data[0][0] and start_port_match
|
||||
|
||||
return packed_data
|
||||
|
||||
fitting_types = tool.Ifc.get().by_type(self.get_mep_element_class_name(segments[0], "FittingType"))
|
||||
for fitting_type in fitting_types:
|
||||
if fitting_type.PredefinedType != predefined_type:
|
||||
continue
|
||||
fittings = tool.Ifc.get_all_element_occurences(fitting_type)
|
||||
fittings = tool.Ifc.get_all_element_occurrences(fitting_type)
|
||||
if not fittings:
|
||||
continue
|
||||
fitting = fittings[0]
|
||||
|
||||
ports = ifcopenshell.util.system.get_ports(fitting)
|
||||
fitting_data = []
|
||||
fitting_connected_to_none_type = False
|
||||
for port in ports:
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
if element_type is None:
|
||||
fitting_connected_to_none_type = True
|
||||
for fitting in fittings:
|
||||
ports = ifcopenshell.util.system.get_ports(fitting)
|
||||
fitting_data = []
|
||||
skipped_the_occurrence = False
|
||||
for port in ports:
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
|
||||
# fitting port is not connected to anything
|
||||
if not connected_port:
|
||||
skipped_the_occurrence = True
|
||||
break
|
||||
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
|
||||
# fitting is connected to none type
|
||||
if element_type is None:
|
||||
skipped_the_occurrence = True
|
||||
break
|
||||
|
||||
fitting_data.append((element_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
# if we skipped the occurrence we still need to check other occurrences
|
||||
# otherwise checking 1 occurrence is enough
|
||||
if not skipped_the_occurrence:
|
||||
if compatible_with_bbim_data(fitting_type) and are_connected_elements_compatible(
|
||||
segments_data, fitting_data
|
||||
):
|
||||
return pack_return_data(fitting_type, ports, segments_data)
|
||||
break
|
||||
fitting_data.append((element_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
if fitting_connected_to_none_type:
|
||||
continue
|
||||
|
||||
if are_connected_elements_compatible(segments_data, fitting_data):
|
||||
return pack_return_data(fitting_type, ports, segments_data)
|
||||
|
||||
def create_obstruction_type(self, segment):
|
||||
# code is very similar to "bim.add_type"
|
||||
@@ -453,13 +579,15 @@ class MEPGenerator:
|
||||
obstruction_type = self.create_obstruction_type(segment)
|
||||
|
||||
profile_joiner = DumbProfileJoiner()
|
||||
# create obstruction occurence and setup it's length and port
|
||||
# create obstruction occurrence and setup it's length and port
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=obstruction_type.id())
|
||||
obstruction_obj = bpy.context.active_object
|
||||
obstruction_obj.matrix_world = segment_matrix
|
||||
|
||||
profile_joiner.set_depth(obstruction_obj, length)
|
||||
# NOTE: we add ports to the obstruction occurence and not to the type
|
||||
# since it's material profile based like segments
|
||||
obstruction_port = tool.System.add_ports(
|
||||
obstruction_obj,
|
||||
add_start_port=not at_segment_start,
|
||||
@@ -525,10 +653,13 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
start_length: bpy.props.FloatProperty(
|
||||
name="Start Length", description="Transition start length in SI units", default=0.1, subtype="DISTANCE"
|
||||
name="Start Length", description="Transition start length in SI units", default=0.1, subtype="DISTANCE", min=0
|
||||
)
|
||||
end_length: bpy.props.FloatProperty(
|
||||
name="End Length", description="Transition end length in SI units", default=0.1, subtype="DISTANCE"
|
||||
name="End Length", description="Transition end length in SI units", default=0.1, subtype="DISTANCE", min=0
|
||||
)
|
||||
angle: bpy.props.FloatProperty(
|
||||
name="Transition Angle", description="Transition angle in degrees", default=pi / 6, subtype="ANGLE", min=0
|
||||
)
|
||||
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
||||
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
||||
@@ -570,57 +701,142 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
start_axis = tool.Model.get_flow_segment_axis(start_object)
|
||||
end_axis = tool.Model.get_flow_segment_axis(end_object)
|
||||
start_object_rotation = start_object.matrix_world.to_quaternion()
|
||||
start_object_z_basis = start_object_rotation.to_matrix().col[2] # z basis vector
|
||||
keep_only_z_axis = lambda p_ws: p_ws.dot(start_object_z_basis) * start_object_z_basis
|
||||
|
||||
# TODO: support cases when segments are partially or completely overlapping each other
|
||||
if not tool.Cad.are_edges_collinear(start_axis, end_axis):
|
||||
self.report({"ERROR"}, f"Failed to add transition - non collinear segments are not yet supported.")
|
||||
if not tool.Cad.are_edges_parallel(start_axis, end_axis):
|
||||
self.report({"ERROR"}, f"Failed to add transition - segments are not parallel.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# TODO: support different profiles rotation by local Z
|
||||
# check rotation difference
|
||||
end_object_rotation = end_object.matrix_world.to_quaternion()
|
||||
rotation_difference_z = (
|
||||
start_object.matrix_world.to_quaternion().rotation_difference(end_object_rotation).to_euler().z
|
||||
)
|
||||
|
||||
def is_multiple_of_pi(value):
|
||||
n = round(value / pi)
|
||||
return tool.Cad.is_x(abs(value - n * pi), 0)
|
||||
|
||||
if not is_multiple_of_pi(rotation_difference_z):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"There is some rotation difference between profiles by local Z axis: "
|
||||
f"{round(degrees(rotation_difference_z))} deg, this kind of transition is not yet supported.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# setup start / end points
|
||||
start_segment_data = MEPGenerator().get_segment_data(start_element)
|
||||
end_segment_data = MEPGenerator().get_segment_data(end_element)
|
||||
end_port = end_segment_data["start_port"]
|
||||
start_port = start_segment_data["end_port"]
|
||||
|
||||
points_ports_map = {
|
||||
start_segment_data["start_point"]: start_segment_data["start_port"],
|
||||
start_segment_data["end_point"]: start_segment_data["end_port"],
|
||||
end_segment_data["start_point"]: end_segment_data["start_port"],
|
||||
end_segment_data["end_point"]: end_segment_data["end_port"],
|
||||
}
|
||||
|
||||
start_point, end_point = tool.Cad.closest_points(
|
||||
# transition points
|
||||
(start_point, end_point), (first_segment_start, second_segment_end) = tool.Cad.closest_points(
|
||||
(start_segment_data["start_point"], start_segment_data["end_point"]),
|
||||
(end_segment_data["start_point"], end_segment_data["end_point"]),
|
||||
)
|
||||
transition_dir = (end_point - start_point).normalized()
|
||||
start_port = points_ports_map[start_point]
|
||||
end_port = points_ports_map[end_point]
|
||||
start_point_on_origin = start_point == start_segment_data["start_point"]
|
||||
start_connection = "ATSTART" if start_point_on_origin else "ATEND"
|
||||
start_segment_sign = -1 if start_point_on_origin else 1
|
||||
|
||||
end_point_on_origin = end_point == end_segment_data["start_point"]
|
||||
end_connection = "ATSTART" if end_point_on_origin else "ATEND"
|
||||
|
||||
# figure profile offset
|
||||
base_transition_dir = keep_only_z_axis(end_point - start_point).normalized()
|
||||
flip_profile_offset = base_transition_dir.dot(start_object_z_basis) < 0
|
||||
|
||||
if tool.Cad.are_edges_collinear(start_axis, end_axis):
|
||||
profile_offset = V(0, 0)
|
||||
else:
|
||||
to_start_object_space = start_object_rotation.inverted()
|
||||
profile_offset = (
|
||||
(to_start_object_space @ end_object.location) - (to_start_object_space @ start_object.location)
|
||||
).xy
|
||||
profile_offset = profile_offset / si_conversion
|
||||
if flip_profile_offset:
|
||||
profile_offset *= V(1, -1)
|
||||
|
||||
# world space profile offset
|
||||
profile_offset_si = (profile_offset * si_conversion).to_3d()
|
||||
profile_offset_ws = start_object_rotation @ profile_offset_si
|
||||
|
||||
def get_segments_length():
|
||||
start_dir = (start_point - first_segment_start).normalized()
|
||||
segments_vector = second_segment_end - first_segment_start
|
||||
return segments_vector.dot(start_dir)
|
||||
|
||||
entire_length = get_segments_length()
|
||||
|
||||
# can't rely on (end_point-start_point) here because
|
||||
# transition might change the segments length and therefore direction will be changed
|
||||
segments_dir = (start_point - first_segment_start).normalized()
|
||||
|
||||
# add transition representation
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
rep, transition_data = builder.mep_transition_shape(
|
||||
start_element, end_element, self.start_length / si_conversion, self.end_length / si_conversion
|
||||
start_element,
|
||||
end_element,
|
||||
self.start_length / si_conversion,
|
||||
self.end_length / si_conversion,
|
||||
angle=degrees(self.angle),
|
||||
profile_offset=profile_offset,
|
||||
)
|
||||
|
||||
if not rep:
|
||||
self.report({"ERROR"}, f"Failed to add transition - this kind of profiles is not yet supported.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
middle_point = (start_point + end_point) / 2
|
||||
full_transition_length = transition_data["full_transition_length"] * si_conversion
|
||||
start_segment_extend_point = middle_point - transition_dir * full_transition_length / 2
|
||||
end_segment_extend_point = middle_point + transition_dir * full_transition_length / 2
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point)
|
||||
if full_transition_length >= entire_length:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Failed to add transition - transition length is larger the segments and the distance between them.\n"
|
||||
+ f"Transition length: {full_transition_length:.2f}m, segments length: {entire_length:.2f}m",
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# calculate bunch of points to for adjustments
|
||||
middle_point = keep_only_z_axis((start_point + end_point) / 2 - start_point) + start_point
|
||||
start_segment_extend_point = middle_point - segments_dir * full_transition_length / 2
|
||||
end_segment_extend_point = middle_point + segments_dir * full_transition_length / 2 + profile_offset_ws
|
||||
transition_dir = keep_only_z_axis(end_segment_extend_point - start_segment_extend_point).normalized()
|
||||
|
||||
# adjust the segments
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point, start_connection)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point, end_connection)
|
||||
|
||||
# For bbim transitions, there is small convention that:
|
||||
# - start_length segment positioned at the start of the transition's Z-axis.
|
||||
# - end_length segment positioned at the of it.
|
||||
# this is why we sort the lengths in parametric data too
|
||||
parametric_data = {
|
||||
"start_length": (self.start_length if start_segment_sign == 1 else self.end_length) / si_conversion,
|
||||
"end_length": (self.end_length if start_segment_sign == 1 else self.start_length) / si_conversion,
|
||||
"profile_offset": profile_offset,
|
||||
"angle": degrees(self.angle),
|
||||
}
|
||||
|
||||
# find the compatible fitting type
|
||||
fitting_data = MEPGenerator().get_compatible_fitting_type(
|
||||
[start_element, end_element], [start_port, end_port], "TRANSITION"
|
||||
[start_element, end_element], [start_port, end_port], "TRANSITION", bbim_data=parametric_data
|
||||
)
|
||||
|
||||
transition_type = fitting_data["fitting_type"] if fitting_data else None
|
||||
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
||||
|
||||
if not transition_type:
|
||||
if transition_type:
|
||||
# TODO: handle the case without creating a representation in the first place?
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
||||
else: # create new fitting type if nothing is compatible
|
||||
mesh = bpy.data.meshes.new("Transition")
|
||||
obj = bpy.data.objects.new("Transition", mesh)
|
||||
transition_type = blenderbim.core.root.assign_class(
|
||||
@@ -634,6 +850,7 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
tool.Model.replace_object_ifc_representation(body, obj, rep)
|
||||
tool.Blender.remove_data_block(mesh)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=transition_type, name="BBIM_Fitting")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
@@ -641,27 +858,399 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset=pset,
|
||||
properties={"Data": json.dumps(transition_data, default=list)},
|
||||
)
|
||||
tool.System.add_ports(obj, offset_end_port=profile_offset_si)
|
||||
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
# create transition element
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=transition_type.id())
|
||||
transition_obj = bpy.context.active_object
|
||||
|
||||
# adjust transition segment rotation and location
|
||||
# required since we'll base our `transition_obj_dir` on this
|
||||
transition_obj.matrix_world = start_object.matrix_world
|
||||
context.view_layer.update()
|
||||
transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj))
|
||||
direction_match = tool.Cad.are_vectors_equal(transition_obj_dir, transition_dir)
|
||||
|
||||
# depending on transition direction we may need to flip it or attach it's origin to end segment
|
||||
# direction can be different depending on:
|
||||
# - order of the current segments
|
||||
# - order of the segments that were used with the same transition type before
|
||||
transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj))
|
||||
direction_match = tool.Cad.are_vectors_equal(transition_dir, transition_obj_dir)
|
||||
# if there are no mismatches or everything matches up we don't need to flip the transition
|
||||
if start_port_match != direction_match:
|
||||
transition_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "X")
|
||||
transition_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point
|
||||
|
||||
# add ports and connect them
|
||||
ports = tool.System.add_ports(transition_obj)
|
||||
ports = tool.System.get_ports(tool.Ifc.get_entity(transition_obj))
|
||||
if not start_port_match:
|
||||
start_port, end_port = end_port, start_port
|
||||
tool.Ifc.run("system.connect_port", port1=ports[0], port2=start_port, direction="NOTDEFINED")
|
||||
tool.Ifc.run("system.connect_port", port1=ports[1], port2=end_port, direction="NOTDEFINED")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.mep_add_bend"
|
||||
bl_label = "Add Bend"
|
||||
bl_description = "Adds a bend between two MEP elements. Elements are either provided by ID or selected in Blender"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
start_length: bpy.props.FloatProperty(
|
||||
name="Start Length", description="Bend start length in SI units", default=0.1, subtype="DISTANCE", min=0
|
||||
)
|
||||
end_length: bpy.props.FloatProperty(
|
||||
name="End Length", description="Bend end length in SI units", default=0.1, subtype="DISTANCE", min=0
|
||||
)
|
||||
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
||||
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
||||
radius: bpy.props.FloatProperty(
|
||||
"Bend Inner Radius", description="Bend inner radius in SI units", default=0.2, subtype="DISTANCE", min=0
|
||||
)
|
||||
|
||||
def _execute(self, context):
|
||||
start_element, end_element = None, None
|
||||
ifc_file = tool.Ifc.get()
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
|
||||
if self.start_segment_id and self.end_segment_id:
|
||||
start_element = ifc_file.by_id(self.start_segment_id)
|
||||
end_element = ifc_file.by_id(self.end_segment_id)
|
||||
start_object = tool.Ifc.get_object(start_element)
|
||||
end_object = tool.Ifc.get_object(end_element)
|
||||
|
||||
elif len(context.selected_objects) == 2:
|
||||
start_object = context.active_object
|
||||
end_object = next(o for o in context.selected_objects if o != context.active_object)
|
||||
start_element = tool.Ifc.get_entity(start_object)
|
||||
end_element = tool.Ifc.get_entity(end_object)
|
||||
if not start_element or not end_element:
|
||||
self.report({"ERROR"}, f"Two IFC elements should be selected for the bend.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
else:
|
||||
self.report({"ERROR"}, f"Two IFC elements should be provided for the bend.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# check rotation difference
|
||||
def rotation_difference_check():
|
||||
end_object_rotation = end_object.matrix_world.to_quaternion()
|
||||
rotation_difference = (
|
||||
start_object.matrix_world.to_quaternion().rotation_difference(end_object_rotation).to_euler()
|
||||
)
|
||||
|
||||
def is_multiple_of_pi(value):
|
||||
n = round(value / pi)
|
||||
return tool.Cad.is_x(abs(value - n * pi), 0)
|
||||
|
||||
if not is_multiple_of_pi(rotation_difference.z):
|
||||
error_msg = (
|
||||
"There is some rotation difference between profiles by local Z axis: "
|
||||
f"{round(degrees(rotation_difference.z))} deg, adding a bend is not possible."
|
||||
)
|
||||
return error_msg
|
||||
|
||||
if error_msg := rotation_difference_check():
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# check segments types
|
||||
def types_check():
|
||||
start_type = ifcopenshell.util.element.get_type(start_element)
|
||||
end_type = ifcopenshell.util.element.get_type(end_element)
|
||||
if not start_type or not end_type:
|
||||
return False
|
||||
return start_type == end_type
|
||||
|
||||
if not types_check():
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"Segments types do not match or one of the segments doesn't have type which is required for a bend.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
profile = tool.Model.get_flow_segment_profile(start_element)
|
||||
if not profile.is_a("IfcRectangleProfileDef") and not profile.is_a("IfcCircleProfileDef"):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"For now Only IfcRectangleProfileDef/IfcCircleProfileDef profiles supported for a bend, "
|
||||
f"the segments are {profile.is_a()}",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def get_dim(profile):
|
||||
if profile.is_a("IfcRectangleProfileDef"):
|
||||
return V(profile.XDim / 2, profile.YDim / 2)
|
||||
elif profile.is_a("IfcCircleProfileDef"):
|
||||
return V(profile.Radius, profile.Radius)
|
||||
return None
|
||||
|
||||
# setup start / end points
|
||||
start_object_rotation = start_object.matrix_world.to_quaternion().to_matrix()
|
||||
start_segment_data = MEPGenerator().get_segment_data(start_element)
|
||||
end_segment_data = MEPGenerator().get_segment_data(end_element)
|
||||
points_ports_map = {
|
||||
start_segment_data["start_point"]: start_segment_data["start_port"],
|
||||
start_segment_data["end_point"]: start_segment_data["end_port"],
|
||||
end_segment_data["start_point"]: end_segment_data["start_port"],
|
||||
end_segment_data["end_point"]: end_segment_data["end_port"],
|
||||
}
|
||||
|
||||
get_z_basis = lambda o: tool.Cad.get_basis_vector(o, 2)
|
||||
segments_intersection_ws = tool.Cad.intersect_edges(
|
||||
(start_object.location, start_object.location + get_z_basis(start_object)),
|
||||
(end_object.location, end_object.location + get_z_basis(end_object)),
|
||||
)[0]
|
||||
|
||||
start_point, first_segment_start = tool.Cad.closest_and_furthest_vectors(
|
||||
segments_intersection_ws, (start_segment_data["start_point"], start_segment_data["end_point"])
|
||||
)
|
||||
end_point, second_segment_end = tool.Cad.closest_and_furthest_vectors(
|
||||
segments_intersection_ws, (end_segment_data["start_point"], end_segment_data["end_point"])
|
||||
)
|
||||
|
||||
start_port = points_ports_map[start_point]
|
||||
end_port = points_ports_map[end_point]
|
||||
start_point_on_origin = start_point == start_segment_data["start_point"]
|
||||
start_connection = "ATSTART" if start_point_on_origin else "ATEND"
|
||||
start_segment_sign = -1 if start_point_on_origin else 1
|
||||
|
||||
end_point_on_origin = end_point == end_segment_data["start_point"]
|
||||
end_connection = "ATSTART" if end_point_on_origin else "ATEND"
|
||||
end_segment_sign = -1 if end_point_on_origin else 1
|
||||
|
||||
profile_dim = get_dim(profile) * si_conversion
|
||||
|
||||
# TODO: profile offset may need to be flipped (check transition code)
|
||||
to_start_object_space = start_object_rotation.inverted()
|
||||
profile_offset = (to_start_object_space @ end_point) - (to_start_object_space @ start_point)
|
||||
z_axis_end_object_local = to_start_object_space @ tool.Cad.get_basis_vector(end_object, 2)
|
||||
|
||||
def check_for_double_bends():
|
||||
# The theory is To avoid double bends, the profile offset should occur along only two axes:
|
||||
# 1) The local Z-axis of the start segment
|
||||
# 2) One of the lateral axes (either X or Y)
|
||||
#
|
||||
# Double bend required when:
|
||||
# - there are 2 or 0 lateral axes involved
|
||||
# - offset appear by the non-lateral axis
|
||||
#
|
||||
# NOTE: some double bends are only possible for square profiles:
|
||||
# https://i.imgur.com/ZhdGbEp.png
|
||||
|
||||
lateral_axes = [i for i in range(2) if not tool.Cad.is_x(z_axis_end_object_local[i], 0)]
|
||||
|
||||
if len(lateral_axes) != 1:
|
||||
return (
|
||||
None,
|
||||
f"For now only one lateral axis is supported for a bend (double bends not supported). Found lateral axes: {len(lateral_axes)}.",
|
||||
)
|
||||
|
||||
non_lateral_axis = 0 if lateral_axes[0] == 1 else 1
|
||||
non_lateral_axis_offset = profile_offset[non_lateral_axis]
|
||||
if not tool.Cad.is_x(non_lateral_axis_offset, 0):
|
||||
return (
|
||||
None,
|
||||
"For now offset by non-lateral axis is not supported for a bend (double bends not supported).\n"
|
||||
f"Detected an offset of {round(non_lateral_axis_offset, 5)} along the local axis {'XY'[non_lateral_axis]} when lateral axis is {'XY'[lateral_axes[0]]}.",
|
||||
)
|
||||
|
||||
return lateral_axes[0], None
|
||||
|
||||
lateral_axis, error_msg = check_for_double_bends()
|
||||
if error_msg:
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"CANCELLED"}
|
||||
non_lateral_axis = 0 if lateral_axis == 1 else 1
|
||||
|
||||
def get_bend_rotation():
|
||||
O = V(0, 0, 0)
|
||||
edge1 = (get_z_basis(start_object) * start_segment_sign, O)
|
||||
edge2 = (get_z_basis(end_object) * end_segment_sign, O)
|
||||
angle = pi - tool.Cad.angle_edges(edge1, edge2)
|
||||
axis = (edge2[1] - edge2[0]).cross(edge1[1] - edge1[0])
|
||||
return angle, axis
|
||||
|
||||
angle, rotation_axis = get_bend_rotation()
|
||||
|
||||
lateral_sign = tool.Cad.sign(profile_offset[lateral_axis])
|
||||
radial_offset = V(0, 0, 0)
|
||||
ref_point_radius = self.radius + profile_dim[lateral_axis]
|
||||
radial_offset[lateral_axis] = ref_point_radius * (1 - cos(angle)) * lateral_sign
|
||||
radial_offset.z = ref_point_radius * sin(angle)
|
||||
second_object_offset = radial_offset + V(0, 0, self.start_length)
|
||||
second_object_offset += z_axis_end_object_local * (self.end_length * -end_segment_sign)
|
||||
|
||||
def get_segments_extend():
|
||||
segments_intersection = segments_intersection_ws - start_point
|
||||
segments_intersection = to_start_object_space @ segments_intersection
|
||||
|
||||
# since tangent segments are equal
|
||||
# if drawn for the circle from the same point
|
||||
required_offset = ref_point_radius * tan(angle / 2)
|
||||
|
||||
current_start_offset = segments_intersection.length
|
||||
current_end_offset = (segments_intersection - profile_offset).length
|
||||
|
||||
start_extend = current_start_offset - (required_offset + self.start_length)
|
||||
end_extend = current_end_offset - (required_offset + self.end_length)
|
||||
|
||||
return start_extend, end_extend
|
||||
|
||||
def check_new_segment_length(start_point, end_point, extend_point):
|
||||
"""Check if segment is placed too near to the bend point.
|
||||
|
||||
The idea is that we can either extend segment toward the bend
|
||||
but we can shrink it only until it's start.
|
||||
|
||||
If the segment is too near it will return offset to fix the problem,
|
||||
otherwise returns `None`.
|
||||
|
||||
"""
|
||||
base_edge = end_point - start_point
|
||||
new_edge = extend_point - start_point
|
||||
projection = new_edge.dot(base_edge.normalized())
|
||||
if projection < 0 or tool.Cad.is_x(projection, 0):
|
||||
return projection
|
||||
return None
|
||||
|
||||
# adjust segments to fit the radius and angle
|
||||
start_segment_extend, end_segment_extend = get_segments_extend()
|
||||
start_segment_extend_point = start_point + start_segment_sign * start_segment_extend * get_z_basis(start_object)
|
||||
projection = check_new_segment_length(first_segment_start, start_point, start_segment_extend_point)
|
||||
if projection is not None:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Start segment starts too near to the bend, need to offset it atleast by {round(projection, 3)} m.",
|
||||
)
|
||||
return {"ERROR"}
|
||||
|
||||
end_segment_extend_point = end_point + end_segment_sign * end_segment_extend * get_z_basis(end_object)
|
||||
projection = check_new_segment_length(second_segment_end, end_point, end_segment_extend_point)
|
||||
if projection is not None:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"End segment starts too near to the bend, need to offset it atleast by {round(projection, 3)} m.",
|
||||
)
|
||||
return {"ERROR"}
|
||||
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point, start_connection)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point, end_connection)
|
||||
|
||||
context.view_layer.update() # update matrices
|
||||
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
rep, bend_data = builder.mep_bend_shape(
|
||||
start_element,
|
||||
self.start_length / si_conversion,
|
||||
self.end_length / si_conversion,
|
||||
angle,
|
||||
self.radius / si_conversion,
|
||||
profile_offset / si_conversion,
|
||||
flip_z_axis=start_segment_sign == -1,
|
||||
)
|
||||
|
||||
parametric_data = {
|
||||
"start_length": self.start_length / si_conversion,
|
||||
"end_length": self.end_length / si_conversion,
|
||||
"radius": self.radius / si_conversion,
|
||||
"angle": degrees(angle),
|
||||
"main_profile_dimension": profile_dim[lateral_axis] / si_conversion,
|
||||
}
|
||||
# find the compatible fitting type
|
||||
fitting_data = MEPGenerator().get_compatible_fitting_type(
|
||||
[start_element, end_element], [start_port, end_port], "BEND", bbim_data=parametric_data
|
||||
)
|
||||
bend_type = fitting_data["fitting_type"] if fitting_data else None
|
||||
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
||||
|
||||
# use current segments axes if no fitting type found
|
||||
lateral_axis_type = lateral_axis
|
||||
lateral_sign_type = lateral_sign
|
||||
z_sign_type = start_segment_sign
|
||||
non_lateral_axis_type = non_lateral_axis
|
||||
if bend_type:
|
||||
bend_obj = tool.Ifc.get_object(bend_type)
|
||||
bbim_data = tool.Model.get_modeling_bbim_pset_data(bend_obj, "BBIM_Fitting")["data_dict"]
|
||||
lateral_axis_type, lateral_sign_type = bbim_data["lateral_axis"], bbim_data["lateral_sign"]
|
||||
non_lateral_axis_type = 0 if lateral_axis_type == 1 else 1
|
||||
z_sign_type = bbim_data.get("z_axis_sign", None)
|
||||
# TODO: drop flip_z_axis a bit later
|
||||
if z_sign_type is None:
|
||||
z_sign_type = -1 if bbim_data["flip_z_axis"] else 1
|
||||
|
||||
# TODO: handle the case without creating a representation in the first place?
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
||||
else: # create new fitting type if nothing is compatible
|
||||
mesh = bpy.data.meshes.new("Bend")
|
||||
obj = bpy.data.objects.new("Bend", mesh)
|
||||
bend_type = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=MEPGenerator().get_mep_element_class_name(start_element, "FittingType"),
|
||||
predefined_type="BEND",
|
||||
should_add_representation=False,
|
||||
)
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
tool.Model.replace_object_ifc_representation(body, obj, rep)
|
||||
tool.Blender.remove_data_block(mesh)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=bend_type, name="BBIM_Fitting")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Data": json.dumps(bend_data, default=list)},
|
||||
)
|
||||
tool.System.add_ports(obj, end_port_pos=second_object_offset)
|
||||
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
# create transition element
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=bend_type.id())
|
||||
fitting_obj = bpy.context.active_object
|
||||
|
||||
# adjust fitting object rotation and location
|
||||
# required since we'll base our `fitting_obj_dir` on this
|
||||
fitting_obj.matrix_world = start_object.matrix_world
|
||||
context.view_layer.update()
|
||||
|
||||
# depending on bend direction we may need to rotate it to match
|
||||
# we just calculate the matrix basises - it's simpler than describing all possible conditions
|
||||
def get_fitting_matrix():
|
||||
matrix = Matrix.Identity(3)
|
||||
start_object_z_basis = tool.Cad.get_basis_vector(start_object, 2)
|
||||
start_object_lateral_basis = tool.Cad.get_basis_vector(start_object, lateral_axis)
|
||||
|
||||
def axis_direction(current_axis_sign, type_axis_sign):
|
||||
return -1 if current_axis_sign != type_axis_sign else 1
|
||||
|
||||
matrix.col[2] = start_object_z_basis * axis_direction(start_segment_sign, z_sign_type)
|
||||
matrix.col[lateral_axis_type] = start_object_lateral_basis * axis_direction(lateral_sign, lateral_sign_type)
|
||||
if not start_port_match:
|
||||
matrix.col[2] *= -1
|
||||
|
||||
if non_lateral_axis_type == 0:
|
||||
non_lateral_axis = matrix.col[lateral_axis_type].cross(matrix.col[2])
|
||||
else:
|
||||
non_lateral_axis = matrix.col[2].cross(matrix.col[lateral_axis_type])
|
||||
matrix.col[non_lateral_axis_type] = non_lateral_axis
|
||||
|
||||
if not start_port_match:
|
||||
angle_sign = np.sign(rotation_axis.dot(non_lateral_axis))
|
||||
matrix = matrix @ Matrix.Rotation(angle * angle_sign, 3, "XY"[non_lateral_axis_type])
|
||||
|
||||
matrix = matrix.to_4x4()
|
||||
matrix.translation = start_segment_extend_point if start_port_match else end_segment_extend_point
|
||||
return matrix
|
||||
|
||||
fitting_obj.matrix_world = get_fitting_matrix()
|
||||
|
||||
# add ports and connect them
|
||||
ports = tool.System.get_ports(tool.Ifc.get_entity(fitting_obj))
|
||||
if not start_port_match:
|
||||
start_port, end_port = end_port, start_port
|
||||
tool.Ifc.run("system.connect_port", port1=ports[0], port2=start_port, direction="NOTDEFINED")
|
||||
tool.Ifc.run("system.connect_port", port1=ports[1], port2=end_port, direction="NOTDEFINED")
|
||||
|
||||
self.report({"INFO"}, f"Success!.. kind of. The angle was {round(bend_data['angle'])}")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
import json
|
||||
import bmesh
|
||||
import shapely
|
||||
import logging
|
||||
@@ -70,7 +71,10 @@ class FilledOpeningGenerator:
|
||||
)
|
||||
|
||||
if target is None:
|
||||
target = bpy.context.scene.cursor.location
|
||||
should_set_z_level = True
|
||||
target = bpy.context.scene.cursor.location.copy()
|
||||
else:
|
||||
should_set_z_level = False
|
||||
|
||||
# Sometimes, the voided_obj may be an aggregate, which won't have any representation.
|
||||
if voided_obj.data:
|
||||
@@ -86,12 +90,17 @@ class FilledOpeningGenerator:
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
new_matrix.translation = tool.Cad.point_on_edge(target, axis)
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
new_matrix.translation.x = point_on_axis.x
|
||||
new_matrix.translation.y = point_on_axis.y
|
||||
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
if should_set_z_level:
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
|
||||
|
||||
filling_obj.matrix_world = new_matrix
|
||||
bpy.context.view_layer.update()
|
||||
@@ -152,7 +161,10 @@ class FilledOpeningGenerator:
|
||||
|
||||
for voided_obj in voided_objs:
|
||||
if voided_obj.data:
|
||||
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
voided_element = tool.Ifc.get_entity(voided_obj)
|
||||
context = tool.Geometry.get_active_representation_context(voided_obj)
|
||||
representation = tool.Geometry.get_representation_by_context(voided_element, context)
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
@@ -266,9 +278,7 @@ class FilledOpeningGenerator:
|
||||
get_curve_2d_from_3d(profile),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -thickness * 0.5 / unit_scale, 0.0]),
|
||||
position_x_axis=Vector((1, 0, 0)),
|
||||
position_z_axis=Vector((0, -1, 0)),
|
||||
extrusion_vector=Vector((0, 0, -1)),
|
||||
**shape_builder.extrude_kwargs("Y")
|
||||
)
|
||||
return shape_builder.get_representation(context, [extrusion])
|
||||
|
||||
@@ -301,9 +311,7 @@ class FilledOpeningGenerator:
|
||||
shape_builder.rectangle(size=opening_size),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=opening_position,
|
||||
position_z_axis=Vector((0.0, -1.0, 0.0)),
|
||||
position_x_axis=Vector((1.0, 0.0, 0.0)),
|
||||
extrusion_vector=Vector((0.0, 0.0, -1.0)),
|
||||
**shape_builder.extrude_kwargs("Y")
|
||||
)
|
||||
|
||||
return shape_builder.get_representation(context, [extrusion])
|
||||
@@ -389,18 +397,7 @@ class FlipFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.FillsVoids:
|
||||
continue
|
||||
|
||||
flip_matrix = Matrix.Rotation(pi, 4, "Z")
|
||||
|
||||
bottom_left = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||
top_right = obj.matrix_world @ Vector(obj.bound_box[6])
|
||||
center = obj.matrix_world.translation.copy()
|
||||
center_offset = center - bottom_left
|
||||
flipped_center = top_right - center_offset
|
||||
|
||||
obj.matrix_world = obj.matrix_world @ flip_matrix
|
||||
obj.matrix_world.translation.xy = flipped_center.xy
|
||||
bpy.context.view_layer.update()
|
||||
tool.Geometry.flip_object(obj, "XY")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -498,10 +495,14 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_boolean"
|
||||
bl_label = "Add Boolean"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Applies a boolean to the selected IFC object using the other selected blender object as a void"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) == 2
|
||||
if not len(context.selected_objects) == 2:
|
||||
cls.poll_message_set("Exactly 2 objects need to be selected.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
@@ -523,10 +524,21 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
elif obj2.data:
|
||||
mesh_data = {"type": "Mesh", "blender_obj": obj1, "blender_void": obj2}
|
||||
|
||||
ifcopenshell.api.run(
|
||||
booleans = ifcopenshell.api.run(
|
||||
"geometry.add_boolean", tool.Ifc.get(), representation=representation, operator="DIFFERENCE", **mesh_data
|
||||
)
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element1, "BBIM_Boolean")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
data = json.loads(pset["Data"])
|
||||
data.extend([b.id() for b in booleans])
|
||||
data = list(set(data))
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element1, name="BBIM_Boolean")
|
||||
data = [b.id() for b in booleans]
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": json.dumps(data)})
|
||||
|
||||
tool.Model.clear_scene_openings()
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -646,6 +658,8 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
upstream_obj = None
|
||||
bbim_boolean_updates = {}
|
||||
for obj in context.selected_objects:
|
||||
if (
|
||||
not obj.data
|
||||
@@ -654,13 +668,21 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
):
|
||||
continue
|
||||
try:
|
||||
boolean = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_boolean_id)
|
||||
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_boolean_id)
|
||||
except:
|
||||
continue
|
||||
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=boolean)
|
||||
|
||||
boolean_id = None
|
||||
for inverse in tool.Ifc.get().get_inverse(item):
|
||||
if inverse.is_a("IfcBooleanResult"):
|
||||
boolean_id = inverse.id()
|
||||
break
|
||||
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=item)
|
||||
|
||||
if obj.data.BIMMeshProperties.obj:
|
||||
upstream_obj = obj.data.BIMMeshProperties.obj
|
||||
element = tool.Ifc.get_entity(upstream_obj)
|
||||
bbim_boolean_updates.setdefault(element, []).append(boolean_id)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if body:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -673,6 +695,21 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
for element, boolean_ids in bbim_boolean_updates.items():
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
||||
if not pset:
|
||||
continue
|
||||
data = set(json.loads(pset["Data"]))
|
||||
data -= set(boolean_ids)
|
||||
data = list(data)
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
if data:
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": json.dumps(data)})
|
||||
else:
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
tool.Blender.set_active_object(upstream_obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -735,12 +772,16 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
building_objs = set()
|
||||
model = tool.Ifc.get()
|
||||
all_openings = model.by_type("IfcOpeningElement")
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
openings = [r.RelatedOpeningElement for r in element.HasOpenings]
|
||||
for opening in openings:
|
||||
similar_openings = [o for o in all_openings if o.ObjectPlacement == opening.ObjectPlacement]
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
if opening_obj:
|
||||
if tool.Ifc.is_edited(opening_obj):
|
||||
@@ -749,6 +790,8 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
blenderbim.core.geometry.edit_object_placement(
|
||||
tool.Ifc, tool.Geometry, tool.Surveyor, obj=opening_obj
|
||||
)
|
||||
for similar_opening in similar_openings:
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
building_objs.add(obj)
|
||||
building_objs.update(self.get_all_building_objects_of_similar_openings(opening))
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
@@ -774,6 +817,38 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
return results
|
||||
|
||||
|
||||
class CloneOpening(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.clone_opening"
|
||||
bl_label = "Clone Opening"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create and assign to the selected element the selected opening and assign to it the same representation and object placement"
|
||||
|
||||
def _execute(self, context):
|
||||
objects = bpy.context.selected_objects
|
||||
|
||||
for obj in objects:
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
if entity.is_a() == "IfcWall":
|
||||
wall = entity
|
||||
continue
|
||||
if entity.is_a() == "IfcOpeningElement":
|
||||
opening = entity
|
||||
continue
|
||||
|
||||
opening_placement = opening.ObjectPlacement
|
||||
opening_representations = opening.Representation.Representations
|
||||
|
||||
new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement")
|
||||
for representation in opening_representations:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=new_opening, representation=representation
|
||||
)
|
||||
|
||||
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=new_opening, element=wall)
|
||||
new_opening.ObjectPlacement = opening_placement
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# TODO: merge with ProfileDecorator?
|
||||
class DecorationsHandler:
|
||||
installed = None
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user