Compare commits

...

435 Commits

Author SHA1 Message Date
Dion Moult 595c72e08d Fix #1995. Bug where section cutaways didn't work in Blender 3.0. 2022-01-21 14:22:07 +11:00
Dion Moult 5709efbdcb Fix bug where you could not add properties to projects themselves. 2022-01-21 12:01:19 +11:00
Dion Moult 67952ed159 Fix #1576. Managing IfcSystem subtypes is now possible. 2022-01-20 18:22:20 +11:00
Thomas Krijnen 000e3acb2b #1948 clear tmp list 2022-01-19 15:36:58 +01:00
Thomas Krijnen b337d5a174 #1993 msvc 2022 config 2022-01-19 14:14:15 +01:00
Dion Moult 6b0bdc564d Nicer looking gantt charts and make it easier to print to PDF. 2022-01-19 17:38:42 +11:00
Dion Moult ab5db2fa23 Minor fix 2022-01-19 12:19:36 +11:00
Dion Moult 810c71dfa2 IfcOpenShell-python licensing and black. See #1082. 2022-01-19 12:18:33 +11:00
Dion Moult 5b281260f8 Licensing and black for BIMTester and IfcBlender. See #1082. 2022-01-19 11:07:43 +11:00
Dion Moult 07264dafb1 Bump IfcOpenShell to latest bot build. See #1984 #1977 #1959 2022-01-18 18:01:27 +11:00
Dion Moult 9572fef222 Fix #1908. Extracting elements using IfcPatch now includes all properties. 2022-01-18 17:47:59 +11:00
Dion Moult 1bbbc82b72 Fix #1962. You can now easily append product assets using the API. 2022-01-18 17:44:50 +11:00
Dion Moult aa6d03ade2 You can now reset and restore your API owner settings. 2022-01-18 17:42:17 +11:00
Thomas Krijnen 4c40fc1629 #1985 consolidate and correct shell count checks in tree 2022-01-17 13:56:21 +01:00
Thomas Krijnen 90e5b5576d Fix propagation of kernel settings (affects multi threading) 2022-01-17 13:55:40 +01:00
Thomas Krijnen 531805fefb #1692 get_info_2() logical 2022-01-17 11:19:30 +01:00
Dion Moult 2852f53afa Fix #1934. Fix bug with improper nesting of subfolders when exporting IfcZip. 2022-01-17 20:30:02 +11:00
Dion Moult 8b06aa9e0b Header data previews for IFCZIP are now supported as well. 2022-01-17 18:21:20 +11:00
Dion Moult 52ea69bcec Fix #1954. Fix bug where importing MSProject XMLs with no project names failed. 2022-01-17 17:37:09 +11:00
Dion Moult 9ebe0aaa95 Fix #1956. One-button upgrade from IFC2X3 to IFC4. 2022-01-17 16:20:01 +11:00
Dion Moult 1a735c4bef Minor improvement to the sequence UI to always allow you to cancel editing mode. 2022-01-17 15:11:48 +11:00
Thomas Krijnen 482788a46f Update build-all.py 2022-01-16 13:59:19 +01:00
Thomas Krijnen a30a8cd920 Missing job dependency in bbim ci 2022-01-16 13:43:14 +01:00
Thomas Krijnen c2e7c27421 ADD_COMMIT_SHA in build scripts 2022-01-16 13:16:44 +01:00
Thomas Krijnen 2f24343f8f Update and rename README.txt to README.md 2022-01-14 11:14:42 +01:00
Dion Moult d259e0f490 Fix IfcCSV import bug. 2022-01-14 20:25:35 +11:00
Dion Moult c09ed228c0 Whoops. 2022-01-14 19:06:17 +11:00
Dion Moult d63be20c8b Fix #1989. Fix bug where you couldn't export to IfcJSON, since it couldn't be imported again. 2022-01-14 18:55:44 +11:00
Dion Moult f8c7f48674 You can now associate annotations with objects for smart annotations in IFC. See #1153. 2022-01-14 18:00:08 +11:00
Dion Moult 258ca7fb8c Fix bug where you could assign a product twice to a process. 2022-01-14 16:04:55 +11:00
Dion Moult ec38fe2edc Fix #1931. Fix bug where duplicated type products should not share material associations. 2022-01-14 13:15:47 +11:00
Dion Moult 5a87ab8171 Minor cleanup to Brick code after failed attempt to detect feed relationships. 2022-01-14 11:47:58 +11:00
Dion Moult d23d382854 Minor fix to BIMTester IDS support. Disable IDS test due to unstable nature of IDS. 2022-01-14 11:46:15 +11:00
Dion Moult 30270db0c3 Fix bug where annotation decorations didn't show correctly in Blender 3.0 2022-01-14 10:54:27 +11:00
Vukas Pajic 6d78ac4918 adjusted UI based on feedback received (#1986)
* adjusted UI based on feedback received

* typo
2022-01-14 10:09:51 +11:00
Benjamin Dahl 81016f3110 define the vector sass [0] as a std::vector<std::unique_ptr<ShapeAnalysis_Surface>>
https: //github.com/IfcOpenShell/IfcOpenShell/issues/1975
Co-Authored-By: Dion Moult <dion@thinkmoult.com>
Co-Authored-By: Thomas Krijnen <1096535+aothms@users.noreply.github.com>
2022-01-13 13:38:51 +01:00
Dion Moult 189e566740 Remove broken and obsolete Blender clasher which is incompatible with the geom.tree broadphase approach. 2022-01-13 17:05:44 +11:00
Thomas Krijnen 5f7e4793c2 #1960 correct distinguish outer bound by pre increment 2022-01-12 15:14:18 +01:00
Thomas Krijnen be286c4d9d #1974 populate attribute map upon schema registration 2022-01-12 15:09:22 +01:00
Dion Moult 5ba542488a Implement adding plain bricks not based on IFC 2022-01-12 16:22:32 +11:00
Dion Moult 39960998de Support type-based class checking for converting IFC2X3 models to Brickschema 2022-01-12 15:39:43 +11:00
Dion Moult 92642e6a6a The IFC Class panel now shows inherited predefined types 2022-01-12 14:29:02 +11:00
Dion Moult 78d33437ea New utility to get predefined type 2022-01-12 14:11:31 +11:00
Gorgious56 65b3960ab3 Show ifc specs in file selector (#1980)
* Create simple parser for ifc header specs

* Refactor logic to be usable with other file selectors

* Fix parsing error
2022-01-12 10:01:49 +11:00
Vukas Pajic e81848dfba UI Simplification with Panel Groups (#1979)
* concept demo

* small naming change + added missing ui classes

* added newline to end of file
2022-01-11 20:03:29 +11:00
Dion Moult fe9cda8ba1 Fix #1925. Fix bug where nested collection deletions were not synchronised properly. 2022-01-11 15:45:29 +11:00
Dion Moult 49221739cf Implement auto and manual refresh of the Brick model navigator 2022-01-11 14:46:58 +11:00
Dion Moult c6966f9b39 You can now create a new blank Brick project 2022-01-11 13:25:03 +11:00
Dion Moult f08ed629b5 New debug feature to purge HDF5 cache in case the cache is corrupted. 2022-01-11 11:24:13 +11:00
Dion Moult ba675c0d28 Implement human readable rdfs labels in Brickschema navigator 2022-01-11 10:44:52 +11:00
Gorgious56 3a80722621 Display IFC schema in file selector right hand side panel (#1978)
* Display IFC schema in file selector right hand side panel

* Formatting

* Fix possible error when the file name or description contains the word "schema"
2022-01-11 08:52:14 +11:00
Dion Moult 9b77bb6e37 New utility to get IFC all elements that are relevant in Brickschema 2022-01-10 17:31:42 +11:00
Dion Moult 7a6df49348 Basic implementation to bulk convert relevant IFC classes to a Brick model 2022-01-10 17:30:49 +11:00
Dion Moult 666e484b2b Run black on IfcOpenShell-python. 2022-01-10 15:42:24 +11:00
Dion Moult 256f40ed44 New IFC to Brick class mapping utility 2022-01-10 15:35:37 +11:00
Dion Moult 8eccb1a5ed You can now select all system objects from the object panel for convenience. 2022-01-10 12:21:38 +11:00
Dion Moult ce3490824d Optimise all editing API functions to be 100x faster when you're dealing with proprietary BIM exports with loads of shared owner histories. See #1904 and #1905. 2022-01-10 12:12:34 +11:00
Dion Moult 43c74010a5 Schema versions are no longer locked to IFC4 and 2X3. You can now load express schemas at run time. 2022-01-10 10:45:46 +11:00
Dion Moult 6c991ed356 Purge obsolete IFC types mappings 2022-01-10 09:40:42 +11:00
Thomas Krijnen c01c2a737a test runner doesn't have dae 2022-01-09 21:26:40 +01:00
Thomas Krijnen c9c11a2905 Install to system local root 2022-01-09 21:11:47 +01:00
Thomas Krijnen e5037dc41a #1960 test cases 2022-01-09 20:52:48 +01:00
Thomas Krijnen acd9250028 Also log perf with quiet 2022-01-09 15:21:16 +01:00
Thomas Krijnen a3dc477921 Fix perf logging in convert 2022-01-09 15:15:50 +01:00
Thomas Krijnen 6246dda8d6 #1960 make configurable 2022-01-09 15:15:29 +01:00
Thomas Krijnen 2683bcba20 Fix: shift count negative or too big 2022-01-09 15:14:13 +01:00
Thomas Krijnen 97f2aa976e Merge branch 'v0.7.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.7.0 2022-01-09 14:32:23 +01:00
Thomas Krijnen 9b96073ac0 #1960 BRepBuilderAPI_MakeFace on non-overlapping 2d subtraction 2022-01-09 14:32:13 +01:00
Thomas Krijnen 7efd9cea2a Log performance aggregates as logger messages 2022-01-09 14:31:13 +01:00
Thomas Krijnen c917881a8e Logging statement for total file conversion 2022-01-09 14:30:56 +01:00
Thomas Krijnen fa46eb140f Update template.py main.schema_identifier doesn't exist 2022-01-09 11:41:24 +01:00
Thomas Krijnen b8ea42540e #1973 Performance logging 2022-01-07 15:12:32 +01:00
Thomas Krijnen 671ab78c76 #1969 take uppercase in register_schema() 2022-01-07 12:49:28 +01:00
jakob-beetz a8550cea26 build-deps.cmd typo 0.7.0 branch 2022-01-07 12:47:28 +01:00
Dion Moult 4f047274bf The IfcOpenShell API now supports automatic primitive casting for editing properties. See #1943. 2022-01-07 17:38:43 +11:00
Dion Moult eb04a7d579 Visual clean up of bulk property editing UI 2022-01-07 17:38:43 +11:00
Dion Moult 84096529e1 Primary measure type lists are now dynamically generated based on the schema. 2022-01-07 17:38:43 +11:00
Dion Moult 3350f31312 Write documentation to help developers get acquainted with testing 2022-01-07 17:38:43 +11:00
Dion Moult 0d79963ed8 Clear outdated README text which we won't maintain to prevent confusion 2022-01-07 17:38:43 +11:00
Dion Moult 43a8fa8bcb It's 2022! 2022-01-07 17:38:43 +11:00
Thomas Krijnen ca8f3cf1e6 Update README.md 2022-01-06 10:17:56 +01:00
garylzimmer 313a1c5705 Update README.md
Updated based on my personal findings. Can provide documentation if necessary.
2022-01-06 10:17:31 +01:00
Vukas Pajic 53f6454784 Fix integers in enumerations #1943 (#1946)
* first draft

* small change
2022-01-04 13:10:42 +11:00
Vukas Pajic 2c0df1d4f5 fix umlauts not showing up correctly in dynamic enums #1941 (#1945)
* first attempt

* adopted new solution for umlauts in enums

* fix typo
2022-01-04 13:09:47 +11:00
molitoris e384dbd548 Correct HeaderFile (#1942)
Rename file_name to filename (without underline)
2022-01-04 13:06:01 +11:00
Cyril Waechter d22a0c7cb8 Boundary geom creation : fix math + small ui fix (#1938)
_Neo accidentally took the red pill so I transposed the matrix to make
it blue_

* Fix how placement matrix of updated geometry is created.
* Fix ui: was throwing an error when boundary had no
`RelatedBuildingElement`
2022-01-04 13:04:06 +11:00
Gorgious56 02a62a943c Fix #1717 : Disable task editing if task was deleted as a result of deleting its parent (#1936)
* Disable task editing if task was deleted as a result of deleting its parent

* Optimize logic for bigger number of tasks

* Don't try to disable editing if not editing in the first place
2022-01-04 13:03:39 +11:00
Vukas Pajic 0625e4153c bulk property editor (#1896)
* first revision

* update incorporating review comments

* added a new feature - remove psets

* type dropdown now references ifc data types

* adopt new solution for enumerations and umlauts

* Revert "adopt new solution for enumerations and umlauts"

This reverts commit 203a31a2bf.
2022-01-04 12:59:55 +11:00
Jesusbill 03f5feca88 refactor structural module - wip - structural analysis models #1848 2022-01-02 00:34:38 +01:00
ssg 85e3c6588f Updated README.md for OCCT/IfcOpenShell7
After #1930 the 0.7 branch isn't compatible anymore with OCCT version < 7.2. That includes the liboce that's found in debian (derivatives), which is OCCT 6.9 or even less. Updated the Linux section of README.md to explain the dependency on OCCT and include instructions to build on Unix with OCCT (from the OS repository as well). Also included changes that became necessary when we moved to v0.7.0 from v0.6.0 (like recursive clone and cmake additions).
2021-12-31 13:46:13 +01:00
bosonprojets 6e913a0ff9 BlenderBIM Feature - Filter By Building Storey 2021-12-28 23:46:22 +01:00
Thomas Krijnen ff4e58a8ff Update README.md 2021-12-20 11:46:07 +01:00
Dion Moult 1db028fa7b Fix bug where IfcPatch IFC4 migration could break on Revit files 2021-12-20 16:39:14 +11:00
Thomas Krijnen 69a7efdab4 Does this make gcc happy for uint64_t enums? 2021-12-19 22:31:40 +01:00
Thomas Krijnen 20dcb68009 Invalidate the boolean result for a particular warning reported by pavefiller 2021-12-19 22:14:09 +01:00
Thomas Krijnen 6b0b64b869 Good news everyone! Option to process wall opening subtractions in 2D. 2021-12-19 22:08:16 +01:00
Thomas Krijnen 09631e9565 Remove some erroneous or wrong (hopefully) lines on mintol and prec 2021-12-19 22:01:07 +01:00
Thomas Krijnen e9bb70f56b Eliminate boolean sub operands b that only touch operand a 2021-12-19 21:59:29 +01:00
Thomas Krijnen bd008bdefe option to write boolean operands to disk for debugging 2021-12-19 21:57:47 +01:00
Thomas Krijnen 03d4782cb7 Ground work for processing opening placement up to parent wall 2021-12-19 21:55:41 +01:00
Thomas Krijnen 49c555c508 Log BRepCheck_Analyser results 2021-12-18 22:02:47 +01:00
Thomas Krijnen c51c5651b2 Small changes to iterator settings 2021-12-18 16:16:44 +01:00
Thomas Krijnen 364c2286d1 #1937 use git branch --contains instead show 2021-12-18 16:16:21 +01:00
Jesusbill 37d20eb360 changes in ifc2ca standard case 2021-12-17 17:01:18 +01:00
Jesusbill dd6912bdaa ifc2ca - possibility to exclude zero length springs in point connections 2021-12-17 16:27:41 +01:00
Jesusbill a1fe68a17e changes in ifc2ca bonded case 2021-12-17 09:58:02 +01:00
Thomas Krijnen 3ec79cac3f #1914 only proceed with 'face addition check' when first operand contains open shells 2021-12-15 13:37:12 +01:00
Thomas Krijnen dbc6f521fe #1912 apply also on types 2021-12-14 09:57:31 +01:00
Thomas Krijnen c52f851d79 #1807 method mapping proposal 2021-12-14 09:57:31 +01:00
Thomas Krijnen 3380158f55 #1692 memory leak in get_info_2() 2021-12-13 10:32:05 +01:00
Jesusbill 4e9a5f246f minor changes in ifc2ca scripts 2021-12-12 19:03:14 +01:00
Dion Moult 664c49a07d Bump IfcOpenShell. See #1906. 2021-12-11 21:10:03 +11:00
Thomas Krijnen 06b9cc6b38 CMakeLists ADD_COMMIT_SHA option 2021-12-10 10:03:41 +01:00
fichter b9ea741e5e OCC7.5 to OCC7.6 -> depracated Poly_Triangulation Nodes() 2021-12-10 08:44:42 +01:00
Thomas Krijnen 0098e38c77 Switch to libocct for yml workflows: add data-exchange libs 2021-12-10 08:08:03 +01:00
Dion Moult ece796fc2d Fix #1917. Fix warning where the relating type classes data couldn't be accessed. 2021-12-10 14:06:54 +11:00
Dion Moult de4659570a See #1917. Refactor and fix warning message when you haven't got any contexts in a project yet. 2021-12-10 13:31:23 +11:00
Dion Moult 62236c44ae Fix #1923. Fix bug where creating a new structural analysis model broke. 2021-12-10 10:58:06 +11:00
Dion Moult ce5db914db Fix #1913. Fix bug where filtering by decomposition didn't work on IFC2X3. 2021-12-10 10:14:43 +11:00
Dion Moult 12699f29b7 Minor fix 2021-12-10 09:44:23 +11:00
Dion Moult 54e326983f Fix #1921. Document issue with installing on outdated Ubuntu distributions. 2021-12-10 09:43:28 +11:00
Thomas Krijnen 5fb40a390c Switch to libocct for yml workflows: update include dir 2021-12-09 20:52:30 +01:00
Thomas Krijnen 0cb8716c6a Switch to libocct for yml workflows 2021-12-09 20:47:00 +01:00
Thomas Krijnen 099013fa05 revert #1927 for mpir 2021-12-09 20:27:55 +01:00
Ttofuuu 4a8843b6bb fix mpfr checking problem
https://github.com/BrianGladman/mpir.git 's mpfr will lead to a merge problem and make IFCOS fail to build depends on Windows. Change the link to anothms will fix this problem.
2021-12-09 20:26:19 +01:00
Thomas Krijnen 33d15b42b7 bop face addition check only for subtraction 2021-12-09 20:24:21 +01:00
Gorgious56 44581c77ac Extend ifc debug panel (#1924)
* Prevent searching for invalid ID in IFC debugger
You can still input <= 0 IDs for debugging purposes
But the field won't let you drag below ID = 1
Also changed default to 1 so it doesn't throw an error by default

* Extend ops descriptions, labels and debug output

* Use Operator property for selecting dense meshes

* Prevent error if id has no geom representation

* Use Poll operator and poll fail message

* Debug op to select highest meshes by polygon percentile
2021-12-09 13:41:15 +01:00
Bernd Hahnebach e848b249a6 bimtester: if lang argument is not set use feature file lang for output messages, improves #1910 2021-12-09 12:04:47 +01:00
Bernd Hahnebach f85c3ae24c bimtester: update log and smartview, they still are deactivated by default, fixes #1892 2021-12-09 09:06:51 +01:00
Dion Moult 7c633fab31 Fix bug where libraries didn't work in IFC2X3 2021-12-09 17:51:29 +11:00
Dion Moult f716f8a357 You can now add feed relationships in a Brick model from IFC elements 2021-12-09 17:50:57 +11:00
Dion Moult d156927e13 From an IFC element, you can now add a new Brick element in a Brick model 2021-12-09 17:48:47 +11:00
Dion Moult 2ef1f4d812 Assigning a brick reference now adds a 2-way IFC reference in the Brick model 2021-12-09 17:45:08 +11:00
Dion Moult 78a36ab429 Fix #1816. Fix bug where cameras weren't allowed to be optional in BCF 2.1. 2021-12-09 13:51:55 +11:00
Dion Moult 763a38bc9c Fix bug with missing system arrows in visualising offset models and sink/source mixed flows. 2021-12-09 13:40:23 +11:00
Dion Moult a23c88a53e Minor fix for #1902. 2021-12-09 13:38:46 +11:00
Dion Moult f6bf626349 Little utility to help printing out maintainable classes and other classes in contractual documentation. 2021-12-09 13:36:33 +11:00
Bernd Hahnebach b66792f981 bimtester: get lang from json report and use it to generate html report, fixes #1928 2021-12-08 15:53:34 +01:00
Bernd Hahnebach 195af0c850 bimtester: do not break bimtester if translation files are missing, fixes #1910 2021-12-08 15:19:36 +01:00
Bernd Hahnebach ee56e06d86 bimtester: German eleclass attributes steps, fix translation redirection 2021-12-08 08:42:21 +01:00
Bernd Hahnebach 146dd1e74d bimtester: German geometrirc detail step, fix quotation marks 2021-12-08 08:23:00 +01:00
Bernd Hahnebach 6d47cf3a51 bimtester, attributes ele class steps, use generic assert methond 2021-12-07 18:01:10 +01:00
Bernd Hahnebach 9b6d53fe38 bimtester: example steps, delete more steps which are implemented already 2021-12-07 15:51:31 +01:00
Bernd Hahnebach 6902e2e2c6 bimtester: features, move geometric detail tests from examples to implemented steps 2021-12-07 15:47:15 +01:00
Bernd Hahnebach 82d136b9b3 bimtester: util, improve generic assert method 2021-12-07 14:58:18 +01:00
Bernd Hahnebach b8dd0450d1 bimtester: features, fixes in element class attributes 2021-12-07 14:47:13 +01:00
Bernd Hahnebach 85d76c084b bimstester: features, move element class attributes tests from examples to implemented steps 2021-12-07 12:59:50 +01:00
Bernd Hahnebach c658f7610e bimstester: features, move spatial structure tests from examples to implemented steps 2021-12-07 12:16:18 +01:00
Bernd Hahnebach 3fad28572b bimstester: features, move application tests from examples to implemented steps 2021-12-07 11:44:21 +01:00
Bernd Hahnebach 455fe80497 bimtester: features import module, sort by abc 2021-12-07 11:27:42 +01:00
Bernd Hahnebach 3f46d54ad9 bimtester: features, move qset test from example steps to implemented steps 2021-12-07 09:38:22 +01:00
Bernd Hahnebach 89e3276c90 bimtester: example steps, delete the ones which are implemented already 2021-12-07 08:47:31 +01:00
aothms bb9eba07c0 build-all.py python library proposal #1884 2021-12-03 10:21:55 +01:00
aothms 6d4573fb58 build-all.py option PYTHON_USER_SITE 2021-12-03 10:21:55 +01:00
aothms 24890dd001 build-all.py option NO_CLEAN 2021-12-03 10:21:55 +01:00
Dion Moult 65d599bbd9 Fix bug where Blender name wouldn't match IFC Name when reassigning class. See #1902. 2021-12-03 11:00:54 +11:00
Dion Moult ebe5460a9f Fix segfault thanks to aothms. 2021-12-02 09:28:04 +01:00
Dion Moult 01bc29c5b2 Purge by_ref_cached_ 2021-12-02 09:28:04 +01:00
Dion Moult 5ad6d455f9 get_total_inverses now efficiently gets the full number of references to an entity 2021-12-02 09:28:04 +01:00
Dion Moult cef1c86f6d Experimental get_inverse_cardinality proposal. See #1904. 2021-12-02 09:28:04 +01:00
Cyril Waechter d1caa3242f Refactor to usecase as asked in #1881 (#1907) 2021-11-30 21:24:19 +11:00
Dion Moult 4f7bfec026 Merge branch 'v0.7.0' of github.com:IfcOpenShell/IfcOpenShell into v0.7.0 2021-11-30 21:23:16 +11:00
Dion Moult df6ad70345 Fix percentage loading implementation with the new more accurate progress number. 2021-11-30 21:23:06 +11:00
Bernd Hahnebach a7a3ccb23a bimtester: gui, initialize ifc file and feature file text 2021-11-30 10:30:41 +01:00
Cyril Waechter b65f2ca062 Allow to assign IfcRelSpaceBoundary to an object (#1902) 2021-11-30 13:35:55 +11:00
Cyril Waechter f991b053d3 Allow to manually update boundary connection geometry (#1899)
* Allow to manually update boundary geometry

Following have been created in this purpose:
* usecase in ifcopenshell.api
* blenderbim operator
* button in blenderbim manually trigger it

* Assign kwargs to object instead of settings dict

* Add forgotten helper file
2021-11-30 13:29:22 +11:00
Cyril Waechter 47a25ca8b9 Prevent crash on export for obj with no material (#1903)
During export material_checksum was always checked against a stringed
list `'[]'` for an empty material while default value
for material_checksum default was `''`. So it was trying to update
material of object which have none (eg. IfcRelSpaceBoundary).
See: https://github.com/IfcOpenShell/IfcOpenShell/blob/a3f27830c579e567218f79faa52dfc941a441241/src/blenderbim/blenderbim/bim/export_ifc.py#L147
2021-11-30 13:27:08 +11:00
Bernd Hahnebach a3f27830c5 bimtester: gui, improve prints and error handling 2021-11-29 14:59:30 +01:00
Bernd Hahnebach 1a5b5dd303 bimtester: fix gui, works with feature file now instead of feature directory 2021-11-29 10:28:49 +01:00
Dion Moult f529bc37ee Fix bug where selecting type occurrences did not work 2021-11-29 20:25:51 +11:00
Dion Moult 825ffe03b0 Converting brickschema projects to IFC libraries now works on IFC2X3 2021-11-29 20:19:58 +11:00
Dion Moult 622ff05247 Fix bug where updating owner history may not work if you were using it outside Blender. 2021-11-29 20:17:02 +11:00
Dion Moult d638405ff8 Assigning library references now works on IFC2X3. 2021-11-29 20:16:25 +11:00
Dion Moult c0f547c6e4 Test expectations for new remove_deep2 proposal. See #1812. 2021-11-29 20:14:50 +11:00
Dion Moult 3accc1dbd6 Editing object placements is now 3x faster on large files. See #1807. 2021-11-29 19:58:03 +11:00
aothms 6eab9fb593 #1785 HdfSerializer read-only option 2021-11-28 19:34:54 +00:00
Felipe Mesquita 2708133493 Added information for debugging level parameter 2021-11-27 08:53:17 +01:00
Thomas Krijnen c5e6c3e2c7 #1897 handle nesting in get_decomposing_entity_impl() 2021-11-26 14:15:51 +01:00
Thomas Krijnen d7b27c0620 ifcopenshell.ifcopenshell_wrapper.turn_on_detailed_logging() 2021-11-25 15:23:14 +01:00
Thomas Krijnen 75ffc4454d #1895 don't signal element ready when result is empty 2021-11-25 12:05:39 +01:00
Dion Moult a1817e8a61 Fix #1889. Allow explicitly overriding data types when editing psets. 2021-11-24 16:38:19 +11:00
Bernd Hahnebach b4b334145f BIMTester: steps, improve imports in the regard of rebases 2021-11-23 15:18:01 +01:00
Bernd Hahnebach 04535d27ec Bimtester, improve German attribute feature translation, get rid of umlaut since it makes problems in some circumstances 2021-11-21 21:19:10 +01:00
Thomas Krijnen 3c41f6b0f8 #1153 draw.py timing 2021-11-20 14:05:53 +01:00
Thomas Krijnen a17c44c076 #1153 update draw.py with two new options 2021-11-20 14:05:11 +01:00
Thomas Krijnen eccf72ee8a #1153 option to preprocess shape into sets of profile edges 2021-11-20 14:02:12 +01:00
Thomas Krijnen 9c9bec9c4e Some more things related to #1853 2021-11-19 15:08:24 +01:00
Thomas Krijnen 71c1cd6a9e Check for null result and handle some bop alerts 2021-11-19 10:39:14 +01:00
Thomas Krijnen 0504556e7a #1885 consider schema type when copying attribute values 2021-11-19 09:38:28 +01:00
Thomas Krijnen e9a18d030e Small cleanup and try to please gcc 2021-11-19 09:22:09 +01:00
Thomas Krijnen 6d725f60fa finish up 2021-11-19 09:22:09 +01:00
Thomas Krijnen 72206bc441 async init, crashes now on get() 2021-11-19 09:22:09 +01:00
Dion Moult c0eb9addfd Fix bug in FM utility for grabbing classes from the draft FMHEM standard 2021-11-19 19:13:21 +11:00
Dion Moult ff3a98234d Fix bug in convert length unit recipe where IFC2X3 ownership history fails. 2021-11-19 19:12:55 +11:00
Dion Moult ebf6b9fc88 Fix #1886. 2021-11-19 19:08:34 +11:00
Cyril Waechter c2e4b5f1fa [BlenderBIM] Boundaries: add relations editing (#1881)
* SelectGlobalId: set object as active

Use tool module instead of previous custom search loop.

* Boundaries: add relations editing

For entity of class IfcRelSpaceBoundary :
* Display relationship attributes
* Add button to jump to related objects
* Allow to edit relations attributes including entity filter.
UI similar to current base attributes editing.
2021-11-19 10:06:22 +11:00
Bernd Hahnebach 9728c4d345 bimtester: features psets, use quots the same way as in all other features 2021-11-18 18:16:37 +01:00
Gorgious f0d6500539 Merge branch 'v0.7.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.7.0 2021-11-18 10:38:27 +01:00
Gorgious 124ccf0404 (slightly) Optimise batch removing objects 2021-11-18 10:38:17 +01:00
Bernd Hahnebach df03f7ad29 bimtester: steps, fix undefined name 2021-11-18 10:20:50 +01:00
Bernd Hahnebach ff8fc898a1 bimtester: examples, trailing ws 2021-11-18 09:53:53 +01:00
Bernd Hahnebach 2d62d8181a bimtester: improve French translation for the provided example 2021-11-17 16:08:42 +01:00
Bernd Hahnebach 6853735bb2 bimtester: fix example feature files to work with this bimtester 2021-11-17 10:36:37 +01:00
Bernd Hahnebach 1c3980b6f0 bimtester: fix italian and dutch project setup step files 2021-11-17 10:36:37 +01:00
Dion Moult 5c224150ec More slow progress in getting annotations round-tripping. See #1153. 2021-11-17 18:25:01 +11:00
Dion Moult 18719c16df Fix bug where subelement coordinates were incorrectly calculated in non SI units 2021-11-17 16:22:23 +11:00
Dion Moult ddb8abff0f Minor fix 2021-11-17 15:32:26 +11:00
Dion Moult f9221717ff You can now associate IFC elements with Brick elements and navigate to them 2021-11-17 13:27:07 +11:00
Dion Moult da6945b352 You can now assign and unassign library references 2021-11-17 11:43:53 +11:00
Dion Moult 62cdf76a5a Fix bug where you couldn't copy quantities to other elements 2021-11-17 10:35:46 +11:00
Dion Moult fae607f8ce New UI to add / edit / remove library information and references. 2021-11-16 22:15:54 +11:00
Dion Moult 468192e849 Fix bug where unlinking a style that was invalidly linked didn't work. 2021-11-16 22:08:55 +11:00
Dion Moult edd01637e2 Minor fix 2021-11-16 21:51:23 +11:00
Bruno Postle 604fbc4bc9 Raise default section height from storey from 1.0 to 1.2 2021-11-16 09:12:42 +01:00
Dion Moult 4617d9f234 A whole bunch of new usecases for managing IFC library information and references 2021-11-16 18:30:23 +11:00
Dion Moult 41486ba08a New utility to get layers of an element. See #1869. 2021-11-16 18:07:47 +11:00
Dion Moult 2120653104 Minor fix. 2021-11-16 17:52:08 +11:00
Dion Moult 43869c33f6 Rebuild text annotations to have leaders in the same object and use text literals. See #1153. 2021-11-16 08:54:29 +11:00
Dion Moult fcfdc179df Viewport annotation decorations now scale based on paper size. See #1153. 2021-11-16 08:48:50 +11:00
Dion Moult 30c164d223 Fix minor error in console when changing drawings. See #1153. 2021-11-16 08:45:52 +11:00
Dion Moult b28d8b0cca Drawing annotations now are stored in IFC using ObjectType conventions. See #1153. 2021-11-16 08:35:49 +11:00
Dion Moult 4b5907f5dd Reimplement roundtrip of most annotation elements for drawings. See #1153. 2021-11-16 08:32:43 +11:00
Dion Moult 78a27ef44c New experimental OBJ2IFC script 2021-11-14 18:30:56 +11:00
Thomas Krijnen 528e27234c tree.protrusion_distances() 2021-11-13 15:54:42 +01:00
Dion Moult 8ac885ced1 Fix bug where annotative grids did not sync. 2021-11-12 15:16:10 +11:00
Dion Moult 63872abaef Purge old 2D element drawing system, so we don't get double the symbols on drawings. 2021-11-12 14:01:13 +11:00
Dion Moult 5ba155eb17 Fix #1857. Fix bug where deleting from the outliner was not tracked. 2021-11-11 22:27:40 +11:00
Dion Moult 2ec3c4c06b Swapping a Blender material for another is now auto synchronised. 2021-11-11 18:21:02 +11:00
Dion Moult 6f55cd5169 Optimise object placement change checking, so medium sized project exports are 5 seconds faster at minimum, but can be much higher since false positives are culled. 2021-11-11 17:31:23 +11:00
Dion Moult f5f88f4b97 Fix #1872. Bug where duplicating an object didn't duplicate its styles. 2021-11-11 17:31:23 +11:00
Dion Moult d5b92c0fea Fix bug where style colours were not auto synchronised. 2021-11-11 17:31:23 +11:00
Laurens Oostwegel 9355969110 Load CJIO on execution instead of initialisation 2021-11-10 20:08:18 +01:00
Dion Moult 6c998e0b8a Fix #1871. 2021-11-10 22:23:52 +11:00
Dion Moult 51f8d648ad Minor fix 2021-11-10 21:01:17 +11:00
Vukas Pajic a26539d6a5 show property description in pset_template ui 2021-11-10 10:18:55 +01:00
Dion Moult a9f27fcf17 Fix bug #1854. Materials are now auto replaced in Blender when you assign a new IFC material. 2021-11-10 18:04:54 +11:00
Dion Moult 28e5af853d Fix bug where an added default material was not linked and auto synced. 2021-11-10 18:01:10 +11:00
Dion Moult a7b3b5261f Fix bug in IFC2X3 where updating style colours didn't work. 2021-11-10 16:45:16 +11:00
Dion Moult 0dac56dbdf Fix bug where diffuse colour was not correctly extracted from nodes 2021-11-10 16:39:06 +11:00
Dion Moult 0992f43f11 Geometry caching is now enabled for model importing 2021-11-10 13:52:14 +11:00
Dion Moult c1c50f8868 Minor fix. 2021-11-10 12:46:07 +11:00
Dion Moult d733960068 Fix #1856. Bug where you lost your existing container or aggregation when you added a new aggregation assembly. 2021-11-09 20:50:31 +11:00
Dion Moult 66fa3f0096 Changes are now synchronised prior to switching representations. 2021-11-09 20:49:25 +11:00
Dion Moult cbf7b0a77a Minor clean up for CityJSON PR. 2021-11-09 20:46:46 +11:00
Dion Moult 91f2f9438f Fix #1764. Bug where OpenLCA UI would freeze if it couldn't connect to OpenLCA. 2021-11-09 20:43:30 +11:00
Dion Moult fcbb6e60ac Another Makefile fix :( 2021-11-09 10:05:06 +11:00
Dion Moult f33d613f9d Minor Makefile fix 2021-11-09 09:45:36 +11:00
Cyril Waechter 849dfbed28 [BlenderBIM] Tools to load IfcRelSpaceBoundary (#1864)
* Tools to load IfcRelSpaceBoundary

At scene level to load all.
At space level to load ones from a specific space or a specific one.

* Add a tool to colour boundaries

Colour is based on its related building element.
2021-11-09 09:28:44 +11:00
Dion Moult 8a4bc34fad Scale changes are now auto synchronised on export. 2021-11-09 09:22:03 +11:00
Laurens Oostwegel aa18c1b7d6 Small fix in Makefile error due to ifccityjson 2021-11-08 23:03:31 +01:00
Laurens Oostwegel ae45a9b9a5 Fix error in Makefile 2021-11-08 22:59:09 +01:00
ecke101 7aa75d98d3 Improve support for Powerproject import (#1865)
This is tested with swedish PowerProject 14.0 file
2021-11-09 08:52:24 +11:00
Laurens Oostwegel ff6db5f24d Integrate ifccityjson in BlenderBIM (#1859)
* ifccityjson-BB integration: include CJIO in makefile

* Add ifccityjson to BlenderBIM

* Make ifccityjson work like a package

* Use pip while installing CJIO dependency for ifccityjson

* revert removal of brick module

* Changes from pull request review

* Use ifccityjson as CLI and as package for BlenderBIM
2021-11-09 08:51:30 +11:00
Dion Moult de7c54b771 Fix #1855. Bug where authoring type dropdowns didn't refresh. 2021-11-08 21:12:38 +11:00
Dion Moult 7fbf317ad2 Fix representation where removing a representation left objects with an empty mesh. Now a proper empty is used. 2021-11-08 21:12:38 +11:00
Dion Moult 0471f17d9a Fix bug where removing a representation from a typed instance didn't remove all data users. 2021-11-08 21:12:38 +11:00
Dion Moult bf134eb0f4 Implement cache updating for drawings. See #1153. 2021-11-08 21:12:38 +11:00
Dion Moult 7ccb6ee110 Resolve #1836. Scaling in Blender is now auto resolved when representations are updated. 2021-11-08 21:12:38 +11:00
Dion Moult 9076043a5c Purge old dumb door and dumb window tool to prevent confusing users 2021-11-08 21:12:38 +11:00
Dion Moult 1c0fcf7b40 Add annotation symbols for doors, windows, and bunny to the demo library 2021-11-08 21:12:38 +11:00
Dion Moult 4fdcb87a28 New IfcOpenShell utility to get instances of an element type easily 2021-11-08 21:12:38 +11:00
Dion Moult 246460dec4 Fix bug where non-geometric objects caused the representations UI to not display 2021-11-08 21:12:38 +11:00
Thomas Krijnen a89a40e637 #1785 omplete stored_settings / requested_settings check 2021-11-08 10:59:49 +01:00
Thomas Krijnen c1fcf2528e swig uint64_t 2021-11-08 10:58:55 +01:00
Thomas Krijnen d4f0495caa Consistent setting type 2021-11-08 09:59:15 +01:00
Dion Moult 669295d4ac Resolve #1860. Brickschema is now optional. 2021-11-08 11:17:11 +11:00
Dion Moult e4a27d49c9 Drawing module now is in sync with latest C++ upgrades. See #1153. 2021-11-08 08:36:13 +11:00
Thomas Krijnen e0e8281a1e Allow tree.select(BRepElement) 2021-11-07 19:49:37 +01:00
Thomas Krijnen 1cdf695891 #1785 check settings in cache and upgrade to world coords 2021-11-07 19:23:48 +01:00
Thomas Krijnen 580678c2c1 Fix casts in XML serializer 2021-11-07 11:16:47 +01:00
Laurens Oostwegel a038ae434b ifccityjson: it is now easier to extract one LOD 2021-11-07 11:04:27 +01:00
Laurens Oostwegel 698ef40ada ifccityjson: prevent interior holes in faces as there is a bug in the geometry generation 2021-11-07 10:55:47 +01:00
Laurens Oostwegel 276077964d ifccityjson: refactor GeometryIO class 2021-11-07 10:51:09 +01:00
Laurens Oostwegel 45080a8a1f ifccityjson: temporary fix until CJIO can build its LOD dictionary again 2021-11-07 10:50:12 +01:00
Laurens Oostwegel dd348de0b7 ifccityjson: lazy build vertex dictionary 2021-11-07 10:40:01 +01:00
Dion Moult 61e510e45c New feature to select all objects in same container 2021-11-07 17:02:05 +11:00
Dion Moult 05068754b9 New feature to select the container of an object 2021-11-07 15:32:33 +11:00
Dion Moult de561b78e0 Resolve #1750. You can now save IFCs under a relative path to your Blend file. 2021-11-07 13:20:33 +11:00
Thomas Krijnen e6e1978584 only call expensive wstring_convert() when necessary 2021-11-06 14:36:58 +01:00
Thomas Krijnen 0b7bdc0f9d #1785 geometry caching caching 2021-11-06 13:16:13 +01:00
Dion Moult e868186897 Resolve #1749. You can now save as another IFC filename. 2021-11-06 21:41:31 +11:00
Dion Moult 5450233f8e Minor fix 2021-11-06 21:10:56 +11:00
Dion Moult 8e215382f7 Fix bug where assigning a type did not use existing representation data in the scene if the context had changed 2021-11-06 20:56:53 +11:00
Dion Moult 4149292a13 Fix bug where adding a representation to a type instance didn't share meshes 2021-11-06 18:51:00 +11:00
Dion Moult 117f663f48 Fix bug where you couldn't add a plan representation to something with no context identifier 2021-11-06 17:48:31 +11:00
Dion Moult 960610325f Fix interface bug where representations assigned directly to a context aren't shown 2021-11-06 17:36:47 +11:00
Thomas Krijnen ab50609854 Rename some files with more sensical names 2021-11-05 16:54:16 +01:00
Thomas Krijnen c203b78d34 #1153 #1852 project points to form SVG HLR cut plane 2021-11-05 13:27:27 +01:00
Thomas Krijnen 5633445008 #1153 evenodd 2021-11-05 11:21:19 +01:00
Thomas Krijnen 22ceb35f47 IfcConvert : SEW_SHELLS on svg serializer 2021-11-05 11:07:27 +01:00
Thomas Krijnen 06a988e141 #1153 boolean subtraction on parts 2021-11-05 11:04:46 +01:00
Thomas Krijnen 2dc29978f6 --cache and subtraction in draw.py 2021-11-05 11:03:44 +01:00
Thomas Krijnen 10b502512f #1853 don't blindly explode compounds but check whether there are solids inside 2021-11-05 10:09:12 +01:00
Thomas Krijnen 2d0c44e25a #1785 Set trsf form 2021-11-05 09:22:24 +01:00
Dion Moult 3e52d93db0 Fix bug where existing meshes were not deleted when switching representations. 2021-11-05 17:57:05 +11:00
Dion Moult 07bee4acfa Fix bug where adding a representation to type instances didn't ensure it was added to a geometric type 2021-11-05 15:14:10 +11:00
Dion Moult 0da5d807b8 Minor fix 2021-11-05 13:15:28 +11:00
Dion Moult 657aafbd2e Minor fix 2021-11-05 12:20:33 +11:00
Dion Moult 04449d08e8 Add schema caching to Brick schema loading. 2021-11-05 09:19:42 +11:00
Dion Moult 2b710ffbde Fix bug where pie update container didn't work. 2021-11-04 22:23:26 +11:00
Dion Moult 7fbe365599 Implement new Brickschema project browser. 2021-11-04 18:02:50 +11:00
Dion Moult f49b9f6781 Slightly more robust deletion tracking. 2021-11-03 21:39:17 +11:00
Dion Moult 901318b73b Fix bug where copying an instance did not auto link the meshes. 2021-11-03 19:04:22 +11:00
Laurens Oostwegel 8545d870c0 ifccityjson: Fix bug dependency updated CJIO
In the new CJIO module, cityjson vertices got transformed automatically when calling the object faces. Now this is not the case anymore.
2021-11-03 08:45:36 +01:00
Thomas Krijnen 3124d306d6 #1153 another small hack to get boolean projection working more generally 2021-11-02 15:52:51 +01:00
Thomas Krijnen 8e529f3f10 #1850 Process extrusions of comp profiles seperately 2021-11-02 15:11:56 +01:00
Thomas Krijnen 1ff45165d9 #1743 don't consider bbox in opening subtractions 2021-11-02 15:09:26 +01:00
Thomas Krijnen 05256fc0f4 #1785 geometry caching on multi threaded iterator 2021-11-02 11:39:49 +01:00
Thomas Krijnen be9ef33932 #1153 svg subtraction settings 2021-11-02 08:57:00 +01:00
Thomas Krijnen fc424ebd6b #1785 quick identity check to keep trsf form at identity 2021-11-01 21:47:56 +01:00
Laurens Oostwegel 030f3cf307 Add tutorial to ifccityjson 2021-11-01 18:18:46 +01:00
HassanEmam c8eb933453 Added support for Powerproject (#1849)
* implemented initial cli and support for resource parsing for ms project

* Added command line interface and resource support for MS Project XML files

* Removed the test code

* Check for output exists before writting the file to avoid failure

* Added support for Asta Powerproject files
2021-11-01 10:52:43 +11:00
Dion Moult 208132785f Minor fix 2021-10-31 10:08:41 +01:00
Dion Moult cfa435e9c8 Do the same things in 10 times less code thanks to aothms :) 2021-10-31 10:08:41 +01:00
Dion Moult 515e972906 Fix projection of origin onto camera plane. 2021-10-31 10:08:41 +01:00
Thomas Krijnen 100b0637ac submodule 2021-10-30 16:16:03 +02:00
HassanEmam 2d8ec8a6bf Command Line Interface and MS Project Resource Parsing (#1845)
* implemented initial cli and support for resource parsing for ms project

* Added command line interface and resource support for MS Project XML files

* Removed the test code

* Check for output exists before writting the file to avoid failure
2021-10-30 22:29:10 +11:00
Dion Moult 02461233a6 Reimplement drawing material classes, fix cut projection order, and auto refresh camera representation 2021-10-30 22:13:50 +11:00
Dion Moult dcac1e69ca We now support importing 3D annotations, not just 2D ones. Neat. 2021-10-30 22:13:50 +11:00
Dion Moult b118400cc6 Invalid annotation contexts coming from Revit are now accepted 2021-10-30 22:13:50 +11:00
Roch Bertucat bc430b80f6 Fix Python validate to handle Logical (see #1787) 2021-10-30 13:12:45 +02:00
Dion Moult d2d3addf10 New feature to draw system arrows showing flow direction and port connections. 2021-10-30 18:12:58 +11:00
Dion Moult 683e316088 New MEP utility to select and print debug information about connected system elements 2021-10-30 18:00:36 +11:00
HassanEmam e279fee518 Resource Parsing for P6XML (#1842)
* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

* merged updats

* refactoring code for the 4D model to avoid duplicate code

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* Initial support for Primavera XER files

* merged updats

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* deleted repetitive classes for P6XER import

* Resource Parsing added Primavera P6 XML module

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

* merged updats

* refactoring code for the 4D model to avoid duplicate code

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* Initial support for Primavera XER files

* merged updats

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* deleted repetitive classes for P6XER import

* Resource Parsing added Primavera P6 XML module

* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

* refactoring code for the 4D model to avoid duplicate code

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* Initial support for Primavera XER files

* merged updats

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* deleted repetitive classes for P6XER import

* Resource Parsing added Primavera P6 XML module

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

* merged updats

* refactoring code for the 4D model to avoid duplicate code

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* Initial support for Primavera XER files

* merged updats

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* deleted repetitive classes for P6XER import

* Resource Parsing added Primavera P6 XML module

* removed the testing code

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-30 10:31:18 +11:00
Thomas Krijnen 93c5906ad7 #1153 temporary y offset fix 2021-10-29 14:16:10 +02:00
HassanEmam 6a06607f3f BlenderBIM: Import Resources for XER files (#1841)
* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

* merged updats

* refactoring code for the 4D model to avoid duplicate code

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* Initial support for Primavera XER files

* merged updats

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

* Added resource parsing support for Primavera XER

* added relative path for importing common as it was failing in BlenderBIM

* deleted repetitive classes for P6XER import

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-29 21:16:47 +11:00
Thomas Krijnen 9f73bd43b4 Don't wrap HdfSerializer 2021-10-29 10:47:40 +02:00
Thomas Krijnen 5f7d92aeb0 #1153 HdfSerializer.remove() for cache eviction 2021-10-29 10:41:21 +02:00
Thomas Krijnen c754d15da8 don't create empty HdfSerializer symbol 2021-10-29 10:40:42 +02:00
Thomas Krijnen 13f5d2d5de add hdf serializer to wrapper 2021-10-29 10:40:15 +02:00
Thomas Krijnen 280eac6127 parse_ifcxml now handled by swig config 2021-10-29 10:40:03 +02:00
Thomas Krijnen ea0e70ef0d populate more serializers if available 2021-10-29 10:39:32 +02:00
Thomas Krijnen a5af8807ee pass config to swig preproc 2021-10-29 10:39:02 +02:00
Chun 0bae861b6a IfcType Filter suggestions for better User Experience (#1840)
* 1.Added Selecting/Deselecting All function
2.Suggested an idea for UI improvment
3.Fixed an UX bug

* Improve the user experience of IfcType Filter
2021-10-29 19:29:43 +11:00
Dion Moult b32bbb89a1 Optimistically track all object edits whenever you toggle edit mode, not just meshes. 2021-10-29 18:04:47 +11:00
Dion Moult 109149a0be Optimise owner check depending on your implementation and no longer create default users on IFC4. Can result in 50% faster script running on long scripted operations. 2021-10-29 18:04:17 +11:00
Dion Moult 4bf4622498 You can now manually type in start/end dates for construction sequence visualisation instead of clicking the date picker hundreds of times. 2021-10-29 17:44:57 +11:00
Dion Moult a4c054ef62 Objects now sync spatial hierarchy more robustly on export, and clean the collection tree in case the user messed it up. 2021-10-29 17:43:38 +11:00
Dion Moult 1141fd5784 Fix bug where during import sometimes aggregation collections don't match object names 2021-10-29 17:39:59 +11:00
luzpaz 0aa8a19b6d Fix various typos (#1837)
Found via `codespell`
2021-10-29 08:56:02 +11:00
Vukas Pajic b46f1647d2 BlenderBIM: Add UI Preset to Preferences (#1825)
* inital commit

* update based on comments

* incorporate suggestion from @Gorgious56

* change curly brackets to square brackets ;)

* update based on comments from @Moult

* code suggestion from @Gorgious56
2021-10-28 23:41:56 +11:00
Dion Moult 72d9bc9256 Moving grid axes are now synchronised automatically 2021-10-28 19:35:40 +11:00
Dion Moult b67d56ada1 Fix bug where imported grids were not linked and didn't auto track edits 2021-10-28 19:35:40 +11:00
Dion Moult d013d6ed09 Resize object to storey now works for a variable number of storeys 2021-10-28 19:35:40 +11:00
HassanEmam 4bb549f3e9 IFC4D code refactoring (#1834)
* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

* merged updats

* refactoring code for the 4D model to avoid duplicate code

* Assign process in the api module was missing an atrribute QuantityIn Process

* Merged changes from master

* refactoring

* resolved conflicts

* refactoring code for IFC4D

* refactoring code for IFC4D

* refactoring code for IFC4D

* Reverted the changes to IfcRelAssignsToProcess usecase

* Changed the class to be more discriptive from Utils to ScheduleIfcGenerator

* reverted changes to add_work_plan and Makefile

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-28 18:49:02 +11:00
Dion Moult d772299871 Fix bug where auto geolocation offset didn't occur if placements were relative 2021-10-28 11:55:11 +11:00
Dion Moult 081ca81a7e Adding annotation now auto enables viewport decoration drawing. See #1153. 2021-10-27 22:11:05 +11:00
Dion Moult 2ad2a102cb First pass at connecting to new ifcopenshell.draw. See #1153. 2021-10-27 19:42:36 +11:00
Dion Moult 52e8ac386e New hacktastic IFC patch to fix ArchiCAD spaces being loaded into Revit. 2021-10-27 12:29:40 +11:00
Dion Moult f69de3324a You can now split objects using a drawn edge, especially useful when creating slab pour breaks and fixing lazily modeled walls and columns. Thanks Gorgious56! 2021-10-27 12:06:12 +11:00
HassanEmam 04b3b9b60e Ifc4D xer calendars (#1832)
* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

* Initial support for Primavera XER files

* IFC4D impl XER calendars exceptions and work time

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-27 10:13:50 +11:00
Dion Moult f64e1dbd7f New feature to calculate side formwork, such as for columns. 2021-10-27 10:09:07 +11:00
Dion Moult 341451f59b Formwork area calculation now unions objects before remeshing to account for overlapping geometry 2021-10-27 09:58:29 +11:00
Dion Moult 80b8da629d Types in type dropdowns are now sorted alphabetically for convenience. 2021-10-27 09:35:31 +11:00
Dion Moult 78e5016619 Minor fix. 2021-10-26 21:05:48 +11:00
Dion Moult 3406c23f98 Fix bug where copying a type also copied its type relations which is invalid. 2021-10-26 20:51:27 +11:00
Dion Moult ee308d294f Spatial elements in the import filter will now be sorted by elevation for convenience. 2021-10-26 17:37:17 +11:00
Dion Moult 955a9af461 Users copying and pasting via ctrl-c/v will now also copy IFC objects. 2021-10-26 17:36:58 +11:00
Dion Moult 34e8d9563d Using duplicate move linked in a BIM project now also triggers IFC copying 2021-10-26 17:17:20 +11:00
Dion Moult e5b5403eaa Minor code review for #1804. 2021-10-26 09:29:15 +11:00
Laurens Oostwegel 47cf2ea29e BlenderBIM: import IFC PointCloud Representation (#1804)
* Implemented export of point cloud representation

* Do representation geometry data no-edge check before no-polygon check

* Initial implementation import of point clouds in BlenderBIM

* create point cloud elements on the appropriate place

* Updated import_ifc.py based on review PR

* delete is_pointcloud() because it is replaced by get_pointcloud_representation()
2021-10-26 09:04:49 +11:00
HassanEmam 9d8074f1fe Initial support for importing Primavera XER files (#1829)
* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

* Initial support for Primavera XER files

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-26 08:51:17 +11:00
HassanEmam 3fd944a956 Fixing bugs for Scheduling Module IFC4D (#1826)
* P6XML Parser failing on calendar type

* add work plan references a get_person which is not an attribute. modified to get_user to work

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-25 20:56:45 +11:00
Thomas Krijnen 566512f7bc #1807 argument_types() on schema entity 2021-10-25 11:52:05 +02:00
Dion Moult 1a84967b1b Code review for Ifc Type filter feature. See #1823. 2021-10-25 20:12:36 +11:00
Chun 41afb26d31 Added IfcTypeFilter into BlenderBIM Search module (#1823) 2021-10-25 19:33:57 +11:00
Dion Moult 818e8010a4 Minor fix. 2021-10-25 19:32:37 +11:00
Dion Moult 58ee049a8f New feature to show inherited element type psets. 2021-10-25 16:21:40 +11:00
Dion Moult 54fa45850a Get psets utility now supports getting material and profile psets 2021-10-25 16:09:04 +11:00
Dion Moult fbadcc017b Get psets utility can now filter only psets or qtos 2021-10-25 15:36:55 +11:00
Dion Moult 6337698494 Element get_psets utility now returns the pset ID for convenience. 2021-10-25 14:21:41 +11:00
Dion Moult 2e6a70b338 New feature to copy properties from one object to all selected objects 2021-10-25 14:21:41 +11:00
Thomas Krijnen e70f675f20 Make build-all a bit friendlier for python partial install success 2021-10-24 14:56:50 +02:00
Thomas Krijnen 13d0b27ef4 Hacky way to return distances in the tree with extend>0 2021-10-24 14:40:18 +02:00
Thomas Krijnen 40b112cd48 Add back platform.machine to build-all path 2021-10-24 12:10:15 +02:00
Thomas Krijnen 14c0400f73 Fix case after #1781 2021-10-24 10:08:06 +02:00
Thomas Krijnen 4733c82b82 Separate out consume_iterator() 2021-10-23 21:34:25 +02:00
Dion Moult 7bccf10fec Fix regression bug where you couldn't add a grid. 2021-10-23 21:19:10 +11:00
Dion Moult 918915aeae Reintroduced feature to copy attributes to selected objects. 2021-10-23 19:55:22 +11:00
Dion Moult 295568453c Minor code review for module visibility feature. See #1798. 2021-10-23 19:01:43 +11:00
Vukas Pajic 56ff0e7472 BlenderBIM: added dropdown in addon preferences to toggle module visibility (#1798)
* added dropdown in addon preferences to toggle module visibility

* Update ui.py

* updated ui to allow individual toggling of modules

* working

* code cleanup and update

* fix issue where non relevant operators shown

* update based on review comments

* add contingency for intermodule panel dependancy

* removed 'magic' operators :)

* clean up some leftover lines

* updated to only register/deregister panel classes
2021-10-23 18:16:11 +11:00
Dion Moult e779a57a09 Fix bug where imported types with representations but no instances were not fully linked. 2021-10-23 18:15:23 +11:00
Dion Moult 6d817703aa Copying parametric geometry from any source now attempts to preserve parametric geometry when possible. 2021-10-22 18:07:34 +11:00
Dion Moult c551275db2 Fix bug where switching to an object with modifiers applied would double the effect of modifiers. 2021-10-22 16:23:36 +11:00
Dion Moult 9ffe92eae5 Assigning a parametric layer type (for walls for now) now attempts to switch from non parametric to parametric geometry. 2021-10-22 15:43:04 +11:00
Dion Moult f19e334cd5 Fix bug where large resizing of walls could result in broken geometry. 2021-10-22 15:21:26 +11:00
Dion Moult a5b8728aec Fix bug where copying elements didn't ensure that material usages were unique 2021-10-22 12:09:57 +11:00
Dion Moult a8c99cebaf Fix terrible bug #1819 where duplicating mapped elements didn't actually copy the geometry mapping. 2021-10-22 11:16:03 +11:00
Dion Moult 9ae2c6352a Minor fix 2021-10-21 23:06:39 +11:00
Dion Moult 0bc849be5a New feature to conveniently add a default material in a blank project when none exists. 2021-10-21 19:40:10 +11:00
Dion Moult a99df36351 The type of mapped representations is now shown in the interface, with an asterisk denoting it is mapped. 2021-10-21 19:16:15 +11:00
Dion Moult 525e6debd5 When updating representations, the existing representation type is prioritised unless you tell it otherwise. See #1748. 2021-10-21 18:39:55 +11:00
Gorgious d6cb94c6f1 Port root, bimtester and sequence to feature tests 2021-10-21 09:33:45 +02:00
Dion Moult c437ba8f9d Assigning a parametric profile type now auto switches from non parametric geometry to parametric geometry 2021-10-21 16:28:55 +11:00
Dion Moult aee7fbcc79 Only show type selection buttons when a type is available. 2021-10-21 14:10:24 +11:00
Dion Moult 456adf453d New feature to calculate the radius of a polygonal circle 2021-10-21 12:43:16 +11:00
Dion Moult d2e9194636 Adding type empties now adds it to the scene collection for convenience in case your types collection is hidden (which is most of the time) 2021-10-21 11:37:44 +11:00
Dion Moult 9907651d4a Fix bug where you could reassign class to another product type and create invalid relationships 2021-10-21 11:28:49 +11:00
Massimo Fabbro bb2f59e2f8 Add tooltips. Finished the "Authoring" section in the n-panel. (See issue #1646) (#1818)
* Add first tooltip (#1646)

* Add tooltips for new project

* Small fix

* Added more tootips

* Add other tooltip to the n-panel

* Add other tooltips in N Panel
2021-10-21 10:10:54 +11:00
Gorgious56 fec295d021 Migration (#1817) 2021-10-21 10:10:22 +11:00
Dion Moult cb08c19942 Fix bug where BCF header file info was not loaded. See #1816. 2021-10-20 23:41:43 +11:00
Thomas Krijnen b1b121ebc8 #1814 check for the succesful creation of edge curves in IfcIndexedPolyCurve 2021-10-20 11:14:35 +02:00
Alexander Nitsch ba9c2a3280 Fix initialization of local variable "data" (#1813)
Co-authored-by: Nitsch Alexander <alexander.nitsch@scs.ch>
2021-10-20 19:57:13 +11:00
Dion Moult 575de12d79 Fix bug where auto assignment in the collection hierarchy failed if you had filtered out aggregates and you were assigning a subelement of an aggregate 2021-10-20 19:56:46 +11:00
Dion Moult 503f0399ff Fix bug where you couldn't bulk copy non-geometric elements to other spatial containers 2021-10-20 18:47:55 +11:00
Vukas Pajic 871ad1ab7f Added operator to add psets to all selected objects. (#1805)
* first attempt

* Accommodate object vs non-object psets, and directly fetch applicable pset names

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2021-10-20 14:49:02 +11:00
Dion Moult 58859ef8c3 Fix bug where when deleting representations it could sometimes overreach and delete geometric, style, or layer data of other elements. See #1812. 2021-10-20 14:11:49 +11:00
Dion Moult 52062bc7e8 Experimental proposed deep removal. See #1812. 2021-10-20 11:44:19 +11:00
Gorgious56 8fdbe30dc5 Ux docs test (#1808)
* First draft

* Final Update
2021-10-19 10:55:25 +02:00
Dion Moult f438c48c23 Fix bug where copying objects may sometimes result in incorrect location in the Blender collection tree 2021-10-19 19:15:01 +11:00
Dion Moult 6478601f79 Fix bug where copying objects did not necessarily retain the existing context assignment 2021-10-19 19:15:01 +11:00
Thomas Krijnen 816059eefd Reapply 869cc25061 2021-10-19 08:49:46 +02:00
Dion Moult 3daf4203d1 When assigning types, your preference of dynamic voids are now preserved. 2021-10-19 15:43:14 +11:00
Dion Moult c1b53859ee WIP refactor of geometry for stabilisation and optimisation. Switch representation now done. 2021-10-19 12:57:29 +11:00
Thomas Krijnen 63f85beb7e #1803 Update include in wrapper code as well 2021-10-18 16:10:20 +02:00
Thomas Krijnen bdc6fcb66f #1803 Use abstract GeometrySerializer as cache to eliminate cyclic dependencies 2021-10-18 11:38:01 +02:00
Dion Moult 4577b7bf60 Potentially risky refactor of add_representation in preparation of stabilisation and optimization of geometric operators. 2021-10-18 19:23:40 +11:00
Dion Moult 33f4461a87 New feature to resize / squash / stretch objects to their storey height. Useful for remodeling IFCs for construction simulations. 2021-10-18 14:15:16 +11:00
Dion Moult c78e7b3ba8 Fix minor bug where Python warnings showed up when delete operations were called 2021-10-18 14:12:07 +11:00
Dion Moult 699ccddda3 New utility function to get storey elevations with elevation attribute fallback for more compliant sorting of building storey elevations. 2021-10-18 12:39:04 +11:00
Dion Moult a9a48bdd1d Refactor misc functions into new misc module for better organisation. 2021-10-18 11:21:49 +11:00
amoki 8ed395a29f build python only if needed 2021-10-17 10:15:08 +02:00
amoki 63bfe761bf use platform.system() instead of uname, install python module directly in current python 2021-10-17 10:15:08 +02:00
amoki f8c11e3af7 rollback boost version 2021-10-17 10:15:08 +02:00
amoki 993ae71968 upgrade dependencies, remove x86 build 2021-10-17 10:15:08 +02:00
amoki 81b8f7e39b remove last TAG 2021-10-17 10:15:08 +02:00
amoki b1e897197b more cleanup 2021-10-17 10:15:08 +02:00
amoki 46cf1362f6 more f-strings, add some env variables 2021-10-17 10:15:08 +02:00
amoki f03992dcde remove old python versions and use f-strings 2021-10-17 10:15:08 +02:00
Dion Moult aedc1751b6 Resolve #1802. Unlinking an object now also unlinks materials and styles too for convenience. 2021-10-16 16:55:06 +11:00
Dion Moult 26cbd82da1 New feature to unlink Blender materials and IFC materials 2021-10-16 16:53:37 +11:00
Dion Moult 516503b9ca Fix #1801. Fix bug where importing multiple assets sharing an already imported material fails. 2021-10-16 15:46:02 +11:00
Dion Moult 23be5858b5 Fix bug where the derived placements interface didn't take into account rotations 2021-10-15 17:30:33 +11:00
Dion Moult 776842a064 Spatial containers are now shown sorted by Z index order for convenience. 2021-10-15 17:07:23 +11:00
Dion Moult bb090bd9a0 Fix deep magick bug where copying elements without having one of them active didn't actually copy them in IFC too 2021-10-15 14:42:08 +11:00
862 changed files with 34737 additions and 8171 deletions
+2 -2
View File
@@ -28,7 +28,7 @@ jobs:
sudo apt update
sudo apt-get install --no-install-recommends \
git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev
-
@@ -47,7 +47,7 @@ jobs:
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-DBUILD_PACKAGE=On \
-DOCC_INCLUDE_DIR=/usr/include/oce \
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
@@ -23,6 +23,7 @@ jobs:
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
+2 -3
View File
@@ -41,7 +41,7 @@ jobs:
python3-all-dev python3-pip \
swig libpcre3-dev libxml2-dev \
libtbb-dev nlohmann-json3-dev \
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev
- name: ccache
@@ -53,11 +53,10 @@ jobs:
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-DOCC_INCLUDE_DIR=/usr/include/oce \
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
run: |
sudo apt update
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev
- name: Build
@@ -39,7 +39,7 @@ jobs:
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-DBUILD_PACKAGE=On \
-DOCC_INCLUDE_DIR=/usr/include/oce \
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
+27 -10
View File
@@ -1,7 +1,9 @@
IfcOpenShell
============
IfcOpenShell is an open source ([LGPL]) software library for working with the Industry Foundation Classes ([IFC])
file format. Currently supported IFC releases are [IFC2x3 TC1] and [IFC4 Add2 TC1].
file format. Extensive geometric support is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1].
Support for parsing is provided for IFC4x1, IFC4x2, and the IFC4x3 release candidates. Extending with support for
arbitrary IFC schemas is possible at compile-time when using C++ and at run-time when using Python.
For more information, see
* [http://ifcopenshell.org](http://ifcopenshell.org)
@@ -13,7 +15,7 @@ Prerequisites
-------------
* Git
* CMake (2.6 or newer)
* Windows: [Visual Studio] 2008 or newer with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
* *nix: GCC 4.7 or newer, or Clang (any version)
Dependencies
@@ -33,7 +35,9 @@ Dependencies
Building IfcOpenShell
---------------------
**Note:** The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
### Compiling on Windows
The preferred way to fetch and build this project's dependencies is to use the build scripts
@@ -81,17 +85,26 @@ The following instructions are for Ubuntu, modify as required for other operatin
can be experimented with and studied for pointers for other operating systems, but note that this script is not currently
meant to be used for a typical IfcOpenShell workspace setup.
Note: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
Note 1: It is recommeded to use OCCT for IfcOpenShell. You could use OCE as well, but sometimes it may lag behind OCCT and
therefore not compile with the latest IfcOpenShell.
Note 2: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
**1)** Install most of the prerequisites and dependencies:
$ sudo apt-get install git cmake gcc g++ libboost-all-dev
$ sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
**2a)** Either use an OCE package from your operating system's software repository
**2a)** Either use an OCCT package from your operating system's software repository
$ sudo apt-get install libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev
**2b)** or, if OCCT is not available or the latest code is wanted, obtain and compile OCCT from https://dev.opencascade.org/release
**2c)** or, if you'd like to try using OCE, use the package from your operating system's software repository
$ sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
**2b)** or (if not available, or the latest code is wanted) compile OCE yourself (note that the build takes a long time):
**2d)** or if OCE is not available, or the latest code is wanted, compile OCE yourself (note that the build takes a long time)
$ sudo apt-get install libftgl-dev libtbb2 libtbb-dev libgl1-mesa-dev libfreetype6-dev
$ git clone https://github.com/tpaviot/oce.git
@@ -101,8 +114,6 @@ Note: where `make -j` is written, add a number roughly equal to the amount of CP
$ make -j
$ sudo make install
**2c)** or obtain and compile OCCT from https://dev.opencascade.org/release
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
$ sudo apt-get install libpcre3-dev libxml2-dev
@@ -128,7 +139,13 @@ dependencies' paths. `OCC_INCLUDE_DIR` might be needed to set also. `OPENCOLLADA
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
-DCGAL_INCLUDE_DIR=/usr/include \
-DGMP_INCLUDE_DIR=/usr/include \
-DMPFR_INCLUDE_DIR=/usr/include \
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_SUPPORT=Off
$ make -j
If all worked out correctly you can now use IfcOpenShell. See the examples below.
+16 -10
View File
@@ -60,6 +60,7 @@ if (${BUILD_CONVERT})
OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
endif()
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
@@ -144,6 +145,7 @@ ELSE()
MESSAGE(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
ENDIF()
add_definitions(-DWITH_GLTF)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
endif()
# Set INSTALL_RPATH for target
@@ -200,6 +202,7 @@ find_package(LibXml2 REQUIRED)
if (IFCXML_SUPPORT)
add_definitions(-DWITH_IFCXML)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
endif()
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
@@ -371,6 +374,8 @@ IF(COLLADA_SUPPORT)
IF(COLLADASWStreamWriter_h)
MESSAGE(STATUS "OpenCOLLADA header files found")
ADD_DEFINITIONS(-DWITH_OPENCOLLADA)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
SET(OPENCOLLADA_LIBRARY_NAMES
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
OpenCOLLADAStreamWriter UTF buffer ftoa
@@ -462,6 +467,7 @@ if(HDF5_SUPPORT)
endif()
add_definitions(-DWITH_HDF5)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
endif()
IF(NOT CMAKE_BUILD_TYPE)
@@ -692,7 +698,7 @@ TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_
if (BUILD_IFCGEOM)
# IfcGeom
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h)
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h ../src/ifcgeom/*.i)
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/*.cpp)
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
@@ -784,21 +790,20 @@ INSTALL(TARGETS IfcGeomServer
endif()
if (ADD_COMMIT_SHA)
find_package (Git)
if (GIT_FOUND)
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
execute_process(
COMMAND ${GIT_EXECUTABLE} show -s --pretty=%D HEAD
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
OUTPUT_VARIABLE git_branches
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE ", " ";" git_branch_list "${git_branches}")
string(REPLACE " -> " ";" git_branch_list "${git_branch_list}")
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
if (NOT git_branch_candidate MATCHES "/")
if (NOT git_branch_candidate MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate})
endif()
string(STRIP "${git_branch_candidate}" git_branch_candidate_2)
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate_2})
endif()
endforeach()
execute_process(
@@ -806,11 +811,12 @@ if (GIT_FOUND)
OUTPUT_VARIABLE git_sha
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "IfcOpenShell branch ${git_branch}")
message(STATUS "IfcOpenShell commit ${git_sha}")
message(STATUS "IfcOpenShell branch: \"${git_branch}\"")
message(STATUS "IfcOpenShell commit: \"${git_sha}\"")
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
endif()
endif()
# Documentation
IF(BUILD_DOCUMENTATION)
+275 -294
View File
@@ -56,16 +56,10 @@ import subprocess as sp
import shutil
import tarfile
import multiprocessing
import platform
import sysconfig
PYTHON_MAJOR = sys.version_info[0]
if PYTHON_MAJOR >= 3:
from urllib.request import urlretrieve
else:
# Not Python 3 - today, it is most likely to be Python 2
# But note that this might need an update when Python 4
# might be around one day
from urllib import urlretrieve
from urllib.request import urlretrieve
logger = logging.getLogger(__name__)
@@ -74,145 +68,121 @@ ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
logger.addHandler(ch)
PROJECT_NAME="IfcOpenShell"
PYTHON_VERSIONS=["2.7.18", "3.5.5", "3.6.13", "3.7.10", "3.8.8", "3.9.2"]
JSON_VERSION="v3.6.1"
OCE_VERSION="0.18"
OCCT_VERSION="7.5.3"
BOOST_VERSION="1.71.0"
PCRE_VERSION="8.41"
LIBXML2_VERSION="2.9.9"
SWIG_VERSION="3.0.12"
OPENCOLLADA_VERSION="v1.6.68"
HDF5_VERSION="1.8.22"
PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
GMP_VERSION="6.1.2"
MPFR_VERSION="3.1.5"
CGAL_VERSION="5.2"
PYTHON_VERSIONS = ["3.6.14", "3.7.12", "3.8.12", "3.9.7", "3.10.0"]
JSON_VERSION = "v3.6.1"
OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.5.3"
BOOST_VERSION = "1.71.0"
PCRE_VERSION = "8.41"
LIBXML2_VERSION = "2.9.11"
SWIG_VERSION = "4.0.2"
OPENCOLLADA_VERSION = "v1.6.68"
HDF5_VERSION = "1.12.1"
GMP_VERSION = "6.1.2"
MPFR_VERSION = "3.1.5" # latest is 4.1.0
CGAL_VERSION = "5.3"
# binaries
cp="cp"
bash="bash"
uname="uname"
git="git"
bunzip2="bunzip2"
tar="tar"
cc="cc"
cplusplus="c++"
autoconf="autoconf"
automake="automake"
yacc="yacc"
make="make"
cp = "cp"
bash = "bash"
git = "git"
bunzip2 = "bunzip2"
tar = "tar"
cc = "cc"
cplusplus = "c++"
autoconf = "autoconf"
automake = "automake"
yacc = "yacc"
make = "make"
date = "date"
curl="curl"
wget="wget"
strip="strip"
curl = "curl"
wget = "wget"
strip = "strip"
# Helper function for coloured printing
NO_COLOR="\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
BLACK_ON_WHITE="\033[0;30;107m"
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
MAGENTA="\033[35m"
NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
BLACK_ON_WHITE = "\033[0;30;107m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
MAGENTA = "\033[35m"
def cecho(message, color=NO_COLOR):
"""Logs message `message` in color `color`."""
logger.info("%s%s\033[0m" % (color, message))
logger.info(f"{color}{message}\033[0m")
def which(cmd):
for path in os.environ["PATH"].split(":"):
for path in os.getenv("PATH").split(":"):
if os.path.exists(path) and cmd in os.listdir(path):
return cmd
return None
def get_os():
ret_value = sp.check_output([uname, "-s"]).strip()
return ret_value
def to_pystring(x):
""" Python 2 & 3 compatibility function for strings handling
(to solve TypeError "Can't mix strings and bytes in path components" for Python 3).
Reference https://github.com/hugsy/gef/issues/382 """
res = str(x, encoding="utf-8") if PYTHON_MAJOR == 3 else x
substs = [("\n","\\n"), ("\r","\\r"), ("\t","\\t"), ("\v","\\v"), ("\b","\\b"), ]
for x,y in substs: res = res.replace(x,y)
return res
# Set defaults for missing empty environment variables
USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
TOOLSET = None
if get_os() == "Darwin":
if platform.system() == "Darwin":
# C++11 features used in OCCT 7+ need a more recent stdlib
TOOLSET = "10.9" if USE_OCCT else "10.6"
try:
IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"]
except KeyError:
IFCOS_NUM_BUILD_PROCS=multiprocessing.cpu_count() + 1
try:
TARGET_ARCH = os.environ["TARGET_ARCH"]
except KeyError:
TARGET_ARCH = sp.check_output([uname, "-m"]).strip()
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
CMAKE_DIR=os.path.realpath(os.path.join("..", "cmake"))
CMAKE_DIR = os.path.realpath(os.path.join("..", "cmake"))
try:
DEPS_DIR = os.environ["DEPS_DIR"]
except KeyError:
path = [b"..", b"build", sp.check_output(uname).strip(), TARGET_ARCH]
if TOOLSET:
path.append(TOOLSET)
DEPS_DIR = to_pystring(os.path.realpath(os.path.join(*path)))
path = ["..", "build", platform.system(), platform.machine()]
if TOOLSET:
path.append(TOOLSET)
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
DEPS_DIR = os.getenv("DEPS_DIR", DEFAULT_DEPS_DIR)
if not os.path.exists(DEPS_DIR):
os.makedirs(DEPS_DIR)
try:
BUILD_CFG=os.environ["BUILD_CFG"]
except KeyError:
BUILD_CFG="RelWithDebInfo"
BUILD_CFG = os.getenv("BUILD_CFG", "RelWithDebInfo")
# Print build configuration information
cecho ("""This script fetches and builds %s and its dependencies
""" % (PROJECT_NAME,), BLACK_ON_WHITE)
cecho (f"""This script fetches and builds {PROJECT_NAME} and its dependencies
""", BLACK_ON_WHITE)
cecho("""Script configuration:
""", GREEN)
cecho("""* Target Architecture = %s""" % (TARGET_ARCH,), MAGENTA)
cecho(" - Whether 32-bit (i686) or 64-bit (x86_64) will be built.")
cecho("""* USE_OCCT = %r""" % (USE_OCCT,), MAGENTA)
cecho(f"""* USE_OCCT = {USE_OCCT}""", MAGENTA)
if USE_OCCT:
cecho(" - Compiling against official Open Cascade")
else:
cecho(" - Compiling against Open Cascade Community Edition")
cecho("* Dependency Directory = %s" % (DEPS_DIR,), MAGENTA)
cecho(" - The directory where %s dependencies are installed." % (PROJECT_NAME,))
cecho("* Build Config Type = %s" % (BUILD_CFG,), MAGENTA)
cecho(f"* Dependency Directory = {DEPS_DIR}", MAGENTA)
cecho(f" - The directory where {PROJECT_NAME} dependencies are installed.")
cecho(f"* Build Config Type = {BUILD_CFG}", MAGENTA)
cecho(""" - The used build configuration type for the dependencies.
Defaults to RelWithDebInfo if not specified.""")
if BUILD_CFG == "MinSizeRel":
cecho(" WARNING: MinSizeRel build can suffer from a significant performance loss.", RED)
cecho("* IFCOS_NUM_BUILD_PROCS = %s" % (IFCOS_NUM_BUILD_PROCS,), MAGENTA)
cecho(f"* IFCOS_NUM_BUILD_PROCS = {IFCOS_NUM_BUILD_PROCS}", MAGENTA)
cecho(""" - How many compiler processes may be run in parallel.
""")
dependency_tree = {
'IfcParse': ('boost', 'libxml2', 'hdf5'),
'IfcGeom': ('IfcParse', 'occ', 'OpenCOLLADA', 'json', 'cgal'),
'IfcConvert': ('IfcGeom', ),
'OpenCOLLADA': ('libxml2', 'pcre'),
'IfcParse': ('boost', 'libxml2', 'hdf5'),
'IfcGeom': ('IfcParse', 'occ', 'json', 'cgal'),
'IfcConvert': ('IfcGeom',),
'OpenCOLLADA': ('libxml2', 'pcre'),
'IfcGeomServer': ('IfcGeom',),
'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
'swig': ('pcre',),
'boost': (),
'libxml2': (),
@@ -252,7 +222,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]:
if which(cmd) is None:
raise ValueError("Required tool '%s' not installed or not added to PATH" % (cmd,))
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
download_tool_default = download_tool_py = "py"
@@ -263,10 +233,10 @@ download_tool_git = "git"
log_dir = os.path.join(DEPS_DIR, "logs")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
LOG_FILE="%s.log" % (os.path.join(log_dir, to_pystring(sp.check_output([date, "+%Y%m%d"]).strip())),)
LOG_FILE = os.path.join(log_dir, sp.check_output([date, "+%Y%m%d"], encoding="utf-8").strip()) + ".log"
if not os.path.exists(LOG_FILE):
open(LOG_FILE, "w").close()
logger.info("using command log file '%s'" % (LOG_FILE,))
logger.info(f"using command log file '{LOG_FILE}'")
def run(cmds, cwd=None):
@@ -276,43 +246,47 @@ def run(cmds, cwd=None):
with leading and trailing whitespace removed.
"""
logger.debug("running command %r in directory %r" % (" ".join(cmds), cwd))
log_file_handle = open(LOG_FILE, "ab")
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE)
logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
log_file_handle = open(LOG_FILE, "a")
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
stdout, stderr = proc.communicate()
log_file_handle.write(stdout)
log_file_handle.write(stderr)
log_file_handle.close()
logger.debug("command returned %r" % proc.returncode)
logger.debug(f"command returned {proc.returncode}")
if proc.returncode != 0:
print("-" * 70)
print(stderr)
print("-" * 70)
raise Exception("Command `%s` returned exit code %d" % (" ".join(cmds), proc.returncode))
raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")
return stdout.strip()
BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_")
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,)
BOOST_LOCATION="https://boostorg.jfrog.io/artifactory/main/release/%s/source/" % (BOOST_VERSION,)
OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"
# Helper functions
def run_autoconf(arg1, configure_args, cwd):
configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
if not os.path.exists(configure_path):
run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
# Using `sh` over `bash` fixes issues with building swig
run(["/bin/sh", "../configure"]+configure_args+["--prefix=%s" % (os.path.realpath("%s/install/%s" % (DEPS_DIR, arg1)),)], cwd=cwd)
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
run(["/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
if cmake_dir is None:
P=".."
P = ".."
else:
P=cmake_dir
run(["cmake", P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd)
P = cmake_dir
run(["cmake", P] + cmake_args + [f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
"""Lazily clones the `git` repository denoted by `clone_url` into
@@ -321,10 +295,10 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
`revision` after cloning or in the existing clone if `revision` is not
`None`."""
if not os.path.exists(target_dir):
logger.info("cloning '%s' into '%s'" % (clone_url, target_dir))
logger.info(f"cloning '{clone_url}' into '{target_dir}'")
run([git, "clone", "--recursive", clone_url, target_dir])
else:
logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,))
logger.info(f"directory '{target_dir}' already cloned. Pulling latest changes.")
# detect whether we are on a branch and pull
if run([git, "rev-parse", "--abbrev-ref", "HEAD"], cwd=target_dir) != "HEAD":
@@ -341,13 +315,13 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
linker flags."""
check_dir = os.path.join(DEPS_DIR, "install", name)
if os.path.exists(check_dir):
logger.info( "Found existing %s, skipping" % (name,))
logger.info(f"Found existing {name}, skipping")
return
build_dir = os.path.join(DEPS_DIR, "build")
if not os.path.exists(build_dir):
os.makedirs(build_dir)
logger.info("\rFetching %s... " % (name,))
logger.info(f"\rFetching {name}... ")
if download_tool == download_tool_py:
if no_append_name:
@@ -359,16 +333,16 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
if not os.path.exists(download_path):
urlretrieve(url, os.path.join(build_dir, download_path))
else:
logger.info("Download '%s' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping" % (download_path,))
logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
elif download_tool == download_tool_git:
logger.info("\rChecking %s... " % (name,))
logger.info(f"\rChecking {name}... ")
git_clone_or_pull_repository(download_url, target_dir=os.path.join(build_dir, download_name), revision=revision)
else:
raise ValueError("download tool '%s' is not supported" % (download_tool,))
raise ValueError(f"download tool '{download_tool}' is not supported")
download_dir = os.path.join(build_dir, download_name)
if os.path.isdir(download_dir):
extract_dir_name=download_name
extract_dir_name = download_name
extract_dir = os.path.join(build_dir, extract_dir_name)
else:
download_tarfile_path = os.path.join(build_dir, download_name)
@@ -378,12 +352,12 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
compr = "bz2"
else:
raise RuntimeError("fix source for new download type")
download_tarfile = tarfile.open(name=download_tarfile_path, mode="r:%s" % (compr,))
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
# tarfile seriously doesn't have a function to retrieve the root directory more easily
extract_dir_name= os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
extract_dir_name = os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
#run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
if extract_dir_name is None:
extract_dir_name= run([bash, "-c", "tar -tf %s 2> /dev/null | head -n 1 | cut -f1 -d /" % (download_name,)], cwd=build_dir)
extract_dir_name = run([bash, "-c", f"tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d /"], cwd=build_dir)
extract_dir = os.path.join(build_dir, extract_dir_name)
if not os.path.exists(extract_dir):
run([tar, "-xf", download_name], cwd=build_dir)
@@ -413,71 +387,66 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
shutil.rmtree(extract_build_dir)
os.makedirs(extract_build_dir)
logger.info("\rConfiguring %s..." % (name,))
logger.info(f"\rConfiguring {name}...")
if mode == "autoconf":
run_autoconf(name, build_tool_args, cwd=extract_build_dir)
elif mode == "cmake":
run_cmake(name, build_tool_args, cwd=extract_build_dir)
else:
raise ValueError()
logger.info("\rBuilding %s... " % (name,))
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "VERBOSE=1"], cwd=extract_build_dir)
logger.info( "\rInstalling %s... " % (name,))
logger.info(f"\rBuilding {name}... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
logger.info(f"\rInstalling {name}... ")
run([make, "install"], cwd=extract_build_dir)
logger.info( "\rInstalled %s \n" % (name,))
logger.info(f"\rInstalled {name} \n")
else:
logger.info( "\rConfiguring %s..." % (name,))
logger.info(f"\rConfiguring {name}...")
run([bash, "./bootstrap.sh"], cwd=extract_dir)
logger.info("\rBuilding %s... " % (name,))
run(["./b2", "-j%s" % (IFCOS_NUM_BUILD_PROCS,)]+build_tool_args, cwd=extract_dir)
logger.info("\rInstalling %s... " % (name,))
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", "boost-%s" % BOOST_VERSION, "boost"))
logger.info("\rInstalled %s \n" % (name,))
logger.info(f"\rBuilding {name}... ")
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir)
logger.info(f"\rInstalling {name}... ")
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
logger.info(f"\rInstalled {name} \n")
cecho("Collecting dependencies:", GREEN)
# Set compiler flags for 32bit builds on 64bit system
# TODO: This is untested
ADDITIONAL_ARGS=[]
BOOST_ADDRESS_MODEL=[]
if TARGET_ARCH == "i686" and run([uname, "-m"]).strip() == "x86_64":
if get_os() == "Darwin":
ADDITIONAL_ARGS=["-m32", "-arch i386"]
else:
ADDITIONAL_ARGS=["-m32"]
BOOST_ADDRESS_MODEL=["architecture=x86", "address-model=32"]
ADDITIONAL_ARGS = []
BOOST_ADDRESS_MODEL = []
if get_os() == "Darwin":
ADDITIONAL_ARGS=["-mmacosx-version-min=%s" % TOOLSET]+ADDITIONAL_ARGS
if platform.system() == "Darwin":
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
# If the linker supports GC sections, set it up to reduce binary file size
# -fPIC is required for the shared libraries to work
CXXFLAGS=os.environ.get("CXXFLAGS", "")
CFLAGS=os.environ.get("CFLAGS", "")
LDFLAGS=os.environ.get("LDFLAGS", "")
CXXFLAGS = os.environ.get("CXXFLAGS", "")
CFLAGS = os.environ.get("CFLAGS", "")
LDFLAGS = os.environ.get("LDFLAGS", "")
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
if sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
CXXFLAGS_MINIMAL="%s %s %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CFLAGS_MINIMAL="%s %s %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
if BUILD_STATIC:
CXXFLAGS="%s %s -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CFLAGS="%s %s -fdata-sections -ffunction-sections -fvisibility=hidden %s"% (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CXXFLAGS = f"{CXXFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
CFLAGS = f"{CFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden {ADDITIONAL_ARGS_STR}"
else:
CXXFLAGS=CXXFLAGS_MINIMAL
CFLAGS=CFLAGS_MINIMAL
LDFLAGS="%s -Wl,--gc-sections %s" % (LDFLAGS, str.join(" ", ADDITIONAL_ARGS))
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
LDFLAGS = f"{LDFLAGS} -Wl,--gc-sections {ADDITIONAL_ARGS_STR}"
else:
CXXFLAGS_MINIMAL="%s %s %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CFLAGS_MINIMAL="%s %s %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
if BUILD_STATIC:
CXXFLAGS="%s %s -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CFLAGS="%s %s -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
CXXFLAGS = f"{CXXFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
CFLAGS = f"{CFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
else:
CXXFLAGS=CXXFLAGS_MINIMAL
CFLAGS=CFLAGS_MINIMAL
LDFLAGS="%s %s" % (LDFLAGS, str.join(" ", ADDITIONAL_ARGS))
LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
os.environ["CXXFLAGS"] = CXXFLAGS
os.environ["CFLAGS"] = CFLAGS
@@ -488,21 +457,20 @@ os.environ["LDFLAGS"] = LDFLAGS
# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))
if 'hdf5' in targets:
HDF5_MAJOR=".".join(HDF5_VERSION.split(".")[:-1])
get_os_result = get_os()
HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
build_dependency(
name="hdf5-{HDF5_VERSION}".format(**locals()),
name=f"hdf5-{HDF5_VERSION}",
mode="ctest",
build_tool_args=[],
download_url="https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/".format(**locals()),
download_name="CMake-hdf5-{HDF5_VERSION}.tar.gz".format(**locals()),
ctest_result="HDF5-{HDF5_VERSION}-{get_os_result}".format(**locals()),
ctest_result_path="HDF_Group/HDF5/{HDF5_VERSION}".format(**locals())
download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}"
)
if "json" in targets:
json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals())
json_install_path = "{DEPS_DIR}/install/json/nlohmann/json.hpp".format(**locals())
json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
if not os.path.exists(os.path.dirname(json_install_path)):
os.makedirs(os.path.dirname(json_install_path))
if not os.path.exists(json_install_path):
@@ -510,11 +478,11 @@ if "json" in targets:
if "pcre" in targets:
build_dependency(
name="pcre-{PCRE_VERSION}".format(**locals()),
name=f"pcre-{PCRE_VERSION}",
mode="autoconf",
build_tool_args=[DISABLE_FLAG],
download_url="https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/".format(**locals()),
download_name="pcre-{PCRE_VERSION}.tar.bz2".format(**locals())
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/",
download_name=f"pcre-{PCRE_VERSION}.tar.bz2"
)
# An issue exists with swig-1.3 and python >= 3.2
@@ -523,20 +491,20 @@ if "swig" in targets:
build_dependency(
name="swig",
mode="autoconf",
build_tool_args=["--disable-ccache", "--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())],
build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
download_url="https://github.com/swig/swig.git",
download_name="swig",
download_tool=download_tool_git,
revision="rel-{SWIG_VERSION}".format(**locals())
revision=f"rel-{SWIG_VERSION}"
)
if USE_OCCT and "occ" in targets:
build_dependency(
name="occt-{OCCT_VERSION}".format(**locals()),
name=f"occt-{OCCT_VERSION}",
mode="cmake",
build_tool_args=[
"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}".format(**locals()),
"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}".format(**locals()),
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
"-DBUILD_MODULE_Draw=0",
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
],
@@ -548,7 +516,7 @@ if USE_OCCT and "occ" in targets:
)
elif "occ" in targets:
build_dependency(
name="oce-{OCE_VERSION}".format(**locals()),
name=f"oce-{OCE_VERSION}",
mode="cmake",
build_tool_args=[
"-DOCE_DISABLE_TKSERVICE_FONT=ON",
@@ -557,15 +525,15 @@ elif "occ" in targets:
"-DOCE_DISABLE_X11=ON",
"-DOCE_VISUALISATION=OFF",
"-DOCE_OCAF=OFF",
"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}".format(**locals())
f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}"
],
download_url="https://github.com/tpaviot/oce/archive/",
download_name="OCE-{OCE_VERSION}.tar.gz".format(**locals())
download_name=f"OCE-{OCE_VERSION}.tar.gz"
)
if "libxml2" in targets:
build_dependency(
"libxml2-{LIBXML2_VERSION}".format(**locals()),
f"libxml2-{LIBXML2_VERSION}",
"autoconf",
build_tool_args=[
"--without-python",
@@ -576,7 +544,7 @@ if "libxml2" in targets:
"--without-lzma"
],
download_url="ftp://xmlsoft.org/libxml2/",
download_name="libxml2-{LIBXML2_VERSION}.tar.gz".format(**locals())
download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
)
if "OpenCOLLADA" in targets:
@@ -584,12 +552,12 @@ if "OpenCOLLADA" in targets:
"OpenCOLLADA",
"cmake",
build_tool_args=[
"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2".format(**locals()),
"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}".format(**locals()),
"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include".format(**locals()),
"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}".format(**locals()),
"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}".format(**locals()),
"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/".format(**locals())
f"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
f"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
f"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include",
f"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}",
f"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/"
],
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
download_name="OpenCOLLADA",
@@ -598,158 +566,155 @@ if "OpenCOLLADA" in targets:
revision=OPENCOLLADA_VERSION
)
if "python" in targets:
if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
OLD_CXX_FLAGS=os.environ["CXXFLAGS"]
OLD_C_FLAGS=os.environ["CFLAGS"]
os.environ["CXXFLAGS"]=CXXFLAGS_MINIMAL
os.environ["CFLAGS"]=CFLAGS_MINIMAL
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
OLD_C_FLAGS = os.environ["CFLAGS"]
os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
os.environ["CFLAGS"] = CFLAGS_MINIMAL
# On OSX a dynamic python library is built or it would not be compatible
# with the system python because of some threading initialization
PYTHON_CONFIGURE_ARGS=[]
if get_os() == "Darwin":
PYTHON_CONFIGURE_ARGS=["--disable-static", "--enable-shared"]
PYTHON_CONFIGURE_ARGS = []
if platform.system() == "Darwin":
PYTHON_CONFIGURE_ARGS = ["--disable-static", "--enable-shared"]
def get_python_unicode_confs(py_ver):
if py_ver < "3.3":
return [("--enable-unicode=ucs2",""), ("--enable-unicode=ucs4","u")]
else: return [("","")]
def PYTHON_VERSION_CONFS():
for v in PYTHON_VERSIONS:
for unicode_conf, abi_tag in get_python_unicode_confs(v):
yield v, unicode_conf, abi_tag
for PYTHON_VERSION, unicode_conf, abi_tag in PYTHON_VERSION_CONFS():
for PYTHON_VERSION in PYTHON_VERSIONS:
try:
build_dependency(
"python-{PYTHON_VERSION}{abi_tag}".format(**locals()),
f"python-{PYTHON_VERSION}",
"autoconf",
PYTHON_CONFIGURE_ARGS + [unicode_conf],
"http://www.python.org/ftp/python/{PYTHON_VERSION}/".format(**locals()),
"Python-{PYTHON_VERSION}.tgz".format(**locals())
PYTHON_CONFIGURE_ARGS,
f"http://www.python.org/ftp/python/{PYTHON_VERSION}/",
f"Python-{PYTHON_VERSION}.tgz"
)
except Exception as e:
pyver2 = PYTHON_VERSION[:PYTHON_VERSION.rfind('.')]
if os.path.exists("{DEPS_DIR}/install/python-{PYTHON_VERSION}{abi_tag}/bin/python{pyver2}".format(**locals())):
print("Installation partially failed")
else: raise e
except RuntimeError as e:
# Sometimes setting up modules such as pip/lzma can cause
# the python installer script to return a non zero exit
# code where actually the headers and dynamic libraries
# are installed correctly. This is all we need so we catch
# the exception and only reraise if a partially successful
# install is not detected.
if not os.path.exists(
os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")
):
raise e
os.environ["CXXFLAGS"]=OLD_CXX_FLAGS
os.environ["CFLAGS"]=OLD_C_FLAGS
os.environ["CXXFLAGS"] = OLD_CXX_FLAGS
os.environ["CFLAGS"] = OLD_C_FLAGS
if "boost" in targets:
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
build_dependency(
"boost-{BOOST_VERSION}".format(**locals()),
f"boost-{BOOST_VERSION}",
mode="bjam",
build_tool_args=[
"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
f"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}",
"--with-system",
"--with-program_options",
"--with-regex",
"--with-thread",
"--with-date_time",
"--with-iostreams",
"link={LINK_TYPE}".format(**locals())
f"link={LINK_TYPE}"
] + \
BOOST_ADDRESS_MODEL + \
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
["stage", "-s", "NO_BZIP2=1"],
download_url=BOOST_LOCATION,
download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals())
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
)
if "cgal" in targets:
build_dependency(
name="gmp-%s" % (GMP_VERSION,),
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
build_tool_args=["--disable-shared", "--with-pic"],
download_url="https://ftp.gnu.org/gnu/gmp/",
download_name="gmp-{GMP_VERSION}.tar.bz2".format(**locals())
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
)
build_dependency(
name="mpfr-%s" % (MPFR_VERSION,),
name=f"mpfr-{MPFR_VERSION}",
mode="autoconf",
build_tool_args=["--disable-shared", "--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}".format(**locals())],
download_url="http://www.mpfr.org/mpfr-{MPFR_VERSION}/".format(**locals()),
download_name="mpfr-{MPFR_VERSION}.tar.bz2".format(**locals())
build_tool_args=["--disable-shared", f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
)
build_dependency(
name="cgal-{CGAL_VERSION}".format(**locals()),
name=f"cgal-{CGAL_VERSION}",
mode="cmake",
build_tool_args=[
"-DGMP_LIBRARIES=%s/install/gmp-%s/lib/libgmp.a" % (DEPS_DIR, GMP_VERSION),
"-DGMP_INCLUDE_DIR=%s/install/gmp-%s/include" % (DEPS_DIR, GMP_VERSION),
"-DMPFR_LIBRARIES=%s/install/mpfr-%s/lib/libmpfr.a" % (DEPS_DIR, MPFR_VERSION),
"-DMPFR_INCLUDE_DIR=%s/install/mpfr-%s/include" % (DEPS_DIR, MPFR_VERSION),
"-DBoost_INCLUDE_DIR=%s/install/boost-%s" % (DEPS_DIR, BOOST_VERSION),
"-DCMAKE_INSTALL_PREFIX=%s/install/cgal-%s/" % (DEPS_DIR, CGAL_VERSION),
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.a",
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.a" ,
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
"-DCGAL_HEADER_ONLY=On",
"-DBUILD_SHARED_LIBS=Off"
],
download_url="https://github.com/CGAL/cgal.git",
download_name="cgal",
download_tool=download_tool_git,
revision="v{CGAL_VERSION}".format(**locals())
revision=f"v{CGAL_VERSION}"
)
cecho("Building IfcOpenShell:", GREEN)
IFCOS_DIR=os.path.join(DEPS_DIR, "build", "ifcopenshell")
if os.path.exists(IFCOS_DIR):
shutil.rmtree(IFCOS_DIR)
os.makedirs(IFCOS_DIR)
IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
if os.environ.get("NO_CLEAN", "").lower() not in {"1", "on", "true"}:
if os.path.exists(IFCOS_DIR):
shutil.rmtree(IFCOS_DIR)
os.makedirs(IFCOS_DIR, exist_ok=True)
executables_dir = os.path.join(IFCOS_DIR, "executables")
if not os.path.exists(executables_dir):
os.makedirs(executables_dir)
os.makedirs(executables_dir, exist_ok=True)
logger.info("\rConfiguring executables...")
OFF_ON = ["OFF", "ON"]
exec_args=[
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
exec_args = [
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
"-DBUILD_IFCPYTHON=" "OFF",
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell".format(**locals()),
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
]
cmake_args=[
cmake_args = [
"-DUSE_MMAP=" "OFF",
"-DBUILD_EXAMPLES=" "OFF",
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
"-DBOOST_ROOT=" "{DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
"-DBOOST_ROOT=" f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
"-DGLTF_SUPPORT=" "ON",
"-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals()),
"-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
"-DBoost_NO_BOOST_CMAKE=" "On",
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
]
if "cgal" in targets:
cmake_args.extend([
"-DCGAL_INCLUDE_DIR=" "{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include".format(**locals()),
"-DGMP_INCLUDE_DIR=" "{DEPS_DIR}/install/gmp-{GMP_VERSION}/include".format(**locals()),
"-DGMP_LIBRARY_DIR=" "{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib".format(**locals()),
"-DMPFR_INCLUDE_DIR=" "{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include".format(**locals()),
"-DMPFR_LIBRARY_DIR=" "{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib".format(**locals()),
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
"-DGMP_INCLUDE_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
"-DGMP_LIBRARY_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
"-DMPFR_INCLUDE_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
"-DMPFR_LIBRARY_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
])
if "occ" in targets and USE_OCCT:
occ_include_dir = "{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade".format(**locals())
occ_library_dir = "{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib".format(**locals())
occ_include_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
occ_library_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
cmake_args.extend([
"-DOCC_INCLUDE_DIR=" +occ_include_dir,
"-DOCC_LIBRARY_DIR=" +occ_library_dir
])
elif "occ" in targets:
occ_include_dir = "{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce".format(**locals())
occ_library_dir = "{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
occ_include_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce"
occ_library_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
cmake_args.extend([
"-DOCC_INCLUDE_DIR=" +occ_include_dir,
"-DOCC_LIBRARY_DIR=" +occ_library_dir
@@ -757,82 +722,98 @@ elif "occ" in targets:
if "OpenCOLLADA" in targets:
cmake_args.extend([
"-DOPENCOLLADA_INCLUDE_DIR=" "{DEPS_DIR}/install/OpenCOLLADA/include/opencollada".format(**locals()),
"-DOPENCOLLADA_LIBRARY_DIR=" "{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada".format(**locals())
"-DOPENCOLLADA_INCLUDE_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
"-DOPENCOLLADA_LIBRARY_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada"
])
else:
cmake_args.extend([
"-DCOLLADA_SUPPORT=" "Off",
])
if "pcre" in targets:
cmake_args.append(
"-DPCRE_LIBRARY_DIR=" "{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib".format(**locals())
"-DPCRE_LIBRARY_DIR=" f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib"
)
if "libxml2" in targets:
cmake_args.extend([
"-DLIBXML2_INCLUDE_DIR=" "{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2".format(**locals()),
"-DLIBXML2_LIBRARIES=" "{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}".format(**locals())
"-DLIBXML2_INCLUDE_DIR=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
"-DLIBXML2_LIBRARIES=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}"
])
if "hdf5" in targets:
cmake_args.extend([
"-DHDF5_INCLUDE_DIR=" "{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include".format(**locals()),
"-DHDF5_LIBRARY_DIR=" "{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib".format(**locals())
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
])
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
logger.info("\rBuilding executables... ")
run([make, "-j{IFCOS_NUM_BUILD_PROCS}".format(**locals())], cwd=executables_dir)
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
if "IfcOpenShell-Python" in targets:
# On OSX the actual Python library is not linked against.
ADDITIONAL_ARGS=""
if get_os() == "Darwin":
ADDITIONAL_ARGS="-Wl,-flat_namespace,-undefined,suppress"
ADDITIONAL_ARGS = ""
if platform.system() == "Darwin":
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
os.environ["CXXFLAGS"]="%s %s" % (CXXFLAGS_MINIMAL, ADDITIONAL_ARGS)
os.environ["CFLAGS"]="%s %s" % (CFLAGS_MINIMAL, ADDITIONAL_ARGS)
os.environ["LDFLAGS"]="%s %s" % (LDFLAGS, ADDITIONAL_ARGS)
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
for PYTHON_VERSION, _, TAG in PYTHON_VERSION_CONFS():
logger.info("\rConfiguring python {PYTHON_VERSION}{TAG} wrapper...".format(**locals()))
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
os.makedirs(python_dir, exist_ok=True)
# python_dir = os.path.join(IFCOS_DIR, "python-{PYTHON_VERSION}{TAG}".format(**locals()))
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
if not os.path.exists(python_dir):
os.makedirs(python_dir)
def compile_python_wrapper(python_version, python_library, python_include, python_executable):
logger.info(f"\rConfiguring python {python_version} wrapper...")
cache_path = os.path.join(python_dir, "CMakeCache.txt")
if os.path.exists(cache_path):
os.unlink(cache_path)
os.remove(cache_path)
PYTHON_LIBRARY=run([bash, "-c", "ls {DEPS_DIR}/install/python-{PYTHON_VERSION}{TAG}/lib/libpython*.*".format(**locals())])
PYTHON_INCLUDE=run([bash, "-c", "ls -d {DEPS_DIR}/install/python-{PYTHON_VERSION}{TAG}/include/python*".format(**locals())])
PYTHON_EXECUTABLE=os.path.join(DEPS_DIR, "install", "python-{PYTHON_VERSION}{TAG}".format(**locals()), "bin", "python{PYTHON_VERSION[0]}".format(**locals()))
os.environ["PYTHON_LIBRARY_BASENAME"]=os.path.basename(PYTHON_LIBRARY)
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
run_cmake("",
cmake_args + [
"-DPYTHON_LIBRARY=" +PYTHON_LIBRARY,
"-DPYTHON_EXECUTABLE=" +PYTHON_EXECUTABLE,
"-DPYTHON_INCLUDE_DIR=" +PYTHON_INCLUDE,
"-DSWIG_EXECUTABLE=" "{DEPS_DIR}/install/swig/bin/swig".format(**locals()),
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell/tmp".format(**locals()),
"-DPYTHON_LIBRARY=" +python_library,
"-DPYTHON_EXECUTABLE=" +python_executable,
"-DPYTHON_INCLUDE_DIR=" +python_include,
"-DSWIG_EXECUTABLE=" f"{DEPS_DIR}/install/swig/bin/swig",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}]
], cmake_dir=CMAKE_DIR, cwd=python_dir)
logger.info("\rBuilding python %s%s wrapper... " % (PYTHON_VERSION, TAG))
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "_ifcopenshell_wrapper"], cwd=python_dir)
logger.info(f"\rBuilding python {python_version} wrapper... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
module_dir = os.path.dirname(run([PYTHON_EXECUTABLE, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
if get_os() != "Darwin":
if platform.system() != "Darwin":
# TODO: This symbol name depends on the Python version?
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
return module_dir
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", "python-%s%s" % (PYTHON_VERSION, TAG))])
if USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
python_lib = os.path.join(
sysconfig.get_config_var('LIBDIR'),
sysconfig.get_config_var('multiarchsubdir').replace("/", ""),
sysconfig.get_config_var("INSTSONAME")
)
compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
else:
for python_version in PYTHON_VERSIONS:
python_library = run([bash, "-c", f"ls {DEPS_DIR}/install/python-{python_version}/lib/libpython*.*"])
python_include = run([bash, "-c", f"ls -d {DEPS_DIR}/install/python-{python_version}/include/python*"])
python_executable = os.path.join(DEPS_DIR, "install", f"python-{python_version}", "bin", f"python{python_version[0]}")
module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
logger.info("\rBuilt IfcOpenShell...\n\n")
+3 -2
View File
@@ -645,9 +645,10 @@ class BcfXml:
def get_comments(self, guid):
comments = {}
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
if "Comments" not in data["Topics"]:
return comments
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
for item in data["Topic"]["Comments"].get("Comment", []):
comment = bcf.v3.data.Comment()
mandatory_keys = {
@@ -675,9 +676,9 @@ class BcfXml:
def get_viewpoints(self, guid):
viewpoints = {}
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
if "Viewpoints" not in data["Topic"]:
return viewpoints
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
for item in data["Topic"]["Viewpoints"]:
viewpoint = self.get_viewpoint(item, guid)
viewpoints[viewpoint.guid] = viewpoint
+1 -1
View File
@@ -46,7 +46,7 @@ class RelatedTopic:
class HeaderFile:
def __init__(self):
self.file_name = ""
self.filename = ""
self.date = None
self.reference = ""
self.ifc_project = None
+62 -3
View File
@@ -93,7 +93,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-9853d3d-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-4c40fc1-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -133,6 +133,8 @@ endif
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc4d/ifc4d dist/blenderbim/libs/site/packages/
# Provides IFC5D functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc5d/ifc5d dist/blenderbim/libs/site/packages/
# Provides IFCCityJSON functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccityjson dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Mustache templating in construction documentation
@@ -240,6 +242,30 @@ endif
cp -r dist/working/lark-parser-0.8.5/lark dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFC4D
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/b0/bb/9c4dddd3ca173cb56241cfb2eddfae24690dc676d357ac4cab17d0a36d9d/PyP6Xer-1.13.0.tar.gz
cd dist/working && tar -xzvf PyP6Xer*
cp -r dist/working/PyP6Xer-1.13.0/xerparser dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by xerparser and IFC4D
# TODO: remove this dependency. It's only used to show a progress bar.
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/9f/7b/76c4e5ef1a1b528fcaada4133f972e77d33c252831676cf414119ca6093d/tqdm-4.50.0.tar.gz
cd dist/working && tar -xzvf tqdm*
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCityJSON
mkdir dist/working
cd dist/working && python -m venv env
cd dist/working && mkdir site-packages
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
cd dist/working/site-packages/ && rm -r *dist-info*
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCClash
mkdir dist/working
cd dist/working && wget $(HPPFCL_URL)
@@ -345,7 +371,7 @@ endif
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
rm -rf dist/working
# Required by ids
# Required by ids - uh, not any more, but I'm trying it out in the drawing module
mkdir dist/working
cd dist/working && wget $(LXML_URL)
cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
@@ -429,6 +455,39 @@ endif
cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Brickschema fucntionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
cd dist/working && tar -xzvf brickschema*
# This is an evil hack because we don't want to bundle flask
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
# For now lets bundle the latest nightly schema
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
cd dist/working && cp Brick.ttl ../../blenderbim/bim/schema/Brick.ttl
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
cd dist/working && tar -xzvf pyshacl*
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
cd dist/working && tar -xzvf rdflib*
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
cd dist/working && tar -xzvf owlrl*
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
rm -rf dist/working
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
@@ -464,7 +523,7 @@ coverage:
.PHONY: license
license:
copyright-header --license GPL3 --copyright-holder "Dion Moult <dion@thinkmoult.com>" --copyright-year "2021" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
copyright-header --license GPL3 --copyright-holder "Dion Moult <dion@thinkmoult.com>" --copyright-year "2022" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
.PHONY: clean
clean:
+3 -125
View File
@@ -1,127 +1,5 @@
# Blender BIM IFC
# BlenderBIM Add-on
_Under heavy development!_
An add-on to Blender to allow BIM functionality.
Let's use Blender to create BIM models, because why not?
## Features
- IFC4 is a first-class citizen. IFC4 has been an ISO standard since 2013, with
the latest published in 2018. It contains many more features than IFC2X3,
published back in 2005.
- Available for Windows, Mac, and Linux!
- Use open-source Blender 2.8 to do digital modeling! Used by NASA, Hollywood,
and Ubisoft, Blender allows real-time modeling in rendered view, animations,
file linking, proxy objects, Blender Cloud collaboration, lighting analysis,
CFD, energy modeling, and a huge ecosystem of add-ons with a thriving
community. No licensing, and with a strong Python API to integrate into your
custom workflow.
- Create complex spatial structures with multiple sites, buildings, facilities,
bridges, and building parts. Sites can be broken down into sub-sites and
multiple buildings per site. You can categorise different floors to different
buildings.
- Classify any geometry as any IFC type. Just because it is a wall doesn't
mean you should be limited to modeling it in a certain way. Easily change
from one type to another, switch relationships and aggregations, reuse or
even remove geometry altogether, without damaging your geometry or breaking
your BIM model.
- Use collections to create element compositions and decompositions with full
control over the classifications of their parts. Most BIM programs don't give
this flexibility. Create collection instances to efficiently duplicate
composite objects.
- Allow BIM objects to instance a shared geometric representations. This allows
you to place the same shape in multiple objects without drastically
increasing your filesize.
- Provide a custom value to any simple IFC attribute of an IFC product.
- Create product types that regular IFC products can inherit attributes from.
- Define your own property sets with control over exactly how the data in the
property sets are stored using property set template definitions which you
can download or create yourself. Property sets can apply to both types and
objects, and object property sets can selectively override type property
sets.
- Applies array modifiers to create instances.
- Basic quantity take-off support for costing.
- Settings to toggle export of quantities or geometric representations.
Exporting IFC data without geometric representations is much faster and a
fraction of the filesize.
- Maintain `GlobalId` between exports, so that you can manipulate the IFC
reliably in other software while making changes.
- Not all objects need full solid geometry. Create representations as 2D or 3D
curves / wireframes for rapid and lightweight BIM development.
- Create map conversions and coordinate reference system definitions to link
BIM to GIS.
- Export basic materials with surface and diffuse colours for representation.
You can also link materials to an externally defined file, such as a `.blend`
Cycles material, V-Ray material, or even Radiance material for lighting
simulation.
- Switch between different types of metric units when modeling.
- Model multiple representations for different purposes for a single BIM
object or type, such as footprints, axis, reference, or clearance geometry.
You can export just the type of representation required for your usecase, or
export multiple for the user to choose from.
- Record qualitative objectives such as design intentions, health and safety
strategies, or code compliance, performance solutions, and more, in the BIM
model and directly associate them to objects or types.
- Classify objects and types to different classification systems such as
Uniclass, Omniclass, or your custom system.
- Associate external document files such as plans, brochures, specifications,
warranties, or anything to objects and types.
- Specify swept solid geometries, allowing you to round-trip geometry
manipulation with other BIM software which have more limited geometric tools,
like Revit.
- Create material layer sets, for objects which have materials in predefined
layers with thicknesses.
- Create material constituent sets, for objects which are made up of
arbitrarily mixed ingredients.
- Specify nested and hosted element relationships.
- Specify predefined door attributes as defined in the IFC4 specification.
- Specify predefined window attributes as defined in the IFC4 specification.
- Specify surface styles either defined as part of a material or assigned
directly to an object's representation.
- Colour coding by IFC class to visually spot mistakes
Some more experimental features:
- Cut sections for documentation with OpenCascade
- Export gbXML files that are linked to your IFC file to do energy analysis
- QA module to generate Gherkin unit tests to validate your BIM models
- HTML formatter to generate reports from QA results
- COBie spreadsheet extraction to import into CAFM systems
## Demo
[See demo video](https://www.youtube.com/watch?v=iD3v3eu2AjY)
It's only just started, so everything can and will change. But if you want to
check it out:
1. Install [Blender](https://www.blender.org/) 64-bit version.
2. Download the latest packaged version of Blender BIM from
[here](https://blenderbim.org/builds/).
3. Launch Blender. Go to `Edit->Preferences->Add-ons->Install...`. Browse to
the downloaded `.zip` file from step 2.
4. Enable the checkbox next to the Import-Export IFC Blender plugin which will
show up.
5. All done! You will find an import and export IFC option in the `File` menu.
By default, Blender BIM will export whatever you have selected. The objects you
have selected _must_ belong in an `IfcProject` and spatially contained, such as
witihn an `IfcBuilding`. It must also follow the naming convention. Please see
the screenshot below for an example scene.
A naming convention of `IfcClass/IfcName` is enforced. If you name your object
`IfcWall/Foo`, it will turn into an `IfcWall` with the name attribute set to
`Foo`. You can override object attributes using Blender's `Custom Properties`.
Your objects must live in a top level `Collection` defined as a context class,
usually something like `IfcProject/Foo`. You can then nest spatial structures
underneath to create a hierarchy.
## Pictures
![UI](https://aws1.discourse-cdn.com/business6/uploads/buildingsmart1/original/1X/ce820c3ee22adcffae59b40f98ff23379f7e3547.png)
![Blender screenshot](blender-screenshot.png)
... this Blender files creates this IFC file ...
![IfcOpenShell screenshot](ifcopenshell-screenshot.png)
More information on the [BlenderBIM Add-on website](https://blenderbim.org).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

+26 -6
View File
@@ -18,7 +18,7 @@
import bpy
import importlib
from . import handler, ui, prop, operator
from . import handler, ui, prop, operator, helper
modules = {
"project": None,
@@ -30,6 +30,7 @@ modules = {
"georeference": None,
"context": None,
"drawing": None,
"misc": None,
"attribute": None,
"type": None,
"spatial": None,
@@ -42,6 +43,7 @@ modules = {
"sequence": None,
"group": None,
"system": None,
"brick": None,
"structural": None,
"boundary": None,
"profile": None,
@@ -52,6 +54,7 @@ modules = {
"pset": None,
"qto": None,
"classification": None,
"library": None,
"constraint": None,
"document": None,
"pset_template": None,
@@ -61,14 +64,17 @@ modules = {
"bimtester": None,
"diff": None,
"patch": None,
"gis": None,
"covetool": None,
"augin": None,
"debug": None,
}
for name in modules.keys():
modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
classes = [
operator.OpenUri,
operator.SelectDataDir,
@@ -80,12 +86,11 @@ classes = [
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
operator.SetOverrideColour,
operator.SetViewportShadowFromSun,
operator.SnapSpacesTogether,
operator.OverrideDelete,
operator.ConfigureVisibility,
prop.StrProperty,
prop.ObjProperty,
prop.Attribute,
prop.ModuleVisibility,
prop.BIMProperties,
prop.IfcParameter,
prop.PsetQto,
@@ -97,13 +102,28 @@ classes = [
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
# scene panel groups -->
ui.BIM_PT_project_setup,
ui.BIM_PT_utilities,
ui.BIM_PT_collaboration,
ui.BIM_PT_geometry,
ui.BIM_PT_4D5D,
ui.BIM_PT_structural,
ui.BIM_PT_services,
ui.BIM_PT_misc,
ui.BIM_PT_quality_control,
# object panel groups -->
ui.BIM_PT_object_metadata,
ui.BIM_PT_geometry_object,
ui.BIM_PT_services_object,
ui.BIM_PT_utilities_object,
ui.BIM_PT_misc_object,
]
for mod in modules.values():
classes.extend(mod.classes)
def on_register(scene):
handler.setDefaultProperties(scene)
bpy.app.handlers.depsgraph_update_post.remove(on_register)
@@ -1,5 +1,39 @@
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
<script src="jsgantt.js" type="text/javascript"></script>
<style>
.gtaskcellwkend,
.gtaskcellcurrent,
.gminorheadingwkend {
background-color: #e1e1e1;
}
.gitemhighlight td {
background-color: #ffdaaa;
}
.gtaskblue {
background: #4281A4;
}
.gtaskred {
background: #C1666B;
}
.gtaskgreen {
background: #48A9A6;
}
.gtaskyellow {
background: #D4B483;
}
.gmainleft {
overflow: visible;
flex: 0 1 auto;
}
</style>
<a href="#print" id="print">Print Mode</a>
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
<script type="text/javascript">
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
@@ -14,7 +48,14 @@ g.setOptions({
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
vTotalHeight: 1000,
vShowRes: false, // Disable the resource column.
vShowComp: false, // Disable the completion column.
vUseToolTip: false, // Disable tooltips.
vTotalHeight: 900,
});
document.getElementById('print').addEventListener('click', function() {
g.setTotalHeight("");
g.Draw();
});
var json_data = `
{{{json_data}}}
@@ -19,7 +19,8 @@
*/
* { stroke-linecap: round; stroke-linejoin: round; }
*[id] { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
text { fill: black; stroke: none; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.hidden { stroke-dasharray: 3, 2; }
@@ -0,0 +1,214 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
presets = {
"Basic": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
"Admin": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"cobie",
"resource",
"cost",
"sequence",
"group",
"system",
"structural",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"constraint",
"document",
"pset_template",
"clash",
"lca",
"csv",
"bimtester",
"diff",
"patch",
"covetool",
"augin",
"debug",
],
"BIM Coordination": [
"project",
"search",
"bcf",
"root",
"unit",
"georeference",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"owner",
"pset",
"qto",
"clash",
"csv",
"bimtester",
"diff",
"patch",
"debug",
],
"Architecture": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"group",
"system",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"pset_template",
"clash",
"csv",
],
"Structural Engineering": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"group",
"system",
"structural",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"pset_template",
"clash",
"csv",
],
"MEP": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"group",
"system",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"pset_template",
"clash",
"csv",
],
"Quantity Surveying": ["project", "search", "attribute", "spatial", "void", "pset", "qto", "classification", "csv"],
"Model Enrichment": [
"project",
"search",
"root",
"attribute",
"spatial",
"pset",
"qto",
"pset_template",
"csv",
"patch",
],
"3D Visualisation": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
"4D Scheduling": ["project", "search", "attribute", "spatial", "resource", "cost", "sequence", "pset", "qto"],
"5D Cost Management": [
"project",
"search",
"attribute",
"spatial",
"resource",
"cost",
"sequence",
"pset",
"qto",
"lca",
],
"Facility Management": ["project", "search", "attribute", "spatial", "cobie", "pset", "qto", "lca"],
}
+73 -78
View File
@@ -32,6 +32,7 @@ import blenderbim.core.aggregate
import blenderbim.core.spatial
import blenderbim.core.style
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector
class IfcExporter:
@@ -41,20 +42,22 @@ class IfcExporter:
def export(self):
self.file = IfcStore.get_file()
self.set_header()
IfcStore.update_cache()
if bpy.context.scene.BIMProjectProperties.is_authoring:
self.sync_object_placements_and_deletions()
self.sync_deletions()
self.sync_all_objects()
self.sync_edited_objects()
extension = self.ifc_export_settings.output_file.split(".")[-1]
if extension == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
tmp_name = "{}.ifc".format(filename)
tmp_file = os.path.join(unzipped_path, tmp_name)
tmp_filename = f"{filename}.ifc"
tmp_file = os.path.join(unzipped_path, tmp_filename)
self.file.write(tmp_file)
with zipfile.ZipFile(
self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
) as zf:
zf.write(tmp_file)
zf.write(tmp_file, arcname=tmp_filename)
elif extension == "ifc":
self.file.write(self.ifc_export_settings.output_file)
elif extension == "ifcjson":
@@ -83,27 +86,39 @@ class IfcExporter:
self.get_application_name(), self.get_application_version()
)
def sync_object_placements_and_deletions(self):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
to_delete = []
def sync_deletions(self):
results = []
for ifc_definition_id in IfcStore.deleted_ids:
try:
product = self.file.by_id(ifc_definition_id)
if hasattr(product, "GlobalId"):
results.append(product.GlobalId)
except:
continue
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
return results
for ifc_definition_id, obj in IfcStore.id_map.items():
def sync_all_objects(self):
results = []
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
for ifc_definition_id in list(IfcStore.id_map.keys()):
obj = IfcStore.id_map[ifc_definition_id]
try:
if isinstance(obj, bpy.types.Material):
continue
self.sync_object_placement(obj)
self.sync_object_container(ifc_definition_id, obj)
tool.Collector.sync(obj)
result = self.sync_object_placement(obj)
if result:
results.append(result)
result = self.sync_object_material(obj)
if result:
results.append(result)
except ReferenceError:
pass # The object is likely deleted
if self.should_delete(obj):
to_delete.append(ifc_definition_id)
for ifc_definition_id in to_delete:
product = self.file.by_id(ifc_definition_id)
IfcStore.unlink_element(product)
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
return results
def sync_edited_objects(self):
results = []
for obj in IfcStore.edited_objs.copy():
if not obj:
continue
@@ -111,79 +126,59 @@ class IfcExporter:
if isinstance(obj, bpy.types.Material):
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
else:
element = tool.Ifc.get_entity(obj)
if element:
results.append(element)
bpy.ops.bim.update_representation(obj=obj.name)
except ReferenceError:
pass # The object is likely deleted
IfcStore.edited_objs.clear()
return results
def sync_object_material(self, obj):
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
return
if not self.has_changed_materials(obj):
return
bpy.ops.bim.update_representation(obj=obj.name)
def has_changed_materials(self, obj):
checksum = obj.data.BIMMeshProperties.material_checksum
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
def sync_object_placement(self, obj):
if not self.has_object_moved(obj):
return
blender_matrix = np.array(obj.matrix_world)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
bpy.ops.bim.update_representation(obj=obj.name)
return element
if element.is_a("IfcGridAxis"):
return self.sync_grid_axis_object_placement(obj, element)
if not hasattr(element, "ObjectPlacement"):
return
ifc_matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
ifc_matrix[0][3] *= self.unit_scale
ifc_matrix[1][3] *= self.unit_scale
ifc_matrix[2][3] *= self.unit_scale
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
return element
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT":
ifc_matrix = ifcopenshell.util.geolocation.global2local(
ifc_matrix,
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
def sync_object_container(self, guid, obj):
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element_collection = bpy.data.collections.get(obj.name)
if self.file.schema == "IFC2X3":
if element.is_a("IfcProject"):
return
elif element.is_a("IfcContext"):
return
if (
(element.is_a("IfcElement") and element_collection)
or element.is_a("IfcSpatialStructureElement")
or element.is_a("IfcGrid")
):
try:
parent_collection = [c for c in bpy.data.collections if c.children.get(element_collection.name)][0]
except:
return # Out of the spatial tree
else:
parent_collection = obj.users_collection[0]
parent_obj = bpy.data.objects.get(parent_collection.name)
if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id:
return
parent = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id)
if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"):
if parent != ifcopenshell.util.element.get_container(element):
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Container, structure_obj=parent_obj, element_obj=obj
)
elif parent != ifcopenshell.util.element.get_aggregate(element):
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
)
def should_delete(self, obj):
try:
# This will throw an exception if the Blender object no longer exists
foo = obj.name
foo
def has_object_moved(self, obj):
if not obj.BIMObjectProperties.location_checksum:
return True # Let's be conservative
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
return False
except:
return True
return True
def sync_grid_axis_object_placement(self, obj, element):
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
grid_obj = tool.Ifc.get_object(grid)
if grid_obj:
self.sync_object_placement(grid_obj)
if grid_obj.matrix_world != obj.matrix_world:
bpy.ops.bim.update_representation(obj=obj.name)
def get_application_name(self):
return "BlenderBIM"
+5 -12
View File
@@ -50,9 +50,7 @@ def mode_callback(obj, data):
):
continue
if obj.data.BIMMeshProperties.ifc_definition_id:
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType in ["Tessellation", "Brep", "Annotation2D"]:
IfcStore.edited_objs.add(obj)
IfcStore.edited_objs.add(obj)
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
IfcStore.edited_objs.add(obj)
@@ -107,13 +105,6 @@ def color_callback(obj, data):
def active_object_callback():
obj = bpy.context.active_object
for obj in bpy.context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
stored_obj = IfcStore.get_element(obj.BIMObjectProperties.ifc_definition_id)
if stored_obj and stored_obj != obj:
bpy.ops.bim.copy_class(obj=obj.name)
refresh_ui_data()
@@ -209,6 +200,7 @@ def ensureIfcExported(scene):
def get_application(ifc):
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
version = get_application_version()
for element in ifc.by_type("IfcApplication"):
if element.ApplicationIdentifier == "BlenderBIM" and element.Version == version:
@@ -216,7 +208,7 @@ def get_application(ifc):
return ifcopenshell.api.run(
"owner.add_application",
ifc,
version=get_application_version(),
version=version,
application_full_name="BlenderBIM Add-on",
application_identifier="BlenderBIM",
)
@@ -244,6 +236,7 @@ def setDefaultProperties(scene):
)
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application
# TODO: Move to drawing module
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Technical"
@@ -297,7 +290,7 @@ def setDefaultProperties(scene):
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
+44 -1
View File
@@ -19,6 +19,7 @@
import bpy
import json
import math
import zipfile
import ifcopenshell
import ifcopenshell.util.attribute
from mathutils import geometry
@@ -46,7 +47,7 @@ def draw_attribute(attribute, layout, copy_operator=None):
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.data = json.dumps({"name": attribute.name, "value": attribute.get_value(), "is_null": attribute.is_null})
op.name = attribute.name
def import_attributes(ifc_class, props, data, callback=None):
@@ -97,3 +98,45 @@ def export_attributes(props, callback=None):
continue # Our job is done
attributes[prop.name] = prop.get_value()
return attributes
class IfcHeaderExtractor:
def __init__(self, filepath: str):
self.filepath = filepath
def extract(self):
extension = self.filepath.split(".")[-1]
if extension.lower() == "ifc":
with open(self.filepath) as ifc_file:
return self.extract_ifc_spf(ifc_file)
elif extension.lower() == "ifczip":
return self.extract_ifc_zip()
def extract_ifc_spf(self, ifc_file):
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
data = {}
max_lines_to_parse = 50
for _ in range(max_lines_to_parse):
line = next(ifc_file)
if isinstance(line, bytes):
line = line.decode("utf-8")
if line.startswith("FILE_DESCRIPTION"):
for i, part in enumerate(line.split("'")):
if i == 1:
data["description"] = part
elif i == 3:
data["implementation_level"] = part
elif line.startswith("FILE_NAME"):
for i, part in enumerate(line.split("'")):
if i == 1:
data["name"] = part
elif i == 3:
data["time_stamp"] = part
elif line.startswith("FILE_SCHEMA"):
data["schema_name"] = line.split("'")[1]
break
return data
def extract_ifc_zip(self):
archive = zipfile.ZipFile(self.filepath, "r")
return self.extract_ifc_spf(archive.open(archive.filelist[0]))
+44 -9
View File
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -16,8 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import uuid
import hashlib
import zipfile
import tempfile
import ifcopenshell
@@ -29,8 +31,11 @@ class IfcStore:
path = ""
file = None
schema = None
cache = None
cache_path = None
id_map = {}
guid_map = {}
deleted_ids = set()
edited_objs = set()
pset_template_path = ""
pset_template_file = None
@@ -42,24 +47,31 @@ class IfcStore:
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3"]
@staticmethod
def purge():
IfcStore.path = ""
IfcStore.file = None
IfcStore.schema = None
IfcStore.cache = None
IfcStore.cache_path = None
IfcStore.id_map = {}
IfcStore.guid_map = {}
IfcStore.deleted_ids = set()
IfcStore.edited_objs = set()
IfcStore.pset_template_path = ""
IfcStore.pset_template_file = None
IfcStore.library_path = ""
IfcStore.library_file = None
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
@staticmethod
def get_file():
if IfcStore.file is None:
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
if not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
if IfcStore.path:
try:
IfcStore.load_file(IfcStore.path)
@@ -67,6 +79,30 @@ class IfcStore:
pass
return IfcStore.file
@staticmethod
def get_cache():
if IfcStore.cache is None and IfcStore.path:
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
cache_settings = ifcopenshell.geom.settings()
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except:
return
return IfcStore.cache
@staticmethod
def update_cache():
if not IfcStore.cache:
return
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
IfcStore.cache = None
os.replace(IfcStore.cache_path, new_cache_path)
IfcStore.get_cache()
@staticmethod
def load_file(path):
extension = path.split(".")[-1]
@@ -85,7 +121,9 @@ class IfcStore:
@staticmethod
def get_schema():
if IfcStore.file is None:
return
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
bpy.context.scene.BIMProjectProperties.export_schema
)
elif IfcStore.schema is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
return IfcStore.schema
@@ -224,7 +262,9 @@ class IfcStore:
except:
pass
if obj:
if element and element.is_a("IfcSurfaceStyle"):
obj.BIMMaterialProperties.ifc_style_id = 0
elif obj:
obj.BIMObjectProperties.ifc_definition_id = 0
@staticmethod
@@ -232,7 +272,6 @@ class IfcStore:
is_top_level_operator = not bool(IfcStore.current_transaction)
if is_top_level_operator:
active_object = context.active_object
IfcStore.begin_transaction(operator)
IfcStore.get_file().begin_transaction()
# This empty transaction ensures that each operator has at least one transaction
@@ -248,11 +287,7 @@ class IfcStore:
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
)
IfcStore.end_transaction(operator)
try:
active_object.name
context.view_layer.objects.active = active_object
except:
pass
blenderbim.bim.handler.refresh_ui_data()
return result
+239 -44
View File
@@ -31,6 +31,7 @@ import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.drawing.prop import get_diagram_scales
@@ -67,6 +68,7 @@ class MaterialCreator:
self.add_default_material(element)
if self.parse_representations(element):
self.assign_material_slots_to_faces()
tool.Geometry.record_object_materials(obj)
def add_default_material(self, element):
element_material = ifcopenshell.util.element.get_material(element)
@@ -217,7 +219,7 @@ class IfcImporter:
self.profile_code("Create materials")
self.create_styles()
self.profile_code("Create styles")
self.create_annotation()
self.create_annotations()
self.profile_code("Create annotation")
self.parse_native_elements()
self.profile_code("Parsing native elements")
@@ -254,9 +256,11 @@ class IfcImporter:
self.update_progress(100)
bpy.context.window_manager.progress_end()
def is_element_far_away(self, element, is_meters=True):
def is_element_far_away(self, element):
try:
return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location, is_meters=is_meters)
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
point = placement[:, 3][0:3]
return self.is_point_far_away(point, is_meters=False)
except:
pass
@@ -278,19 +282,24 @@ class IfcImporter:
]
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
self.non_body_contexts = [
# Annotation 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("IfcGeometricRepresentationSubContext")
if c.ContextIdentifier not in ["Body", "Facetation"]
for c in self.file.by_type("IfcGeometricRepresentationContext")
if c.ContextType in ["Plan", "Annotation"]
]
if self.non_body_contexts:
self.settings_2d.set_context_ids(self.non_body_contexts)
if self.plan_contexts:
self.settings_2d.set_context_ids(self.plan_contexts)
def process_element_filter(self):
if self.ifc_import_settings.has_filter:
self.elements = set(self.ifc_import_settings.elements)
# TODO: enable filtering for annotations
self.annotations = set(self.file.by_type("IfcAnnotation"))
else:
self.elements = set(self.file.by_type("IfcElement"))
self.annotations = set(self.file.by_type("IfcAnnotation"))
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
@@ -375,24 +384,26 @@ class IfcImporter:
else:
project = self.file.by_type("IfcContext")[0]
site = self.find_decomposed_ifc_class(project, "IfcSite")
if site and self.is_element_far_away(site[0], is_meters=False):
return self.guess_georeferencing(site[0])
if site and self.is_element_far_away(site):
return self.guess_georeferencing(site)
building = self.find_decomposed_ifc_class(project, "IfcBuilding")
if building and self.is_element_far_away(building[0], is_meters=False):
return self.guess_georeferencing(building[0])
if building and self.is_element_far_away(building):
return self.guess_georeferencing(building)
return self.guess_absolute_coordinate()
def guess_georeferencing(self, element):
if not element.ObjectPlacement.is_a("IfcLocalPlacement"):
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
return
placement = element.ObjectPlacement.RelativePlacement
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
props = bpy.context.scene.BIMGeoreferenceProperties
props.blender_eastings = str(placement.Location.Coordinates[0])
props.blender_northings = str(placement.Location.Coordinates[1])
props.blender_orthogonal_height = str(placement.Location.Coordinates[2])
if placement.RefDirection:
props.blender_x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0])
props.blender_x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1])
props.blender_eastings = str(placement[0][3])
props.blender_northings = str(placement[1][3])
props.blender_orthogonal_height = str(placement[2][3])
x_axis = mathutils.Vector(placement[:, 0][0:3])
default_x_axis = mathutils.Vector((1, 0, 0))
if (default_x_axis - x_axis).length > 0.01:
props.blender_x_axis_abscissa = str(placement[0][0])
props.blender_x_axis_ordinate = str(placement[1][0])
props.has_blender_offset = True
def guess_absolute_coordinate(self):
@@ -499,8 +510,8 @@ class IfcImporter:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
obj.BIMObjectProperties.ifc_definition_id = axis.id()
obj.matrix_world = grid_obj.matrix_world
self.link_element(axis, obj)
self.set_matrix_world(obj, grid_obj.matrix_world)
grid_collection.objects.link(obj)
def create_type_products(self):
@@ -527,6 +538,7 @@ class IfcImporter:
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
@@ -585,14 +597,23 @@ class IfcImporter:
def create_spatial_elements(self):
self.create_generic_elements(self.spatial_elements)
def create_annotations(self):
self.create_generic_elements(self.annotations)
def create_elements(self):
self.create_generic_elements(self.elements)
def create_generic_elements(self, elements):
# Based on my experience in viewing BIM models, representations are prioritised as follows:
# 1. 3D Body, 2. 2D Plans, 3. Point clouds, 4. No representation
# If an element has a representation that doesn't follow 1, 2, or 3, it will not show by default.
# The user can load them later if they want to view them.
products = self.create_products(elements)
elements -= products
products = self.create_curve_products(elements)
elements -= products
products = self.create_pointclouds(elements)
elements -= products
for element in elements:
self.create_product(element)
@@ -606,6 +627,9 @@ class IfcImporter:
)
else:
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
@@ -622,7 +646,7 @@ class IfcImporter:
)
)
checkpoint = time.time()
# self.update_progress(((total / approx_total_products) * progress_range) + start_progress)
self.update_progress((iterator.progress() / 100 * progress_range) + start_progress)
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
@@ -641,9 +665,6 @@ class IfcImporter:
print("Done creating geometry")
return results
def create_annotation(self):
self.create_curve_products(self.file.by_type("IfcAnnotation"))
def create_structural_items(self):
# Create structural collections
self.structural_member_collection = bpy.data.collections.new("Members")
@@ -681,9 +702,69 @@ class IfcImporter:
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, placement_matrix)
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
self.link_element(product, obj)
def get_pointcloud_representation(self, product):
if hasattr(product, "Representation") and hasattr(product.Representation, "Representations"):
representations = product.Representation.Representations
elif hasattr(product, "RepresentationMaps") and hasattr(product.RepresentationMaps, "RepresentationMaps"):
representations = product.RepresentationMaps
else:
return None
for representation in representations:
if representation.RepresentationType == "PointCloud":
return representation
elif self.file.schema == "IFC2X3" and representation.RepresentationType == "GeometricSet":
for item in representation.Items:
if not (item.is_a("IfcCartesianPointList") or item.is_a("IfcCartesianPoint")):
break
else:
return representation
elif representation.RepresentationType == "MappedRepresentation":
for item in representation.Items:
mapped_representation = self.get_pointcloud_representation(item)
if mapped_representation is not None:
return mapped_representation
return None
def create_pointclouds(self, products):
result = set()
for product in products:
representation = self.get_pointcloud_representation(product)
if representation is not None:
pointcloud = self.create_pointcloud(product, representation)
if pointcloud is not None:
result.add(pointcloud)
return result
def create_pointcloud(self, product, representation):
placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
vertex_list = []
for item in representation.Items:
if item.is_a("IfcCartesianPointList"):
vertex_list.extend(
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
)
elif item.is_a("IfcCartesianPoint"):
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
if len(vertex_list) == 0:
return None
mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}"
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata(vertex_list, [], [])
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
self.link_element(product, obj)
return product
def create_curve_products(self, products):
results = set()
if not products:
@@ -694,6 +775,9 @@ class IfcImporter:
)
else:
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
@@ -724,15 +808,16 @@ class IfcImporter:
pass
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
mesh = self.create_camera(element, shape)
self.link_mesh(shape, mesh)
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element):
mesh = self.create_curve(element, shape)
self.link_mesh(shape, mesh)
elif shape:
mesh_name = self.get_mesh_name(shape.geometry)
mesh = self.meshes.get(mesh_name)
if mesh is None:
mesh = self.create_mesh(element, shape)
if "-" in shape.geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id.split("-")[0])
else:
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id)
self.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
else:
mesh = None
@@ -746,13 +831,13 @@ class IfcImporter:
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])
)
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, mat)
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, mat))
self.material_creator.create(element, obj, mesh)
elif mesh:
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
self.material_creator.create(element, obj, mesh)
elif hasattr(element, "ObjectPlacement"):
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
self.add_opening_relation(element, obj)
@@ -868,6 +953,34 @@ class IfcImporter:
curve.bevel_depth = self.unit_scale * item.Radius
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 = {}
for obj in self.added_data.values():
@@ -1016,6 +1129,7 @@ class IfcImporter:
def create_spatial_decomposition_collections(self):
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
self.create_views_collection()
self.create_type_collection()
def create_type_collection(self):
@@ -1027,6 +1141,22 @@ class IfcImporter:
self.type_collection = bpy.data.collections.new("Types")
self.project["blender"].children.link(self.type_collection)
def create_views_collection(self):
view_collection = None
for collection in self.project["blender"].children:
if collection.name == "Views":
view_collection = collection
break
if not view_collection:
view_collection = bpy.data.collections.new("Views")
self.project["blender"].children.link(view_collection)
for element in self.file.by_type("IfcAnnotation"):
if element.ObjectType == "DRAWING":
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
collection = bpy.data.collections.new("IfcGroup/" + group.Name)
self.collections[group.GlobalId] = collection
view_collection.children.link(collection)
def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects:
if element not in self.spatial_elements:
@@ -1142,8 +1272,12 @@ class IfcImporter:
def place_object_in_decomposition_collection(self, element, obj):
if element.is_a("IfcProject"):
return
elif element.is_a("IfcGridAxis"):
return
elif element.GlobalId in self.collections:
return self.collections[element.GlobalId].objects.link(obj)
collection = self.collections[element.GlobalId]
collection.name = obj.name
return collection.objects.link(obj)
elif getattr(element, "Decomposes", None):
aggregate = ifcopenshell.util.element.get_aggregate(element)
return self.collections[aggregate.GlobalId].objects.link(obj)
@@ -1153,6 +1287,8 @@ class IfcImporter:
def place_object_in_spatial_decomposition_collection(self, element, obj):
if element.is_a("IfcProject"):
return
elif element.is_a("IfcGridAxis"):
return
elif element.GlobalId in self.collections:
return self.collections[element.GlobalId].objects.link(obj)
elif element.is_a("IfcTypeObject"):
@@ -1163,16 +1299,9 @@ class IfcImporter:
return self.structural_member_collection.objects.link(obj)
elif element.is_a("IfcStructuralConnection"):
return self.structural_connection_collection.objects.link(obj)
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
view_collection = bpy.data.collections.get("Views")
if not view_collection:
view_collection = bpy.data.collections.new("Views")
bpy.context.scene.collection.children.link(view_collection)
elif element.is_a("IfcAnnotation") and self.is_drawing_annotation(element):
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
drawing_collection = bpy.data.collections.new("IfcGroup/" + group.Name)
view_collection.children.link(drawing_collection)
drawing_collection.objects.link(obj)
return
return self.collections[group.GlobalId].objects.link(obj)
container = ifcopenshell.util.element.get_container(element)
if container:
@@ -1188,6 +1317,33 @@ class IfcImporter:
self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element)
bpy.context.scene.collection.objects.link(obj)
def is_curve_annotation(self, element):
return element.ObjectType in [
"DIMENSION",
"EQUAL_DIMENSION",
"MISC",
"PLAN_LEVEL",
"SECTION_LEVEL",
"STAIR_ARROW",
"TEXT_LEADER",
]
def is_drawing_annotation(self, element):
return element.ObjectType in [
"BREAKLINE",
"DIMENSION",
"DRAWING",
"EQUAL_DIMENSION",
"GRID",
"HIDDEN_LINE",
"MISC",
"PLAN_LEVEL",
"SECTION_LEVEL",
"STAIR_ARROW",
"TEXT",
"TEXT_LEADER",
]
def get_element_matrix(self, element):
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
result[0][3] *= self.unit_scale
@@ -1314,6 +1470,31 @@ class IfcImporter:
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
return camera
def create_curve(self, element, shape):
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
geometry = shape
curve = bpy.data.curves.new(self.get_mesh_name(geometry), type="CURVE")
curve.dimensions = "3D"
curve.resolution_u = 2
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 = mathutils.Vector(v1)
v2 = vertices[edge[1]]
polyline.points.add(1)
polyline.points[-1].co = mathutils.Vector(v2)
return curve
def create_mesh(self, element, shape):
try:
if hasattr(shape, "geometry"):
@@ -1422,6 +1603,20 @@ class IfcImporter:
self.added_data[element.id()] = obj
IfcStore.link_element(element, obj)
def link_mesh(self, shape, mesh):
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
geometry = shape
if "-" in geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
else:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id)
def set_matrix_world(self, obj, matrix_world):
obj.matrix_world = matrix_world
tool.Geometry.record_object_position(obj)
class IfcImportSettings:
def __init__(self):
@@ -17,8 +17,11 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.core.aggregate as core
import blenderbim.core.spatial
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
@@ -92,10 +95,34 @@ class AddAggregate(bpy.types.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
element = tool.Ifc.get_entity(obj)
if not element:
return {"FINISHED"}
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
tool.Collector.sync(obj)
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
current_container = ifcopenshell.util.element.get_container(element)
if current_aggregate:
core.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(current_aggregate),
related_obj=aggregate,
)
elif current_container:
blenderbim.core.spatial.assign_container(
tool.Ifc,
tool.Collector,
tool.Spatial,
structure_obj=tool.Ifc.get_object(current_container),
element_obj=aggregate,
)
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
return {"FINISHED"}
@@ -28,6 +28,7 @@ class BIM_PT_aggregate(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -24,6 +24,7 @@ classes = (
operator.DisableEditingAttributes,
operator.EditAttributes,
operator.GenerateGlobalId,
operator.CopyAttributeToSelection,
prop.BIMAttributeProperties,
ui.BIM_PT_object_attributes,
ui.BIM_PT_material_attributes,
@@ -0,0 +1,74 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import blenderbim.tool as tool
def refresh():
AttributesData.is_loaded = False
MaterialAttributesData.is_loaded = False
class AttributesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"attributes": cls.attributes()}
cls.is_loaded = True
@classmethod
def attributes(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
data = element.get_info()
if hasattr(element, "GlobalId"):
excluded_keys = ["id", "type"]
else:
excluded_keys = ["type"]
for key, value in data.items():
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in excluded_keys:
continue
if key == "id":
key = "STEP ID"
results.append({"name": key, "value": str(value)})
return results
class MaterialAttributesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"attributes": cls.attributes()}
cls.is_loaded = True
@classmethod
def attributes(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
data = element.get_info()
for key, value in data.items():
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in ["id", "type"]:
continue
results.append({"name": key, "value": str(value)})
return results
@@ -20,10 +20,21 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.attribute as core
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class EnableEditingAttributes(bpy.types.Operator):
bl_idname = "bim.enable_editing_attributes"
bl_label = "Enable Editing Attributes"
@@ -40,22 +51,9 @@ class EnableEditingAttributes(bpy.types.Operator):
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
props.attributes.clear()
for attribute in Data.products[oprops.ifc_definition_id]:
new = props.attributes.add()
if attribute["type"] == "entity" or (attribute["type"] == "list" and attribute["list_type"] == "entity"):
continue
new.name = attribute["name"]
new.is_null = attribute["is_null"]
if attribute["type"] == "string" or attribute["type"] == "list":
new.string_value = attribute["value"] or ""
elif attribute["type"] == "integer":
new.int_value = attribute["value"] or 0
elif attribute["type"] == "float":
new.float_value = attribute["value"] or 0.0
elif attribute["type"] == "enum":
new.enum_items = json.dumps(attribute["enum_items"])
if attribute["value"]:
new.enum_value = attribute["value"]
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes)
props.is_editing_attributes = True
return {"FINISHED"}
@@ -77,16 +75,13 @@ class DisableEditingAttributes(bpy.types.Operator):
return {"FINISHED"}
class EditAttributes(bpy.types.Operator):
class EditAttributes(bpy.types.Operator, Operator):
bl_idname = "bim.edit_attributes"
bl_label = "Edit Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
@@ -139,3 +134,14 @@ class GenerateGlobalId(bpy.types.Operator):
global_id.data_type = "string"
global_id.string_value = ifcopenshell.guid.new()
return {"FINISHED"}
class CopyAttributeToSelection(bpy.types.Operator, Operator):
bl_idname = "bim.copy_attribute_to_selection"
bl_label = "Copy Attribute To Selection"
name: bpy.props.StringProperty()
def _execute(self, context):
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value()
for obj in context.selected_objects:
core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj)
@@ -16,17 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data
from blenderbim.bim.module.attribute.data import AttributesData, MaterialAttributesData
def draw_ui(context, layout, obj_type):
def draw_ui(context, layout, obj_type, attributes):
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
if props.is_editing_attributes:
row = layout.row(align=True)
@@ -37,46 +36,17 @@ def draw_ui(context, layout, obj_type):
op.obj_type = obj_type
op.obj = obj.name
for attribute in Data.products[oprops.ifc_definition_id]:
if attribute["type"] == "entity":
continue
row = layout.row(align=True)
blender_attribute = props.attributes.get(attribute["name"])
if attribute["type"] == "string" or attribute["type"] == "list":
row.prop(blender_attribute, "string_value", text=attribute["name"])
elif attribute["type"] == "integer":
row.prop(blender_attribute, "int_value", text=attribute["name"])
elif attribute["type"] == "float":
row.prop(blender_attribute, "float_value", text=attribute["name"])
elif attribute["type"] == "enum":
row.prop(blender_attribute, "enum_value", text=attribute["name"])
if attribute["name"] == "GlobalId":
row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
if attribute["is_optional"]:
row.prop(
blender_attribute,
"is_null",
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
text="",
)
blenderbim.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
else:
row = layout.row()
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
op.obj_type = obj_type
op.obj = obj.name
if "GlobalId" not in [a["name"] for a in Data.products[oprops.ifc_definition_id]]:
row = layout.row(align=True)
row.label(text="STEP ID")
row.label(text=str(oprops.ifc_definition_id))
for attribute in Data.products[oprops.ifc_definition_id]:
if attribute["value"] is None or attribute["type"] == "entity":
continue
for attribute in attributes:
row = layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=str(attribute["value"]))
row.label(text=attribute["value"])
# TODO: reimplement, see #1222
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
@@ -89,6 +59,8 @@ class BIM_PT_object_attributes(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -99,7 +71,9 @@ class BIM_PT_object_attributes(Panel):
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
draw_ui(context, self.layout, "Object")
if not AttributesData.is_loaded:
AttributesData.load()
draw_ui(context, self.layout, "Object", AttributesData.data["attributes"])
class BIM_PT_material_attributes(Panel):
@@ -108,6 +82,8 @@ class BIM_PT_material_attributes(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
bl_parent_id = "BIM_PT_object_metadata"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -119,4 +95,6 @@ class BIM_PT_material_attributes(Panel):
return False
def draw(self, context):
draw_ui(context, self.layout, "Material")
if not MaterialAttributesData.is_loaded:
MaterialAttributesData.load()
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
@@ -93,6 +93,7 @@ class LoadBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.get_topic(self.topic_guid)
bcfxml.get_header(self.topic_guid)
new = context.scene.BCFProperties.topics[self.topic_index]
data_map = {
"name": topic.guid,
@@ -841,6 +842,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj.data.angle = 2 * atan(
(0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))
)
else:
return
z_axis = Vector(
(-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)
@@ -29,6 +29,7 @@ class BIM_PT_bcf(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
def draw(self, context):
layout = self.layout
@@ -27,6 +27,7 @@ class BIM_PT_qa(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
self.layout.use_property_split = True
@@ -17,14 +17,28 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui
from . import ui, operator, prop
classes = (ui.BIM_PT_boundary,)
classes = (
operator.LoadProjectSpaceBoundaries,
operator.LoadSpaceBoundaries,
operator.LoadBoundary,
operator.SelectProjectBoundaries,
operator.ColourByRelatedBuildingElement,
operator.EnableEditingBoundary,
operator.DisableEditingBoundary,
operator.EditBoundaryAttributes,
operator.UpdateBoundaryGeometry,
ui.BIM_PT_Boundary,
ui.BIM_PT_SpaceBoundaries,
ui.BIM_PT_SceneBoundaries,
prop.BIMBoundaryProperties,
)
def register():
pass
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
def unregister():
pass
del bpy.types.Object.bim_boundary_properties
@@ -0,0 +1,320 @@
# 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 logging
import bpy
import mathutils
import ifcopenshell.util.attribute
import ifcopenshell.api
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.boundary.data import Data
import blenderbim.bim.import_ifc as import_ifc
from blenderbim.bim.module.geometry.helper import Helper
def get_boundaries_collection(blender_space):
space_collection = bpy.data.collections.get(blender_space.name)
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
boundaries_collection = space_collection.children.get(collection_name)
if not boundaries_collection:
boundaries_collection = bpy.data.collections.new(collection_name)
space_collection.children.link(boundaries_collection)
return boundaries_collection
class Loader:
def __init__(self):
self.ifc_file = None
self.logger = None
self.ifc_importer = None
self.settings = None
self.load_settings()
self.load_importer()
def load_settings(self):
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
self.settings.set(self.settings.USE_BREP_DATA, False)
def load_importer(self):
self.ifc_file = tool.Ifc.get()
self.logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, self.logger)
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
self.ifc_importer.file = self.ifc_file
def load_boundary(self, boundary_id, blender_space):
boundary = self.ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary)
if obj:
return obj
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
obj.matrix_world = blender_space.matrix_world
boundaries_collection = get_boundaries_collection(blender_space)
boundaries_collection.objects.link(obj)
tool.Ifc.link(boundary, obj)
return obj
class LoadProjectSpaceBoundaries(bpy.types.Operator):
bl_idname = "bim.load_project_space_boundaries"
bl_label = "Load all project space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
loader = Loader()
if not Data.is_loaded:
Data.load(tool.Ifc.get())
for space_id, boundaries_id in Data.spaces.items():
blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
for boundary_id in boundaries_id:
loader.load_boundary(boundary_id, blender_space)
return {"FINISHED"}
class LoadBoundary(bpy.types.Operator):
bl_idname = "bim.load_boundary"
bl_label = "Load boundary"
bl_options = {"REGISTER", "UNDO"}
boundary_id: bpy.props.IntProperty()
def execute(self, context):
loader = Loader()
blender_space = context.active_object
for obj in context.visible_objects:
obj.select_set(False)
obj = loader.load_boundary(self.boundary_id, blender_space)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
return {"FINISHED"}
class LoadSpaceBoundaries(bpy.types.Operator):
bl_idname = "bim.load_space_boundaries"
bl_label = "Load selected space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
blender_space = context.active_object
oprops = context.active_object.BIMObjectProperties
loader = Loader()
if not Data.is_loaded:
Data.load(tool.Ifc.get())
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
loader.load_boundary(boundary_id, blender_space)
return {"FINISHED"}
class SelectProjectBoundaries(bpy.types.Operator):
bl_idname = "bim.select_project_space_boundaries"
bl_label = "Select all project space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
ifc_file = tool.Ifc.get()
for boundary_id in Data.boundaries:
boundary = ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary)
if obj:
obj.select_set(True)
return {"FINISHED"}
def get_colour(ifc_boundary):
"""Return a color depending on IfcClass given"""
product_colors = {
"IfcWall": (0.7, 0.3, 0, 1),
"IfcWindow": (0, 0.7, 1, 1),
"IfcSlab": (0.7, 0.7, 0.5, 1),
"IfcRoof": (0, 0.3, 0, 1),
"IfcDoor": (1, 1, 1, 1),
}
if ifc_boundary.PhysicalOrVirtualBoundary == "VIRTUAL":
return (1, 0, 1, 1)
element = ifc_boundary.RelatedBuildingElement
if not element:
return (1, 0, 0, 1)
for product, colour in product_colors.items():
if element.is_a(product):
return colour
return (0, 0, 0, 1)
class ColourByRelatedBuildingElement(bpy.types.Operator):
bl_idname = "bim.colour_by_related_building_element"
bl_label = "Apply colour based on related building elements"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
self.store_state(context)
result = self._execute(context)
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(self)
return result
def _execute(self, context):
self.file = tool.Ifc.get()
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not element.is_a("IfcRelSpaceBoundary"):
continue
obj.color = get_colour(element)
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
def store_state(self, context):
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
def rollback(self, data):
if data:
data["area"].spaces[0].shading.color_type = data["color_type"]
def commit(self, data):
if data:
data["area"].spaces[0].shading.color_type = "OBJECT"
EDITABLE_ATTRIBUTES = {
"RelatingSpace": "relating_space",
"RelatedBuildingElement": "related_building_element",
"ParentBoundary": "parent_boundary",
"CorrespondingBoundary": "corresponding_boundary",
}
class EnableEditingBoundary(bpy.types.Operator):
bl_idname = "bim.enable_editing_boundary"
bl_label = "Edit boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.active_object.bim_boundary_properties
bprops.is_editing = True
boundary = tool.Ifc.get_entity(context.active_object)
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
entity = getattr(boundary, ifc_attribute, None)
if not entity:
continue
obj = tool.Ifc.get_object(entity)
if entity and obj:
setattr(bprops, blender_property, obj)
return {"FINISHED"}
class DisableEditingBoundary(bpy.types.Operator):
bl_idname = "bim.disable_editing_boundary"
bl_label = "Disable editing boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.active_object.bim_boundary_properties
bprops.is_editing = False
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
setattr(bprops, blender_property, None)
return {"FINISHED"}
class EditBoundaryAttributes(bpy.types.Operator):
bl_idname = "bim.edit_boundary_attributes"
bl_label = "Disable editing boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
bprops = context.active_object.bim_boundary_properties
boundary = tool.Ifc.get_entity(context.active_object)
attributes = dict()
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
obj = getattr(bprops, blender_property, None)
entity = tool.Ifc.get_entity(obj)
attributes[blender_property] = entity
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
bpy.ops.bim.disable_editing_boundary()
return {"FINISHED"}
def polyline_from_indexes(mesh, indexes):
return tuple(mesh.vertices[i].co for i in indexes)
def polyline_to_2d(polyline, placement_matrix):
matrix_inv = placement_matrix.inverted()
return tuple((matrix_inv @ v).to_2d() for v in polyline)
class UpdateBoundaryGeometry(bpy.types.Operator):
bl_idname = "bim.update_boundary_geometry"
bl_label = "Update boundary geometry"
bl_description = """
Update boundary connection geometry from mesh.
Mesh must lie on a single plane. It should look like a face or a face with holes.
"""
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifc_file = tool.Ifc.get()
helper = Helper(ifc_file)
mesh = context.active_object.data
curves = helper.auto_detect_curve_bounded_plane(mesh)
outer_boundary = polyline_from_indexes(mesh, curves["outer_curve"])
inner_boundaries = tuple(polyline_from_indexes(mesh, boundary) for boundary in curves["inner_curves"])
# Create placement matrix
location = outer_boundary[0]
i = (outer_boundary[1] - outer_boundary[0]).normalized()
k = mesh.polygons[0].normal
j = k.cross(i)
matrix = mathutils.Matrix()
matrix[0].xyz = i
matrix[1].xyz = j
matrix[2].xyz = k
matrix.transpose()
matrix.translation = location
settings = {
"rel_space_boundary": tool.Ifc.get_entity(context.active_object),
"outer_boundary": polyline_to_2d(outer_boundary, matrix),
"inner_boundaries": tuple(polyline_to_2d(boundary, matrix) for boundary in inner_boundaries),
"location": location,
"axis": k,
"ref_direction": i,
}
ifcopenshell.api.run("boundary.assign_connection_geometry", ifc_file, **settings)
return {"FINISHED"}
@@ -0,0 +1,60 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.tool as tool
def space_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
return False
def boundary_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcRelSpaceBoundary")
return False
def element_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcElement")
return False
class BIMBoundaryProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
@@ -20,16 +20,37 @@ import bpy
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from ifcopenshell.api.boundary.data import Data
class BIM_PT_boundary(Panel):
class BIM_PT_SceneBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_idname = "BIM_PT_boundary"
bl_id_name = "BIM_PT_scene_boundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
row = self.layout.row(align=True)
row.operator("bim.load_project_space_boundaries")
row.operator("bim.select_project_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
row.operator("bim.colour_by_related_building_element", text="", icon="BRUSH_DATA")
class BIM_PT_Boundary(Panel):
bl_label = "IFC Space Boundary"
bl_idname = "BIM_PT_Boundary"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
@@ -40,15 +61,97 @@ class BIM_PT_boundary(Panel):
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a() not in ["IfcSpace", "IfcExternalSpatialElement"]:
return False
return True
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
return entity.is_a("IfcRelSpaceBoundary")
def draw(self, context):
self.oprops = context.active_object.BIMObjectProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
boundary = Data.boundaries[boundary_id]
props = context.active_object.BIMObjectProperties
ifc_file = tool.Ifc.get()
element = ifc_file.by_id(props.ifc_definition_id)
boundary = ifc_file.by_id(props.ifc_definition_id)
self.bprops = context.active_object.bim_boundary_properties
if self.bprops.is_editing:
row = self.layout.row(align=True)
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
row.operator("bim.disable_editing_boundary", icon="CANCEL", text="")
self.draw_relation_editor(boundary, "RelatingSpace", "relating_space")
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
else:
row = self.layout.row()
row.label(text=f"{boundary_id}", icon="GHOST_ENABLED")
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
self.draw_relation_data(boundary, "RelatingSpace")
self.draw_relation_data(boundary, "RelatedBuildingElement")
self.draw_relation_data(boundary, "ParentBoundary")
self.draw_relation_data(boundary, "CorrespondingBoundary")
if hasattr(boundary, "InnerBoundaries"):
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
row = self.layout.row(align=True)
row.label(text="InnerBoundaries")
row.label(text=f"[{i}]")
row.label(text=f"{inner_boundary.is_a()}/{inner_boundary.Name}")
row = self.layout.row()
row.operator("bim.update_boundary_geometry")
def draw_relation_data(self, boundary, ifc_attribute: str):
row = self.layout.row(align=True)
if hasattr(boundary, ifc_attribute):
row.label(text=ifc_attribute)
entity = getattr(boundary, ifc_attribute)
if entity:
row.label(text=f"{entity.is_a()}/{entity.Name}")
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
op.global_id = entity.GlobalId
else:
row.label(text="")
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
if hasattr(boundary, ifc_attribute):
row = self.layout.row(align=True)
row.prop(self.bprops, blender_property)
class BIM_PT_SpaceBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_idname = "BIM_PT_SpaceBoundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
for ifc_class in valid_classes:
if entity.is_a(ifc_class):
return True
return False
def draw(self, context):
self.props = context.active_object.BIMObjectProperties
self.ifc_file = tool.Ifc.get()
row = self.layout.row()
row.operator("bim.load_space_boundaries")
if not Data.is_loaded:
Data.load(self.ifc_file)
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
boundary_data = Data.boundaries[boundary_id]
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
row = self.layout.row()
if building_element:
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
else:
bld_el_description = None
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
op.boundary_id = boundary_id
@@ -16,17 +16,33 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import test.bim.bootstrap
import bpy
from . import ui, prop, operator
classes = (
operator.AddBrick,
operator.AddBrickFeed,
operator.AssignBrickReference,
operator.CloseBrickProject,
operator.ConvertBrickProject,
operator.ConvertIfcToBrick,
operator.LoadBrickProject,
operator.NewBrickFile,
operator.RefreshBrickViewer,
operator.RewindBrickClass,
operator.ViewBrickClass,
operator.ViewBrickItem,
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
ui.BIM_PT_ifc_brickschema_references,
ui.BIM_UL_bricks,
)
class TestExecuteIfcPatch(test.bim.bootstrap.NewFile):
@test.bim.bootstrap.scenario
def test_executing_ifcpatch(self):
return """
Given I set "scene.BIMPatchProperties.ifc_patch_recipes" to "OffsetObjectPlacements"
And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc"
And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/basic-patched.ifc"
And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]"
When I press "bim.execute_ifc_patch"
Then the file "{cwd}/test/files/basic-patched.ifc" should contain "123454321"
"""
def register():
bpy.types.Scene.BIMBrickProperties = bpy.props.PointerProperty(type=prop.BIMBrickProperties)
def unregister():
del bpy.types.Scene.BIMBrickProperties
@@ -0,0 +1,177 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
from blenderbim.tool.brick import BrickStore
try:
from rdflib import URIRef, BNode
except:
# See #1860
print("Warning: brickschema not available.")
def refresh():
BrickschemaData.is_loaded = False
BrickschemaReferencesData.is_loaded = False
class BrickschemaData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"is_loaded": cls.get_is_loaded(),
"attributes": cls.attributes(),
"namespaces": cls.namespaces(),
"brick_equipment_classes": cls.brick_equipment_classes(),
}
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
@classmethod
def attributes(cls):
if BrickStore.graph is None:
return []
props = bpy.context.scene.BIMBrickProperties
try:
brick = props.bricks[props.active_brick_index]
except:
return []
results = []
uri = brick.uri
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?name ?value ?sp ?sv WHERE {
<{uri}> ?name ?value .
OPTIONAL {
{ ?name rdfs:range brick:TimeseriesReference . }
UNION
{ ?name a brick:EntityProperty . }
?value ?sp ?sv }
}
""".replace(
"{uri}", uri
)
)
for row in query:
name = row.get("name").toPython().split("#")[-1]
value = row.get("value")
results.append(
{
"name": name,
"value": value.toPython().split("#")[-1],
"is_uri": isinstance(value, URIRef),
"value_uri": value.toPython(),
"is_globalid": name == "globalID",
}
)
if isinstance(row.get("value"), BNode):
for s, p, o in BrickStore.graph.triples((value, None, None)):
results.append(
{
"name": name + ":" + p.toPython().split("#")[-1],
"value": o.toPython().split("#")[-1],
"is_uri": isinstance(o, URIRef),
"value_uri": o.toPython(),
"is_globalid": p.toPython().split("#")[-1] == "globalID",
}
)
return results
@classmethod
def namespaces(cls):
if BrickStore.graph is None:
return []
results = []
for alias, uri in BrickStore.graph.namespaces():
results.append((uri, f"{alias}: {uri}", ""))
return results
@classmethod
def brick_equipment_classes(cls):
if BrickStore.graph is None:
return []
results = []
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?class WHERE {
?class rdfs:subClassOf* brick:Equipment .
}
"""
)
for uri in sorted([x[0].toPython() for x in query]):
results.append((uri, uri.split("#")[-1], ""))
return results
class BrickschemaReferencesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {"is_loaded": cls.get_is_loaded(), "libraries": cls.libraries(), "references": cls.references()}
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
@classmethod
def libraries(cls):
results = []
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
if tool.Ifc.get_schema() == "IFC2X3":
results.append((str(library.id()), library.Name or "Unnamed", ""))
elif ".ttl" in library.Location:
results.append((str(library.id()), library.Name or "Unnamed", ""))
return results
@classmethod
def references(cls):
results = []
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
if rel.is_a("IfcRelAssociatesLibrary"):
reference = rel.RelatingLibrary
if tool.Ifc.get_schema() == "IFC2X3" and "#" not in reference.ItemReference:
continue
if tool.Ifc.get_schema() != "IFC2X3" and "#" not in reference.Identification:
continue
results.append(
{
"id": reference.id(),
"identification": reference.ItemReference
if tool.Ifc.get_schema() == "IFC2X3"
else reference.Identification,
"name": reference.Name or "Unnamed",
}
)
return results
@@ -0,0 +1,176 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.brick as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
class Operator:
def execute(self, context):
self._execute(context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class LoadBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.load_brick_project"
bl_label = "Load Brickschema Project"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
def _execute(self, context):
core.load_brick_project(tool.Brick, filepath=self.filepath)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ViewBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_class"
bl_label = "View Brick Class"
bl_options = {"REGISTER", "UNDO"}
brick_class: bpy.props.StringProperty(name="Brick Class")
def _execute(self, context):
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
class ViewBrickItem(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_item"
bl_label = "View Brick Item"
bl_options = {"REGISTER", "UNDO"}
item: bpy.props.StringProperty(name="Brick Item")
def _execute(self, context):
core.view_brick_item(tool.Brick, item=self.item)
class RewindBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.rewind_brick_class"
bl_label = "Rewind Brick Class"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.rewind_brick_class(tool.Brick)
class CloseBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.close_brick_project"
bl_label = "Close Brick Project"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.close_brick_project(tool.Brick)
class ConvertBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.convert_brick_project"
bl_label = "Convert Brick Project"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.convert_brick_project(tool.Ifc, tool.Brick)
class AssignBrickReference(bpy.types.Operator, Operator):
bl_idname = "bim.assign_brick_reference"
bl_label = "Assign Brick Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
core.assign_brick_reference(
tool.Ifc,
tool.Brick,
element=tool.Ifc.get_entity(context.active_object),
library=tool.Ifc.get().by_id(int(props.libraries)),
brick_uri=props.bricks[props.active_brick_index].uri,
)
class AddBrick(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick"
bl_label = "Add Brick"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
library = None
if props.libraries:
library = tool.Ifc.get().by_id(int(props.libraries))
core.add_brick(
tool.Ifc,
tool.Brick,
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
namespace=props.namespace,
brick_class=props.brick_equipment_class,
library=library,
)
class AddBrickFeed(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_feed"
bl_label = "Add Brick Feed"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
source = tool.Ifc.get_entity([o for o in context.selected_objects if o != context.active_object][0])
destination = tool.Ifc.get_entity(context.active_object)
core.add_brick_feed(
tool.Ifc,
tool.Brick,
source=source,
destination=destination,
)
class ConvertIfcToBrick(bpy.types.Operator, Operator):
bl_idname = "bim.convert_ifc_to_brick"
bl_label = "Convert IFC To Brick"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
library = None
if props.libraries:
library = tool.Ifc.get().by_id(int(props.libraries))
core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library)
class NewBrickFile(bpy.types.Operator, Operator):
bl_idname = "bim.new_brick_file"
bl_label = "New Brick File"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.new_brick_file(tool.Brick)
class RefreshBrickViewer(bpy.types.Operator, Operator):
bl_idname = "bim.refresh_brick_viewer"
bl_label = "Refresh Brick Viewer"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick)
@@ -0,0 +1,71 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
def update_active_brick_index(self, context):
BrickschemaData.is_loaded = False
def get_libraries(self, context):
if not BrickschemaReferencesData.is_loaded:
BrickschemaReferencesData.load()
return BrickschemaReferencesData.data["libraries"]
def get_namespaces(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
return BrickschemaData.data["namespaces"]
def get_brick_equipment_classes(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
return BrickschemaData.data["brick_equipment_classes"]
class Brick(PropertyGroup):
name: StringProperty(name="Name")
label: StringProperty(name="Label")
uri: StringProperty(name="URI")
total_items: IntProperty(name="Total Items")
class BIMBrickProperties(PropertyGroup):
active_brick_class: StringProperty(name="Active Brick Class")
brick_breadcrumbs: CollectionProperty(name="Brick Breadcrumbs", type=StrProperty)
bricks: CollectionProperty(name="Bricks", type=Brick)
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
libraries: EnumProperty(name="Libraries", items=get_libraries)
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
@@ -0,0 +1,128 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
class BIM_PT_brickschema(Panel):
bl_label = "Brickschema Project"
bl_idname = "BIM_PT_brickschema"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
self.props = context.scene.BIMBrickProperties
if not BrickschemaData.data["is_loaded"]:
row = self.layout.row(align=True)
row.operator("bim.new_brick_file", text="Create Project")
row.operator("bim.load_brick_project", text="Load Project")
return
row = self.layout.row(align=True)
if len(self.props.brick_breadcrumbs):
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
row.label(text=self.props.active_brick_class)
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
row.operator("bim.close_brick_project", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(self.props, "namespace", text="")
row.prop(self.props, "brick_equipment_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
for attribute in BrickschemaData.data["attributes"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
if attribute["is_uri"]:
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
op.item = attribute["value_uri"]
if attribute["is_globalid"]:
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
op.global_id = attribute["value"]
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "IFC Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not BrickschemaReferencesData.is_loaded:
BrickschemaReferencesData.load()
self.props = context.scene.BIMBrickProperties
if not BrickschemaReferencesData.data["is_loaded"]:
row = self.layout.row()
row.label(text="No Brickschema Project Loaded")
return
if not BrickschemaReferencesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
return
row = self.layout.row(align=True)
row.prop(self.props, "libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
row = self.layout.row(align=True)
row.operator("bim.assign_brick_reference", icon="ADD")
row.operator("bim.convert_ifc_to_brick", icon="IMPORT")
if not BrickschemaReferencesData.data["references"]:
row = self.layout.row()
row.label(text="No References")
for reference in BrickschemaReferencesData.data["references"]:
row = self.layout.row(align=True)
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
class BIM_UL_bricks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
row.label(text=item.label if item.label else item.name)
if item.total_items:
row.label(text=str(item.total_items))
@@ -34,9 +34,6 @@ classes = (
operator.SmartClashGroup,
operator.SelectSmartGroup,
operator.LoadSmartGroupsForActiveClashSet,
operator.SetBlenderClashSetA,
operator.SetBlenderClashSetB,
operator.ExecuteBlenderClash,
prop.ClashSource,
prop.ClashSet,
prop.SmartClashGroup,
@@ -411,82 +411,3 @@ class SelectSmartGroup(bpy.types.Operator):
obj.select_set(True)
return {"FINISHED"}
class BlenderClasher:
def process_clash_set(self, context):
import collision
a_cm = collision.CollisionManager()
b_cm = collision.CollisionManager()
self.add_to_cm(a_cm, context.scene.BIMClashProperties.blender_clash_set_a, context)
self.add_to_cm(b_cm, context.scene.BIMClashProperties.blender_clash_set_b, context)
results = a_cm.in_collision_other(b_cm, return_data=True)
if not results[0]:
print("No clashes")
return
for contact in results[1]:
if contact.raw.penetration_depth < 0.01:
continue
print("-----")
print(contact.names)
print(contact.raw.normal)
print(contact.raw.pos)
def add_to_cm(self, cm, object_names, context):
import ifcclash
for object_name in object_names:
name = object_name.name
obj = bpy.data.objects[name]
triangulated_mesh = self.triangulate_mesh(obj, context)
mesh = ifcclash.Mesh()
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
cm.add_object(name, mesh)
def triangulate_mesh(self, obj, context):
mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm
return mesh
class SetBlenderClashSetA(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_a"
bl_label = "Set Blender Clash Set A"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMClashProperties.blender_clash_set_a.clear()
for obj in context.selected_objects:
new = context.scene.BIMClashProperties.blender_clash_set_a.add()
new.name = obj.name
return {"FINISHED"}
class SetBlenderClashSetB(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_b"
bl_label = "Set Blender Clash Set B"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMClashProperties.blender_clash_set_b.clear()
for obj in context.selected_objects:
new = context.scene.BIMClashProperties.blender_clash_set_b.add()
new.name = obj.name
return {"FINISHED"}
class ExecuteBlenderClash(bpy.types.Operator):
bl_idname = "bim.execute_blender_clash"
bl_label = "Execute Blender Clash"
def execute(self, context):
blender_clasher = BlenderClasher()
blender_clasher.process_clash_set(context)
return {"FINISHED"}
@@ -27,6 +27,7 @@ class BIM_PT_ifcclash(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
layout = self.layout
@@ -34,17 +35,6 @@ class BIM_PT_ifcclash(Panel):
scene = context.scene
props = scene.BIMClashProperties
layout.label(text="Blender Clash:")
row = layout.row(align=True)
row.operator("bim.set_blender_clash_set_a")
row.operator("bim.set_blender_clash_set_b")
row = layout.row(align=True)
row.operator("bim.execute_blender_clash")
layout.label(text="IFC Clash:")
row = layout.row(align=True)
row.operator("bim.add_clash_set")
row.operator("bim.import_clash_sets", text="", icon="IMPORT")
@@ -29,6 +29,7 @@ class BIM_PT_classifications(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
@@ -86,6 +87,7 @@ class BIM_PT_classification_references(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -27,6 +27,7 @@ class BIM_PT_cobie(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
def draw(self, context):
layout = self.layout
@@ -29,6 +29,7 @@ class BIM_PT_constraints(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_misc"
@classmethod
def poll(cls, context):
@@ -73,6 +74,7 @@ class BIM_PT_object_constraints(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_misc_object"
@classmethod
def poll(cls, context):
@@ -29,6 +29,7 @@ class BIM_PT_context(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
@@ -31,6 +31,7 @@ class BIM_PT_cost_schedules(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
@classmethod
def poll(cls, context):
@@ -170,6 +170,8 @@ class ImportIfcCsv(bpy.types.Operator):
else:
ifc_csv.delimiter = props.csv_delimiter
ifc_csv.Import(ifc_file)
if not props.should_load_from_memory:
ifc_file.write(props.csv_ifc_file)
purge_module_data()
return {"FINISHED"}
@@ -32,7 +32,7 @@ from bpy.props import (
class CsvProperties(PropertyGroup):
csv_ifc_file: StringProperty(default="", name="CSV IFC File")
csv_ifc_file: StringProperty(default="", name="IFC File")
ifc_selector: StringProperty(default="", name="IFC Selector")
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
csv_delimiter: EnumProperty(
@@ -27,6 +27,7 @@ class BIM_PT_ifccsv(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
def draw(self, context):
layout = self.layout
@@ -20,17 +20,21 @@ import bpy
from . import ui, prop, operator
classes = (
operator.PrintIfcFile,
operator.PurgeIfcLinks,
operator.PrintObjectPlacement,
operator.ValidateIfcFile,
operator.ProfileImportIFC,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
operator.SelectHighPolygonMeshes,
operator.InspectFromStepId,
operator.InspectFromObject,
operator.InspectFromStepId,
operator.ParseExpress,
operator.PrintIfcFile,
operator.PrintObjectPlacement,
operator.ProfileImportIFC,
operator.PurgeHdf5Cache,
operator.PurgeIfcLinks,
operator.RewindInspector,
operator.SelectExpressFile,
operator.SelectHighPolygonMeshes,
operator.SelectHighestPolygonMeshes,
operator.ValidateIfcFile,
prop.BIMDebugProperties,
ui.BIM_PT_debug,
)
@@ -16,20 +16,25 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import time
import logging
import ifcopenshell
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.core.debug as core
import blenderbim.bim.handler
import blenderbim.bim.import_ifc as import_ifc
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
class PrintIfcFile(bpy.types.Operator):
bl_idname = "bim.print_ifc_file"
bl_label = "Print IFC File"
bl_description = "Prints the file contents in the system console.\nAccess it with Window > Toggle System Console"
@classmethod
def poll(cls, context):
@@ -43,6 +48,7 @@ class PrintIfcFile(bpy.types.Operator):
class PurgeIfcLinks(bpy.types.Operator):
bl_idname = "bim.purge_ifc_links"
bl_label = "Purge IFC Links"
bl_description = "Purge all definitions and references from the file.\nWarning : Cannot be undone."
def execute(self, context):
for obj in bpy.data.objects:
@@ -96,7 +102,8 @@ class ProfileImportIFC(bpy.types.Operator):
class CreateAllShapes(bpy.types.Operator):
bl_idname = "bim.create_all_shapes"
bl_label = "Create All Shapes"
bl_label = "Test All Shapes"
bl_description = "Look for errors in all the shapes contained in the file"
@classmethod
def poll(cls, context):
@@ -126,7 +133,7 @@ class CreateAllShapes(bpy.types.Operator):
except:
failures.append(element)
print("***** FAILURE *****")
print("Failures:")
print(f"Failures: {len(failures)}")
for failure in failures:
print(failure)
return {"FINISHED"}
@@ -135,6 +142,7 @@ class CreateAllShapes(bpy.types.Operator):
class CreateShapeFromStepId(bpy.types.Operator):
bl_idname = "bim.create_shape_from_step_id"
bl_label = "Create Shape From STEP ID"
bl_description = "Recreate a mesh object from a STEP ID"
bl_options = {"REGISTER", "UNDO"}
should_include_curves: bpy.props.BoolProperty()
@@ -154,9 +162,12 @@ class CreateShapeFromStepId(bpy.types.Operator):
if self.should_include_curves:
settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, element)
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
if shape:
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
else:
mesh = None
obj = bpy.data.objects.new("Debug", mesh)
context.scene.collection.objects.link(obj)
return {"FINISHED"}
@@ -165,20 +176,37 @@ class CreateShapeFromStepId(bpy.types.Operator):
class SelectHighPolygonMeshes(bpy.types.Operator):
bl_idname = "bim.select_high_polygon_meshes"
bl_label = "Select High Polygon Meshes"
bl_description = "Select objects containing more polygons than the specified number"
bl_options = {"REGISTER", "UNDO"}
threshold: bpy.props.IntProperty()
def execute(self, context):
[
o.select_set(True)
for o in context.view_layer.objects
if o.type == "MESH" and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons
]
for obj in context.view_layer.objects:
if obj.type == "MESH" and len(obj.data.polygons) > self.threshold:
obj.select_set(True)
return {"FINISHED"}
class SelectHighestPolygonMeshes(bpy.types.Operator):
bl_idname = "bim.select_highest_polygon_meshes"
bl_label = "Select Highest Polygon Meshes"
bl_description = "Select objects with a number of polygons superior to the specified percentile"
bl_options = {"REGISTER", "UNDO"}
percentile: bpy.props.IntProperty()
def execute(self, context):
objects = [obj for obj in context.view_layer.objects if obj.type == "MESH"]
if objects:
percentile = len(max(objects, key=lambda o: len(o.data.polygons)).data.polygons) * self.percentile / 100
print(f"Selected all Meshes with more than {int(percentile)} polygons")
[obj.select_set(True) for obj in objects if len(obj.data.polygons) > percentile]
return {"FINISHED"}
class RewindInspector(bpy.types.Operator):
bl_idname = "bim.rewind_inspector"
bl_label = "Rewind Inspector"
bl_description = "Rewind the Inspector to the previously inspected element"
def execute(self, context):
props = context.scene.BIMDebugProperties
@@ -195,6 +223,7 @@ class RewindInspector(bpy.types.Operator):
class InspectFromStepId(bpy.types.Operator):
bl_idname = "bim.inspect_from_step_id"
bl_label = "Inspect From STEP ID"
bl_description = "Inspect the attributes and references by looking up the specified STEP ID"
step_id: bpy.props.IntProperty()
@classmethod
@@ -245,12 +274,21 @@ class InspectFromStepId(bpy.types.Operator):
class InspectFromObject(bpy.types.Operator):
bl_idname = "bim.inspect_from_object"
bl_label = "Inspect From Object"
bl_description = "Inspect the Active Object's attributes and references"
@classmethod
def poll(cls, context):
if not context.active_object:
if bpy.app.version >= (3, 0, 0):
cls.poll_message_set("No Active Object")
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
if bpy.app.version >= (3, 0, 0):
cls.poll_message_set("Active Object doesn't have an IFC definition")
else:
return True
def execute(self, context):
ifc_definition_id = context.active_object.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
return {"FINISHED"}
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
bpy.ops.bim.inspect_from_step_id(step_id=context.active_object.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -262,3 +300,40 @@ class PrintObjectPlacement(bpy.types.Operator):
def execute(self, context):
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
return {"FINISHED"}
class ParseExpress(bpy.types.Operator):
bl_idname = "bim.parse_express"
bl_label = "Parse Express"
def execute(self, context):
core.parse_express(tool.Debug, context.scene.BIMDebugProperties.express_file)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class SelectExpressFile(bpy.types.Operator):
bl_idname = "bim.select_express_file"
bl_label = "Select Express File"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select an IFC EXPRESS definition"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"})
def execute(self, context):
if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]:
context.scene.BIMDebugProperties.express_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class PurgeHdf5Cache(bpy.types.Operator):
bl_idname = "bim.purge_hdf5_cache"
bl_label = "Purge HDF5 Cache"
def execute(self, context):
core.purge_hdf5_cache(tool.Debug)
return {"FINISHED"}
@@ -33,8 +33,10 @@ from bpy.props import (
class BIMDebugProperties(PropertyGroup):
step_id: IntProperty(name="STEP ID")
number_of_polygons: IntProperty(name="Number of Polygons", min=0)
active_step_id: IntProperty(name="STEP ID")
percentile_of_polygons: IntProperty(name="Percentile of Polygons", min=0, max=100, default=90, subtype="PERCENTAGE")
active_step_id: IntProperty(name="STEP ID", default=1, soft_min=1)
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
attributes: CollectionProperty(name="Attributes", type=Attribute)
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
express_file: StringProperty(name="Express File")
@@ -27,6 +27,7 @@ class BIM_PT_debug(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
layout = self.layout
@@ -38,9 +39,17 @@ class BIM_PT_debug(Panel):
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row.prop(props, "express_file", text="")
row.operator("bim.parse_express", icon="IMPORT", text="")
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.operator("bim.print_ifc_file")
row = layout.row()
row.operator("bim.purge_hdf5_cache")
row = layout.row()
row.operator("bim.purge_ifc_links")
@@ -56,8 +65,13 @@ class BIM_PT_debug(Panel):
row.prop(props, "step_id", text="")
row = layout.split(factor=0.7, align=True)
row.operator("bim.select_high_polygon_meshes")
row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons
row.prop(props, "number_of_polygons", text="")
row = layout.split(factor=0.7, align=True)
row.operator(
"bim.select_highest_polygon_meshes"
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
row.prop(props, "percentile_of_polygons", text="")
layout.label(text="Inspector:")
@@ -27,6 +27,7 @@ class BIM_PT_diff(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
layout = self.layout
@@ -29,6 +29,7 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
@classmethod
def poll(cls, context):
@@ -77,6 +78,7 @@ class BIM_PT_object_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_misc_object"
@classmethod
def poll(cls, context):
@@ -20,40 +20,43 @@ import bpy
from . import ui, prop, operator, handler, gizmos
classes = (
operator.AddDrawing,
operator.CreateDrawing,
operator.AddAnnotation,
operator.AddSheet,
operator.OpenSheet,
operator.AddDrawingToSheet,
operator.CreateSheets,
operator.OpenView,
operator.OpenViewCamera,
operator.ActivateView,
operator.SelectDocIfcFile,
operator.GenerateReferences,
operator.ResizeText,
operator.AddVariable,
operator.RemoveVariable,
operator.PropagateTextData,
operator.RemoveDrawing,
operator.AddDrawingStyle,
operator.RemoveDrawingStyle,
operator.SaveDrawingStyle,
operator.ActivateDrawingStyle,
operator.EditVectorStyle,
operator.RemoveSheet,
operator.AddSchedule,
operator.RemoveSchedule,
operator.SelectScheduleFile,
operator.BuildSchedule,
operator.AddScheduleToSheet,
operator.ActivateView,
operator.AddAnnotation,
operator.AddDrawing,
operator.AddDrawingStyle,
operator.AddDrawingStyleAttribute,
operator.RemoveDrawingStyleAttribute,
operator.RefreshDrawingList,
operator.AddDrawingToSheet,
operator.AddSchedule,
operator.AddScheduleToSheet,
operator.AddSectionsAnnotations,
operator.AddSheet,
operator.BuildSchedule,
operator.CleanWireframes,
operator.CopyGrid,
operator.AddSectionsAnnotations,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableEditingText,
operator.DisableEditingTextProduct,
operator.EditText,
operator.EditTextProduct,
operator.EditVectorStyle,
operator.EnableEditingText,
operator.EnableEditingTextProduct,
operator.GenerateReferences,
operator.OpenSheet,
operator.OpenView,
operator.OpenViewCamera,
operator.RefreshDrawingList,
operator.RemoveDrawing,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule,
operator.RemoveSheet,
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SelectDocIfcFile,
operator.SelectScheduleFile,
prop.Variable,
prop.Drawing,
prop.Schedule,
@@ -81,6 +84,7 @@ classes = (
def register():
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
@@ -18,6 +18,7 @@
import bpy
import os
import blenderbim.tool as tool
from mathutils import Vector
@@ -150,18 +151,30 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(name, data_type, context):
def get_annotation_obj(object_type, data_type, context):
collection = context.scene.camera.users_collection[0]
for obj in collection.objects:
if name in obj.name:
return obj
if data_type == "mesh":
data = bpy.data.meshes.new(name)
elif data_type == "curve":
data = bpy.data.curves.new(name, type="CURVE")
if object_type == "TEXT":
obj = bpy.data.objects.new(object_type, None)
collection.objects.link(obj)
return obj
elif object_type == "TEXT_LEADER":
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
obj = bpy.data.objects.new(name, data)
obj = bpy.data.objects.new(object_type, data)
collection.objects.link(obj)
return obj
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if element and element.ObjectType == object_type:
return obj
if data_type == "mesh":
data = bpy.data.meshes.new(object_type)
elif data_type == "curve":
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
obj = bpy.data.objects.new(object_type, data)
collection.objects.link(obj)
return obj
@@ -0,0 +1,56 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.util.representation
import blenderbim.tool as tool
def refresh():
TextData.is_loaded = False
class TextData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"attributes": cls.attributes(), "relating_product": cls.relating_product()}
cls.is_loaded = True
@classmethod
def attributes(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
return []
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
return [
{"name": "Literal", "value": text_literal.Literal},
{"name": "BoxAlignment", "value": text_literal.BoxAlignment},
]
@classmethod
def relating_product(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
return
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct"):
return rel.RelatingProduct.Name or "Unnamed"
@@ -16,32 +16,32 @@
# 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/>.
"""Viewport decorations"""
import os
import gpu
import bpy
import blf
import bgl
import math
import bmesh
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
from functools import reduce
from itertools import chain
from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
import bpy
import bmesh
import blf
from bpy_extras.view3d_utils import location_3d_to_region_2d
import gpu
import bgl
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
from gpu_extras.batch import batch_for_shader
import blenderbim.bim.module.drawing.helper as helper
class BaseDecorator:
# base name of objects to decorate
basename = "IfcAnnotation/Something"
objecttype = "NOTDEFINED"
DEF_GLSL = """
#define PI 3.141592653589793
#define MAX_POINTS 64
#define CIRCLE_SEGS 24
#define MAX_POINTS 32
#define CIRCLE_SEGS 12
"""
LIB_GLSL = """
@@ -90,6 +90,7 @@ class BaseDecorator:
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=2) out;
@@ -134,13 +135,48 @@ class BaseDecorator:
defines=self.DEF_GLSL,
)
# Horrific prototype code to ensure bgl draws at drawing scales
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
def is_landscape(render):
return render.resolution_x > render.resolution_y
def get_scale(camera):
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
return float(numerator) / float(denominator)
camera = bpy.context.scene.camera
render = bpy.context.scene.render
if is_landscape(render):
camera_width_model = camera.data.ortho_scale
else:
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
self.camera_width_mm = get_scale(camera) * camera_width_model
self.font_id = blf.load(
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
def camera_zoom_to_factor(self, zoom):
return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2)
def get_objects(self, collection):
"""find relevant objects
using class.basename
using class.objecttype
returns: iterable of blender objects
"""
return filter(lambda o: self.basename in o.name, collection.all_objects)
results = []
for obj in collection.all_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcAnnotation") and element.ObjectType == self.objecttype:
results.append(obj)
return results
def get_path_geom(self, obj, topo=True):
"""Parses path geometry into line segments
@@ -208,7 +244,7 @@ class BaseDecorator:
return vertices, indices
def decorate(self, context, object):
"""perform actuall drawing stuff"""
"""perform actual drawing stuff"""
raise NotImplementedError()
def draw_lines(self, context, obj, vertices, indices, topology=None):
@@ -241,6 +277,15 @@ class BaseDecorator:
self.shader.uniform_float("winsize", (region.width, region.height))
self.shader.uniform_float("color", color)
# Horrific prototype code
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
mm_to_px = camera_width_px / self.camera_width_mm
# 0.00025 is a magic constant number I visually discovered to get the right number.
# It probably should be dynamically calculated using system.dpi or something.
viewport_drawing_scale = 0.00025 * mm_to_px
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
batch.draw(self.shader)
def draw_label(self, context, text, pos, dir, gap=4, center=True, vcenter=False):
@@ -251,8 +296,9 @@ class BaseDecorator:
aligned and centered at segment middle
"""
font_id = 0
font_size = 16
# 0 is the default font, but we're fancier than that
font_id = self.font_id
dpi = context.preferences.system.dpi
color = context.scene.DocProperties.decorations_colour
@@ -261,6 +307,16 @@ class BaseDecorator:
cos = math.cos(ang)
sin = math.sin(ang)
# Horrific prototype code
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
mm_to_px = camera_width_px / self.camera_width_mm
# 0.004118616 is a magic constant number I visually discovered to get the right number.
# In particular it works only for the OpenGOST font and produces a 2.5mm font size.
# It probably should be dynamically calculated using system.dpi or something.
# font_size = 16 <-- this is a good default
font_size = int(0.004118616 * mm_to_px)
blf.size(font_id, font_size, dpi)
w, h = 0, 0
@@ -284,6 +340,9 @@ class BaseDecorator:
blf.rotation(font_id, ang)
blf.color(font_id, *color)
# blf.enable(font_id, blf.SHADOW)
# blf.shadow(font_id, 5, 0, 0, 0, 1)
# blf.shadow_offset(font_id, 1, -1)
blf.draw(font_id, text)
blf.disable(font_id, blf.ROTATION)
@@ -302,7 +361,7 @@ class BaseDecorator:
else:
return
return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision, split_unit=True)
return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision=precision, split_unit=True)
class DimensionDecorator(BaseDecorator):
@@ -311,7 +370,7 @@ class DimensionDecorator(BaseDecorator):
- puts metric text next to each segment
"""
basename = "IfcAnnotation/Dimension"
objecttype = "DIMENSION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -323,6 +382,7 @@ class DimensionDecorator(BaseDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -339,7 +399,7 @@ class DimensionDecorator(BaseDecorator):
vec4 p;
vec4 head[3];
arrow_head(dir, ARROW_SIZE, ARROW_ANGLE, head);
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
// start edge arrow
gl_Position = p0;
@@ -406,7 +466,7 @@ class EqualityDecorator(DimensionDecorator):
- puts 'EQ' label
"""
basename = "IfcAnnotation/Equal"
objecttype = "EQUAL_DIMENSION"
def draw_labels(self, context, obj, vertices, indices):
region = context.region
@@ -423,12 +483,11 @@ class EqualityDecorator(DimensionDecorator):
class LeaderDecorator(BaseDecorator):
"""Decorating stairs
"""Decorating text with arrows
- head point with arrow
- middle points w/out decorations
"""
basename = "IfcAnnotation/Leader"
objecttype = "TEXT_LEADER"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -440,6 +499,7 @@ class LeaderDecorator(BaseDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -461,7 +521,7 @@ class LeaderDecorator(BaseDecorator):
// end edge arrow for last segment
if (t1 == 2u) {
vec4 head[3];
arrow_head(dir, ARROW_SIZE, ARROW_ANGLE, head);
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
gl_Position = p1;
EmitVertex();
@@ -475,7 +535,7 @@ class LeaderDecorator(BaseDecorator):
EmitVertex();
EndPrimitive();
gap1 = dir * ARROW_SIZE;
gap1 = dir * viewportDrawingScale * ARROW_SIZE;
}
// stem, adjusted for and arrow
@@ -488,9 +548,24 @@ class LeaderDecorator(BaseDecorator):
}
"""
def get_spline_end(self, obj):
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if not spline_points:
return Vector((0, 0, 0))
return obj.matrix_world @ spline_points[0].co
def decorate(self, context, obj):
verts, idxs, topo = self.get_path_geom(obj)
self.draw_lines(context, obj, verts, idxs, topo)
self.draw_labels(context, obj)
def draw_labels(self, context, obj):
region = context.region
region3d = context.region_data
dir = Vector((1, 0))
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
class StairDecorator(BaseDecorator):
@@ -500,12 +575,12 @@ class StairDecorator(BaseDecorator):
- middle points w/out decorations
"""
basename = "IfcAnnotation/Stair"
objecttype = "STAIR_ARROW"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define CIRCLE_SIZE 6.0
#define ARROW_ANGLE PI / 3.0
#define ARROW_SIZE 24.0
"""
@@ -513,6 +588,7 @@ class StairDecorator(BaseDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -534,7 +610,7 @@ class StairDecorator(BaseDecorator):
// start edge circle for first segment
if (t0 == 1u) {
vec4 head[CIRCLE_SEGS];
circle_head(CIRCLE_SIZE, head);
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + head[i];
@@ -546,13 +622,13 @@ class StairDecorator(BaseDecorator):
EmitVertex();
EndPrimitive();
gap0 = dir * CIRCLE_SIZE;
gap0 = dir * viewportDrawingScale * CIRCLE_SIZE;
}
// end edge arrow for last segment
if (t1 == 2u) {
vec4 head[3];
arrow_head(dir, ARROW_SIZE, ARROW_ANGLE, head);
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
p = p1w - head[1];
gl_Position = WIN2CLIP(p);
@@ -583,7 +659,7 @@ class StairDecorator(BaseDecorator):
class HiddenDecorator(BaseDecorator):
basename = "IfcAnnotation/Hidden"
objecttype = "HIDDEN_LINE"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -628,13 +704,14 @@ class HiddenDecorator(BaseDecorator):
"""
FRAG_GLSL = """
uniform float viewportDrawingScale;
uniform vec4 color;
in vec2 gl_FragCoord;
in float dist;
out vec4 fragColor;
void main() {
uint bit = uint(fract(dist / DASH_SIZE) * 32);
uint bit = uint(fract(dist / (viewportDrawingScale * DASH_SIZE)) * 32);
if ((DASH_PATTERN & (1U<<bit)) == 0U) discard;
fragColor = color;
}
@@ -649,7 +726,7 @@ class HiddenDecorator(BaseDecorator):
class MiscDecorator(HiddenDecorator):
basename = "IfcAnnotation/Misc"
objecttype = "MISC"
FRAG_GLSL = BaseDecorator.FRAG_GLSL
@@ -677,7 +754,7 @@ class LevelDecorator(BaseDecorator):
class PlanLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Plan Level"
objecttype = "PLAN_LEVEL"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -689,6 +766,7 @@ class PlanLevelDecorator(LevelDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -710,7 +788,7 @@ class PlanLevelDecorator(LevelDecorator):
// end edge cross+circle for last segment
if (t1 == 2u) {
vec4 head_o[CIRCLE_SEGS];
circle_head(CIRCLE_SIZE, head_o);
circle_head(viewportDrawingScale * CIRCLE_SIZE, head_o);
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w + head_o[i];
@@ -723,7 +801,7 @@ class PlanLevelDecorator(LevelDecorator):
EndPrimitive();
vec4 head_x[3];
cross_head(dir, CROSS_SIZE, head_x);
cross_head(dir, viewportDrawingScale * CROSS_SIZE, head_x);
p = p1w + head_x[1];
gl_Position = WIN2CLIP(p);
@@ -762,7 +840,7 @@ class PlanLevelDecorator(LevelDecorator):
class SectionLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Section Level"
objecttype = "SECTION_LEVEL"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -774,6 +852,7 @@ class SectionLevelDecorator(LevelDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -781,8 +860,8 @@ class SectionLevelDecorator(LevelDecorator):
out float dist; // distance from starging point along segment
void callout_head(in vec4 dir, out vec4 head[4]) {
vec2 gap = dir.xy * CALLOUT_GAP;
vec2 tail = dir.xy * -CALLOUT_SIZE;
vec2 gap = dir.xy * viewportDrawingScale * CALLOUT_GAP;
vec2 tail = dir.xy * viewportDrawingScale * -CALLOUT_SIZE;
vec2 side = cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy * CALLOUT_GAP;
head[0] = vec4(tail + side, 0, 0);
@@ -850,7 +929,7 @@ class BreakDecorator(BaseDecorator):
Uses first two vertices in verts list.
"""
basename = "IfcAnnotation/Break"
objecttype = "BREAKLINE"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -862,6 +941,7 @@ class BreakDecorator(BaseDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -878,8 +958,8 @@ class BreakDecorator(BaseDecorator):
vec4 p;
vec4 quart = dir * BREAK_LENGTH * .25;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0) * BREAK_WIDTH;
vec4 quart = dir * viewportDrawingScale * BREAK_LENGTH * .25;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0) * viewportDrawingScale * BREAK_WIDTH;
// TODO: check if there's enough length for zigzag
@@ -928,7 +1008,7 @@ class BreakDecorator(BaseDecorator):
class GridDecorator(BaseDecorator):
basename = "IfcGridAxis/"
objecttype = "GRID"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -941,6 +1021,7 @@ class GridDecorator(BaseDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -955,12 +1036,12 @@ class GridDecorator(BaseDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * CIRCLE_SIZE;
vec4 gap = dir * CIRCLE_SIZE * viewportDrawingScale;
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(CIRCLE_SIZE, head);
circle_head(CIRCLE_SIZE * viewportDrawingScale, head);
dist = 0;
@@ -1045,7 +1126,7 @@ class GridDecorator(BaseDecorator):
class SectionViewDecorator(LevelDecorator):
basename = "IfcAnnotation/Section"
objecttype = "SECTION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
@@ -1058,6 +1139,7 @@ class SectionViewDecorator(LevelDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -1078,12 +1160,12 @@ class SectionViewDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * TRIANGLE_L * .5;
vec4 gap = dir * viewportDrawingScale * TRIANGLE_L * .5;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(CIRCLE_SIZE, head);
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head3[3];
@@ -1099,7 +1181,7 @@ class SectionViewDecorator(LevelDecorator):
EndPrimitive();
// start edge triangle
triangle_head(dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
triangle_head(dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p0w + head3[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
@@ -1123,7 +1205,7 @@ class SectionViewDecorator(LevelDecorator):
EndPrimitive();
// end edge triangle
triangle_head(-dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
triangle_head(-dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p1w + head3[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
@@ -1149,6 +1231,90 @@ class SectionViewDecorator(LevelDecorator):
self.draw_lines(context, obj, verts, [(0, 1)])
class TextDecorator(BaseDecorator):
"""Decorator for text objects
- draws the text next to the origin
"""
objecttype = "TEXT"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define ARROW_ANGLE PI / 12.0
#define ARROW_SIZE 16.0
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 p;
vec4 head[3];
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
// start edge arrow
gl_Position = p0;
EmitVertex();
p = p0w + head[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
gl_Position = p0;
EmitVertex();
EndPrimitive();
// end edge arrow
gl_Position = p1;
EmitVertex();
p = p1w - head[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - head[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
// stem, with gaps for arrows
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
self.draw_labels(context, obj)
def draw_labels(self, context, obj):
region = context.region
region3d = context.region_data
dir = Vector((1, 0))
pos = location_3d_to_region_2d(region, region3d, obj.matrix_world.translation)
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
class DecorationsHandler:
decorators_classes = [
DimensionDecorator,
@@ -1162,6 +1328,7 @@ class DecorationsHandler:
StairDecorator,
BreakDecorator,
SectionViewDecorator,
TextDecorator,
]
installed = None
@@ -263,7 +263,7 @@ def get_active_drawing(scene):
return None, None
try:
drawing = props.active_drawing
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
return drawing.camera.users_collection[0], drawing.camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
@@ -31,13 +31,17 @@ import ifcopenshell.geom
import ifcopenshell.util.selector
import ifcopenshell.util.representation
import blenderbim.bim.schema
import blenderbim.tool as tool
import blenderbim.core.drawing as core
import blenderbim.bim.module.drawing.svgwriter as svgwriter
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
import blenderbim.bim.export_ifc
from lxml import etree
from mathutils import Vector, Matrix, Euler, geometry
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data as GroupData
from ifcopenshell.api.pset.data import Data as PsetData
@@ -54,6 +58,13 @@ def open_with_user_command(user_command, path):
webbrowser.open("file://" + path)
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class AddDrawing(bpy.types.Operator):
bl_idname = "bim.add_drawing"
bl_label = "Add Drawing"
@@ -76,7 +87,7 @@ class AddDrawing(bpy.types.Operator):
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
views_collection.children.link(view_collection)
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
camera.data.type = "ORTHO"
camera.data.ortho_scale = 50 # The default of 6m is too small
camera.data.clip_end = 10 # A slightly more reasonable default
@@ -140,12 +151,16 @@ class CreateDrawing(bpy.types.Operator):
def execute(self, context):
self.camera = context.scene.camera
self.camera_element = tool.Ifc.get_entity(self.camera)
self.file = IfcStore.get_file()
self.time = None
start = time.time()
self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
self.get_scale()
if self.camera.data.BIMCameraProperties.update_representation(self.camera):
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
underlay_svg = self.generate_underlay(context)
self.profile_code("Generate underlay")
linework_svg = self.generate_linework(context)
@@ -190,7 +205,7 @@ class CreateDrawing(bpy.types.Operator):
elif "<defs>" in line:
should_skip = True
continue
elif "</style>" in line:
elif "</defs>" in line:
should_skip = False
continue
elif should_skip:
@@ -248,21 +263,15 @@ class CreateDrawing(bpy.types.Operator):
bpy.data.objects[name].hide_set(value)
svg_writer = svgwriter.SvgWriter()
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
if self.camera.data.BIMCameraProperties.is_nts:
svg_writer.human_scale = "NTS"
else:
svg_writer.human_scale = human_scale
render = context.scene.render
if self.is_landscape():
if self.is_landscape(render):
width = self.camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
height = self.camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
svg_writer.human_scale = self.human_scale
svg_writer.scale = self.scale
svg_writer.output = svg_path
svg_writer.data_dir = context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
@@ -280,62 +289,162 @@ class CreateDrawing(bpy.types.Operator):
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
# All very hackish whilst prototyping
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_guids = exporter.sync_deletions()
invalidated_elements = exporter.sync_all_objects()
invalidated_elements += exporter.sync_edited_objects()
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
# If we have already calculated it in the SVG in the past, don't recalculate
edited_guids = set()
for obj in IfcStore.edited_objs:
element = tool.Ifc.get_entity(obj)
edited_guids.add(element.GlobalId) if hasattr(element, "GlobalId") else None
cached_linework = set()
if os.path.isfile(svg_path):
tree = etree.parse(svg_path)
root = tree.getroot()
cached_linework = {
el.get("{http://www.ifcopenshell.org/ns}guid")
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]")
}
cached_linework -= edited_guids
# This is a work in progress. See #1153 and #1564.
# Switch from old to new if you are testing v0.7.0
self.generate_linework_old(context, svg_path)
# self.generate_linework_new(svg_path)
import hashlib
import ifcopenshell.draw
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
for ifc_path, ifc in files.items():
# Don't use draw.main() just whilst we're prototyping and experimenting
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
# Get all representation contexts to see what we're dealing with
# A drawing prioritises a target view context first, followed by a body context as a fallback
body_contexts = []
target_view_contexts = []
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
if rep_context.TargetView == context.scene.camera.data.BIMCameraProperties.target_view:
target_view_contexts.append(rep_context.id())
continue
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
body_contexts.append(rep_context.id())
continue
if rep_context.is_a() == "IfcGeometricRepresentationContext" and rep_context.ContextType == "Model":
body_contexts.append(rep_context.id())
continue
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
self.setup_serialiser(ifc)
cache_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
if target_view_contexts and elements:
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
geom_settings.set_context_ids(target_view_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
processed = set()
for elem in self.yield_from_iterator(it):
processed.add(ifc.by_id(elem.id))
self.serialiser.write(elem)
elements -= processed
if body_contexts and elements:
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True,
)
geom_settings.set_context_ids(body_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True,
)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
self.serialiser.finalize()
results = self.svg_buffer.get_value()
root = etree.fromstring(results)
self.enrich_linework_with_metadata(root)
self.move_projection_to_bottom(root)
with open(svg_path, "wb") as svg:
svg.write(etree.tostring(root))
return svg_path
def generate_linework_new(self, svg_path):
settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True,
DISABLE_TRIANGULATION=True,
INCLUDE_CURVES=True,
EXCLUDE_SOLIDS_AND_SURFACES=False,
)
buffer = ifcopenshell.geom.serializers.buffer()
serialiser = ifcopenshell.geom.serializers.svg(buffer, settings)
serialiser.setFile(self.file)
serialiser.setElevationRef("DRAWING")
serialiser.setUseNamespace(True)
serialiser.setAlwaysProject(True)
serialiser.setUseHlrPoly(True)
serialiser.setWithoutStoreys(True)
excluded_elements = []
for ifc_class in ["IfcSpace", "IfcOpeningElement", "IfcDoor", "IfcWindow"]:
excluded_elements += self.file.by_type(ifc_class)
for element in ifcopenshell.geom.iterate(
settings, self.file, multiprocessing.cpu_count(), exclude=excluded_elements
):
serialiser.write(element)
serialiser.finalize()
with open(svg_path, "w") as svg:
svg.write(buffer.get_value())
def setup_serialiser(self, ifc):
self.svg_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser.setFile(ifc)
self.serialiser.setWithoutStoreys(True)
self.serialiser.setPolygonal(True)
self.serialiser.setUseHlrPoly(True)
self.serialiser.setProfileThreshold(64)
self.serialiser.setUseNamespace(True)
self.serialiser.setAlwaysProject(True)
self.serialiser.setAutoElevation(False)
self.serialiser.setAutoSection(False)
self.serialiser.setPrintSpaceNames(False)
self.serialiser.setPrintSpaceAreas(False)
self.serialiser.setDrawDoorArcs(False)
self.serialiser.setNoCSS(True)
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
self.serialiser.setScale(self.scale)
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
# 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()
# tree.enable_face_styles(True)
def generate_linework_old(self, context, svg_path):
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
subprocess.run(
[
ifcconvert_path,
context.scene.BIMProperties.ifc_file,
"-yv",
svg_path,
"--plan",
"--model",
"--elevation-ref=DRAWING",
"--svg-xmlns",
"--svg-project",
"--svg-poly",
"--svg-without-storeys",
"--exclude",
"entities",
"IfcSpace",
"IfcOpeningElement",
"IfcDoor",
"IfcWindow",
]
)
return svg_path
def yield_from_iterator(self, it):
if it.initialize():
while True:
yield it.get()
if not it.next():
break
def enrich_linework_with_metadata(self, root):
ifc = tool.Ifc.get()
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
classes = ["cut"]
try:
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
except:
# See #1861 - freshly created guids cannot be extracted unfortunately
continue
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
if material:
if material.is_a("IfcMaterialLayerSet"):
name = material.LayerSetName or "null"
else:
name = getattr(material, "Name", "null") or "null"
name = self.canonicalise_class_name(name)
classes.append(f"material-{name}")
el.set("class", (el.get("class", "") + " " + " ".join(classes)).strip())
def move_projection_to_bottom(self, root):
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
group = root.find("{http://www.w3.org/2000/svg}g")
# group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
group[:] = reversed(group)
def canonicalise_class_name(self, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
def generate_annotation(self, context):
if not self.props.has_annotation:
@@ -347,18 +456,6 @@ class CreateDrawing(bpy.types.Operator):
camera = self.camera
svg_writer = svgwriter.SvgWriter()
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
if camera.data.BIMCameraProperties.is_nts:
svg_writer.human_scale = "NTS"
else:
svg_writer.human_scale = human_scale
drawing_style = context.scene.DocProperties.drawing_styles[
camera.data.BIMCameraProperties.active_drawing_style_index
]
@@ -371,7 +468,8 @@ class CreateDrawing(bpy.types.Operator):
height = camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
svg_writer.scale = float(numerator) / float(denominator)
svg_writer.human_scale = self.human_scale
svg_writer.scale = self.scale
svg_writer.output = svg_path
svg_writer.data_dir = context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
@@ -381,43 +479,55 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
for obj in camera.users_collection[0].objects:
if "IfcGrid" in obj.name:
if "IfcAnnotation/" not in obj.name:
continue
element = tool.Ifc.get_entity(obj)
if element.ObjectType == "GRID":
svg_writer.annotations.setdefault("grid_objs", []).append(obj)
elif obj.type == "CAMERA":
continue
if "IfcAnnotation/" not in obj.name:
continue
if "Leader" in obj.name:
svg_writer.annotations["leader_obj"] = (obj, obj.data)
elif "Stair" in obj.name:
elif element.ObjectType == "TEXT_LEADER":
svg_writer.annotations.setdefault("leader_objs", []).append(obj)
elif element.ObjectType == "STAIR_ARROW":
svg_writer.annotations["stair_obj"] = obj
elif "Equal" in obj.name:
elif element.ObjectType == "EQUAL_DIMENSION":
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
elif "Dimension" in obj.name:
elif element.ObjectType == "DIMENSION":
svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
elif "Break" in obj.name:
elif element.ObjectType == "BREAKLINE":
svg_writer.annotations["break_obj"] = obj
elif "Hidden" in obj.name:
elif element.ObjectType == "HIDDEN_LINE":
svg_writer.annotations.setdefault("hidden_objs", []).append((obj, obj.data))
elif "Solid" in obj.name:
elif element.ObjectType == "SOLID_LINE":
svg_writer.annotations.setdefault("solid_objs", []).append((obj, obj.data))
elif "Plan Level" in obj.name:
svg_writer.annotations["plan_level_obj"] = obj
elif "Section Level" in obj.name:
elif element.ObjectType == "PLAN_LEVEL":
svg_writer.annotations.setdefault("plan_level_objs", []).append(obj)
elif element.ObjectType == "SECTION_LEVEL":
svg_writer.annotations["section_level_obj"] = obj
elif obj.type == "FONT":
elif element.ObjectType == "TEXT":
svg_writer.annotations.setdefault("text_objs", []).append(obj)
else:
elif element.ObjectType == "MISC":
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
svg_writer.annotations["annotation_objs"] = self.get_annotation(svg_writer)
# TODO: This was the old 2D annotation box checking system, prepare to deprecate
svg_writer.annotations["annotation_objs"] = []
svg_writer.write("annotation")
return svg_writer.output
def get_scale(self):
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
self.human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
self.human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
if self.camera.data.BIMCameraProperties.is_nts:
self.human_scale = "NTS"
numerator, denominator = fraction.split("/")
self.scale = float(numerator) / float(denominator)
def is_landscape(self, render):
return render.resolution_x > render.resolution_y
@@ -430,56 +540,6 @@ class CreateDrawing(bpy.types.Operator):
continue
return space
def get_annotation(self, svg_writer):
results = []
x = svg_writer.camera_width / 2
y = svg_writer.camera_height / 2
z = 0.01
camera_box = helper.BoundingBox(
self.camera,
[
self.camera.matrix_world @ Vector((-x, -y, -z)),
self.camera.matrix_world @ Vector((-x, -y, 0)),
self.camera.matrix_world @ Vector((-x, y, 0)),
self.camera.matrix_world @ Vector((-x, y, -z)),
self.camera.matrix_world @ Vector((x, -y, -z)),
self.camera.matrix_world @ Vector((x, -y, 0)),
self.camera.matrix_world @ Vector((x, y, 0)),
self.camera.matrix_world @ Vector((x, y, -z)),
],
)
# This should probably be also part of IfcConvert in the future, here is a Python prototype
settings_2d = ifcopenshell.geom.settings()
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
if obj == self.camera:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.is_a("IfcTypeProduct"):
continue
representation = ifcopenshell.util.representation.get_representation(
element, "Plan", "Annotation", self.camera.data.BIMCameraProperties.target_view
)
if not representation:
continue
if not camera_box.intersect(helper.BoundingBox(obj)):
continue
shape = ifcopenshell.geom.create_shape(settings_2d, representation)
geometry = shape
e = geometry.edges
v = geometry.verts
results.append(
{
"raw": element,
"classes": self.get_classes(element, "annotation", svg_writer),
"edges": [[e[i], e[i + 1]] for i in range(0, len(e), 2)],
"vertices": [obj.matrix_world @ Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)],
}
)
return results
def get_classes(self, element, position, svg_writer):
classes = [position, element.is_a()]
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
@@ -506,7 +566,7 @@ class AddAnnotation(bpy.types.Operator):
bl_idname = "bim.add_annotation"
bl_label = "Add Annotation"
bl_options = {"REGISTER", "UNDO"}
obj_name: bpy.props.StringProperty()
object_type: bpy.props.StringProperty()
data_type: bpy.props.StringProperty()
@classmethod
@@ -517,33 +577,51 @@ class AddAnnotation(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
context.scene.DocProperties.should_draw_decorations = True
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
)
if not subcontext:
return {"FINISHED"}
if self.data_type == "text":
if context.selected_objects:
for selected_object in context.selected_objects:
obj = annotation.Annotator.add_text(context, related_element=selected_object)
else:
obj = annotation.Annotator.add_text(context)
else:
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context)
if self.obj_name == "Break":
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
else:
obj = annotation.Annotator.add_line_to_annotation(obj, context)
# TODO: reimplement bulk smart tagging
# if self.data_type == "text":
# if context.selected_objects:
# for selected_object in context.selected_objects:
# obj = annotation.Annotator.add_text(context, related_element=selected_object)
# else:
# obj = annotation.Annotator.add_text(context)
# else:
obj = annotation.Annotator.get_annotation_obj(self.object_type, self.data_type, context)
if self.object_type == "BREAKLINE":
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
elif self.object_type != "TEXT":
obj = annotation.Annotator.add_line_to_annotation(obj, context)
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
ifc_representation_class = ""
if self.object_type == "TEXT":
ifc_representation_class = "IfcTextLiteral"
elif self.object_type == "TEXT_LEADER":
ifc_representation_class = "IfcGeometricCurveSet/IfcTextLiteral"
bpy.ops.bim.assign_class(
obj=obj.name,
ifc_class="IfcAnnotation",
context_id=subcontext.id(),
ifc_representation_class=ifc_representation_class,
)
element = tool.Ifc.get_entity(obj)
element.ObjectType = self.object_type
camera = tool.Ifc.get_entity(context.scene.camera)
group = [r for r in camera.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
bpy.ops.bim.assign_group(product=obj.name, group=group.id())
else:
bpy.ops.bim.update_representation(obj=obj.name)
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.mode_set(mode="EDIT")
if obj.data:
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
@@ -717,9 +795,8 @@ class ActivateView(bpy.types.Operator):
):
project_collection.children["Views"].children[collection.name].hide_viewport = True
bpy.data.collections.get(collection.name).hide_render = True
context.view_layer.layer_collection.children["Views"].children[
camera.users_collection[0].name
].hide_viewport = False
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
bpy.ops.bim.activate_drawing_style()
return {"FINISHED"}
@@ -787,12 +864,6 @@ class GenerateReferences(bpy.types.Operator):
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
width_in_mm = width * 1000
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
scale = float(numerator) / float(denominator)
real_world_width_in_mm = width_in_mm * scale
offset_in_mm = 20
offset_percentage = offset_in_mm / real_world_width_in_mm
@@ -823,50 +894,6 @@ class ResizeText(bpy.types.Operator):
return {"FINISHED"}
class AddVariable(bpy.types.Operator):
bl_idname = "bim.add_variable"
bl_label = "Add Variable"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.active_object.data.BIMTextProperties.variables.add()
return {"FINISHED"}
class RemoveVariable(bpy.types.Operator):
bl_idname = "bim.remove_variable"
bl_label = "Remove Variable"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
context.active_object.data.BIMTextProperties.variables.remove(self.index)
return {"FINISHED"}
class PropagateTextData(bpy.types.Operator):
bl_idname = "bim.propagate_text_data"
bl_label = "Propagate Text Data"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
source = context.active_object
for obj in context.selected_objects:
if obj == source:
continue
obj.data.body = source.data.body
obj.data.align_x = source.data.align_x
obj.data.align_y = source.data.align_y
obj.data.BIMTextProperties.font_size = source.data.BIMTextProperties.font_size
obj.data.BIMTextProperties.symbol = source.data.BIMTextProperties.symbol
obj.data.BIMTextProperties.variables.clear()
for variable in source.data.BIMTextProperties.variables:
new_variable = obj.data.BIMTextProperties.variables.add()
new_variable.name = variable.name
new_variable.prop_key = variable.prop_key
return {"FINISHED"}
class RemoveDrawing(bpy.types.Operator):
bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing"
@@ -1186,6 +1213,12 @@ class CopyGrid(bpy.types.Operator):
return helper.get_active_drawing(context.scene)[0] is not None
def execute(self, context):
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
)
if not subcontext:
return {"FINISHED"}
proj_coll = helper.get_project_collection(context.scene)
view_coll, camera = helper.get_active_drawing(context.scene)
is_ortho = camera.data.type == "ORTHO"
@@ -1194,11 +1227,19 @@ class CopyGrid(bpy.types.Operator):
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
def grep(coll):
return [obj for obj in coll.all_objects if obj.name.startswith("IfcGridAxis")]
results = []
for obj in coll.all_objects:
element = tool.Ifc.get_entity(obj)
if element and element.is_a("IfcAnnotation") and element.ObjectType == "GRID":
results.append(obj)
return results
def clone(src):
dst = src.copy()
dst.data = dst.data.copy()
dst.name = dst.name.replace("IfcGridAxis/", "")
dst.BIMObjectProperties.ifc_definition_id = 0
dst.data.BIMMeshProperties.ifc_definition_id = 0
return dst
def disassemble(obj):
@@ -1245,17 +1286,25 @@ class CopyGrid(bpy.types.Operator):
for obj in grep(view_coll):
view_coll.objects.unlink(obj)
grid = [localize(*disassemble(clone(obj))) for obj in grep(proj_coll)]
grids = []
for element in tool.Ifc.get().by_type("IfcGridAxis"):
obj = tool.Ifc.get_object(element)
if not obj:
continue
grids.append((*localize(*disassemble(clone(obj))), element))
if clipping:
grid = [(obj, clip(mesh)) for obj, mesh in grid]
grids = [(obj, clip(mesh), element) for obj, mesh, element in grids]
elif elevating:
grid = [(obj, elev(mesh)) for obj, mesh in grid]
grids = [(obj, elev(mesh), element) for obj, mesh, element in grids]
for obj, mesh in grid:
for obj, mesh, element in grids:
if mesh is not None:
view_coll.objects.link(assemble(obj, mesh))
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
annotation = tool.Ifc.get_entity(obj)
annotation.Name = element.AxisTag
annotation.ObjectType = "GRID"
return {"FINISHED"}
@@ -1314,3 +1363,60 @@ class AddSectionsAnnotations(bpy.types.Operator):
view_coll.objects.link(obj)
return {"FINISHED"}
class EditText(bpy.types.Operator, Operator):
bl_idname = "bim.edit_text"
bl_label = "Edit Text"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_text(tool.Ifc, tool.Drawing, obj=context.active_object)
class EnableEditingText(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_text"
bl_label = "Enable Editing Text"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.enable_editing_text(tool.Drawing, obj=context.active_object)
class DisableEditingText(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_text"
bl_label = "Disable Editing Text"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_text(tool.Drawing, obj=context.active_object)
class EditTextProduct(bpy.types.Operator, Operator):
bl_idname = "bim.edit_text_product"
bl_label = "Edit Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
product = None
if context.active_object.BIMTextProperties.relating_product:
product = tool.Ifc.get_entity(context.active_object.BIMTextProperties.relating_product)
core.edit_text_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
class EnableEditingTextProduct(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_text_product"
bl_label = "Enable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.enable_editing_text_product(tool.Drawing, obj=context.active_object)
class DisableEditingTextProduct(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_text_product"
bl_label = "Disable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_text_product(tool.Drawing, obj=context.active_object)
@@ -18,6 +18,9 @@
import os
import bpy
import json
import ifcopenshell
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.decoration as decoration
import enum
@@ -53,6 +56,22 @@ def purge():
vector_styles_enum = []
def update_diagram_scale(self, context):
scale = self.diagram_scale
if scale == "CUSTOM":
scale = self.custom_diagram_scale
scale = scale.split("|")[1]
element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale})
def get_diagram_scales(self, context):
global diagram_scales_enum
if (
@@ -150,6 +169,10 @@ def refreshTitleblocks(self, context):
def toggleDecorations(self, context):
toggle = self.should_draw_decorations
if toggle:
# TODO: design a proper text variable templating renderer
collection = context.scene.camera.users_collection[0]
for obj in collection.objects:
tool.Drawing.update_text_value(obj)
decoration.DecorationsHandler.install(context)
else:
decoration.DecorationsHandler.uninstall()
@@ -265,7 +288,7 @@ class DocProperties(PropertyGroup):
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
decorations_colour: FloatVectorProperty(
name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
)
@property
@@ -282,6 +305,7 @@ class DocProperties(PropertyGroup):
class BIMCameraProperties(PropertyGroup):
representation: StringProperty(name="Representation")
view_name: StringProperty(name="View Name")
target_view: EnumProperty(
items=[
@@ -294,8 +318,8 @@ class BIMCameraProperties(PropertyGroup):
name="Target View",
default="PLAN_VIEW",
)
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale")
custom_diagram_scale: StringProperty(name="Custom Scale")
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000)
is_nts: BoolProperty(name="Is NTS")
@@ -314,8 +338,29 @@ class BIMCameraProperties(PropertyGroup):
cut_objects_custom: StringProperty(name="Custom Cut")
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
# By checking this, you will know whether or not the camera IFC representation needs to be refreshed
def update_representation(self, obj):
representation = json.dumps(
{
"matrix": [list(x) for x in obj.matrix_world],
"raster_x": self.raster_x,
"raster_y": self.raster_y,
"ortho_scale": obj.data.ortho_scale,
"clip_end": obj.data.clip_end,
}
)
if self.representation != representation:
self.representation = representation
return True
return False
class BIMTextProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
attributes: CollectionProperty(name="Attributes", type=Attribute)
value: StringProperty(name="Value", default="TEXT")
font_size: EnumProperty(
items=[
("1.8", "1.8 - Small", ""),
@@ -324,6 +369,7 @@ class BIMTextProperties(PropertyGroup):
("5.0", "5.0 - Header", ""),
("7.0", "7.0 - Title", ""),
],
default="2.5",
update=refreshFontSize,
name="Font Size",
)
@@ -336,5 +382,4 @@ class BIMTextProperties(PropertyGroup):
update=refreshFontSize,
name="Symbol",
)
related_element: PointerProperty(name="Related Element", type=bpy.types.Object)
variables: CollectionProperty(name="Variables", type=Variable)
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
@@ -24,6 +24,8 @@ import pystache
import xml.etree.ElementTree as ET
import svgwrite
import ifcopenshell
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.module.drawing.annotation as annotation
from mathutils import Vector
@@ -168,7 +170,7 @@ class SvgWriter:
line["marker-start"] = "url(#grid-marker)"
line["marker-end"] = "url(#grid-marker)"
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = IfcStore.get_file().by_id(grid_obj.BIMObjectProperties.ifc_definition_id).AxisTag
axis_tag = tool.Ifc.get_entity(grid_obj).Name
self.svg.add(
self.svg.text(
axis_tag,
@@ -207,53 +209,10 @@ class SvgWriter:
for obj_data in self.annotations.get("solid_objs", []):
self.draw_line_annotation(obj_data, ["solid"])
if self.annotations.get("leader_obj"):
self.draw_line_annotation(self.annotations["leader_obj"], ["leader"])
self.draw_leader_annotations()
if self.annotations.get("plan_level_obj"):
matrix_world = self.annotations["plan_level_obj"].matrix_world
for spline in self.annotations["plan_level_obj"].data.splines:
classes = ["annotation", "plan-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-end"] = "url(#plan-level-marker)"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 2.5,
)
)
# TODO: allow metric to be configurable
rl = ((matrix_world @ points[0].co).xyz + self.annotations["plan_level_obj"].location).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl)
else:
rl = "{:.3f}m".format(rl)
if projected_points[0].x > projected_points[-1].x:
text_anchor = "end"
else:
text_anchor = "start"
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": text_anchor,
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
},
)
)
for obj in self.annotations.get("plan_level_objs", []):
self.draw_plan_level_annotation(obj)
if self.annotations.get("section_level_obj"):
matrix_world = self.annotations["section_level_obj"].matrix_world
@@ -455,72 +414,99 @@ class SvgWriter:
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
def draw_leader_annotations(self):
for obj in self.annotations.get("leader_objs", []):
self.draw_line_annotation((obj, obj.data), ["leader"])
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if spline_points:
position = obj.matrix_world @ spline_points[0].co.xyz
else:
position = Vector((0, 0, 0))
self.draw_text_annotation(obj, position)
def draw_text_annotations(self):
for text_obj in self.annotations.get("text_objs", []):
self.draw_text_annotation(text_obj, text_obj.location)
def draw_text_annotation(self, text_obj, position):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
element = tool.Ifc.get_entity(text_obj)
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
for text_obj in self.annotations.get("text_objs", []):
text_position = self.project_point_onto_camera(text_obj.location)
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
text_position = self.project_point_onto_camera(position)
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis)
angle = math.degrees(
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
Vector((1, 0))
)
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
projected_x_axis = self.project_point_onto_camera(position + local_x_axis)
angle = math.degrees(
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
Vector((1, 0))
)
)
transform = "rotate({}, {}, {})".format(
angle,
(text_position * self.scale)[0],
(text_position * self.scale)[1],
)
if text_obj.BIMTextProperties.symbol != "None":
self.svg.add(
self.svg.use("#{}".format(text_obj.BIMTextProperties.symbol), insert=tuple(text_position * self.scale))
)
transform = "rotate({}, {}, {})".format(
angle,
(text_position * self.scale)[0],
(text_position * self.scale)[1],
if text_literal.BoxAlignment == "top-left":
alignment_baseline = "hanging"
text_anchor = "start"
elif text_literal.BoxAlignment == "top-middle":
alignment_baseline = "hanging"
text_anchor = "middle"
elif text_literal.BoxAlignment == "top-right":
alignment_baseline = "hanging"
text_anchor = "end"
elif text_literal.BoxAlignment == "middle-left":
alignment_baseline = "middle"
text_anchor = "start"
elif text_literal.BoxAlignment == "center":
alignment_baseline = "middle"
text_anchor = "middle"
elif text_literal.BoxAlignment == "middle-right":
alignment_baseline = "middle"
text_anchor = "end"
elif text_literal.BoxAlignment == "bottom-left":
alignment_baseline = "baseline"
text_anchor = "start"
elif text_literal.BoxAlignment == "bottom-middle":
alignment_baseline = "baseline"
text_anchor = "middle"
elif text_literal.BoxAlignment == "bottom-right":
alignment_baseline = "baseline"
text_anchor = "end"
text_body = text_literal.Literal
if text_obj.name in self.annotations.get("template_variables", {}):
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
for line_number, text_line in enumerate(text_body.split("\n")):
self.svg.add(
self.svg.text(
text_line,
insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))),
class_=" ".join(self.get_attribute_classes(text_obj)),
**{
"font-size": annotation.Annotator.get_svg_text_size(text_obj.BIMTextProperties.font_size),
"font-family": "OpenGost Type B TT",
"text-anchor": text_anchor,
"alignment-baseline": alignment_baseline,
"dominant-baseline": alignment_baseline,
"transform": transform,
},
)
)
if text_obj.data.BIMTextProperties.symbol != "None":
self.svg.add(
self.svg.use(
"#{}".format(text_obj.data.BIMTextProperties.symbol), insert=tuple(text_position * self.scale)
)
)
if text_obj.data.align_x == "CENTER":
text_anchor = "middle"
elif text_obj.data.align_x == "RIGHT":
text_anchor = "end"
else:
text_anchor = "start"
if text_obj.data.align_y == "CENTER":
alignment_baseline = "middle"
elif text_obj.data.align_y == "TOP":
alignment_baseline = "hanging"
else:
alignment_baseline = "baseline"
text_body = text_obj.data.body
if text_obj.name in self.annotations.get("template_variables", {}):
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
for line_number, text_line in enumerate(text_body.split("\n")):
self.svg.add(
self.svg.text(
text_line,
insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))),
class_=" ".join(self.get_attribute_classes(text_obj)),
**{
"font-size": annotation.Annotator.get_svg_text_size(
text_obj.data.BIMTextProperties.font_size
),
"font-family": "OpenGost Type B TT",
"text-anchor": text_anchor,
"alignment-baseline": alignment_baseline,
"dominant-baseline": alignment_baseline,
"transform": transform,
},
)
)
def draw_break_annotations(self, break_obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -549,6 +535,53 @@ class SvgWriter:
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(["breakline"])))
def draw_plan_level_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
for spline in obj.data.splines:
classes = ["annotation", "plan-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-end"] = "url(#plan-level-marker)"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 2.5,
)
)
# TODO: allow metric to be configurable
rl = ((matrix_world @ points[0].co).xyz + obj.location).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl)
else:
rl = "{:.3f}m".format(rl)
if projected_points[0].x > projected_points[-1].x:
text_anchor = "end"
else:
text_anchor = "start"
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": text_anchor,
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
},
)
)
def draw_dimension_annotations(self, dimension_obj, text_override=None):
matrix_world = dimension_obj.matrix_world
for spline in dimension_obj.data.splines:
@@ -17,7 +17,10 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.module.drawing.data import TextData
class BIM_PT_camera(Panel):
@@ -233,45 +236,60 @@ class BIM_PT_sheets(Panel):
class BIM_PT_text(Panel):
bl_label = "Text Paper Space"
bl_label = "IFC Text"
bl_idname = "BIM_PT_text"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
bl_context = "object"
@classmethod
def poll(cls, context):
return type(context.curve) is bpy.types.TextCurve
if not tool.Ifc.get() or not context.active_object:
return
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
return element.is_a("IfcAnnotation") and element.ObjectType in ["TEXT", "TEXT_LEADER"]
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = context.active_object.data.BIMTextProperties
if not TextData.is_loaded:
TextData.load()
row = layout.row()
row.operator("bim.propagate_text_data")
props = context.active_object.BIMTextProperties
row = layout.row()
if props.is_editing:
row = self.layout.row(align=True)
row.operator("bim.edit_text", icon="CHECKMARK")
row.operator("bim.disable_editing_text", icon="CANCEL", text="")
blenderbim.bim.helper.draw_attributes(props.attributes, self.layout)
else:
row = self.layout.row()
row.operator("bim.enable_editing_text", icon="GREASEPENCIL")
for attribute in TextData.data["attributes"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
if props.is_editing_product:
row = self.layout.row(align=True)
row.prop(props, "relating_product", text="")
row.operator("bim.edit_text_product", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_text_product", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
row.label(text=TextData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA")
row.operator("bim.enable_editing_text_product", icon="GREASEPENCIL", text="")
row = self.layout.row()
row.prop(props, "font_size")
row = layout.row()
row = self.layout.row()
row.prop(props, "symbol")
row = layout.row()
row.prop(props, "related_element")
row = layout.row()
row.operator("bim.add_variable")
for index, variable in enumerate(props.variables):
row = layout.row(align=True)
row.prop(variable, "name")
row.operator("bim.remove_variable", icon="X", text="").index = index
row = layout.row()
row.prop(variable, "prop_key")
class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Annotation"
bl_label = "Drawing"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
@@ -289,41 +307,42 @@ class BIM_PT_annotation_utilities(Panel):
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT")
op.obj_name = "Dimension"
op.object_type = "DIMENSION"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Dim (Eq)", icon="ARROW_LEFTRIGHT")
op.obj_name = "Equal"
op.object_type = "EQUAL_DIMENSION"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
op.data_type = "text"
op.object_type = "TEXT"
op.data_type = "empty"
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
op.obj_name = "Leader"
op.object_type = "TEXT_LEADER"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
op.obj_name = "Stair"
op.object_type = "STAIR_ARROW"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.obj_name = "Hidden"
op.object_type = "HIDDEN_LINE"
op.data_type = "mesh"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
op.obj_name = "Plan Level"
op.object_type = "PLAN_LEVEL"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
op.obj_name = "Section Level"
op.object_type = "SECTION_LEVEL"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.obj_name = "Break"
op.object_type = "BREAKLINE"
op.data_type = "mesh"
op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY")
op.obj_name = "Misc"
op.object_type = "MISC"
op.data_type = "mesh"
props = context.scene.DocProperties
@@ -24,6 +24,11 @@ classes = (
operator.CopyRepresentation,
operator.EditObjectPlacement,
operator.GetRepresentationIfcParameters,
operator.OverrideDelete,
operator.OverrideOutlinerDelete,
operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveLinked,
operator.OverridePasteBuffer,
operator.RemoveRepresentation,
operator.SwitchRepresentation,
operator.UpdateParametricRepresentation,
@@ -36,11 +41,30 @@ classes = (
)
addon_keymaps = []
def register():
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D")
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
kmi.properties["name"] = "bim.override_paste_buffer"
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER")
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
kmi.properties["name"] = "bim.override_paste_buffer"
addon_keymaps.append((km, kmi))
def unregister():
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
del bpy.types.Scene.BIMGeometryProperties
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
@@ -19,10 +19,49 @@
import bpy
import blenderbim.tool as tool
import ifcopenshell.util.placement
from mathutils import Vector
def refresh():
DerivedPlacementsData.is_loaded = False
RepresentationsData.is_loaded = False
class RepresentationsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"representations": cls.representations()}
cls.is_loaded = True
@classmethod
def representations(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
representations = []
if element.is_a("IfcProduct") and element.Representation:
representations = element.Representation.Representations
elif element.is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in element.RepresentationMaps or []]
for representation in representations:
representation_type = representation.RepresentationType
if representation_type == "MappedRepresentation":
representation_type = representation.Items[0].MappingSource.MappedRepresentation.RepresentationType
representation_type += "*"
data = {
"id": representation.id(),
"ContextType": representation.ContextOfItems.ContextType or "",
"ContextIdentifier": "",
"TargetView": "",
"RepresentationType": representation_type or "",
}
if representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
data["ContextIdentifier"] = representation.ContextOfItems.ContextIdentifier or ""
data["TargetView"] = representation.ContextOfItems.TargetView or ""
results.append(data)
return results
class DerivedPlacementsData:
@@ -31,12 +70,52 @@ class DerivedPlacementsData:
@classmethod
def load(cls):
cls.load_z_values()
cls.load_collection()
cls.data = {
"is_storey": cls.is_storey(),
"storey_height": cls.get_storey_height(),
"storey_height": cls.storey_height(),
"min_global_z": cls.min_global_z(),
"max_global_z": cls.max_global_z(),
"has_collection": cls.has_collection(),
"min_decomposed_z": cls.min_decomposed_z(),
"max_decomposed_z": cls.max_decomposed_z(),
}
cls.is_loaded = True
@classmethod
def load_z_values(cls):
cls.z_values = [
(bpy.context.active_object.matrix_world @ Vector(co))[2] for co in bpy.context.active_object.bound_box
]
@classmethod
def load_collection(cls):
cls.collection = bpy.data.objects.get(bpy.context.active_object.users_collection[0].name)
cls.collection_z = 0
if cls.collection:
cls.collection_z = cls.collection.matrix_world.translation.z
@classmethod
def has_collection(cls):
return bool(cls.collection)
@classmethod
def min_global_z(cls):
return "{0:.3f}".format(round(min(cls.z_values), 3))
@classmethod
def max_global_z(cls):
return "{0:.3f}".format(round(max(cls.z_values), 3))
@classmethod
def min_decomposed_z(cls):
return "{0:.3f}".format(round(min(cls.z_values) - cls.collection_z, 3))
@classmethod
def max_decomposed_z(cls):
return "{0:.3f}".format(round(max(cls.z_values) - cls.collection_z, 3))
@classmethod
def is_storey(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -44,7 +123,7 @@ class DerivedPlacementsData:
return element.is_a("IfcBuildingStorey")
@classmethod
def get_storey_height(cls):
def storey_height(cls):
if not cls.is_storey():
return
element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -22,7 +22,7 @@ import mathutils
import ifcopenshell
import ifcopenshell.util.unit
from math import pi
from mathutils import Vector, Matrix
from mathutils import Vector, Matrix, geometry
class Helper:
@@ -215,6 +215,95 @@ class Helper:
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
# An arbitrary face with voids is similar to a profile with voids for extrusion.
# Before we begin we check that all faces are coplanar instead of finding suitable face.
# Then we process the same way.
# Only 2 parameters are returned as there is no extrusion.
def auto_detect_curve_bounded_plane(self, mesh, tolerance=0.001):
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
faces = bm.faces
normal = faces[0].normal
point = faces[0].verts[0].co
for face in faces:
pt_f = face.verts[0].co
if (
normal.dot(face.normal) < 1 - tolerance
or abs(geometry.distance_point_to_plane(pt_f, point, normal)) > tolerance
):
raise ValueError("All faces must be coplanar and have same orientation")
loop_edges = set()
for face in faces:
potential_edges = set(face.edges)
for face2 in faces:
if face == face2:
continue
potential_edges -= set(face2.edges)
loop_edges |= potential_edges
# Create loops from edges
loops = []
while loop_edges:
edge = loop_edges.pop()
loop = [edge]
has_found_connected_edge = True
while has_found_connected_edge:
has_found_connected_edge = False
for edge in loop_edges.copy():
edge_verts = set(edge.verts)
if edge_verts & set(loop[0].verts):
loop.insert(0, edge)
loop_edges.remove(edge)
has_found_connected_edge = True
elif edge_verts & set(loop[-1].verts):
loop.append(edge)
loop_edges.remove(edge)
has_found_connected_edge = True
loops.append(loop)
# Determine outer loop
max_area = 0
outer_loop = None
inner_loops = []
for loop in loops:
loop_vertices = []
total_edges = len(loop)
for i, edge in enumerate(loop):
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
loop_vertices.append(edge.verts[0])
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[0] in loop[i + 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[1] in loop[i + 1].verts:
loop_vertices.append(edge.verts[0])
loop_bm = bmesh.new()
for vert in loop_vertices:
loop_bm.verts.new(vert.co)
face = loop_bm.faces.new(loop_bm.verts)
loop_vertex_indices = [v.index for v in loop_vertices]
face_area = face.calc_area()
if face_area > max_area:
max_area = face_area
outer_loop = loop_vertex_indices
inner_loops.append(loop_vertex_indices)
loop_bm.free()
inner_loops.remove(outer_loop)
bm.to_mesh(mesh)
mesh.update()
bm.free()
return {"outer_curve": outer_loop, "inner_curves": inner_loops}
# An extrusion edge is an edge that shares a single vertex with a profile
# face and is not on the plane of the face.
def detect_extrusion_edge(self, bm, profile_face):
@@ -27,14 +27,16 @@ import ifcopenshell.util.representation
import ifcopenshell.api
import blenderbim.core.geometry as core
import blenderbim.core.style
import blenderbim.core.root
import blenderbim.tool as tool
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
from mathutils import Vector
from ifcopenshell.api.geometry.data import Data
from ifcopenshell.api.context.data import Data as ContextData
from ifcopenshell.api.void.data import Data as VoidData
from mathutils import Vector
from blenderbim.bim import import_ifc
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.root.prop import get_contexts
class Operator:
@@ -53,7 +55,7 @@ class EditObjectPlacement(bpy.types.Operator, Operator):
def _execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs:
core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
class AddRepresentation(bpy.types.Operator, Operator):
@@ -66,90 +68,25 @@ class AddRepresentation(bpy.types.Operator, Operator):
profile_set_usage: bpy.props.IntProperty()
def _execute(self, context):
ifc_context = self.context_id
if not ifc_context and get_contexts(self, context):
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
if ifc_context:
ifc_context = tool.Ifc.get().by_id(ifc_context)
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
if not obj.data:
return {"FINISHED"}
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context_id = self.context_id or int(context.scene.BIMProperties.contexts)
context_of_items = self.file.by_id(context_id)
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
float(gprop.blender_northings),
float(gprop.blender_orthogonal_height),
)
)
representation_data = {
"context": context_of_items,
"blender_object": obj,
"geometry": obj.data,
"coordinate_offset": coordinate_offset,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
"ifc_representation_class": self.ifc_representation_class,
"profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None,
}
result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
if not result:
print("Failed to write shape representation")
return {"FINISHED"}
[
blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material)
for s in obj.material_slots
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
]
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
**{
"shape_representation": result,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, **{"product": product, "representation": result}
core.add_representation(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
obj=obj,
context=ifc_context,
ifc_representation_class=self.ifc_representation_class,
profile_set_usage=tool.Ifc.get().by_id(self.profile_set_usage) if self.profile_set_usage else None,
)
mesh = obj.data.copy()
mesh.name = "{}/{}".format(context_id, result.id())
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
obj.data = mesh
Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
if product.is_a("IfcTypeProduct"):
if self.file.schema == "IFC2X3":
types = product.ObjectTypeOf
else:
types = product.Types
if types:
for element in types[0].RelatedObjects:
Data.load(self.file, element.id())
return {"FINISHED"}
class SwitchRepresentation(bpy.types.Operator):
class SwitchRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.switch_representation"
bl_label = "Switch Representation"
bl_options = {"REGISTER", "UNDO"}
@@ -159,114 +96,31 @@ class SwitchRepresentation(bpy.types.Operator):
disable_opening_subtractions: bpy.props.BoolProperty()
should_switch_all_meshes: bpy.props.BoolProperty()
def execute(self, context):
self.element_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.oprops = self.element_obj.BIMObjectProperties
self.file = IfcStore.get_file()
self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
self.mesh_name = self.get_mesh_name()
mesh = bpy.data.meshes.get(self.mesh_name)
if mesh:
self.switch_mesh(mesh)
if not mesh or self.should_reload:
self.pull_mesh_from_ifc(context)
return {"FINISHED"}
def switch_mesh(self, mesh):
if self.should_switch_all_meshes or self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcTypeProduct"):
self.element_obj.data.user_remap(mesh)
else:
self.element_obj.data = mesh
def get_mesh_name(self):
representation = self.resolve_mapped_representation(self.file.by_id(self.ifc_definition_id))
return "{}/{}".format(self.context_of_items.id(), representation.id())
def resolve_mapped_representation(self, representation):
if representation.RepresentationType == "MappedRepresentation":
return self.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
def pull_mesh_from_ifc(self, context):
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
element = self.file.by_id(self.oprops.ifc_definition_id)
settings = ifcopenshell.geom.settings()
if self.context_of_items.ContextIdentifier == "Body":
if element.is_a("IfcTypeProduct") or self.disable_opening_subtractions:
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
else:
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.oprops.ifc_definition_id))
else:
settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
mesh.name = self.mesh_name
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
self.switch_mesh(mesh)
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
material_creator.load_existing_materials()
material_creator.create(element, self.element_obj, mesh)
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
if self.oprops.ifc_definition_id not in VoidData.products:
VoidData.load(self.file, self.oprops.ifc_definition_id)
for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
opening = IfcStore.get_element(opening_id)
if not opening:
continue
modifier = self.element_obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
modifier.operation = "DIFFERENCE"
modifier.object = opening
modifier.solver = "EXACT"
modifier.use_self = True
else:
for modifier in self.element_obj.modifiers:
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
self.element_obj.modifiers.remove(modifier)
def _execute(self, context):
core.switch_representation(
tool.Geometry,
obj=bpy.data.objects.get(self.obj) if self.obj else context.active_object,
representation=tool.Ifc.get().by_id(self.ifc_definition_id),
should_reload=self.should_reload,
enable_dynamic_voids=self.disable_opening_subtractions,
is_global=self.should_switch_all_meshes,
should_sync_changes_first=True,
)
class RemoveRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.remove_representation"
bl_label = "Remove Representation"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.representation_id)
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
if is_mapped_representation:
mesh_name = "{}/{}".format(
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
)
else:
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = bpy.data.meshes.get(mesh_name)
if mesh:
if obj.data == mesh:
# TODO we can do better than this
void_mesh = bpy.data.meshes.get("Void")
if not void_mesh:
void_mesh = bpy.data.meshes.new("Void")
obj.data = void_mesh
if not is_mapped_representation:
bpy.data.meshes.remove(mesh)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifcopenshell.api.run(
"geometry.unassign_representation", self.file, **{"product": product, "representation": representation}
core.remove_representation(
tool.Ifc,
tool.Geometry,
obj=context.active_object,
representation=tool.Ifc.get().by_id(self.representation_id),
)
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
Data.load(self.file, product.id())
return {"FINISHED"}
class UpdateRepresentation(bpy.types.Operator):
@@ -302,7 +156,7 @@ class UpdateRepresentation(bpy.types.Operator):
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
return
core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
context_of_items = old_representation.ContextOfItems
@@ -329,6 +183,13 @@ class UpdateRepresentation(bpy.types.Operator):
"ifc_representation_class": self.ifc_representation_class,
}
if not self.ifc_representation_class:
representation_data["ifc_representation_class"] = tool.Geometry.get_ifc_representation_class(
product, old_representation
)
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
[
@@ -351,6 +212,7 @@ class UpdateRepresentation(bpy.types.Operator):
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
)
tool.Geometry.record_object_materials(obj)
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
@@ -358,10 +220,10 @@ class UpdateRepresentation(bpy.types.Operator):
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name)
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
if obj.data.BIMMeshProperties.ifc_parameters:
bpy.ops.bim.get_representation_ifc_parameters()
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
class UpdateParametricRepresentation(bpy.types.Operator):
@@ -381,37 +243,27 @@ class UpdateParametricRepresentation(bpy.types.Operator):
parameter = props.ifc_parameters[self.index]
self.file.by_id(parameter.step_id)[parameter.index] = parameter.value
show_representation_parameters = bool(props.ifc_parameters)
bpy.ops.bim.switch_representation(
ifc_definition_id=props.ifc_definition_id, should_reload=True, should_switch_all_meshes=True
core.switch_representation(
tool.Geometry,
obj=obj,
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
should_reload=True,
enable_dynamic_voids=False,
is_global=True,
should_sync_changes_first=False,
)
if show_representation_parameters:
bpy.ops.bim.get_representation_ifc_parameters()
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
return {"FINISHED"}
class GetRepresentationIfcParameters(bpy.types.Operator):
class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
bl_idname = "bim.get_representation_ifc_parameters"
bl_label = "Get Representation IFC Parameters"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
obj = context.active_object
props = obj.data.BIMMeshProperties
elements = self.file.traverse(self.file.by_id(props.ifc_definition_id))
props.ifc_parameters.clear()
for element in elements:
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
for i in range(0, len(element)):
if element.attribute_type(i) == "DOUBLE":
new = props.ifc_parameters.add()
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
new.step_id = element.id()
new.type = element.attribute_type(i)
new.index = i
if element[i]:
new.value = element[i]
return {"FINISHED"}
def _execute(self, context):
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
class CopyRepresentation(bpy.types.Operator, Operator):
@@ -425,9 +277,272 @@ class CopyRepresentation(bpy.types.Operator, Operator):
return
bm = bmesh.new()
bm.from_mesh(context.active_object.data)
geometric_context = tool.Root.get_representation_context(
tool.Root.get_object_representation(context.active_object)
)
for obj in context.selected_objects:
if obj == context.active_object:
continue
if obj.data:
element = tool.Ifc.get_entity(obj)
if not element:
continue
bm.to_mesh(obj.data)
bpy.ops.bim.add_representation(obj=obj.name)
old_rep = self.get_representation_by_context(element, geometric_context)
if old_rep:
ifcopenshell.api.run(
"geometry.unassign_representation", tool.Ifc.get(), product=element, representation=old_rep
)
ifcopenshell.api.run("geometry.remove_representation", tool.Ifc.get(), representation=old_rep)
core.add_representation(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
obj=obj,
context=geometric_context,
ifc_representation_class=None,
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 OverrideDeleteTrait:
def delete_ifc_object(self, obj):
if obj.BIMObjectProperties.ifc_definition_id:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
IfcStore.deleted_ids.add(element.id())
if getattr(element, "FillsVoids", None):
self.remove_filling(element)
if element.is_a("IfcOpeningElement"):
for rel in element.HasFillings:
self.remove_filling(rel.RelatedBuildingElement)
if element.VoidsElements:
self.delete_opening_element(element)
elif getattr(element, "HasOpenings", None):
for rel in element.HasOpenings:
self.delete_opening_element(rel.RelatedOpeningElement)
def delete_opening_element(self, element):
obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id())
bpy.ops.bim.remove_opening(opening_id=element.id(), obj=obj.name)
def remove_filling(self, element):
obj = IfcStore.get_element(element.id())
bpy.ops.bim.remove_filling(obj=obj.name)
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "object.delete"
bl_label = "Delete"
use_global: bpy.props.BoolProperty(default=False)
confirm: bpy.props.BoolProperty(default=True)
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
return IfcStore.execute_ifc_operator(self, context)
for obj in context.selected_objects:
bpy.data.objects.remove(obj)
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def _execute(self, context):
for obj in context.selected_objects:
self.delete_ifc_object(obj)
bpy.data.objects.remove(obj)
return {"FINISHED"}
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "outliner.delete"
bl_label = "Delete"
hierarchy: bpy.props.BoolProperty(default=False)
@classmethod
def poll(cls, context):
return len(context.selected_ids) > 0
def execute(self, context):
# In this override, we don't check self.hierarchy. This effectively
# makes Delete and Delete Hierarchy identical. This is on purpose, since
# non-hierarchical deletion may imply a whole bunch of potentially
# unintended IFC spatial modifications. To make life less confusing for
# the user, Delete means Delete. End of story.
# Deep magick from the dawn of time
if IfcStore.get_file():
return IfcStore.execute_ifc_operator(self, context)
# https://blender.stackexchange.com/questions/203729/python-get-selected-objects-in-outliner
objects_to_delete = set()
collections_to_delete = set()
for item in context.selected_ids:
if item.bl_rna.identifier == "Collection":
collection = bpy.data.collections.get(item.name)
collection_data = self.get_collection_objects_and_children(collection)
print(collection_data)
objects_to_delete |= collection_data["objects"]
collections_to_delete |= collection_data["children"]
collections_to_delete.add(collection)
elif item.bl_rna.identifier == "Object":
objects_to_delete.add(bpy.data.objects.get(item.name))
for obj in objects_to_delete:
bpy.data.objects.remove(obj)
for collection in collections_to_delete:
bpy.data.collections.remove(collection)
return {"FINISHED"}
def _execute(self, context):
objects_to_delete = set()
collections_to_delete = set()
for item in context.selected_ids:
if item.bl_rna.identifier == "Collection":
collection = bpy.data.collections.get(item.name)
collection_data = self.get_collection_objects_and_children(collection)
objects_to_delete |= collection_data["objects"]
collections_to_delete |= collection_data["children"]
collections_to_delete.add(collection)
elif item.bl_rna.identifier == "Object":
objects_to_delete.add(bpy.data.objects.get(item.name))
for obj in objects_to_delete:
# This is the only difference
self.delete_ifc_object(obj)
bpy.data.objects.remove(obj)
for collection in collections_to_delete:
bpy.data.collections.remove(collection)
return {"FINISHED"}
def get_collection_objects_and_children(self, collection):
objects = set()
children = set()
queue = [collection]
while queue:
collection = queue.pop()
for obj in collection.objects:
objects.add(obj)
queue.extend(collection.children)
children = children.union(collection.children)
return {"objects": objects, "children": children}
class OverrideDuplicateMove(bpy.types.Operator):
bl_idname = "object.duplicate_move"
bl_label = "Duplicate Objects"
@classmethod
def poll(cls, context):
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
bpy.ops.transform.translate("INVOKE_DEFAULT")
return {"FINISHED"}
def _execute(self, context):
self.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:
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)
# This is the only difference
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
bpy.ops.transform.translate("INVOKE_DEFAULT")
blenderbim.bim.handler.purge_module_data()
class OverrideDuplicateMoveLinked(bpy.types.Operator):
bl_idname = "object.duplicate_move_linked"
bl_label = "Duplicate Linked"
@classmethod
def poll(cls, context):
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
bpy.ops.transform.translate("INVOKE_DEFAULT")
return {"FINISHED"}
def _execute(self, context):
self.new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.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)
# This is the only difference
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
bpy.ops.transform.translate("INVOKE_DEFAULT")
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
class OverridePasteBuffer(bpy.types.Operator):
bl_idname = "bim.override_paste_buffer"
bl_label = "Paste BIM Objects"
def execute(self, context):
bpy.ops.view3d.pastebuffer()
if IfcStore.get_file():
for obj in context.selected_objects:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
return {"FINISHED"}
@@ -18,9 +18,8 @@
import bpy
from bpy.types import Panel
from ifcopenshell.api.geometry.data import Data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.geometry.data import DerivedPlacementsData
from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData
class BIM_PT_representations(Panel):
@@ -30,6 +29,7 @@ class BIM_PT_representations(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
@@ -40,33 +40,31 @@ class BIM_PT_representations(Panel):
return IfcStore.get_file()
def draw(self, context):
if not RepresentationsData.is_loaded:
RepresentationsData.load()
layout = self.layout
props = context.active_object.BIMObjectProperties
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
representations = Data.products[props.ifc_definition_id]
if not representations:
if not RepresentationsData.data["representations"]:
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(context.scene.BIMProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
for ifc_definition_id in representations:
representation = Data.representations[ifc_definition_id]
for representation in RepresentationsData.data["representations"]:
row = self.layout.row(align=True)
row.label(text=representation["ContextOfItems"]["ContextType"])
row.label(text=representation["ContextOfItems"]["ContextIdentifier"])
row.label(text=representation["ContextOfItems"]["TargetView"])
row.label(text=representation["ContextType"])
row.label(text=representation["ContextIdentifier"])
row.label(text=representation["TargetView"])
row.label(text=representation["RepresentationType"])
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = ifc_definition_id
op.ifc_definition_id = representation["id"]
op.disable_opening_subtractions = False
row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
class BIM_PT_mesh(Panel):
@@ -109,6 +107,10 @@ class BIM_PT_mesh(Panel):
row = layout.row()
row.operator("bim.update_representation")
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Tessellation")
op.ifc_representation_class = "IfcTessellatedFaceSet"
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Rectangle Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
@@ -157,24 +159,20 @@ class BIM_PT_derived_placements(Panel):
if not DerivedPlacementsData.is_loaded:
DerivedPlacementsData.load()
z = context.active_object.matrix_world.translation.z
z_values = [co[2] for co in context.active_object.bound_box]
row = self.layout.row(align=True)
row.label(text="Min Global Z")
row.label(text="{0:.3f}".format(min(z_values) + z))
row.label(text=DerivedPlacementsData.data["min_global_z"])
row = self.layout.row(align=True)
row.label(text="Max Global Z")
row.label(text="{0:.3f}".format(max(z_values) + z))
row.label(text=DerivedPlacementsData.data["max_global_z"])
collection = bpy.data.objects.get(context.active_object.users_collection[0].name)
if collection:
collection_z = collection.matrix_world.translation.z
if DerivedPlacementsData.data["has_collection"]:
row = self.layout.row(align=True)
row.label(text="Min Local Z")
row.label(text="{0:.3f}".format(min(z_values) + z - collection_z))
row.label(text="Min Decomposed Z")
row.label(text=DerivedPlacementsData.data["min_decomposed_z"])
row = self.layout.row(align=True)
row.label(text="Max Local Z")
row.label(text="{0:.3f}".format(max(z_values) + z - collection_z))
row.label(text="Max Decomposed Z")
row.label(text=DerivedPlacementsData.data["max_decomposed_z"])
if DerivedPlacementsData.data["is_storey"]:
row = self.layout.row(align=True)
@@ -30,6 +30,7 @@ class BIM_PT_gis(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
@@ -0,0 +1,37 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import prop
from . import operator
from . import ui
classes = (
prop.BIMCityJsonProperties,
operator.BIM_OT_cityjson2ifc,
operator.BIM_OT_find_cityjson_lod,
ui.BIM_PT_cityjson_converter,
)
def register():
bpy.types.Scene.ifccityjson_properties = bpy.props.PointerProperty(type=prop.BIMCityJsonProperties)
def unregister():
del bpy.types.Scene.ifccityjson_properties
@@ -0,0 +1,69 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import Operator
class BIM_OT_cityjson2ifc(Operator):
bl_idname = "bim.convert_cityjson2ifc"
bl_label = "Convert CityJSON to IFC"
bl_context = "scene"
def execute(self, context):
from cjio import cityjson
from ifccityjson.cityjson2ifc.cityjson2ifc import Cityjson2ifc
props = context.scene.ifccityjson_properties
city_model = cityjson.load(props.input, transform=False)
data = {
"file_destination": props.output,
"lod": props.lod,
"split": props.split_lod,
}
if props.name != "":
data["name_attribute"] = props.name
converter = Cityjson2ifc()
converter.configuration(**data)
converter.convert(city_model)
if props.load_after_convert:
bpy.ops.bim.load_project(filepath=props.output)
return {"FINISHED"}
class BIM_OT_find_cityjson_lod(Operator):
bl_idname = "bim.find_cityjson_lod"
bl_label = "Find LODs in CityJSON file"
bl_context = "scene"
def execute(self, context):
from cjio import cityjson
props = context.scene.ifccityjson_properties
city_model = cityjson.load(props.input)
lods = set()
for obj_id, obj in city_model.get_cityobjects().items():
for geometry in obj.geometry:
lod = str(geometry.lod)
if lod not in lods:
lods.add(lod)
props.lods.add()
props.lods[-1].name = lod
props.is_lod_found = True
return {"FINISHED"}
@@ -0,0 +1,36 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import PropertyGroup
from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty
from blenderbim.bim.prop import StrProperty
class BIMCityJsonProperties(PropertyGroup):
def get_lods(self, context):
return [(item.name, "LOD" + item.name, "Level of Detail " + item.name) for item in self.lods]
# TODO: instead of subtype it would be nice to have a helper operator that allows filtered file browsing
input: StringProperty(name="CityJSON Input", default="", subtype="FILE_PATH")
output: StringProperty(name="IFC Output", default="", subtype="FILE_PATH")
name: StringProperty(name="Identifier", default="")
split_lod: BoolProperty(name="Should Split LOD", default=True)
lods: CollectionProperty(name="LODs", type=StrProperty)
lod: EnumProperty(name="LOD", description="", items=get_lods, options={"ANIMATABLE"}, default=None)
is_lod_found: BoolProperty(name="Is LOD Found", default=False)
load_after_convert: BoolProperty(name="Load After Converting", default=True)
@@ -0,0 +1,52 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import Panel
class BIM_PT_cityjson_converter(Panel):
bl_label = "IFC CityJSON"
bl_idname = "BIM_PT_ifccityjson"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.ifccityjson_properties
row = layout.row(align=True)
row.prop(props, "input")
row = layout.row(align=True)
row.operator("bim.find_cityjson_lod")
row = layout.row(align=True)
row.prop(props, "output")
row = layout.row(align=True)
row.prop(props, "name")
row = layout.row(align=True)
row.prop(props, "split_lod")
row.prop(props, "load_after_convert")
row = layout.row(align=True)
row.prop(props, "lod")
if not props.is_lod_found:
row.enabled = False
row = layout.row(align=True)
row.operator("bim.convert_cityjson2ifc")
@@ -28,6 +28,7 @@ class BIM_PT_groups(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_utilities"
@classmethod
def poll(cls, context):
@@ -74,6 +75,7 @@ class BIM_PT_object_groups(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_utilities_object"
@classmethod
def poll(cls, context):
@@ -28,6 +28,7 @@ class BIM_PT_layers(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
@@ -21,6 +21,7 @@ from . import ui, prop, operator
classes = (
operator.CalculateLCA,
operator.ConnectOpenLCA,
prop.BIMLCAProperties,
ui.BIM_PT_lca,
)
@@ -0,0 +1,49 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import olca
import blenderbim.tool as tool
def refresh():
LcaData.is_loaded = False
class LcaData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"product_systems": cls.product_systems(),
}
cls.is_loaded = True
@classmethod
def product_systems(cls):
if not bpy.context.scene.BIMLCAProperties.is_connected:
return []
results = []
client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port)
try:
results = [(ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem)]
except:
pass
return results
@@ -43,3 +43,13 @@ class CalculateLCA(bpy.types.Operator):
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ConnectOpenLCA(bpy.types.Operator):
bl_idname = "bim.connect_openlca"
bl_label = "Connect OpenLCA"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.scene.BIMLCAProperties.is_connected = True
return {"FINISHED"}
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import olca
from blenderbim.bim.module.lca.data import LcaData
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -31,25 +31,14 @@ from bpy.props import (
CollectionProperty,
)
productsystems_enum = []
def purge():
global productsystems_enum
productsystems_enum = []
def get_product_systems(self, context):
global productsystems_enum
if not len(productsystems_enum):
client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port)
try:
productsystems_enum = [(ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem)]
except:
pass
return productsystems_enum
if not LcaData.is_loaded:
LcaData.load()
return LcaData.data["product_systems"]
class BIMLCAProperties(PropertyGroup):
is_connected: BoolProperty(name="Is Connected")
amount: FloatProperty(name="Amount")
product_systems: EnumProperty(items=get_product_systems, name="Product Systems")
+10 -6
View File
@@ -30,9 +30,13 @@ class BIM_PT_lca(Panel):
def draw(self, context):
props = context.scene.BIMLCAProperties
row = self.layout.row()
row.prop(props, "product_systems", text="")
row = self.layout.row()
row.prop(props, "amount", text="")
row = self.layout.row()
row.operator("bim.calculate_lca")
if props.is_connected:
row = self.layout.row()
row.prop(props, "product_systems", text="")
row = self.layout.row()
row.prop(props, "amount", text="")
row = self.layout.row()
row.operator("bim.calculate_lca")
else:
row = self.layout.row()
row.operator("bim.connect_openlca")
@@ -0,0 +1,51 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, prop, operator
classes = (
operator.AddLibrary,
operator.AddLibraryReference,
operator.AssignLibraryReference,
operator.DisableEditingLibrary,
operator.DisableEditingLibraryReference,
operator.DisableEditingLibraryReferences,
operator.EditLibrary,
operator.EditLibraryReference,
operator.EnableEditingLibrary,
operator.EnableEditingLibraryReference,
operator.EnableEditingLibraryReferences,
operator.RemoveLibrary,
operator.RemoveLibraryReference,
operator.UnassignLibraryReference,
prop.LibraryReference,
prop.BIMLibraryProperties,
ui.BIM_PT_libraries,
ui.BIM_PT_library_references,
ui.BIM_UL_library_references,
ui.BIM_UL_object_library_references,
)
def register():
bpy.types.Scene.BIMLibraryProperties = bpy.props.PointerProperty(type=prop.BIMLibraryProperties)
def unregister():
del bpy.types.Scene.BIMLibraryProperties
@@ -0,0 +1,110 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
def refresh():
LibrariesData.is_loaded = False
LibraryReferencesData.is_loaded = False
class LibrariesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"libraries": cls.libraries(),
"library_attributes": cls.library_attributes(),
"reference_attributes": cls.reference_attributes(),
}
@classmethod
def libraries(cls):
results = []
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
results.append({"id": library.id(), "name": library.Name})
return results
@classmethod
def library_attributes(cls):
library_id = bpy.context.scene.BIMLibraryProperties.active_library_id
if not library_id:
return []
results = []
data = tool.Ifc.get().by_id(library_id).get_info()
if tool.Ifc.get_schema() == "IFC2X3":
del data["VersionDate"]
for key, value in data.items():
if key in ["id", "type"]:
continue
if value is not None:
results.append({"name": key, "value": str(value)})
return results
@classmethod
def reference_attributes(cls):
props = bpy.context.scene.BIMLibraryProperties
try:
reference_id = props.references[props.active_reference_index].ifc_definition_id
except:
return []
if not reference_id:
return []
results = []
data = tool.Ifc.get().by_id(reference_id).get_info()
del data["ReferencedLibrary"]
for key, value in data.items():
if key in ["id", "type"]:
continue
if value is not None:
results.append({"name": key, "value": str(value)})
return results
class LibraryReferencesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"references": cls.references(),
}
@classmethod
def references(cls):
results = []
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
if rel.is_a("IfcRelAssociatesLibrary"):
library = rel.RelatingLibrary
results.append(
{
"id": library.id(),
"identification": library.ItemReference
if tool.Ifc.get_schema() == "IFC2X3"
else library.Identification,
"name": library.Name or "Unnamed",
}
)
return results
@@ -0,0 +1,167 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.library as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class AddLibrary(bpy.types.Operator, Operator):
bl_idname = "bim.add_library"
bl_label = "Add Library"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_library(tool.Ifc)
class RemoveLibrary(bpy.types.Operator, Operator):
bl_idname = "bim.remove_library"
bl_label = "Remove Library"
bl_options = {"REGISTER", "UNDO"}
library: bpy.props.IntProperty()
def _execute(self, context):
core.remove_library(tool.Ifc, library=tool.Ifc.get().by_id(self.library))
class EnableEditingLibraryReferences(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_library_references"
bl_label = "Enable Editing Library References"
bl_options = {"REGISTER", "UNDO"}
library: bpy.props.IntProperty()
def _execute(self, context):
core.enable_editing_library_references(tool.Library, library=tool.Ifc.get().by_id(self.library))
class DisableEditingLibraryReferences(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_library_references"
bl_label = "Disable Editing Library References"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_library_references(tool.Library)
class EnableEditingLibrary(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_library"
bl_label = "Enable Editing Library"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.enable_editing_library(tool.Library)
class DisableEditingLibrary(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_library"
bl_label = "Disable Editing Library"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_library(tool.Library)
class EditLibrary(bpy.types.Operator, Operator):
bl_idname = "bim.edit_library"
bl_label = "Edit Library"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_library(tool.Ifc, tool.Library)
class AddLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.add_library_reference"
bl_label = "Add Library Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_library_reference(tool.Ifc, tool.Library)
class RemoveLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.remove_library_reference"
bl_label = "Remove Library Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.remove_library_reference(tool.Ifc, tool.Library, reference=tool.Ifc.get().by_id(self.reference))
class EnableEditingLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_library_reference"
bl_label = "Enable Editing Library Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.enable_editing_library_reference(tool.Library, reference=tool.Ifc.get().by_id(self.reference))
class DisableEditingLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_library_reference"
bl_label = "Disable Editing Library Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_library_reference(tool.Library)
class EditLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.edit_library_reference"
bl_label = "Edit Library Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_library_reference(tool.Ifc, tool.Library)
class AssignLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.assign_library_reference"
bl_label = "Assign Library Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.assign_library_reference(
tool.Ifc, obj=context.active_object, reference=tool.Ifc.get().by_id(self.reference)
)
class UnassignLibraryReference(bpy.types.Operator, Operator):
bl_idname = "bim.unassign_library_reference"
bl_label = "Unassign Library Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.unassign_library_reference(
tool.Ifc, obj=context.active_object, reference=tool.Ifc.get().by_id(self.reference)
)
@@ -0,0 +1,51 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.library.data import LibrariesData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
def update_active_reference_index(self, context):
LibrariesData.is_loaded = False
class LibraryReference(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMLibraryProperties(PropertyGroup):
editing_mode: StringProperty(name="Editing Mode")
library_attributes: CollectionProperty(name="Library Attributes", type=Attribute)
active_library_id: IntProperty(name="Active Library Id")
reference_attributes: CollectionProperty(name="Library Attributes", type=Attribute)
active_reference_id: IntProperty(name="Active Reference Id")
references: CollectionProperty(type=LibraryReference, name="References")
active_reference_index: IntProperty(name="Active Reference Index", update=update_active_reference_index)
@@ -0,0 +1,147 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesData
class BIM_PT_libraries(Panel):
bl_label = "IFC Libraries"
bl_idname = "BIM_PT_libraries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not LibrariesData.is_loaded:
LibrariesData.load()
self.props = context.scene.BIMLibraryProperties
if self.props.editing_mode == "LIBRARY":
self.draw_editable_library_ui()
elif self.props.editing_mode == "REFERENCE":
self.draw_editable_reference_ui()
elif self.props.editing_mode == "REFERENCES":
self.draw_editable_references_ui()
else:
self.draw_readonly_library_ui()
def draw_editable_library_ui(self):
row = self.layout.row(align=True)
row.operator("bim.edit_library", icon="CHECKMARK")
row.operator("bim.disable_editing_library", text="", icon="CANCEL")
blenderbim.bim.helper.draw_attributes(self.props.library_attributes, self.layout)
def draw_editable_references_ui(self):
row = self.layout.row(align=True)
row.operator("bim.enable_editing_library", icon="GREASEPENCIL")
row.operator("bim.disable_editing_library_references", text="", icon="CANCEL")
for attribute in LibrariesData.data["library_attributes"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
row = self.layout.row(align=True)
row.operator("bim.add_library_reference", icon="ADD")
self.layout.template_list(
"BIM_UL_library_references", "", self.props, "references", self.props, "active_reference_index"
)
for attribute in LibrariesData.data["reference_attributes"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
def draw_editable_reference_ui(self):
row = self.layout.row(align=True)
row.operator("bim.edit_library_reference", icon="CHECKMARK")
row.operator("bim.disable_editing_library_reference", text="", icon="CANCEL")
blenderbim.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
def draw_readonly_library_ui(self):
row = self.layout.row()
row.operator("bim.add_library", icon="ADD")
for library in LibrariesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text=library["name"], icon="ASSET_MANAGER")
row.operator("bim.enable_editing_library_references", text="", icon="OUTLINER").library = library["id"]
row.operator("bim.remove_library", text="", icon="X").library = library["id"]
class BIM_PT_library_references(Panel):
bl_label = "IFC Library References"
bl_idname = "BIM_PT_library_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_misc_object"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not LibraryReferencesData.is_loaded:
LibraryReferencesData.load()
self.props = context.scene.BIMLibraryProperties
if self.props.editing_mode == "REFERENCES":
self.layout.template_list(
"BIM_UL_object_library_references", "", self.props, "references", self.props, "active_reference_index"
)
if not LibraryReferencesData.data["references"]:
row = self.layout.row()
row.label(text="No References")
for reference in LibraryReferencesData.data["references"]:
row = self.layout.row(align=True)
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
class BIM_UL_library_references(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.name)
op = row.operator("bim.enable_editing_library_reference", text="", icon="GREASEPENCIL")
op.reference = item.ifc_definition_id
op = row.operator("bim.remove_library_reference", text="", icon="X")
op.reference = item.ifc_definition_id
class BIM_UL_object_library_references(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.name)
op = row.operator("bim.assign_library_reference", text="", icon="ADD")
op.reference = item.ifc_definition_id
@@ -20,27 +20,29 @@ import bpy
from . import ui, prop, operator
classes = (
operator.CopyMaterial,
operator.AddMaterial,
operator.RemoveMaterial,
operator.AssignMaterial,
operator.UnassignMaterial,
operator.AddConstituent,
operator.RemoveConstituent,
operator.AddProfile,
operator.RemoveProfile,
operator.AssignParameterizedProfile,
operator.AddDefaultMaterial,
operator.AddLayer,
operator.RemoveLayer,
operator.ReorderMaterialSetItem,
operator.AddListItem,
operator.RemoveListItem,
operator.EnableEditingAssignedMaterial,
operator.AddMaterial,
operator.AddProfile,
operator.AssignMaterial,
operator.AssignParameterizedProfile,
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.EditAssignedMaterial,
operator.EnableEditingMaterialSetItem,
operator.DisableEditingMaterialSetItem,
operator.EditAssignedMaterial,
operator.EditMaterialSetItem,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterialSetItem,
operator.RemoveConstituent,
operator.RemoveLayer,
operator.RemoveListItem,
operator.RemoveMaterial,
operator.RemoveProfile,
operator.ReorderMaterialSetItem,
operator.UnassignMaterial,
operator.UnlinkMaterial,
prop.BIMObjectMaterialProperties,
ui.BIM_PT_material,
ui.BIM_PT_object_material,
@@ -23,12 +23,21 @@ import ifcopenshell.util.element
import ifcopenshell.util.attribute
import ifcopenshell.util.representation
import blenderbim.bim.helper
import blenderbim.tool as tool
import blenderbim.core.material as core
from blenderbim.bim.module.material.prop import purge as material_prop_purge
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class AssignParameterizedProfile(bpy.types.Operator):
bl_idname = "bim.assign_parameterized_profile"
bl_label = "Assign Parameterized Profile"
@@ -59,6 +68,16 @@ class AssignParameterizedProfile(bpy.types.Operator):
return {"FINISHED"}
class AddDefaultMaterial(bpy.types.Operator, Operator):
bl_idname = "bim.add_default_material"
bl_label = "Add Default Material"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_default_material(tool.Ifc, tool.Material)
Data.load(IfcStore.get_file())
class AddMaterial(bpy.types.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
@@ -112,6 +131,15 @@ class RemoveMaterial(bpy.types.Operator):
return {"FINISHED"}
class UnlinkMaterial(bpy.types.Operator, Operator):
bl_idname = "bim.unlink_material"
bl_label = "Unlink Material"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.unlink_material(tool.Ifc, obj=context.active_object.active_material)
class AssignMaterial(bpy.types.Operator):
bl_idname = "bim.assign_material"
bl_label = "Assign Material"
@@ -36,9 +36,10 @@ class BIM_PT_material(Panel):
return IfcStore.get_file() and context.active_object and context.active_object.active_material
def draw(self, context):
row = self.layout.row()
row = self.layout.row(align=True)
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material")
row.operator("bim.unlink_material", icon="UNLINKED", text="")
else:
row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
@@ -49,6 +50,7 @@ class BIM_PT_object_material(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -76,8 +78,9 @@ class BIM_PT_object_material(Panel):
self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials:
row = self.layout.row()
row = self.layout.row(align=True)
row.label(text="No Materials Available")
row.operator("bim.add_default_material", icon="ADD", text="")
return
if self.product_data:
@@ -0,0 +1,40 @@
# 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 ui, prop, operator
classes = (
operator.DrawSystemArrows,
operator.GetConnectedSystemElements,
operator.ResizeToStorey,
operator.SetOverrideColour,
operator.SetViewportShadowFromSun,
operator.SnapSpacesTogether,
operator.SplitAlongEdge,
prop.BIMMiscProperties,
ui.BIM_PT_misc_utilities,
)
def register():
bpy.types.Scene.BIMMiscProperties = bpy.props.PointerProperty(type=prop.BIMMiscProperties)
def unregister():
del bpy.types.Scene.BIMMiscProperties
@@ -0,0 +1,262 @@
# 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 numpy as np
import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.misc as core
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class SetOverrideColour(bpy.types.Operator):
bl_idname = "bim.set_override_colour"
bl_label = "Set Override Colour"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def execute(self, context):
for obj in context.selected_objects:
obj.color = context.scene.BIMMiscProperties.override_colour
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
class SetViewportShadowFromSun(bpy.types.Operator):
bl_idname = "bim.set_viewport_shadow_from_sun"
bl_label = "Set Viewport Shadow from Sun"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.active_object
def execute(self, context):
# Does this belong in the drawing module? Perhaps.
# The vector used for the light direction is a bit funny
mat = Matrix(((-1.0, 0.0, 0.0, 0.0), (0.0, 0, 1.0, 0.0), (-0.0, -1.0, 0, 0.0), (0.0, 0.0, 0.0, 1.0)))
context.scene.display.light_direction = mat.inverted() @ (
context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, -1))
)
return {"FINISHED"}
class SnapSpacesTogether(bpy.types.Operator):
bl_idname = "bim.snap_spaces_together"
bl_label = "Snap Spaces Together"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def execute(self, context):
threshold = 0.5
processed_polygons = set()
selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
for obj in selected_mesh_objects:
for polygon in obj.data.polygons:
center = obj.matrix_world @ polygon.center
distance = None
for obj2 in selected_mesh_objects:
if obj2 == obj:
continue
result = obj2.ray_cast(obj2.matrix_world.inverted() @ center, polygon.normal, distance=threshold)
if not result[0]:
continue
hit = obj2.matrix_world @ result[1]
distance = (hit - center).length / 2
if distance < 0.01:
distance = None
break
if (obj2.name, result[3]) in processed_polygons:
distance *= 2
continue
offset = polygon.normal * distance * -1
processed_polygons.add((obj2.name, result[3]))
for v in obj2.data.polygons[result[3]].vertices:
obj2.data.vertices[v].co += offset
break
if distance:
offset = polygon.normal * distance
processed_polygons.add((obj.name, polygon.index))
for v in polygon.vertices:
obj.data.vertices[v].co += offset
return {"FINISHED"}
class ResizeToStorey(bpy.types.Operator, Operator):
bl_idname = "bim.resize_to_storey"
bl_label = "Resize To Storey"
bl_options = {"REGISTER", "UNDO"}
total_storeys: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
return context.selected_objects and tool.Ifc.get()
def _execute(self, context):
for obj in context.selected_objects:
core.resize_to_storey(tool.Misc, obj=obj, total_storeys=self.total_storeys)
class SplitAlongEdge(bpy.types.Operator, Operator):
bl_idname = "bim.split_along_edge"
bl_label = "Split Along Edge"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
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)
class GetConnectedSystemElements(bpy.types.Operator, Operator):
bl_idname = "bim.get_connected_system_elements"
bl_label = "Get Connected System Elements"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects and tool.Ifc.get()
def _execute(self, context):
# Just dumped here for now before the system module gets properly planned
def pprint_element(e):
return "{} ({})".format(e.Name, e.GlobalId)
start = tool.Ifc.get_entity(bpy.context.active_object)
connected_elements = []
# Note: this code is for IFC2X3. IFC4 has a different approach.
print("Investigating element:", pprint_element(start))
for rel in start.HasPorts:
for rel2 in rel.RelatingPort.ConnectedTo:
print(
"{} is connected as via {} ({}) TO {} ({}), contained in {}".format(
pprint_element(start),
rel.RelatingPort.FlowDirection,
rel.RelatingPort.GlobalId,
rel2.RelatedPort.FlowDirection,
rel2.RelatedPort.GlobalId,
[pprint_element(r.RelatedElement) for r in rel2.RelatedPort.ContainedIn],
)
)
connected_elements.extend([r.RelatedElement for r in rel2.RelatedPort.ContainedIn])
for rel2 in rel.RelatingPort.ConnectedFrom:
print(
"{} is connected as via {} ({}) FROM {} ({}), contained in {}".format(
pprint_element(start),
rel.RelatingPort.FlowDirection,
rel.RelatingPort.GlobalId,
rel2.RelatingPort.FlowDirection,
rel2.RelatingPort.GlobalId,
[pprint_element(r.RelatedElement) for r in rel2.RelatingPort.ContainedIn],
)
)
connected_elements.extend([r.RelatedElement for r in rel2.RelatingPort.ContainedIn])
for element in connected_elements:
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
class DrawSystemArrows(bpy.types.Operator, Operator):
bl_idname = "bim.draw_system_arrows"
bl_label = "Draw System Arrows"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
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)
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)
)
)
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:
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()
def get_absolute_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
matrix = np.array(
ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
)
return matrix
@@ -0,0 +1,38 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMMiscProperties(PropertyGroup):
total_storeys: IntProperty(name="Total Storeys", default=1)
override_colour: FloatVectorProperty(
name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
)
@@ -0,0 +1,49 @@
# 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
class BIM_PT_misc_utilities(bpy.types.Panel):
bl_idname = "BIM_PT_misc_utilities"
bl_label = "Miscellaneous"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
def draw(self, context):
layout = self.layout
props = context.scene.BIMMiscProperties
row = layout.split(factor=0.2, align=True)
row.prop(props, "override_colour", text="")
row.operator("bim.set_override_colour")
row = layout.row()
row.operator("bim.set_viewport_shadow_from_sun")
row = layout.row()
row.operator("bim.snap_spaces_together")
row = layout.split(factor=0.2, align=True)
row.prop(props, "total_storeys", text="")
row.operator("bim.resize_to_storey").total_storeys = props.total_storeys
row = layout.row()
row.operator("bim.split_along_edge")
row = layout.row()
row.operator("bim.get_connected_system_elements")
row = layout.row()
row.operator("bim.draw_system_arrows")
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import handler, prop, ui, grid, product, wall, slab, stair, door, window, opening, pie, workspace
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace
classes = (
product.AddEmptyType,
@@ -34,11 +34,8 @@ classes = (
prop.BIMModelProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_authoring_architectural,
ui.BIM_PT_misc_utilities,
grid.BIM_OT_add_object,
stair.BIM_OT_add_object,
door.BIM_OT_add_object,
window.BIM_OT_add_object,
opening.BIM_OT_add_object,
pie.OpenPieClass,
pie.PieUpdateContainer,
@@ -56,8 +53,6 @@ def register():
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
bpy.types.VIEW3D_MT_add.append(product.add_empty_type_button)
bpy.app.handlers.load_post.append(handler.load_post)
@@ -76,8 +71,6 @@ def unregister():
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button)
bpy.types.VIEW3D_MT_add.remove(product.add_empty_type_button)
wm = bpy.context.window_manager
@@ -0,0 +1,59 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
def refresh():
AuthoringData.is_loaded = False
class AuthoringData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"ifc_classes": cls.ifc_classes(),
"relating_types": cls.relating_types(),
}
@classmethod
def ifc_classes(cls):
results = []
classes = {e.is_a() for e in tool.Ifc.get().by_type("IfcElementType")}
results.extend([(c, c, "") for c in sorted(classes)])
return results
@classmethod
def relating_types(cls):
ifc_classes = cls.ifc_classes()
if not ifc_classes:
return []
results = []
ifc_class = bpy.context.scene.BIMModelProperties.ifc_class
if not ifc_class and ifc_classes:
ifc_class = ifc_classes[0][0]
if ifc_class:
elements = [(str(e.id()), e.Name, "") for e in tool.Ifc.get().by_type(ifc_class)]
results.extend(sorted(elements, key=lambda s: s[1]))
return results
return []
@@ -1,226 +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 math
import ifcopenshell
from bpy.types import Operator
from bpy.props import FloatProperty
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width - 0.045 - 0.045
# TODO reimplement 2D. See #1222.
# verts = [
# # Left lining
# Vector((0, 0, 0)),
# Vector((0, self.depth, 0)),
# Vector((0.04, self.depth, 0)),
# Vector((0.04, self.depth - 0.04, 0)),
# Vector((0.065, self.depth - 0.04, 0)),
# Vector((0.065, 0, 0)),
# # Right lining
# Vector((self.overall_width, 0, 0)),
# Vector((self.overall_width, self.depth, 0)),
# Vector((self.overall_width - 0.04, self.depth, 0)),
# Vector((self.overall_width - 0.04, self.depth - 0.04, 0)),
# Vector((self.overall_width - 0.065, self.depth - 0.04, 0)),
# Vector((self.overall_width - 0.065, 0, 0)),
# # Door panel
# Vector((0.045, self.depth, 0)),
# Vector((0.045, self.depth + leaf_width, 0)),
# Vector((0.080, self.depth + leaf_width, 0)),
# Vector((0.080, self.depth, 0)),
# ]
# edges = [
# [0, 1],
# [1, 2],
# [2, 3],
# [3, 4],
# [4, 5], # Left lining
# [6, 7],
# [7, 8],
# [8, 9],
# [9, 10],
# [10, 11], # Right lining
# [12, 13],
# [13, 14],
# [14, 15],
# [15, 12], # Door panel
# ]
## Door swing
# for i in range(0, 9):
# verts.append(
# Vector(
# (
# 0.045 + (leaf_width * math.cos((math.pi / 2) / 8 * i)),
# self.depth + (leaf_width * math.sin((math.pi / 2) / 8 * i)),
# 0,
# )
# )
# )
# edges.append([16 + i, 17 + i])
# edges.pop()
# faces = []
# mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
# mesh.use_fake_user = True
# mesh.from_pydata(verts, edges, faces)
# Door lining profile
verts = [
Vector((0, 0, 0)),
Vector((0, -self.depth, 0)),
Vector((0.04, -self.depth, 0)),
Vector((0.04, -self.depth + 0.04, 0)),
Vector((0.065, -self.depth + 0.04, 0)),
Vector((0.065, 0, 0)),
]
edges = []
faces = [[0, 1, 2, 3, 4, 5]]
mesh = bpy.data.meshes.new(name="Dumb Door Profile")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Door Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
# Door lining sweep
verts = [
Vector((0, 0, 0)),
Vector((0, self.overall_height, 0)),
Vector((self.overall_width, self.overall_height, 0)),
Vector((self.overall_width, 0, 0)),
]
edges = [[0, 1], [1, 2], [2, 3]]
faces = []
mesh = bpy.data.meshes.new(name="Dumb Door")
mesh.from_pydata(verts, edges, faces)
obj2 = bpy.data.objects.new("Door", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
obj2.data.dimensions = "2D"
obj2.data.bevel_mode = "OBJECT"
obj2.data.bevel_object = obj
obj2.rotation_euler[0] = math.pi / 2
bpy.ops.object.convert(target="MESH")
bpy.ops.object.transform_apply(location=False)
bpy.data.objects.remove(obj, do_unlink=True)
# Door panel
verts = [
Vector((0.045, self.depth, 0)),
Vector((0.045, self.depth - 0.035, 0)),
Vector((self.overall_width - 0.045, self.depth - 0.035, 0)),
Vector((self.overall_width - 0.045, self.depth, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Door Panel")
mesh.from_pydata(verts, edges, faces)
obj3 = bpy.data.objects.new("Door Panel", mesh)
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = 1
modifier.thickness = self.overall_height - 0.045
context.view_layer.active_layer_collection.collection.objects.link(obj3)
context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
ctx = context.copy()
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
# Door Opening
verts = [
Vector((0, -0.1, -0.1)),
Vector((0, self.depth + 0.1, -0.1)),
Vector((self.overall_width, self.depth + 0.1, -0.1)),
Vector((self.overall_width, -0.1, -0.1)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Door Opening")
mesh.from_pydata(verts, edges, faces)
obj4 = bpy.data.objects.new("Door Opening", mesh)
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = -1
modifier.thickness = self.overall_height + 0.1
context.view_layer.active_layer_collection.collection.objects.link(obj4)
context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
obj4.parent = obj2
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
obj4.hide_render = True
obj4.name = "Door Opening"
obj2.name = "Door"
if IfcStore.get_file():
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcDoor")
obj2.location = context.scene.cursor.location
# obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
# obj2.data.use_fake_user = True
# TODO: reimplement. See #1222.
# rep = obj2.BIMObjectProperties.representation_contexts.add()
# rep.context = "Model"
# rep.name = "Body"
# rep.target_view = "MODEL_VIEW"
# rep = obj2.BIMObjectProperties.representation_contexts.add()
# rep.context = "Plan"
# rep.name = "Annotation"
# rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_door"
bl_label = "Dumb Door"
bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name="Overall Width", default=0.85)
overall_height: FloatProperty(name="Overall Height", default=2.1)
depth: FloatProperty(name="Depth", default=0.2)
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
add_object(self, context)
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -18,6 +18,8 @@
import bpy
import ifcopenshell.api
import blenderbim.core.spatial
import blenderbim.tool as tool
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty
from mathutils import Vector
@@ -51,8 +53,8 @@ def add_object(self, context):
if has_site_collection:
site_obj = bpy.data.objects.get(grandchild.name)
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_container(
relating_structure=site_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=site_obj, element_obj=obj
)
axes_collection = bpy.data.collections.new("UAxes")
@@ -60,41 +60,32 @@ def load_post(*args):
IfcStore.add_element_listener(slab.element_listener)
IfcStore.add_element_listener(profile.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.GenerateAxis", wall.generate_axis
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.CalculateQuantities", wall.calculate_quantities
)
ifcopenshell.api.add_pre_listener(
ifcopenshell.api.add_post_listener(
"material.edit_layer", "BlenderBIM.DumbWall.RegenerateFromLayer", wall.DumbWallPlaner().regenerate_from_layer
)
ifcopenshell.api.add_pre_listener(
ifcopenshell.api.add_post_listener(
"type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.EnsureSolid", slab.ensure_solid
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.GenerateFootprint", slab.generate_footprint
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.CalculateQuantities", slab.calculate_quantities
)
ifcopenshell.api.add_pre_listener(
ifcopenshell.api.add_post_listener(
"material.edit_layer", "BlenderBIM.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer
)
ifcopenshell.api.add_pre_listener(
ifcopenshell.api.add_post_listener(
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid
)
ifcopenshell.api.add_pre_listener(
"material.edit_profile",
"BlenderBIM.DumbProfile.SyncObjectFromProfile",
@@ -21,6 +21,8 @@ import bmesh
import blenderbim.bim.handler
import ifcopenshell
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.core.geometry
from blenderbim.bim.ifc import IfcStore
from math import pi
from mathutils import Vector
@@ -57,12 +59,14 @@ def mode_callback(obj, data):
)
if not representation:
continue
bpy.ops.bim.switch_representation(
obj=building_element_obj.name,
should_switch_all_meshes=True,
blenderbim.core.geometry.switch_representation(
tool.Geometry,
obj=building_element_obj,
representation=representation,
should_reload=True,
ifc_definition_id=representation.id(),
disable_opening_subtractions=True,
enable_dynamic_voids=True,
is_global=True,
should_sync_changes_first=False,
)
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)

Some files were not shown because too many files have changed in this diff Show More